Repository: kovidgoyal/kitty Branch: master Commit: c57305addc2a Files: 951 Total size: 13.6 MB Directory structure: gitextract_7m1o6j63/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── copilot-instructions.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.py │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── depscan.yml │ └── macos_crash_report.py ├── .gitignore ├── 3rdparty/ │ ├── base64/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── config.h │ │ ├── include/ │ │ │ └── libbase64.h │ │ └── lib/ │ │ ├── arch/ │ │ │ ├── avx/ │ │ │ │ ├── codec.c │ │ │ │ └── enc_loop_asm.c │ │ │ ├── avx2/ │ │ │ │ ├── codec.c │ │ │ │ ├── dec_loop.c │ │ │ │ ├── dec_reshuffle.c │ │ │ │ ├── enc_loop.c │ │ │ │ ├── enc_loop_asm.c │ │ │ │ ├── enc_reshuffle.c │ │ │ │ └── enc_translate.c │ │ │ ├── avx512/ │ │ │ │ ├── codec.c │ │ │ │ ├── enc_loop.c │ │ │ │ └── enc_reshuffle_translate.c │ │ │ ├── generic/ │ │ │ │ ├── 32/ │ │ │ │ │ ├── dec_loop.c │ │ │ │ │ └── enc_loop.c │ │ │ │ ├── 64/ │ │ │ │ │ └── enc_loop.c │ │ │ │ ├── codec.c │ │ │ │ ├── dec_head.c │ │ │ │ ├── dec_tail.c │ │ │ │ ├── enc_head.c │ │ │ │ └── enc_tail.c │ │ │ ├── neon32/ │ │ │ │ ├── codec.c │ │ │ │ ├── dec_loop.c │ │ │ │ ├── enc_loop.c │ │ │ │ ├── enc_reshuffle.c │ │ │ │ └── enc_translate.c │ │ │ ├── neon64/ │ │ │ │ ├── codec.c │ │ │ │ ├── dec_loop.c │ │ │ │ ├── enc_loop.c │ │ │ │ ├── enc_loop_asm.c │ │ │ │ └── enc_reshuffle.c │ │ │ ├── sse41/ │ │ │ │ └── codec.c │ │ │ ├── sse42/ │ │ │ │ └── codec.c │ │ │ └── ssse3/ │ │ │ ├── codec.c │ │ │ ├── dec_loop.c │ │ │ ├── dec_reshuffle.c │ │ │ ├── enc_loop.c │ │ │ ├── enc_loop_asm.c │ │ │ ├── enc_reshuffle.c │ │ │ └── enc_translate.c │ │ ├── codec_choose.c │ │ ├── codecs.h │ │ ├── env.h │ │ ├── exports.txt │ │ ├── lib.c │ │ ├── lib_openmp.c │ │ └── tables/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── table_dec_32bit.h │ │ ├── table_enc_12bit.h │ │ ├── table_enc_12bit.py │ │ ├── table_generator.c │ │ ├── tables.c │ │ └── tables.h │ ├── ringbuf/ │ │ ├── ringbuf.c │ │ └── ringbuf.h │ └── verstable.h ├── Brewfile ├── CHANGELOG.rst ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE ├── Makefile ├── README.asciidoc ├── SECURITY.md ├── __main__.py ├── benchmark.py ├── build-terminfo ├── bypy/ │ ├── devenv.go │ ├── init_env.py │ ├── linux/ │ │ └── __main__.py │ ├── linux.conf │ ├── macos/ │ │ └── __main__.py │ ├── macos.conf │ ├── rsync.conf │ ├── site.py │ └── sources.json ├── count-lines-of-code ├── dev.sh ├── docs/ │ ├── Makefile │ ├── _static/ │ │ ├── custom.css │ │ ├── custom.js │ │ ├── timestamps.css │ │ └── timestamps.js │ ├── _templates/ │ │ └── base.html │ ├── actions.rst │ ├── basic.rst │ ├── binary.rst │ ├── build.rst │ ├── changelog.rst │ ├── clipboard.rst │ ├── color-stack.rst │ ├── conf.py │ ├── conf.rst │ ├── deccara.rst │ ├── desktop-notifications.rst │ ├── extract-rst-targets.py │ ├── faq.rst │ ├── file-transfer-protocol.rst │ ├── glossary.rst │ ├── graphics-protocol.rst │ ├── index.rst │ ├── installer.sh │ ├── integrations.rst │ ├── intro_vid.rst │ ├── invocation.rst │ ├── keyboard-protocol.rst │ ├── kittens/ │ │ ├── broadcast.rst │ │ ├── choose-files.rst │ │ ├── choose-fonts.rst │ │ ├── clipboard.rst │ │ ├── custom.rst │ │ ├── desktop-ui.rst │ │ ├── developing-builtin-kittens.rst │ │ ├── diff.rst │ │ ├── hints.rst │ │ ├── hyperlinked_grep.rst │ │ ├── icat.rst │ │ ├── notify.rst │ │ ├── panel.rst │ │ ├── query_terminal.rst │ │ ├── quick-access-terminal.rst │ │ ├── remote_file.rst │ │ ├── ssh.rst │ │ ├── themes.rst │ │ ├── transfer.rst │ │ └── unicode_input.rst │ ├── kittens_intro.rst │ ├── launch.rst │ ├── layouts.rst │ ├── mapping.rst │ ├── marks.rst │ ├── misc-protocol.rst │ ├── multiple-cursors-protocol.rst │ ├── notifications.py │ ├── open_actions.rst │ ├── overview.rst │ ├── performance.rst │ ├── pipe.rst │ ├── pointer-shapes.rst │ ├── press-mentions.rst │ ├── protocol-extensions.rst │ ├── quake-screenshots.rst │ ├── quickstart.rst │ ├── rc_protocol.rst │ ├── remote-control.rst │ ├── requirements.txt │ ├── sessions.rst │ ├── shell-integration.rst │ ├── support.html │ ├── support.rst │ ├── text-sizing-protocol.rst │ ├── underlines.rst │ ├── unscroll.rst │ └── wide-gamut-colors.rst ├── embeds.go ├── gen/ │ ├── README.rst │ ├── __init__.py │ ├── __main__.py │ ├── apc_parsers.py │ ├── bitfields.py │ ├── color_names.py │ ├── config.py │ ├── cursors.py │ ├── go_code.py │ ├── key_constants.py │ ├── rowcolumn-diacritics.txt │ ├── srgb_lut.py │ └── wcwidth.py ├── glad/ │ └── generate.py ├── glfw/ │ ├── __init__.py │ ├── backend_utils.c │ ├── backend_utils.h │ ├── cocoa_displaylink.m │ ├── cocoa_init.m │ ├── cocoa_joystick.h │ ├── cocoa_joystick.m │ ├── cocoa_monitor.m │ ├── cocoa_platform.h │ ├── cocoa_window.m │ ├── context.c │ ├── dbus_glfw.c │ ├── dbus_glfw.h │ ├── egl_context.c │ ├── egl_context.h │ ├── glfw.py │ ├── glfw3.h │ ├── glx_context.c │ ├── glx_context.h │ ├── ibus_glfw.c │ ├── ibus_glfw.h │ ├── init.c │ ├── input.c │ ├── internal.h │ ├── kwin-blur-v1.xml │ ├── linux_desktop_settings.c │ ├── linux_desktop_settings.h │ ├── linux_joystick.c │ ├── linux_joystick.h │ ├── linux_notify.c │ ├── linux_notify.h │ ├── main_loop.h │ ├── mappings.h │ ├── memfd.h │ ├── momentum-scroll.c │ ├── monitor.c │ ├── monotonic.c │ ├── nsgl_context.h │ ├── nsgl_context.m │ ├── null_init.c │ ├── null_joystick.c │ ├── null_joystick.h │ ├── null_monitor.c │ ├── null_platform.h │ ├── null_window.c │ ├── osmesa_context.c │ ├── osmesa_context.h │ ├── posix_thread.c │ ├── posix_thread.h │ ├── source-info.json │ ├── vulkan.c │ ├── window.c │ ├── wl_client_side_decorations.c │ ├── wl_client_side_decorations.h │ ├── wl_cursors.c │ ├── wl_cursors.h │ ├── wl_init.c │ ├── wl_monitor.c │ ├── wl_platform.h │ ├── wl_text_input.c │ ├── wl_text_input.h │ ├── wl_window.c │ ├── wlr-layer-shell-unstable-v1.xml │ ├── x11_init.c │ ├── x11_monitor.c │ ├── x11_platform.h │ ├── x11_window.c │ ├── xkb-compat-shim.h │ ├── xkb_glfw.c │ └── xkb_glfw.h ├── go.mod ├── go.sum ├── key_encoding.json ├── kittens/ │ ├── __init__.py │ ├── ask/ │ │ ├── __init__.py │ │ ├── choices.go │ │ ├── get_line.go │ │ ├── main.go │ │ └── main.py │ ├── broadcast/ │ │ ├── __init__.py │ │ └── main.py │ ├── choose_files/ │ │ ├── __init__.py │ │ ├── archive.go │ │ ├── calibre.go │ │ ├── cmd_preview.go │ │ ├── collection.go │ │ ├── ffmpeg.go │ │ ├── filters.go │ │ ├── footer.go │ │ ├── graphics.go │ │ ├── image_preview.go │ │ ├── main.go │ │ ├── main.py │ │ ├── preview.go │ │ ├── results.go │ │ ├── results_test.go │ │ ├── save-file.go │ │ ├── scan.go │ │ ├── scan_test.go │ │ └── search-bar.go │ ├── choose_fonts/ │ │ ├── __init__.py │ │ ├── backend.go │ │ ├── backend.py │ │ ├── face.go │ │ ├── faces.go │ │ ├── family_list.go │ │ ├── final.go │ │ ├── graphics.go │ │ ├── index_feature.go │ │ ├── list.go │ │ ├── main.go │ │ ├── main.py │ │ ├── styles.go │ │ ├── types.go │ │ └── ui.go │ ├── clipboard/ │ │ ├── __init__.py │ │ ├── legacy.go │ │ ├── main.go │ │ ├── main.py │ │ ├── read.go │ │ └── write.go │ ├── command_palette/ │ │ ├── __init__.py │ │ ├── main.go │ │ ├── main.py │ │ └── main_test.go │ ├── desktop_ui/ │ │ ├── __init__.py │ │ ├── main.go │ │ ├── main.py │ │ └── portal.go │ ├── diff/ │ │ ├── __init__.py │ │ ├── collect.go │ │ ├── collect_test.go │ │ ├── diff.go │ │ ├── highlight.go │ │ ├── main.go │ │ ├── main.py │ │ ├── mouse.go │ │ ├── patch.go │ │ ├── patch_test.go │ │ ├── render.go │ │ ├── search.go │ │ └── ui.go │ ├── hints/ │ │ ├── __init__.py │ │ ├── main.go │ │ ├── main.py │ │ ├── marks.go │ │ └── marks_test.go │ ├── hyperlinked_grep/ │ │ ├── __init__.py │ │ ├── main.go │ │ ├── main.py │ │ └── main_test.go │ ├── icat/ │ │ ├── __init__.py │ │ ├── detect.go │ │ ├── main.go │ │ ├── main.py │ │ ├── process_images.go │ │ ├── scaling_test.go │ │ └── transmit.go │ ├── notify/ │ │ ├── __init__.py │ │ ├── main.go │ │ └── main.py │ ├── pager/ │ │ ├── __init__.py │ │ ├── file_input.go │ │ ├── main.go │ │ └── main.py │ ├── panel/ │ │ ├── __init__.py │ │ ├── main.go │ │ └── main.py │ ├── query_terminal/ │ │ ├── __init__.py │ │ ├── main.go │ │ └── main.py │ ├── quick_access_terminal/ │ │ ├── __init__.py │ │ ├── main.go │ │ └── main.py │ ├── remote_file/ │ │ ├── __init__.py │ │ └── main.py │ ├── resize_window/ │ │ ├── __init__.py │ │ └── main.py │ ├── runner.py │ ├── show_key/ │ │ ├── __init__.py │ │ ├── kitty.go │ │ ├── legacy.go │ │ ├── main.go │ │ └── main.py │ ├── ssh/ │ │ ├── __init__.py │ │ ├── askpass.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── main.go │ │ ├── main.py │ │ ├── main_test.go │ │ ├── utils.go │ │ ├── utils.py │ │ └── utils_test.go │ ├── themes/ │ │ ├── __init__.py │ │ ├── list.go │ │ ├── main.go │ │ ├── main.py │ │ └── ui.go │ ├── transfer/ │ │ ├── __init__.py │ │ ├── algorithm.c │ │ ├── ftc.go │ │ ├── ftc_test.go │ │ ├── main.go │ │ ├── main.py │ │ ├── receive.go │ │ ├── rsync.pyi │ │ ├── send.go │ │ ├── send_test.go │ │ ├── utils.go │ │ └── utils.py │ ├── tui/ │ │ ├── __init__.py │ │ ├── dircolors.py │ │ ├── handler.py │ │ ├── images.py │ │ ├── line_edit.py │ │ ├── loop.py │ │ ├── operations.py │ │ ├── operations_stub.py │ │ ├── path_completer.py │ │ ├── progress.py │ │ ├── spinners.py │ │ └── utils.py │ └── unicode_input/ │ ├── __init__.py │ ├── main.go │ ├── main.py │ └── table.go ├── kitty/ │ ├── __init__.py │ ├── actions.py │ ├── alpha_blend.glsl │ ├── animation.c │ ├── animation.h │ ├── arches.h │ ├── arena.h │ ├── backtrace.h │ ├── banned.h │ ├── base64.h │ ├── bash.py │ ├── bgimage_fragment.glsl │ ├── bgimage_vertex.glsl │ ├── binary.h │ ├── blit_common.glsl │ ├── blit_fragment.glsl │ ├── blit_vertex.glsl │ ├── border_fragment.glsl │ ├── border_vertex.glsl │ ├── borders.py │ ├── boss.py │ ├── cell_defines.glsl │ ├── cell_fragment.glsl │ ├── cell_vertex.glsl │ ├── char-props-data.h │ ├── char-props.c │ ├── char-props.h │ ├── charsets.c │ ├── charsets.h │ ├── child-monitor.c │ ├── child.c │ ├── child.py │ ├── choose_entry.py │ ├── cleanup.c │ ├── cleanup.h │ ├── cli.py │ ├── cli_stub.py │ ├── client.py │ ├── clipboard.py │ ├── cocoa_window.h │ ├── cocoa_window.m │ ├── color-names.h │ ├── colors.c │ ├── colors.h │ ├── colors.py │ ├── conf/ │ │ ├── __init__.py │ │ ├── generate.py │ │ ├── types.py │ │ └── utils.py │ ├── config.py │ ├── constants.py │ ├── control-codes.h │ ├── core_text.m │ ├── cross-platform-random.h │ ├── crypto.c │ ├── cursor.c │ ├── cursor_trail.c │ ├── data-types.c │ ├── data-types.h │ ├── debug_config.py │ ├── decorations.c │ ├── decorations.h │ ├── desktop.c │ ├── disk-cache.c │ ├── disk-cache.h │ ├── entry_points.py │ ├── fast-file-copy.c │ ├── fast-file-copy.h │ ├── fast_data_types.pyi │ ├── file_transmission.py │ ├── fixed_size_deque.h │ ├── font-names.c │ ├── fontconfig.c │ ├── fonts/ │ │ ├── __init__.py │ │ ├── common.py │ │ ├── core_text.py │ │ ├── features.py │ │ ├── fontconfig.py │ │ ├── list.py │ │ └── render.py │ ├── fonts.c │ ├── fonts.h │ ├── freetype.c │ ├── freetype_render_ui_text.c │ ├── freetype_render_ui_text.h │ ├── gl-wrapper.c │ ├── gl-wrapper.h │ ├── gl.c │ ├── gl.h │ ├── glfw-wrapper.c │ ├── glfw-wrapper.h │ ├── glfw.c │ ├── glyph-cache.c │ ├── glyph-cache.h │ ├── graphics.c │ ├── graphics.h │ ├── graphics_fragment.glsl │ ├── graphics_vertex.glsl │ ├── guess_mime_type.py │ ├── history.c │ ├── history.h │ ├── hsluv.glsl │ ├── hyperlink.c │ ├── hyperlink.h │ ├── iqsort.h │ ├── key_encoding.c │ ├── key_encoding.py │ ├── key_names.py │ ├── keys.c │ ├── keys.h │ ├── keys.py │ ├── kittens.c │ ├── kitty-verstable.h │ ├── launch.py │ ├── launcher/ │ │ ├── cli-parser.h │ │ ├── cmdline.c │ │ ├── launcher.h │ │ ├── main.c │ │ ├── shlex.h │ │ ├── single-instance.c │ │ └── utils.h │ ├── layout/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── grid.py │ │ ├── interface.py │ │ ├── splits.py │ │ ├── stack.py │ │ ├── tall.py │ │ └── vertical.py │ ├── line-buf.c │ ├── line-buf.h │ ├── line.c │ ├── line.h │ ├── linear2srgb.glsl │ ├── lineops.h │ ├── logging.c │ ├── loop-utils.c │ ├── loop-utils.h │ ├── macos_process_info.c │ ├── main.py │ ├── marks.py │ ├── modes.h │ ├── monotonic.c │ ├── monotonic.h │ ├── mouse.c │ ├── multiprocessing.py │ ├── notifications.py │ ├── open_actions.py │ ├── options/ │ │ ├── __init__.py │ │ ├── definition.py │ │ ├── parse.py │ │ ├── to-c-generated.h │ │ ├── to-c.h │ │ ├── types.py │ │ └── utils.py │ ├── os_window_size.py │ ├── parse-graphics-command.h │ ├── parse-multicell-command.h │ ├── png-reader.c │ ├── png-reader.h │ ├── print-graphics.h │ ├── progress.py │ ├── rc/ │ │ ├── __init__.py │ │ ├── action.py │ │ ├── base.py │ │ ├── close_tab.py │ │ ├── close_window.py │ │ ├── create_marker.py │ │ ├── detach_tab.py │ │ ├── detach_window.py │ │ ├── disable_ligatures.py │ │ ├── env.py │ │ ├── focus_tab.py │ │ ├── focus_window.py │ │ ├── get_colors.py │ │ ├── get_text.py │ │ ├── goto_layout.py │ │ ├── kitten.py │ │ ├── last_used_layout.py │ │ ├── launch.py │ │ ├── load_config.py │ │ ├── ls.py │ │ ├── new_window.py │ │ ├── remove_marker.py │ │ ├── resize_os_window.py │ │ ├── resize_window.py │ │ ├── run.py │ │ ├── scroll_window.py │ │ ├── select_window.py │ │ ├── send_key.py │ │ ├── send_text.py │ │ ├── set_background_image.py │ │ ├── set_background_opacity.py │ │ ├── set_colors.py │ │ ├── set_enabled_layouts.py │ │ ├── set_font_size.py │ │ ├── set_spacing.py │ │ ├── set_tab_color.py │ │ ├── set_tab_title.py │ │ ├── set_user_vars.py │ │ ├── set_window_logo.py │ │ ├── set_window_title.py │ │ └── signal_child.py │ ├── remote_control.py │ ├── render_cache.py │ ├── resize.c │ ├── resize.h │ ├── rgb.py │ ├── rounded_rect_fragment.glsl │ ├── rounded_rect_vertex.glsl │ ├── rowcolumn-diacritics.c │ ├── safe-wrappers.h │ ├── screen.c │ ├── screen.h │ ├── screenshot_fragment.glsl │ ├── screenshot_vertex.glsl │ ├── search_query_parser.py │ ├── session.py │ ├── shaders.c │ ├── shaders.py │ ├── shell_integration.py │ ├── shlex.c │ ├── shm.py │ ├── short_uuid.py │ ├── simd-string-128.c │ ├── simd-string-256.c │ ├── simd-string-impl.h │ ├── simd-string.c │ ├── simd-string.h │ ├── simple_cli_definitions.py │ ├── srgb_gamma.h │ ├── state.c │ ├── state.h │ ├── systemd.c │ ├── tab_bar.py │ ├── tabs.py │ ├── terminfo.h │ ├── terminfo.py │ ├── text-cache.c │ ├── text-cache.h │ ├── threading.h │ ├── tint_fragment.glsl │ ├── tint_vertex.glsl │ ├── trail_fragment.glsl │ ├── trail_vertex.glsl │ ├── types.py │ ├── typing_compat.py │ ├── typing_compat.pyi │ ├── unicode-data.h │ ├── update_check.py │ ├── utils.glsl │ ├── utils.py │ ├── utmp.c │ ├── vt-parser.c │ ├── vt-parser.h │ ├── wcswidth.c │ ├── wcswidth.h │ ├── window.py │ ├── window_list.py │ ├── window_logo.c │ ├── window_logo.h │ ├── window_title_bar.py │ └── xdg.py ├── kitty_tests/ │ ├── CascadiaCode-Regular.otf │ ├── FiraCode-Medium.otf │ ├── GraphemeBreakTest.json │ ├── __init__.py │ ├── atexit.py │ ├── check_build.py │ ├── clipboard.py │ ├── command_palette.py │ ├── completion.py │ ├── crypto.py │ ├── datatypes.py │ ├── file_transmission.py │ ├── fonts.py │ ├── glfw.py │ ├── gr.py │ ├── graphics.py │ ├── keys.py │ ├── layout.py │ ├── main.py │ ├── mouse.py │ ├── multicell.py │ ├── notifications.py │ ├── open_actions.py │ ├── options.py │ ├── panels.py │ ├── parser.py │ ├── screen.py │ ├── search_query_parser.py │ ├── shell_integration.py │ ├── shm.py │ ├── ssh.py │ ├── tui.py │ ├── twemoji_smiley-cff2_colr_1.otf │ └── utmp.py ├── logo/ │ ├── attribution.txt │ └── make.py ├── publish.py ├── pyproject.toml ├── rsync-and-build.sh ├── session.vim ├── setup.py ├── shell-integration/ │ ├── bash/ │ │ └── kitty.bash │ ├── fish/ │ │ ├── vendor_completions.d/ │ │ │ ├── clone-in-kitty.fish │ │ │ ├── kitten.fish │ │ │ └── kitty.fish │ │ └── vendor_conf.d/ │ │ └── kitty-shell-integration.fish │ ├── ssh/ │ │ ├── bootstrap-utils.sh │ │ ├── bootstrap.py │ │ ├── bootstrap.sh │ │ ├── kitten │ │ └── kitty │ └── zsh/ │ ├── .zshenv │ ├── completions/ │ │ └── _kitty │ ├── kitty-integration │ └── kitty.zsh ├── shell.nix ├── staticcheck.conf ├── terminfo/ │ ├── kitty.termcap │ ├── kitty.terminfo │ └── x/ │ └── xterm-kitty ├── test.py ├── tools/ │ ├── README.rst │ ├── cli/ │ │ ├── bash.go │ │ ├── command.go │ │ ├── completion-main.go │ │ ├── completion-parse-args.go │ │ ├── completion.go │ │ ├── files.go │ │ ├── files_test.go │ │ ├── fish.go │ │ ├── group.go │ │ ├── help.go │ │ ├── markup/ │ │ │ └── prettify.go │ │ ├── option-from-string.go │ │ ├── option.go │ │ ├── parse-args.go │ │ ├── types_test.go │ │ ├── wcswidth_kitten.go │ │ └── zsh.go │ ├── cmd/ │ │ ├── at/ │ │ │ ├── complete_actions.go │ │ │ ├── env.go │ │ │ ├── launch.go │ │ │ ├── main.go │ │ │ ├── main_test.go │ │ │ ├── run.go │ │ │ ├── scroll_window.go │ │ │ ├── send_text.go │ │ │ ├── set_background_opacity.go │ │ │ ├── set_colors.go │ │ │ ├── set_font_size.go │ │ │ ├── set_spacing.go │ │ │ ├── set_tab_color.go │ │ │ ├── set_window_logo.go │ │ │ ├── shell.go │ │ │ ├── socket_io.go │ │ │ ├── template.go │ │ │ └── tty_io.go │ │ ├── atexit/ │ │ │ └── main.go │ │ ├── benchmark/ │ │ │ └── main.go │ │ ├── completion/ │ │ │ └── kitty.go │ │ ├── edit_in_kitty/ │ │ │ └── main.go │ │ ├── main.go │ │ ├── mouse_demo/ │ │ │ └── main.go │ │ ├── pytest/ │ │ │ └── main.go │ │ ├── run_shell/ │ │ │ └── main.go │ │ ├── show_error/ │ │ │ └── main.go │ │ ├── tool/ │ │ │ ├── confirm_and_run_shebang.go │ │ │ └── main.go │ │ └── update_self/ │ │ └── main.go │ ├── config/ │ │ ├── api.go │ │ ├── api_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── crypto/ │ │ └── crypto.go │ ├── disk_cache/ │ │ ├── api.go │ │ ├── implementation.go │ │ └── implementation_test.go │ ├── fzf/ │ │ ├── algo.go │ │ ├── algo_test.go │ │ ├── api.go │ │ └── types.go │ ├── highlight/ │ │ ├── api.go │ │ └── impl.go │ ├── icons/ │ │ └── file-types.go │ ├── ignorefiles/ │ │ ├── api.go │ │ ├── gitignore.go │ │ └── gitignore_test.go │ ├── rsync/ │ │ ├── algorithm.go │ │ ├── api.go │ │ └── api_test.go │ ├── simdstring/ │ │ ├── benchmark.sh │ │ ├── benchmarks_test.go │ │ ├── generate.go │ │ ├── generate.sh │ │ ├── intrinsics.go │ │ ├── intrinsics_test.go │ │ ├── scalar.go │ │ └── test.sh │ ├── themes/ │ │ ├── collection.go │ │ └── collection_test.go │ ├── tty/ │ │ ├── tty.go │ │ ├── tty_bsd.go │ │ └── tty_linux.go │ ├── tui/ │ │ ├── dcs_to_kitty.go │ │ ├── download_with_progress.go │ │ ├── graphics/ │ │ │ ├── collection.go │ │ │ ├── command.go │ │ │ └── command_test.go │ │ ├── hold.go │ │ ├── loop/ │ │ │ ├── api.go │ │ │ ├── capabilities.go │ │ │ ├── key-encoding.go │ │ │ ├── key-encoding_test.go │ │ │ ├── mouse.go │ │ │ ├── read.go │ │ │ ├── run.go │ │ │ ├── terminal-state.go │ │ │ ├── timers.go │ │ │ └── write.go │ │ ├── mouse.go │ │ ├── password.go │ │ ├── progress-bar.go │ │ ├── progress-bar_test.go │ │ ├── readline/ │ │ │ ├── actions.go │ │ │ ├── actions_test.go │ │ │ ├── api.go │ │ │ ├── completion.go │ │ │ ├── draw.go │ │ │ ├── history.go │ │ │ └── keys.go │ │ ├── render_lines.go │ │ ├── run.go │ │ ├── sgr/ │ │ │ ├── insert-formatting.go │ │ │ └── insert-formatting_test.go │ │ ├── shell_integration/ │ │ │ ├── api.go │ │ │ ├── api_test.go │ │ │ └── data.go │ │ ├── shortcuts/ │ │ │ ├── api.go │ │ │ └── implementation.go │ │ ├── spinners.go │ │ ├── subseq/ │ │ │ ├── score.go │ │ │ └── score_test.go │ │ ├── tmux.go │ │ └── ui_kitten.go │ ├── unicode_names/ │ │ ├── names.txt │ │ ├── query.go │ │ └── query_test.go │ ├── utils/ │ │ ├── atexit.go │ │ ├── atomic-write.go │ │ ├── base85/ │ │ │ ├── base85.go │ │ │ └── base85_test.go │ │ ├── cache.go │ │ ├── cached_values.go │ │ ├── clock_with_raw.go │ │ ├── clock_without_raw.go │ │ ├── colors.go │ │ ├── download_file.go │ │ ├── embed.go │ │ ├── filelock.go │ │ ├── filelock_test.go │ │ ├── hostname.go │ │ ├── humanize/ │ │ │ ├── bytes.go │ │ │ ├── times.go │ │ │ └── times_test.go │ │ ├── images/ │ │ │ ├── convert.go │ │ │ ├── formats.go │ │ │ ├── loading.go │ │ │ ├── rowcolumn_diacritics.go │ │ │ ├── serialize_test.go │ │ │ ├── to_rgb.go │ │ │ ├── to_rgba.go │ │ │ └── utils.go │ │ ├── io.go │ │ ├── iso8601.go │ │ ├── iso8601_test.go │ │ ├── levenshtein.go │ │ ├── longest-common.go │ │ ├── longest-common_test.go │ │ ├── mimetypes.go │ │ ├── misc.go │ │ ├── passwd.go │ │ ├── passwd_test.go │ │ ├── paths/ │ │ │ └── well_known.go │ │ ├── paths.go │ │ ├── random/ │ │ │ └── random.go │ │ ├── regexp.go │ │ ├── ring.go │ │ ├── ring_test.go │ │ ├── secrets/ │ │ │ └── tokens.go │ │ ├── select.go │ │ ├── select_posix.go │ │ ├── select_without_pselect.go │ │ ├── set.go │ │ ├── shell.go │ │ ├── shlex/ │ │ │ ├── ansi_c_escapes.go │ │ │ ├── shlex.go │ │ │ └── shlex_test.go │ │ ├── short-uuid.go │ │ ├── short-uuid_test.go │ │ ├── sockets.go │ │ ├── sockets_test.go │ │ ├── stream_decompressor.go │ │ ├── stream_decompressor_test.go │ │ ├── strings.go │ │ ├── strings_test.go │ │ ├── style/ │ │ │ ├── api.go │ │ │ ├── colorspaces.go │ │ │ ├── colorspaces_bench_test.go │ │ │ ├── colorspaces_test.go │ │ │ ├── indent-and-wrap.go │ │ │ ├── indent-and-wrap_test.go │ │ │ ├── wrapper.go │ │ │ └── wrapper_test.go │ │ ├── tar.go │ │ ├── tar_test.go │ │ ├── tmpfile_linux.go │ │ ├── tmpfile_others.go │ │ ├── tpmfile_test.go │ │ ├── types.go │ │ ├── unsafe.go │ │ ├── utf-8.go │ │ └── which.go │ ├── vt/ │ │ ├── cell.go │ │ ├── line.go │ │ └── linebuf.go │ └── wcswidth/ │ ├── char-props-data.go │ ├── char-props.go │ ├── char-props_test.go │ ├── escape-code-parser.go │ ├── escape-code-parser_test.go │ ├── iter.go │ ├── truncate.go │ ├── wcswidth.go │ └── wcswidth_test.go └── update-on-ox ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = 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/char-props-data.h] indent_style = tab [kittens/unicode_input/names.h] indent_style = tab [glfw/wayland-*-protocol.{c,h}] indent_style = tab ================================================ FILE: .gitattributes ================================================ kitty/terminfo.h linguist-generated=true terminfo/kitty.termcap linguist-generated=true terminfo/kitty.terminfo linguist-generated=true terminfo/x/xterm-kitty linguist-generated=true kitty/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/color-names.h 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.* linguist-generated=true *_generated_test.* linguist-generated=true *.py text diff=python *.m text diff=objc *.go text diff=go ================================================ FILE: .github/FUNDING.yml ================================================ github: kovidgoyal patreon: kovidgoyal liberapay: kovidgoyal custom: https://sw.kovidgoyal.net/kitty/support.html ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a bug report title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. XXX 2. YYY 3. ZZZ 4. See error **Screenshots** If applicable, add screenshots to help explain your problem. **Environment details** ``` Press Ctrl+Shift+F6 (cmd+option+comma on macOS) in kitty, to copy debug output about kitty and its configuration to the clipboard and paste it here. On older versions of kitty, run kitty --debug-config instead ``` **Additional context** Try to reproduce the problem with `kitty --config NONE` if you cannot then post a minimal kitty.conf that reproduces the problem. If the problem involves interaction with some other terminal program post a minimal config for that program to reproduce the problem as well. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/copilot-instructions.md ================================================ Before implementing any code changes or responding to a request, run the following three commands: sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev zsh libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev libxxhash-dev uuid-dev libsimde-dev libsystemd-dev libcairo2-dev zsh bash dash systemd-coredump gdb sudo chmod -R og-w /usr/share/zsh ./dev.sh build This will download needed dependencies, then create all generated files and build the project, making it ready for inspection. # Repository Build & Test Instructions ## Build Procedures - **Build command:** Run `./dev.sh build` to build the project - Run `gen/config.py` to update generated config parsing code for both kitty and kitten. The `gen/go_code.py` generator is run automatically by the build command to keep generated Go code files up to date. - To build individual kittens use the build command above **do not** try to run go build yourself. Once a build is done, the kitty and kitten binaries will be in the `kitty/launcher` directory. Note that the kitty binary can run python files using `kitty +launch file.py`. When it does so the kitty fast_data_types module is available to the python code. ## Test Procedures - To run the complete test suite, run `./test.py` - To run a specific test, run `./test.py test-name` t `test-name` is the name of the test without the leading `test_` for Python tests and without the leading `Test` for Go tests. - Do not use go test or ./setup.py test to run tests ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" groups: all-go-deps: patterns: - "*" # group all non-security update PRs cooldown: default-days: 7 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" groups: actions: patterns: - "*" cooldown: default-days: 7 ================================================ FILE: .github/workflows/ci.py ================================================ #!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2020, Kovid Goyal import glob import io import lzma import os import shlex import shutil import subprocess import sys import tarfile import time from urllib.request import Request, urlopen BUNDLE_URL = 'https://download.calibre-ebook.com/ci/kitty/{}-64.tar.xz' FONTS_URL = 'https://download.calibre-ebook.com/ci/fonts.tar.xz' NERD_URL = 'https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.tar.xz' is_bundle = os.environ.get('KITTY_BUNDLE') == '1' is_codeql = os.environ.get('KITTY_CODEQL') == '1' is_macos = 'darwin' in sys.platform.lower() SW = '' def do_print_crash_reports() -> None: print('Printing available crash reports...') if is_macos: end_time = time.monotonic() + 90 while time.monotonic() < end_time: time.sleep(1) items = glob.glob(os.path.join(os.path.expanduser('~/Library/Logs/DiagnosticReports'), 'kitty-*.ips')) if items: break if items: time.sleep(1) print(os.path.basename(items[0])) sdir = os.path.dirname(os.path.abspath(__file__)) subprocess.check_call([sys.executable, os.path.join(sdir, 'macos_crash_report.py'), items[0]]) else: run('sh -c "echo bt | coredumpctl debug"') print(flush=True) def run(*a: str, print_crash_reports: bool = False) -> None: if len(a) == 1: a = tuple(shlex.split(a[0])) cmd = ' '.join(map(shlex.quote, a)) print(cmd) sys.stdout.flush() ret = subprocess.Popen(a).wait() if ret != 0: if ret < 0: import signal try: sig = signal.Signals(-ret) except ValueError: pass else: if print_crash_reports: do_print_crash_reports() raise SystemExit(f'The following process was killed by signal: {sig.name}:\n{cmd}') raise SystemExit(f'The following process failed with exit code: {ret}:\n{cmd}') def download_with_retry(url: str | Request, count: int = 5) -> bytes: for i in range(count): try: print('Downloading', getattr(url, 'full_url', url), flush=True) with urlopen(url) as f: ans: bytes = f.read() return ans except Exception as err: if getattr(err, 'code', -1) == 403: raise if i >= count - 1: raise print(f'Download failed with error {err} retrying...', file=sys.stderr) time.sleep(1) return b'' def install_fonts() -> None: data = download_with_retry(FONTS_URL) fonts_dir = os.path.expanduser('~/Library/Fonts' if is_macos else '~/.local/share/fonts') os.makedirs(fonts_dir, exist_ok=True) with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf: try: tf.extractall(fonts_dir, filter='fully_trusted') except TypeError: tf.extractall(fonts_dir) data = download_with_retry(NERD_URL) with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf: try: tf.extractall(fonts_dir, filter='fully_trusted') except TypeError: tf.extractall(fonts_dir) def install_deps() -> None: print('Installing kitty dependencies...') sys.stdout.flush() if is_macos: if not is_codeql: # for some reason brew fails on CodeQL we dont need it anyway items = [x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew ')] openssl = 'openssl' items.remove('go') # already installed by ci.yml import ssl if ssl.OPENSSL_VERSION_INFO[0] == 1: openssl += '@1.1' run('brew', 'install', 'fish', openssl, *items) else: run('sudo apt-get update') run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates' ' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev zsh' ' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev libxxhash-dev uuid-dev' ' libsimde-dev libsystemd-dev libcairo2-dev zsh bash dash systemd-coredump gdb') # for some reason these directories are world writable which causes zsh # compinit to break run('sudo chmod -R og-w /usr/share/zsh') if is_bundle: install_bundle() else: cmd = 'python3 -m pip install Pillow pygments' if sys.version_info[:2] < (3, 7): cmd += ' importlib-resources dataclasses' run(cmd) install_fonts() def build_kitty() -> None: python = shutil.which('python3') if is_bundle else sys.executable cmd = f'{python} setup.py build --verbose' if is_macos: cmd += ' --debug' # for better crash report to debug SIGILL issue if os.environ.get('KITTY_SANITIZE') == '1': cmd += ' --debug --sanitize' run(cmd) def test_kitty() -> None: if is_macos: run('ulimit -c unlimited') run('sudo chmod -R 777 /cores') run('./test.py', print_crash_reports=True) def package_kitty() -> None: python = 'python3' if is_macos else 'python' run(f'{python} setup.py linux-package --update-check-interval=0 --verbose') if is_macos: run('python3 setup.py kitty.app --update-check-interval=0 --verbose') run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"') def replace_in_file(path: str, src: str, dest: str) -> None: with open(path, 'r+') as f: n = f.read().replace(src, dest) f.seek(0), f.truncate() f.write(n) def setup_bundle_env() -> None: global SW os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw') os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig') if is_macos: os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH']) else: os.environ['LD_LIBRARY_PATH'] = os.path.join(SW, 'lib') os.environ['PYTHONHOME'] = SW os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH']) def install_bundle(dest: str = '', which: str = '') -> None: dest = dest or SW cwd = os.getcwd() os.makedirs(dest, exist_ok=True) os.chdir(dest) which = which or ('macos' if is_macos else 'linux') data = download_with_retry(BUNDLE_URL.format(which)) with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf: try: tf.extractall(filter='fully_trusted') except TypeError: tf.extractall() if not is_macos: replaced = 0 for dirpath, dirnames, filenames in os.walk('.'): for f in filenames: if f.endswith('.pc') or (f.endswith('.py') and f.startswith('_sysconfig')): replace_in_file(os.path.join(dirpath, f), '/sw/sw', dest) replaced += 1 if replaced < 2: raise SystemExit('Failed to replace path to SW in bundle') os.chdir(cwd) def install_grype(exe: str = '/tmp/grype') -> str: raw = download_with_retry('https://download.calibre-ebook.com/ci/grype.xz') raw = lzma.decompress(raw) with open(exe, 'wb') as f: f.write(raw) os.fchmod(f.fileno(), 0o755) subprocess.check_call([exe, 'db', 'update']) return exe IGNORED_DEPENDENCY_CVES = [ # Python stdlib 'CVE-2025-8194', # DoS in tarfile 'CVE-2025-6069', # DoS in HTMLParser 'CVE-2025-13836', # DoS in http client reading from malicious server 'CVE-2025-12084', # DoS in xml.dom.minidom unused in kitty 'CVE-2025-13837', # DoS in plistlib reading plist. We only use plistlib for writing 'CVE-2025-6075', # Quadratic complexity in os.path.expandvars() # python stdlib all these are erroneously marked as fixed in python 3.15 # when it hasnt even been released. Sigh. 'CVE-2026-1299', 'CVE-2026-0865', 'CVE-2025-15282', 'CVE-2026-0672', 'CVE-2025-15366', 'CVE-2025-15367', 'CVE-2025-12781', 'CVE-2025-11468', 'CVE-2026-2297', 'CVE-2026-3644', 'CVE-2026-4224', # github.com/nwaples/rardecode/v2 'CVE-2025-11579', # rardecode is version 2.2.1, not vulnerable 'CVE-2026-2673', # openssl fix not released ] def check_dependencies() -> None: grype = install_grype() with open((gc := os.path.expanduser('~/.grype.yml')), 'w') as f: print('ignore:', file=f) for x in IGNORED_DEPENDENCY_CVES: print(' - vulnerability:', x, file=f) dest = os.path.join(SW, 'linux') os.makedirs(dest, exist_ok=True) install_bundle(dest, os.path.basename(dest)) dest = os.path.join(SW, 'macos') os.makedirs(dest, exist_ok=True) install_bundle(dest, os.path.basename(dest)) cmdline = [grype, '--by-cve', '--config', gc, '--fail-on', 'medium', '--only-fixed', '--add-cpes-if-none'] if (subprocess.run(cmdline + ['dir:' + SW])).returncode != 0: raise SystemExit('grype found problems during filesystem scan') # Now test against the SBOM import runpy orig = sys.argv, sys.stdout sys.argv = ['bypy', 'sbom', 'kovidgoyal/kitty', '1.0.0'] buf = io.StringIO() sys.stdout = buf runpy.run_path('bypy-src') sys.argv, sys.stdout = orig print(buf.getvalue()) if (subprocess.run(cmdline, input=buf.getvalue().encode())).returncode != 0: raise SystemExit('grype found problems during SBOM scan') def main() -> None: if is_bundle: setup_bundle_env() else: if not is_macos and 'pythonLocation' in os.environ: os.environ['LD_LIBRARY_PATH'] = os.path.join(os.environ['pythonLocation'], 'lib') action = sys.argv[-1] if action in ('build', 'package'): install_deps() if action == 'build': build_kitty() elif action == 'package': package_kitty() elif action == 'test': test_kitty() elif action == 'test': test_kitty() elif action == 'govulncheck': subprocess.check_call(['go', 'install', 'golang.org/x/vuln/cmd/govulncheck@latest']) subprocess.check_call(['govulncheck', '-mode=binary', 'kitty/launcher/kitten']) subprocess.check_call(['govulncheck', './...']) elif action == 'gofmt': q = subprocess.check_output('gofmt -s -l tools kittens'.split()).decode() if q.strip(): q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.strip().splitlines())).strip() if q: raise SystemExit(q) elif action == 'check-dependencies': check_dependencies() else: raise SystemExit(f'Unknown action: {action}') if __name__ == '__main__': main() ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] env: CI: 'true' ASAN_OPTIONS: detect_leaks=0 LC_ALL: en_US.UTF-8 LANG: en_US.UTF-8 permissions: contents: read # to fetch code (actions/checkout) jobs: linux: name: Linux (python=${{ matrix.pyver }} cc=${{ matrix.cc }} sanitize=${{ matrix.sanitize }}) runs-on: ubuntu-latest env: CC: ${{ matrix.cc }} KITTY_SANITIZE: ${{ matrix.sanitize }} strategy: matrix: python: [a, b, c] cc: [gcc, clang] include: - python: a pyver: "3.10" sanitize: 0 - python: b pyver: "3.11" sanitize: 1 - python: c pyver: "3.12" sanitize: 1 exclude: - python: a cc: clang - python: b cc: clang - python: c cc: gcc steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 10 persist-credentials: false - name: Set up Python ${{ matrix.pyver }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.pyver }} - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Build kitty run: python .github/workflows/ci.py build - name: Test kitty run: python .github/workflows/ci.py test linux-package: name: Linux package runs-on: ubuntu-latest env: CFLAGS: -funsigned-char steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 0 # needed for :commit: docs role persist-credentials: false - name: Test for trailing whitespace run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi - name: Test for bad code block formatting run: if grep -Inr ':code:`\s' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Space at code block start found, aborting.; exit 1; fi - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.13" - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod cache: false - name: Cache Go build artifacts separately uses: actions/cache@v5 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-golang-static-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-golang-static- - name: Install build-only deps run: python -m pip install -r docs/requirements.txt ruff mypy types-requests types-docutils types-Pygments - name: Run ruff run: ruff check . - name: Run gofmt run: go version && python .github/workflows/ci.py gofmt - name: Build kitty package run: python .github/workflows/ci.py package - name: Build kitty run: python setup.py build --debug - name: Run mypy run: which python && python -m mypy --version && ./test.py mypy - name: Run go vet run: go version && go vet -tags testing ./... - name: Build man page run: make FAIL_WARN=1 man - name: Build HTML docs run: make FAIL_WARN=1 html - name: Build static kittens run: python setup.py build-static-binaries bundle: name: Bundle test (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] env: KITTY_BUNDLE: 1 steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 10 persist-credentials: false - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Build kitty run: which python3 && python3 .github/workflows/ci.py build - name: Test kitty run: python3 .github/workflows/ci.py test brew: name: macOS Brew runs-on: macos-latest steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 0 # needed for :commit: docs role persist-credentials: false - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.11" - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Build kitty run: python3 .github/workflows/ci.py build - name: Test kitty run: python3 .github/workflows/ci.py test - name: Install deps for docs run: python3 -m pip install -r docs/requirements.txt - name: Builds docs run: make FAIL_WARN=1 docs - name: Build kitty package run: python3 .github/workflows/ci.py package - name: Run benchmarks run: ./benchmark.py linux-dev: name: Test ./dev.sh and benchmark runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 10 persist-credentials: false - name: Install build deps run: sudo apt-get update && sudo apt-get install -y curl xz-utils build-essential git pkg-config libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-dev libxkbcommon-x11-dev libfontconfig-dev libx11-xcb-dev libdbus-1-dev - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Build kitty run: ./dev.sh build - name: Run benchmarks run: ./benchmark.py ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 22 * * 5' jobs: CodeQL-Build: permissions: contents: read # to fetch code (actions/checkout) security-events: write # to upload SARIF results (github/codeql-action/analyze) strategy: fail-fast: false matrix: include: - language: python os: ubuntu-latest - language: c os: ubuntu-latest - language: c os: macos-latest - language: go os: ubuntu-latest - language: actions os: ubuntu-latest runs-on: ${{ matrix.os }} env: KITTY_BUNDLE: 1 KITTY_CODEQL: 1 steps: - name: Checkout repository uses: actions/checkout@v6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 persist-credentials: false - name: Install Go if: matrix.language == 'c' || matrix.language == 'go' uses: actions/setup-go@v6 with: go-version-file: go.mod # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} trap-caching: false - name: Build kitty if: matrix.language == 'c' || matrix.language == 'go' run: python3 .github/workflows/ci.py build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 - name: Run govulncheck if: matrix.language == 'go' run: python3 .github/workflows/ci.py govulncheck ================================================ FILE: .github/workflows/depscan.yml ================================================ name: Depscan on: push: branches: [master] schedule: - cron: '0 12 * * 5' env: CI: 'true' ASAN_OPTIONS: detect_leaks=0 LC_ALL: en_US.UTF-8 LANG: en_US.UTF-8 permissions: contents: read # to fetch code (actions/checkout) jobs: dependecy-scanner: name: Scan dependencies for vulnerabilities runs-on: ubuntu-latest env: KITTY_BUNDLE: 1 steps: - name: Checkout source code uses: actions/checkout@v6 with: fetch-depth: 10 persist-credentials: false - name: Checkout bypy uses: actions/checkout@v6 with: fetch-depth: 1 persist-credentials: false repository: kovidgoyal/bypy path: bypy-src - name: Check dependencies run: python3 .github/workflows/ci.py check-dependencies ================================================ FILE: .github/workflows/macos_crash_report.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import json import posixpath import sys from collections import namedtuple from datetime import datetime from enum import Enum from functools import cached_property from typing import IO, List, Mapping, Optional Frame = namedtuple('Frame', 'image_name image_base image_offset symbol symbol_offset') Register = namedtuple('Register', 'name value') def surround(x: str, start: int, end: int) -> str: if sys.stdout.isatty(): x = f'\033[{start}m{x}\033[{end}m' return x def cyan(x: str) -> str: return surround(x, 96, 39) def bold(x: str) -> str: return surround(x, 1, 22) class BugType(Enum): WatchdogTimeout = '28' BasebandStats = '195' GPUEvent = '284' Sandbox = '187' TerminatingStackshot = '509' ServiceWatchdogTimeout = '29' Session = '179' LegacyStackshot = '188' MACorrelation = '197' iMessages = '189' log_power = '278' PowerLog = 'powerlog' DuetKnowledgeCollector2 = '58' BridgeRestore = '83' LegacyJetsam = '198' ExcResource_385 = '385' Modem = '199' Stackshot = '288' SystemInformation = 'system_profile' Jetsam_298 = '298' MemoryResource = '30' Bridge = '31' DifferentialPrivacy = 'diff_privacy' FirmwareIntegrity = '32' CoreAnalytics_33 = '33' AutoBugCapture = '34' EfiFirmwareIntegrity = '35' SystemStats = '36' AnonSystemStats = '37' Crash_9 = '9' Jetsam_98 = '98' LDCM = '100' Panic_10 = '10' Spin = '11' CLTM = '101' Hang = '12' Panic_110 = '110' ConnectionFailure = '13' MessageTracer = '14' LowBattery = '120' Siri = '201' ShutdownStall = '17' Panic_210 = '210' SymptomsCPUUsage = '202' AssumptionViolation = '18' CoreHandwriting = 'chw' IOMicroStackShot = '44' CoreAnalytics_211 = '211' SiriAppPrediction = '203' spin_45 = '45' PowerMicroStackshots = '220' BTMetadata = '212' SystemMemoryReset = '301' ResetCount = '115' AutoBugCapture_204 = '204' WifiCrashBinary = '221' MicroRunloopHang = '310' Rosetta = '213' glitchyspin = '302' System = '116' IOPowerSources = '141' PanicStats = '205' PowerLog_230 = '230' LongRunloopHang = '222' HomeProductsAnalytics = '311' DifferentialPrivacy_150 = '150' Rhodes = '214' ProactiveEventTrackerTransparency = '303' WiFi = '117' SymptomsCPUWakes = '142' SymptomsCPUUsageFatal = '206' Crash_109 = '109' ShortRunloopHang = '223' CoreHandwriting_231 = '231' ForceReset = '151' SiriAppSelection = '215' PrivateFederatedLearning = '304' Bluetooth = '118' SCPMotion = '143' HangSpin = '207' StepCount = '160' RTCTransparency = '224' DiagnosticRequest = '312' MemorySnapshot = '152' Rosetta_B = '216' AudioAccessory = '305' General = '119' HotSpotIOMicroSS = '144' GeoServicesTransparency = '233' MotionState = '161' AppStoreTransparency = '225' SiriSearchFeedback = '313' BearTrapReserved = '153' Portrait = '217' AWDMetricLog = 'metriclog' SymptomsIO = '145' SubmissionReserved = '170' WifiCrash = '209' Natalies = '162' SecurityTransparency = '226' BiomeMapReduce = '234' MemoryGraph = '154' MultichannelAudio = '218' honeybee_payload = '146' MesaReserved = '171' WifiSensing = '235' SiriMiss = '163' ExcResourceThreads_227 = '227' TestA = 'T01' NetworkUsage = '155' WifiReserved = '180' SiriActionPrediction = '219' honeybee_heartbeat = '147' ECCEvent = '172' KeyTransparency = '236' SubDiagHeartBeat = '164' ThirdPartyHang = '228' OSFault = '308' CoreTime = '156' WifiDriverReserved = '181' Crash_309 = '309' honeybee_issue = '148' CellularPerfReserved = '173' TestB = 'T02' StorageStatus = '165' SiriNotificationTransparency = '229' TestC = 'T03' CPUMicroSS = '157' AccessoryUpdate = '182' xprotect = '20' MultitouchFirmware = '149' MicroStackshot = '174' AppLaunchDiagnostics = '238' KeyboardAccuracy = '166' GPURestart = '21' FaceTime = '191' DuetKnowledgeCollector = '158' OTASUpdate = '183' ExcResourceThreads_327 = '327' ExcResource_22 = '22' DuetDB = '175' ThirdPartyHangDeveloper = '328' PrivacySettings = '167' GasGauge = '192' MicroStackShots = '23' BasebandCrash = '159' GPURestart_184 = '184' SystemWatchdogCrash = '409' FlashStatus = '176' SleepWakeFailure = '24' CarouselEvent = '168' AggregateD = '193' WakeupsMonitorViolation = '25' DifferentialPrivacy_50 = '50' ExcResource_185 = '185' UIAutomation = '177' ping = '26' SiriTransaction = '169' SURestore = '194' KtraceStackshot = '186' WirelessDiagnostics = '27' PowerLogLite = '178' SKAdNetworkAnalytics = '237' HangWorkflowResponsiveness = '239' CompositorClientHang = '243' class CrashReportBase: def __init__(self, metadata: Mapping, data: str, filename: str = None): self.filename = filename self._metadata = metadata self._data = data self._parse() def _parse(self): self._is_json = False try: modified_data = self._data if '\n \n' in modified_data: modified_data, rest = modified_data.split('\n \n', 1) rest = '",' + rest.split('",', 1)[1] modified_data += rest self._data = json.loads(modified_data) self._is_json = True except json.decoder.JSONDecodeError: pass @cached_property def bug_type(self) -> BugType: return BugType(self.bug_type_str) @cached_property def bug_type_str(self) -> str: return self._metadata['bug_type'] @cached_property def incident_id(self): return self._metadata.get('incident_id') @cached_property def timestamp(self) -> datetime: timestamp = self._metadata.get('timestamp') timestamp_without_timezone = timestamp.rsplit(' ', 1)[0] return datetime.strptime(timestamp_without_timezone, '%Y-%m-%d %H:%M:%S.%f') @cached_property def name(self) -> str: return self._metadata.get('name') def __repr__(self) -> str: filename = '' if self.filename: filename = f'FILENAME:{posixpath.basename(self.filename)} ' return f'<{self.__class__} {filename}TIMESTAMP:{self.timestamp}>' def __str__(self) -> str: filename = '' if self.filename: filename = self.filename return cyan(f'{self.incident_id} {self.timestamp}\n{filename}\n\n') class UserModeCrashReport(CrashReportBase): def _parse_field(self, name: str) -> str: name += ':' for line in self._data.split('\n'): if line.startswith(name): field = line.split(name, 1)[1] field = field.strip() return field @cached_property def faulting_thread(self) -> int: if self._is_json: return self._data['faultingThread'] else: return int(self._parse_field('Triggered by Thread')) @cached_property def frames(self) -> List[Frame]: result = [] if self._is_json: thread_index = self.faulting_thread images = self._data['usedImages'] for frame in self._data['threads'][thread_index]['frames']: image = images[frame['imageIndex']] result.append( Frame(image_name=image.get('path'), image_base=image.get('base'), symbol=frame.get('symbol'), image_offset=frame.get('imageOffset'), symbol_offset=frame.get('symbolLocation'))) else: in_frames = False for line in self._data.split('\n'): if in_frames: splitted = line.split() if len(splitted) == 0: break assert splitted[-2] == '+' image_base = splitted[-3] if image_base.startswith('0x'): result.append(Frame(image_name=splitted[1], image_base=int(image_base, 16), symbol=None, image_offset=int(splitted[-1]), symbol_offset=None)) else: # symbolicated result.append(Frame(image_name=splitted[1], image_base=None, symbol=image_base, image_offset=None, symbol_offset=int(splitted[-1]))) if line.startswith(f'Thread {self.faulting_thread} Crashed:'): in_frames = True return result @cached_property def registers(self) -> List[Register]: result = [] if self._is_json: thread_index = self._data['faultingThread'] thread_state = self._data['threads'][thread_index]['threadState'] if 'x' in thread_state: for i, reg_x in enumerate(thread_state['x']): result.append(Register(name=f'x{i}', value=reg_x['value'])) for i, (name, value) in enumerate(thread_state.items()): if name == 'x': for j, reg_x in enumerate(value): result.append(Register(name=f'x{j}', value=reg_x['value'])) else: if isinstance(value, dict): result.append(Register(name=name, value=value['value'])) else: in_frames = False for line in self._data.split('\n'): if in_frames: splitted = line.split() if len(splitted) == 0: break for i in range(0, len(splitted), 2): register_name = splitted[i] if not register_name.endswith(':'): break register_name = register_name[:-1] register_value = int(splitted[i + 1], 16) result.append(Register(name=register_name, value=register_value)) if line.startswith(f'Thread {self.faulting_thread} crashed with ARM Thread State'): in_frames = True return result @cached_property def exception_type(self): if self._is_json: return self._data['exception'].get('type') else: return self._parse_field('Exception Type') @cached_property def exception_subtype(self) -> Optional[str]: if self._is_json: return self._data['exception'].get('subtype') else: return self._parse_field('Exception Subtype') @cached_property def application_specific_information(self) -> Optional[str]: result = '' if self._is_json: asi = self._data.get('asi') if asi is None: return None return asi else: in_frames = False for line in self._data.split('\n'): if in_frames: line = line.strip() if len(line) == 0: break result += line + '\n' if line.startswith('Application Specific Information:'): in_frames = True result = result.strip() if not result: return None return result def __str__(self) -> str: result = super().__str__() result += bold(f'Exception: {self.exception_type}\n') if self.exception_subtype: result += bold('Exception Subtype: ') result += f'{self.exception_subtype}\n' if self.application_specific_information: result += bold('Application Specific Information: ') result += str(self.application_specific_information) result += '\n' result += bold('Registers:') for i, register in enumerate(self.registers): if i % 4 == 0: result += '\n' result += f'{register.name} = 0x{register.value:016x} '.rjust(30) result += '\n\n' result += bold('Frames:\n') for frame in self.frames: image_base = '_HEADER' if frame.image_base is not None: image_base = f'0x{frame.image_base:x}' result += f'\t[{frame.image_name}] {image_base}' if frame.image_offset: result += f' + 0x{frame.image_offset:x}' if frame.symbol is not None: result += f' ({frame.symbol} + 0x{frame.symbol_offset:x})' result += '\n' return result def get_crash_report_from_file(crash_report_file: IO) -> CrashReportBase: metadata = json.loads(crash_report_file.readline()) try: bug_type = BugType(metadata['bug_type']) except ValueError: return CrashReportBase(metadata, crash_report_file.read(), crash_report_file.name) bug_type_parsers = { BugType.Crash_109: UserModeCrashReport, BugType.Crash_309: UserModeCrashReport, BugType.ExcResourceThreads_327: UserModeCrashReport, BugType.ExcResource_385: UserModeCrashReport, } parser = bug_type_parsers.get(bug_type) if parser is None: return CrashReportBase(metadata, crash_report_file.read(), crash_report_file.name) return parser(metadata, crash_report_file.read(), crash_report_file.name) if __name__ == '__main__': with open(sys.argv[-1]) as f: print(get_crash_report_from_file(f)) ================================================ FILE: .gitignore ================================================ *.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 _codeql_detected_source_root ================================================ FILE: 3rdparty/base64/LICENSE ================================================ Copyright (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. ================================================ FILE: 3rdparty/base64/README.md ================================================ # 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. ================================================ FILE: 3rdparty/base64/config.h ================================================ ================================================ FILE: 3rdparty/base64/include/libbase64.h ================================================ #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 */ ================================================ FILE: 3rdparty/base64/lib/arch/avx/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/avx/enc_loop_asm.c ================================================ // 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 ================================================ FILE: 3rdparty/base64/lib/arch/avx2/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/avx2/dec_loop.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/avx2/dec_reshuffle.c ================================================ static 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)); } ================================================ FILE: 3rdparty/base64/lib/arch/avx2/enc_loop.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/avx2/enc_loop_asm.c ================================================ // 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 ================================================ FILE: 3rdparty/base64/lib/arch/avx2/enc_reshuffle.c ================================================ static 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 } ================================================ FILE: 3rdparty/base64/lib/arch/avx2/enc_translate.c ================================================ static 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)); } ================================================ FILE: 3rdparty/base64/lib/arch/avx512/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/avx512/enc_loop.c ================================================ static 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; } } ================================================ FILE: 3rdparty/base64/lib/arch/avx512/enc_reshuffle_translate.c ================================================ // 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); } ================================================ FILE: 3rdparty/base64/lib/arch/generic/32/dec_loop.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/generic/32/enc_loop.c ================================================ static 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); } ================================================ FILE: 3rdparty/base64/lib/arch/generic/64/enc_loop.c ================================================ static 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); } ================================================ FILE: 3rdparty/base64/lib/arch/generic/codec.c ================================================ #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" } ================================================ FILE: 3rdparty/base64/lib/arch/generic/dec_head.c ================================================ int 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: ================================================ FILE: 3rdparty/base64/lib/arch/generic/dec_tail.c ================================================ 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; ================================================ FILE: 3rdparty/base64/lib/arch/generic/enc_head.c ================================================ // 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: ================================================ FILE: 3rdparty/base64/lib/arch/generic/enc_tail.c ================================================ 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; ================================================ FILE: 3rdparty/base64/lib/arch/neon32/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/neon32/dec_loop.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/neon32/enc_loop.c ================================================ #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; } } ================================================ FILE: 3rdparty/base64/lib/arch/neon32/enc_reshuffle.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/neon32/enc_translate.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/neon64/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/neon64/dec_loop.c ================================================ // 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; } ================================================ FILE: 3rdparty/base64/lib/arch/neon64/enc_loop.c ================================================ static 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; } } ================================================ FILE: 3rdparty/base64/lib/arch/neon64/enc_loop_asm.c ================================================ // 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 ================================================ FILE: 3rdparty/base64/lib/arch/neon64/enc_reshuffle.c ================================================ static 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; } ================================================ FILE: 3rdparty/base64/lib/arch/sse41/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/sse42/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/codec.c ================================================ #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 } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/dec_loop.c ================================================ // 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; } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/dec_reshuffle.c ================================================ static 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 } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/enc_loop.c ================================================ static 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); } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/enc_loop_asm.c ================================================ // 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 ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/enc_reshuffle.c ================================================ static 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 } ================================================ FILE: 3rdparty/base64/lib/arch/ssse3/enc_translate.c ================================================ static 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)); } ================================================ FILE: 3rdparty/base64/lib/codec_choose.c ================================================ #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; } ================================================ FILE: 3rdparty/base64/lib/codecs.h ================================================ #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); ================================================ FILE: 3rdparty/base64/lib/env.h ================================================ #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 ================================================ FILE: 3rdparty/base64/lib/exports.txt ================================================ base64_encode base64_stream_encode base64_stream_encode_init base64_stream_encode_final base64_decode base64_stream_decode base64_stream_decode_init ================================================ FILE: 3rdparty/base64/lib/lib.c ================================================ #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; } ================================================ FILE: 3rdparty/base64/lib/lib_openmp.c ================================================ // 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; } ================================================ FILE: 3rdparty/base64/lib/tables/.gitignore ================================================ table_generator ================================================ FILE: 3rdparty/base64/lib/tables/Makefile ================================================ .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 $@ $^ ================================================ FILE: 3rdparty/base64/lib/tables/table_dec_32bit.h ================================================ #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 ================================================ FILE: 3rdparty/base64/lib/tables/table_enc_12bit.h ================================================ #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 }; ================================================ FILE: 3rdparty/base64/lib/tables/table_enc_12bit.py ================================================ #!/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() ================================================ FILE: 3rdparty/base64/lib/tables/table_generator.c ================================================ /** * * 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; } ================================================ FILE: 3rdparty/base64/lib/tables/tables.c ================================================ #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 ================================================ FILE: 3rdparty/base64/lib/tables/tables.h ================================================ #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 ================================================ FILE: 3rdparty/ringbuf/ringbuf.c ================================================ /* * 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; } ================================================ FILE: 3rdparty/ringbuf/ringbuf.h ================================================ #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); ================================================ FILE: 3rdparty/verstable.h ================================================ /*------------------------------------------------- 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 ================================================ FILE: Brewfile ================================================ brew "zlib" brew "xxhash" brew "simde" brew "python" brew "imagemagick" brew "harfbuzz" brew "sphinx-doc" brew "go" ================================================ FILE: CHANGELOG.rst ================================================ See https://sw.kovidgoyal.net/kitty/changelog/ ================================================ FILE: CONTRIBUTING.md ================================================ ### 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. ================================================ FILE: INSTALL.md ================================================ [To build from source](https://sw.kovidgoyal.net/kitty/build/) [Pre-built binaries](https://sw.kovidgoyal.net/kitty/binary/) ================================================ FILE: LICENSE ================================================ 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 . ================================================ FILE: Makefile ================================================ ifdef 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 ================================================ FILE: README.asciidoc ================================================ = kitty - the fast, feature-rich, cross-platform, GPU based terminal If you live in the terminal, *kitty* is made for **you**! 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"] ================================================ FILE: SECURITY.md ================================================ # 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 a private security advisory using the GitHub security advisory facility. Note that I will generally respond to security communication within 72 hours. Once the bug is confirmed, it will be fixed or at least mitigated within another 72 hours, at which time the fix will typically be committed to master and hence be public. That timeline might be extended based on the severity of the issue and the current state of master in terms of making a new release, if so, it will be done in consultation with the issue reporter. ================================================ FILE: __main__.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2015, Kovid Goyal if __name__ == '__main__': from kitty.entry_points import main main() ================================================ FILE: benchmark.py ================================================ #!./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|memoryview) -> 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() ================================================ FILE: build-terminfo ================================================ #!/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() ================================================ FILE: bypy/devenv.go ================================================ // 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`) } } ================================================ FILE: bypy/init_env.py ================================================ #!/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() ================================================ FILE: bypy/linux/__main__.py ================================================ #!/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() ================================================ FILE: bypy/linux.conf ================================================ image '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' ================================================ FILE: bypy/macos/__main__.py ================================================ #!/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: str) -> None: 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') # Sign sub-apps for x in os.listdir('.'): if x.endswith('.app'): codesign(x) # 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() self.complete_sub_bundles() 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 complete_sub_bundles(self): count = 0 for qapp in glob.glob(join(self.contents_dir, '*.app')): for exe in glob.glob(join(self.contents_dir, 'MacOS', '*')): os.symlink(f'../../../MacOS/{os.path.basename(exe)}', os.path.join(qapp, 'Contents', 'MacOS', os.path.basename(exe))) count += 1 if count < 2: raise SystemExit(f'Could not complete sub-bundles in {self.contents_dir}') @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 glob.glob(join(self.contents_dir, '*.app', 'Contents', 'MacOS', '*')): if not os.path.islink(f): self.fix_dependencies_in_lib(f) 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() ================================================ FILE: bypy/macos.conf ================================================ # Requires installation of XCode >= 10.3 and go 1.26 and Python 3 and # python3 -m pip install certifi meson ninja vm_name 'macos-kitty' root '/Users/Shared/kitty-build' python '/usr/local/bin/python3' universal 'true' deploy_target '12.0' ================================================ FILE: bypy/rsync.conf ================================================ to_vm_excludes '/dependencies /build /dist /kitty/launcher/kitty* /.build-cache /.cache /.mypy_cache /.ruff_cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go' ================================================ FILE: bypy/site.py ================================================ #!/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() ================================================ FILE: bypy/sources.json ================================================ [ { "name": "zlib 1.3.2", "unix": { "file_extension": "tar.xz", "hash": "sha256:d7a0654783a4da529d1bb793b7ad9c3318020af77667bcae35f95d0e42a792f3", "urls": ["https://zlib.net/{filename}"] } }, { "name": "bzip2 1.0.8", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269", "urls": ["https://www.sourceware.org/pub/{name}/{filename}"] } }, { "name": "pkg-config 0.29.2", "type": "build", "os": "macos", "unix": { "file_extension": "tar.gz", "hash": "sha256:6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591", "urls": ["https://pkg-config.freedesktop.org/releases/{filename}"] } }, { "name": "openssl 3.5.5", "unix": { "file_extension": "tar.gz", "hash": "sha256:b28c91532a8b65a1f983b4c28b7488174e4a01008e29ce8e69bd789f28bc2a89", "urls": ["https://www.openssl.org/source/{filename}"] } }, { "name": "cmake 3.29.3", "type": "build", "os": "macos", "unix": { "file_extension": "tar.gz", "hash": "sha256:252aee1448d49caa04954fd5e27d189dd51570557313e7b281636716a238bccb", "urls": ["https://cmake.org/files/v{version_except_last}/{filename}"] } }, { "name": "expat 2.6.2", "unix": { "file_extension": "tar.xz", "hash": "sha256:ee14b4c5d8908b1bec37ad937607eab183d4d9806a08adee472c3c3121d27364", "urls": ["https://github.com/libexpat/libexpat/releases/download/R_{version_with_underscores}/{filename}"] } }, { "name": "libxml2 2.14.6", "unix": { "file_extension": "tar.xz", "hash": "sha256:7ce458a0affeb83f0b55f1f4f9e0e55735dbfc1a9de124ee86fb4a66b597203a", "urls": ["https://download.gnome.org/sources/libxml2/{version_except_last}/{filename}"] } }, { "name": "xkbcommon 1.7.0", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:65782f0a10a4b455af9c6baab7040e2f537520caa2ec2092805cdfd36863b247", "urls": ["https://xkbcommon.org/download/lib{filename}"] } }, { "name": "sqlite 3.50.4", "unix": { "file_extension": "tar.gz", "hash": "sha256:a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18", "urls": ["https://www.sqlite.org/2025/{name}-autoconf-3500400.{file_extension}"] } }, { "name": "libffi 3.4.6", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e", "urls": ["https://github.com/libffi/libffi/releases/download/v{version}/{filename}"] } }, { "name": "ncurses 6.5", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6", "urls": ["https://ftp.gnu.org/gnu/ncurses/{filename}"] } }, { "name": "readline 8.2", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35", "urls": ["https://ftp.gnu.org/gnu/readline/{filename}"] } }, { "name": "xz 5.8.1", "unix": { "file_extension": "tar.gz", "hash": "sha256:507825b599356c10dca1cd720c9d0d0c9d5400b9de300af00e4d1ea150795543", "urls": ["https://tukaani.org/xz/{filename}"] } }, { "name": "libxxhash 0.8.2", "unix": { "file_extension": "tar.gz", "hash": "sha256:baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4", "urls": ["https://github.com/Cyan4973/xxHash/archive/refs/tags/v{version}.{file_extension}"] } }, { "name": "xcrypt 4.4.36", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:b979838d5f1f238869d467484793b72b8bca64c4eae696fdbba0a9e0b6c28453", "urls": ["https://github.com/besser82/libxcrypt/archive/v{version}.{file_extension}"] } }, { "name": "python 3.14.3", "unix": { "file_extension": "tar.xz", "hash": "sha256:a97d5549e9ad81fe17159ed02c68774ad5d266c72f8d9a0b5a9c371fe85d902b", "urls": ["https://www.python.org/ftp/python/{version}/Python-{version}.{file_extension}"] } }, { "name": "libpng 1.6.55", "unix": { "file_extension": "tar.xz", "hash": "sha256:d925722864837ad5ae2a82070d4b2e0603dc72af44bd457c3962298258b8e82d", "urls": ["https://downloads.sourceforge.net/sourceforge/libpng/{filename}"] } }, { "name": "lcms2 2.17", "unix": { "file_extension": "tar.gz", "hash": "sha256:d11af569e42a1baa1650d20ad61d12e41af4fead4aa7964a01f93b08b53ab074", "urls": ["https://github.com/mm2/Little-CMS/releases/download/lcms{version}/lcms2-{version}.{file_extension}"] } }, { "name": "graphite 1.3.14", "os": "linux", "unix": { "file_extension": "tgz", "hash": "sha256:f99d1c13aa5fa296898a181dff9b82fb25f6cc0933dbaa7a475d8109bd54209d", "urls": ["https://downloads.sourceforge.net/silgraphite/graphite2-{version}.{file_extension}"] } }, { "name": "pcre 10.43", "os": "linux", "unix": { "file_extension": "tar.bz2", "hash": "sha256:e2a53984ff0b07dfdb5ae4486bbb9b21cca8e7df2434096cc9bf1b728c350bcb", "urls": ["https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.43/pcre2-{version}.{file_extension}"] } }, { "name": "iconv 1.17", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313", "urls": ["https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{version}.{file_extension}"] } }, { "name": "glib 2.86.3", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:b3211d8d34b9df5dca05787ef0ad5d7ca75dec998b970e1aab0001d229977c65", "urls": ["https://download.gnome.org/sources/glib/{version_except_last}/{filename}"] } }, { "name": "libbrotli 1.1.0", "os": "linux", "unix": { "file_extension": "tar.gz", "hash": "sha256:e720a6ca29428b803f4ad165371771f5398faba397edf6778837a18599ea13ff", "urls": ["https://github.com/google/brotli/archive/v{version}/{filename}"] } }, { "name": "pixman 0.44.2", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:50baf820dde0c5ff9714d03d2df4970f606a3d3b1024f5404c0398a9821cc4b0", "urls": ["https://www.cairographics.org/releases/{filename}"] } }, { "name": "freetype 2.13.2", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:12991c4e55c506dd7f9b765933e62fd2be2e06d421505d7950a132e4f1bb484d", "urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"] } }, { "name": "fontconfig 2.13.1", "os": "linux", "unix": { "file_extension": "tar.bz2", "hash": "sha256:f655dd2a986d7aa97e052261b36aa67b0a64989496361eca8d604e6414006741", "urls": ["https://www.fontconfig.org/release/{filename}"] } }, { "name": "cairo 1.18.2", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:a62b9bb42425e844cc3d6ddde043ff39dbabedd1542eba57a2eb79f85889d45a", "urls": ["https://www.cairographics.org/releases/{filename}"] } }, { "name": "harfbuzz 12.3.0", "unix": { "file_extension": "tar.xz", "hash": "sha256:8660ebd3c27d9407fc8433b5d172bafba5f0317cb0bb4339f28e5370c93d42b7", "urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/{version}/{filename}"] } }, { "name": "simde 0.7.6", "comment": "Cannot update till gcc in the build VM is updated as simde 0.8 requires newer gcc", "unix": { "file_extension": "tar.xz", "hash": "sha256:703eac1f2af7de1f7e4aea2286130b98e1addcc0559426e78304c92e2b4eb5e1", "urls": ["https://github.com/simd-everywhere/simde/releases/download/v{version}/simde-amalgamated-{version}.{file_extension}"] } }, { "name": "wayland 1.24.0", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:82892487a01ad67b334eca83b54317a7c86a03a89cfadacfef5211f11a5d0536", "urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/{version}/downloads/{filename}"] } }, { "name": "wayland-protocols 1.45", "os": "linux", "unix": { "file_extension": "tar.xz", "hash": "sha256:4d2b2a9e3e099d017dc8107bf1c334d27bb87d9e4aff19a0c8d856d17cd41ef0", "urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/{version}/downloads/{filename}"] } } ] ================================================ FILE: count-lines-of-code ================================================ #!/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/rowcolumn-diacritics.txt'} cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode()) raise SystemExit(cp.returncode) ================================================ FILE: dev.sh ================================================ #!/bin/sh # # dev.sh # Copyright (C) 2023 Kovid Goyal # # Distributed under terms of the GPLv3 license. # exec go run bypy/devenv.go "$@" ================================================ FILE: docs/Makefile ================================================ # 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) ================================================ FILE: docs/_static/custom.css ================================================ /* * 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 } ================================================ FILE: docs/_static/custom.js ================================================ /* 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(); }()); ================================================ FILE: docs/_static/timestamps.css ================================================ .video-with-timestamps dl { display: grid; grid-template-columns: max-content auto; } .video-with-timestamps dt { grid-column-start: 1; } .video-with-timestamps dd { grid-column-start: 2; margin-left: 1em; } ================================================ FILE: docs/_static/timestamps.js ================================================ /*jshint esversion: 6 */ (function() { 'use strict'; function init_timestamps() { document.querySelectorAll('.video-with-timestamps').forEach(loc => { 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); })(); ================================================ FILE: docs/_templates/base.html ================================================ {% extends "!base.html" %} {% block extrahead %} {{ super() }} {%- if analytics_id %} {% endif -%} {% endblock %} ================================================ FILE: docs/actions.rst ================================================ Mappable 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`. You can also browse and trigger these actions by pressing :sc:`command_palette` to run the command palette. .. include:: /generated/actions.rst ================================================ FILE: docs/basic.rst ================================================ Tabs 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`) Search scrollback in less :sc:`search_scrollback` (also :kbd:`⌘+F` on macOS) ========================= ======================= 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 Note that tabs can be re-arranged, detached and moved to another OS Window in the same kitty instance using drag and drop. 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` ================================== ======================= ================================================ FILE: docs/binary.rst ================================================ Install 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 `. ================================================ FILE: docs/build.rst ================================================ Build 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`` * ``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. ================================================ FILE: docs/changelog.rst ================================================ Changelog ============== |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 --------------------------- Mousing [0.46] ~~~~~~~~~~~~~~~ kitty already had excellent mouse support, but now it is taking it to the next level. The kitty scrollback buffer grew support for :opt:`smooth scrolling ` and :opt:`momentum based scrolling ` for a natural, smooth and kinetic scrolling experience. Additionally, you can now :opt:`drag kitty tabs around ` with the mouse to re-order them, move them to another kitty OS Window or even detach them into their own OS Window. Finally, a long requested feature, the ability to resize kitty windows (aka splits) with the mouse was implemented. Choose files, fast [0.45] ~~~~~~~~~~~~~~~~~~~~~~~~~~~ A new :doc:`kitten to select files at the speed of thought ` with a keyboard first interface and support for content previews of text files with syntax highlighting, images, videos, e-books and more. Allows you to select files for use at the shell prompt or other terminal workflows with just a few keystrokes, similar to how fuzzy finders like `fzf `__ operate, but designed for files in particular, leveraging the various innovations of kitty such as image display and variable sized text. On Linux, it can even be used as a :doc:`drop in replacement ` for the File Open/Save dialog boxes in GUI programs. Sessions [0.43] ~~~~~~~~~~~~~~~~ kitty has long had support for :doc:`sessions`, aka simple text files where you can define what tabs, windows and programs you wish to run in kitty. Now in addition to that kitty has the ability to :ref:`create and switch between sessions ` with a single keypress and also to manually setup some tabs/windows in kitty and :ref:`save it as a session file `, for seamless and intuitive session file creation. A scrollbar for the kitty scrollback [0.43] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A long requested feature, kitty has finally :pull:`gotten a scrollbar <8945>` that can be used with the mouse for browsing its scrollback. The bar appear automatically when you start scrolling backwards and is :opt:`extensively configurable ` in kitty.conf. Note that the old ``scrollback_indicator_opacity`` option is deprecated. Multiple cursors [0.43] ~~~~~~~~~~~~~~~~~~~~~~~~ kitty has pioneered a new :doc:`escape code protocol ` that allows terminal applications to use multiple cursors, rendered natively. These are typically used in editors to make the same edit at multiple locations. Now terminal based editors can use properly rendered native cursors, just like their GUI cousins, at last. Access kitty with a single keypress [0.42] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: quake-screenshots.rst kitty now has a Quake like floating, translucent terminal window, so you can access all that kitty goodness instantly with a single keypress. See the screenshots on the side and head over to the :doc:`kitten page for details on how to set it up `. 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.47.0 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Watchers: Add an `on_quit` event to global watchers (:iss:`9675`) - Wayland: Fix a crash on some compositors when dragging a tab between OS Windows (:iss:`9677`) - Fix incorrect behavior when using the actions to move tab forward/backward with a tab_bar_filter active (:iss:`9672`) - Prevent stacking of multiple rename tab windows (:iss:`9691`) - choose files kitten: Fix a regression that caused incorrect highlight of matched letters - macOS: When using :opt:`macos_traditional_fullscreen` do not render content under the notch (:pull:`9678`) - X11: Fix massive scroll when switching focus between kitty and another application (:iss:`9703`) - Markers: Fix marking not working for multicell characters (:iss:`9705`) - Fix a regression in 0.46 that broke drag select in unfocused windows (:iss:`9713`) 0.46.1 [2026-03-16] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - diff kitten: Highlight moved lines using a different background color (:opt:`kitten-diff.mark_moved_lines`) (:iss:`3241`) - Fix a regression that broke ``kitten update-self`` (:iss:`9642`) - macOS: Clear bell alert badge on dock icon on mouse/keyboard activity (:iss:`9640`) - Fix a regression that broke accept anyway shortcut in the paste confirmation dialog (:pull:`9640`) - Fix kitty hanging on startup on Intel macs (:iss:`9643`) - X11: Fix a regression that caused some high res scroll devices to be treated as line based scroll devices (:iss:`9649`) - Wayland: Fix momentum scrolling not working on compositors that send a stop frame with no axis information (:iss:`9653`) - Linux: Fix regression that broke drag and drop from GTK applications (:iss:`9656`) - macOS: Fix using Fn key for start dictation not working (:iss:`9661`) - Don't use neighboring tab colors for tab bar margins in translucent windows (:iss:`9663`) - macOS: Fix OS window focus not restored when switching spaces (:iss:`9665`) 0.46.0 [2026-03-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Pixel scrolling for the kitty scrollback buffer controlled via :opt:`pixel_scroll` (:pull:`9330`) - Linux: momentum scrolling in the kitty scrollback buffer for touchpads and touchscreens, see :opt:`momentum_scroll` - X11: support high resolution scroll events from touchpads, etc - macOS: Implement support for Apple dictation to input text in kitty (:iss:`3732`) - Allow dragging tabs (opt:`tab_bar_drag_threshold`) in the tab bar to re-order, move to another OS Window or detach (:pull:`9296`) - Allow dragging window borders to resize kitty windows in all the different layouts, controlled by :opt:`window_drag_tolerance` (:pull:`9447`) - Allow showing :opt:`configurable window titles ` for individual kitty windows via a window title bar (:pull:`9450`) - A command palette (:sc:`command_palette`) to browse and trigger all mapped and unmapped actions (:pull:`9545`) - choose-files kitten: Fix JXL image preview not working (:iss:`9323`) - Fix tab bar rendering glitches when using :opt:`tab_bar_filter` in some circumstances (:iss:`9328`) - Add support for specifying colors in :file:`kitty.conf` in OKLCH and LAB color spaces (:pull:`9325`) - Fix a regression that broke using line numbers with the edit-in-kitty command (:pull:`9346`) - Key maps: Allow specifying a timeout for multi key mappings and keyboard modes (:pull:`9551`) - macOS: Fix changes to :opt:`macos_titlebar_color` while in full screen not being applied after exiting fullscreen (:iss:`9350`) - ncurses: Fix ncurses not using dim because it is missing from the sgr property in terminfo even though it is present in the dim property. - Fix a regression in the previous release that caused moving between neighbors in the vertical and horizontal layouts to go in the opposite direction (:iss:`9355`) - Fix :ac:`goto_session` not respecting the focus_tab session directive when creating a session in an existing OS window (:iss:`9366`) - Wayland: Fix a regression in the previous release that caused doubled key repeats on compositors that implement compositor side key repeat events (:iss:`9374`) - icat: Fix a regression in the previous release when rendering GIF animations with frames that dispose onto background with non-zero delay using the native engine (:iss:`9376`) - Wayland: Remove usage of the Wayland color management protocol to inform compositors of the color space used by kitty (:iss:`9341`) - Linux: Fix a regression in 0.40 that caused horizontal alignment for emoji to be incorrect in some cases (:iss:`9395`) - icat kitten: When catting multiple images display the images in input order (:iss:`9413`) - kitten @: Fix relative paths for --password-file being resolved relative to CWD instead of the kitty config directory - kitten choose-files: Add a new binding of :kbd:`Alt+Enter` to modify the name of an existing file when choosing a save file name (:iss:`9387`) - kitten choose-files: Fix TAB completion in the choose save file name prompt not working with respect to the current working directory (:iss:`9387`) - Fix line-at-once selection not extending wrapped lines into scrollback (:iss:`9437`) - ssh kitten: Restore keyboard mode even if the ssh connection drops - edit-in-kitty: Handle connection drop more gracefully (:pull:`9480`) - macOS: Fix changing window title with global menubar menu open causes menu to get stuck (:pull:`9490`) - Fix :opt:`focus_follows_mouse` not working during a drag and drop (:iss:`9497`) - :ac:`goto_session`: Add a ``--active-only`` option to select from only active sessions (:pull:`9503`) - Shell integration: Allow sending click events to shells using y co-ordinates relative to prompts (:iss:`9500`) - A new action :ac:`copy_selection_or_last_command_output` (:pull:`9512`) - Wayland: Add support for the background blur extension (:iss:`9534`) - macOS: A new option :opt:`macos_dock_badge_on_bell` to show a badge on the kitty dock icon when a bell occurs (:pull:`9529`) - macOS: Workaround for yet another Tahoe bug causing rendering to fail (:pull:`9520`) - URL detection: Allow trailing asterisks in URLs (:iss:`9543`) - Wayland: Add support for :code:`titlebar-only` in :opt:`hide_window_decorations` to hide the titlebar while keeping shadows for window resizing. (:pull:`9486`) - Text sizing protocol: Fix alignment/cropping issues when rendering text with a fractional scale (:iss:`9471`) - macOS: Fix a crash when using :opt:`macos_traditional_fullscreen` with split view (:pull:`9573`) - macOS: Fix flickering during OS Window resize (:disc:`9582`) - Cursor trail: Show a cursor trail when switching tabs (:pull:`9588`) - Make shift+left click extend the current selection instead of starting a new selection when the mouse is not grabbed by the TUI application (:disc:`9608`) - Allow double clicking on a tab to rename it (:pull:`9609`) - :ac:`remote_control_script` resolve relative paths with respect to kitty config directory (:iss:`9625`) - Splits layout: Add new mappable actions to maximize a window in the splits layout (:iss:`9629`) 0.45.0 [2025-12-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :doc:`kitten to select files at the speed of thought ` with a keyboard first interface and support for content previews of text files with syntax highlighting, images, videos, e-books and more (:iss:`9263`) - Add support for the `paste events protocol `__ (:iss:`9183`) - icat kitten: Add support for animated PNG and animated WebP, netPBM images, ICC color profiles and CCIP color space metadata to the builtin engine - icat kitten: Add a new flag :option:`kitty +kitten icat --fit` to control how images are scaled to fit the screen (:iss:`9201`) - icat kitten: The :option:`kitty +kitten icat --scale-up` flag now takes effect when not using :option:`kitty +kitten icat --place` as well - Add a mappable action :ac:`copy_last_command_output` to copy the output of the last command to the clipboard (:pull:`9185`) - ssh kitten: Fix a bug where automatic login was not working (:iss:`9187`) - Graphics: Fix overwrite composition mode for animation frames not being honored - Automatic color scheme switching: Fix title bar and scroll bar colors not being updated (:iss:`9167`) - macOS: Fix cycle through OS windows only swapping between the two most recent OS Windows. Also add a cycle through OS Windows backwards action. (:iss:`9215`) - :ac:`goto_session`: allow specifying a directory to select a session file from the directory (:pull:`9219`) - Have reloading config also reload the custom tab bar python modules (:disc:`9221`) - kitten @ ls: Also output the neighbors for every window (:disc:`9225`) - Have the :option:`kitty --start-as` flag be respected when used with :option:`kitty --single-instance` (:iss:`9228`) - When expanding environment variables in :opt:`listen_on` allow the :opt:`env` directive to take effect - macOS: Fix closing an OS Window when another OS Window is minimized causing the minimized window to be un-minimized (:iss:`8913`) - Do not rewrap the text in the alternate screen buffer. Avoids flicker during live resize with no :opt:`resize_debounce_time` (:disc:`9142`) - Add a default mapping :ac:`search_scrollback` to open the scrollback in a pager in search mode. If any text is currently selected it is automatically searched for. - Wayland: Fix spurious key repeat events when some user defined callback takes a long time to execute (:iss:`9224`) - When moving windows to a new tab/OS Window fix overlay windows not being grouped with their parent windows (:iss:`9266`) - Linux: Fix a bug causing colors to occasionally all go black when using mesa >= 25.3.0 with nouveau GPU driver (:iss:`9235`) - Fix :opt:`tab_bar_min_tabs` not respecting :opt:`tab_bar_filter` (:iss:`9278`) - macOS: Workaround for regression in Tahoe 26.2 that breaks :option:`kitty --detach` (:iss:`9288`) - macOS: Workaround for yet another Tahoe regression causing macOS to start an AutoFill helper process and not shut it down on application exit (:iss:`9299`) 0.44.0 [2025-11-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow kitty to read a specified set of environment variables from your login shell at startup using the :opt:`env` directive in kitty.conf (:iss:`9042`) - A new option :opt:`draw_window_borders_for_single_window` to force kitty to always draw a window border even when only a single window is present (:pull:`9112`) - Fix a regression in 0.43.0 that caused a black flicker when closing a tab in the presence of a background image (:iss:`9060`) - Further improvements to rounded corner rendering, especially at low DPI (:pull:`9091`) - Splits layout: Fix a bug that could cause a corrupted layout in some circumstances (:iss:`9059`) - Fix a regression in the previous release that broke ``goto_session -1`` - Fix rendering broken on ancient GPU drivers that do not support rendering to 16 bit textures (:iss:`9068`) - Fix tab bar sometimes showing incorrect tabs when it is filtered to show only tabs from the current session (:iss:`9079`) - macOS: Workaround for bug in macOS Tahoe that caused OS Windows that are fullscreen to crash kitty when returning from sleep on some machines (:iss:`8983`) - Graphics: Fix animated images sometimes not auto playing or auto playing at the wrong start frame if the same image id is used for a subsequent image - Fix a regression in 0.43.0 that caused high CPU usage when :opt:`disable_ligatures` was set to ``cursor`` and the tab bar was visible (:iss:`9071`) - macOS: Handle dropping of file promises into kitty in addition to file paths (:pull:`9084`) - macOS: Fix indeterminate progress bar displayed on dock icon increasing speed when indeterminate progress is set without being cleared first (:iss:`9114`) - macOS: Performance and power usage improvements of about 5-10% (:pull:`9131`) - macOS: Add an item to the global menu to Cycle through OS windows - macOS: Quick access terminal: Fix a crash when changing font size (:iss:`9178`) - Wayland: Fix ``center-sized`` panels not working on smithay based compositors (:pull:`9117`) - Wayland: Fix scrolling using some mouse wheels that produce "VALUE120" based scroll events too fast on some compositors (:pull:`9128`) - Add support for Unicode 17 - Fix a regression in 0.43.0 that caused :opt:`tab_bar_margin_width` to be doubled on the right edge of the tab bar (:iss:`9154`) - Session files: Add a new ``focus_tab`` command to specify which tab should be active when a session is loaded. Accepts either a plain number (0-based index) or a match expression for flexible tab selection, allowing sessions to preserve the active tab state (:doc:`sessions`) - :ac:`save_as_session`: Add ``--base-dir`` option to specify a base directory for saving session files with relative paths, useful when the current working directory is not the desired location (:doc:`sessions`) - Add ``state:focused_os_window`` match query to select all windows in the currently focused OS window (:ref:`search_syntax`) - Session saving now preserves visual tab order and active tab rather than tab activation history as this is generally more important. In the future may have it save tab history as well (:pull:`9163`) - The :sc:`reset_terminal` shortcut to reset the terminal now also resets termios state 0.43.1 [2025-10-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ssh kitten: Allow specifying a password and/or TOTP authentication secret to automate interactive logins in scenarios where public key authentication is not supported (:pull:`9020`) - macOS: Fix a bug where the color of a transparent titlebar was off when running in the release build versus the build from source. Also fix using a transparent titlebar causing the background opacity to be doubled. - Fix a regression in the previous release that caused the incorrect tab to be active when loading a session (:iss:`9025`) - macOS: Workaround for bug in macOS Tahoe that caused closed OS Windows to remain as invisible rectangles that intercept mouse events (:iss:`8952`) - macOS: Fix a regression in the previous release that broke automatic switching of dark/light mode when setting :opt:`macos_titlebar_color` to an arbitrary color (:iss:`9034`) - :ac:`goto_session`: Add ``--sort-by=alphabetical`` to have the interactive session picker list the sessions in a fixed order rather than by most recent (:disc:`9033`) - Fix a regression in the previous release that caused the cursor trail to not be hidden properly (:iss:`9039`) - Session files: Fix a regression in the previous release that broke matching on windows in the current tab (:iss:`9037`) - Fix a regression in the previous release that broke clearing screen lines when in margin mode (:iss:`9049`) 0.43.0 [2025-09-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - New support for creating and switching to :doc:`sessions` easily, allowing users to define and use sessions/projects efficiently (:iss:`8911`) - Add a configurable :opt:`scrollbar` for the kitty scrollback (:pull:`8945`) - A new protocol for :doc:`multiple cursors ` in the terminal (:iss:`8927`) - macOS: Allow the window title bar to be semi-transparent when :opt:`background_opacity` is less than one and :opt:`macos_titlebar_color` is set to ``background`` (:pull:`8906`) - A new :opt:`cursor_trail_color` setting to independently control the color of cursor trails (:pull:`8830`) - macOS: Add the default :kbd:`Cmd+L` mapping from Terminal.app to erase the last command and its output (:disc:`6040`) - Fix :opt:`background_opacity` being non-linear especially with light color themes. Note that this might require you to adjust the value of this setting to get back your current look. (:iss:`8869`) - **backward incompatibility**: :opt:`background_opacity` no longer applies to :opt:`background_image` instead add an alpha channel to the image itself - Add support for blinking text. Text marked as blinking now blinks in exact rhythm with the cursor. The blinking animation and max duration are controlled by :opt:`cursor_blink_interval` and :opt:`cursor_stop_blinking_after`. (:pull:`8551`) - Allow using a custom python function to draw tab titles in the tab bar, see :opt:`tab_title_template` - Wayland: Fix incorrect window size calculation when transitioning from full screen to non-full screen with client side decorations (:iss:`8826`) - macOS: Fix hiding quick access terminal window not restoring focus to previously active application (:disc:`8840`) - macOS: Fix showing the quick access terminal on a space other than the space it was last active on, after full screening some application causes the quick access terminal to appear on the old space (:iss:`8740`) - macOS: When toggling open the quick access terminal move it to the currently active monitor (the monitor with the mouse pointer on it) (:iss:`9003`) - macOS: Fix closing an OS Window when another OS Window is minimized causing the minimized window to be un-minimized (:iss:`8913`) - Allow using backspace to move the cursor onto the previous line in cooked mode. This is indicated by the `bw` property in kitty's terminfo (:iss:`8841`) - Watchers: A new event for global watchers corresponding to the tab bar being changed (:disc:`8842`) - Fix a regression in 0.40.0 that broke handling of the VS16 variation selector when it caused a character to flow to the next line (:iss:`8848`) - Fix rendering of underlines when using larger text sizes with the space and en-space characters (:iss:`8950`) - Fix updating panel configuration on visibility toggle and via remote control not working (:iss:`8984`) - Improve rendering of rounded rectangles (:pull:`9000`) - Wayland: Update bundled copy of libwayland to 1.24 from 1.23.1 because the just released mesa 25.2.0 breaks with libwayland < 1.24 (:iss:`8884`) - macOS: Pass the :kbd:`Cmd+C` shortcut to the application running in the terminal when no text is selected (:pull:`8946`) - macOS: Workaround for bug in macOS Tahoe that caused OS Windows that are fullscreen on a monitor that is disconnected while macOS is asleep to crash kitty (:iss:`8983`) 0.42.2 [2025-07-16] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :ref:`protocol extension ` to notify terminal programs that have turned on SGR Pixel mouse reporting when the mouse leaves the window (:disc:`8808`) - clipboard kitten: Can now optionally take a password to avoid repeated permission prompts when accessing the clipboard. Based on a :ref:`protocol extension `. (:iss:`8789`) - A new :option:`launch --hold-after-ssh` to not close a launched window that connects directly to a remote host because of :option:`launch --cwd`:code:`=current` when the connection ends (:pull:`8807`) - Fix :opt:`remember_window_position` not working because of a stupid typo (:iss:`8646`) - A new :option:`kitty --grab-keyboard` that can be used to grab the keyboard so that global shortcuts are sent to kitty instead - Remote control: Fix holding a remote control socket open causing the kitty I/O thread to go into a loop and not respond on other remote control sockets (:disc:`8670`) - hints kitten: Preserve line breaks when the hint is over a line break (:iss:`8674`) - Fix a segfault when using the :ac:`copy_ansi_to_clipboard` action (:iss:`8682`) - Fix a crash when using linear easing curves for animations (:iss:`8692`) - Graphics protocol: Add a note clarifying image update behavior on re-transmission (:iss:`8701`) - Wayland GNOME: Fix incorrect OS Window tracking because GNOME has started activating windows on non-current workspaces (:iss:`8716`) - Fix a regression in 0.40.0 that broke rendering of VS15 variation selectors in some circumstances (:iss:`8731`, :iss:`8794`) - Fix a regression in 0.40.0 that broke serialization of tab characters as ANSI text (:iss:`8741`) - Fix a regression in 0.40.0 that broke erasing of characters in a line in the presence of wide characters (:iss:`8758`) - Fix a regression in 0.40.0 that broke hyperlinking of wide characters (:iss:`8796`) - Fix a regression that broke using :kbd:`esc` to exit visual select window mode (:iss:`8767`) - kitten run-shell: Fix SIGINT blocked when execing the shell (:iss:`8754`) 0.42.1 [2025-05-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix ambiguous width and private use characters not being rendered when used with variable width text-sizing protocol escape codes - Quick access terminal: Restore focus to previously active window when hiding the quick access terminal window on macOS (:iss:`8627`) - Wayland: Fix an abort if the terminal program sets a window title longer than 2KB that contains CSI escape sequences and multibyte UTF-8 (:iss:`8619`) - Quick access terminal: Allow toggling the window to full screen using the standard kitty :sc:`toggle_fullscreen` shortcut (:iss:`8626`) - Quick access terminal: Allow configuring the monitor to display the panel on in Wayland/X11 (:iss:`8630`) - A new setting :opt:`remember_window_position` to optionally use the position of the last closed kitty OS Window as the position of the first kitty OS Window when running a new kitty instance (:pull:`8601`) - Panel kitten: A new ``center-sized`` value for :option:`--edge ` to allow easily creating sized and centered panels - Wayland: The `kitty --name` flag now sets the XDG *window tag* on compositors that support the `xdg-toplevel-tag `__ protocol. 0.42.0 [2025-05-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new kitten: :doc:`quick-access-terminal ` to :ref:`quake` - The :doc:`panel kitten ` works on macOS and X11 as well as Wayland (:iss:`2590`) - **Behavior change**: Now kitty does full grapheme segmentation following the Unicode 16 spec when splitting text into cells (:iss:`8533`) - **Behavior change**: The :ref:`automatic color switching functionality ` now also controls background image settings (:iss:`8603`) - panel kitten: Allow using :option:`kitty +kitten panel --single-instance` to create multiple panels in one process (:iss:`8549`) - launch: Allow creating desktop panels such as those created by the :doc:`panel kitten ` (:iss:`8549`) - Remote control: Allow modifying desktop panels and showing/hiding OS Windows using the ``kitten @ resize-os-window`` command (:iss:`8550`) - Remote control launch: Allow waiting for a program launched in a new window to exit and get the exit code via the `kitty +launch --wait-for-child-to-exit` command line flag (:disc:`8573`) - Allow starting kitty with the OS window hidden via :option:`kitty --start-as=hidden `, useful for single instance mode (:iss:`3466`) - Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`) - diff kitten: Add half page and full page scroll vim-like bindings (:pull:`8514`) - diff kitten: Allow diffing named pipes (:iss:`8597`) - Fix a regression that caused automatic color themes to not be re-applied after config file reload (:iss:`8530`) - Wayland: When the compositor supports the `xdg-system-bell `__ protocol use it to play the default bell sound - panel kitten: Allow specifying panel size in pixels in addition to cells - Fix a regression in 0.36.0 that caused using = with single letter command line flags to no longer work correctly (:iss:`8556`) - Single instance: Preserve environment variables from invoking environment in newly created window (:disc:`8567`) - Single instance: Reset OS Window class and name in new single instance OS windows (:disc:`8567`) - macOS: Fix text color in visual window select ignoring the color theme (:iss:`8579`) - Launch action: Allow using an env var that resolves to a full command-line as the program to launch (:pull:`8613`) - :ac:`change_font_size` allow multiplying/dividing the current font size in addition to incrementing it (:iss:`8616`) - Box drawing: Improve appearance of rounder corners, giving them a uniform line width (:iss:`8299`) 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 ``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 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:`notifications_on_the_desktop` (: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. ================================================ FILE: docs/clipboard.rst ================================================ Copying 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. .. _clipboard_repeated_permission: Avoiding repeated permission prompts -------------------------------------- .. versionadded:: 0.42.2 using a password to avoid repeated confirmations If a program like an editor wants to make use of the system clipboard, by default, the user is prompted on every read request. This can become quite fatiguing. To avoid this situation, this protocol allows sending a password and human friendly name with ``type=write`` and ``type=read`` requests. The terminal can then ask the user to allow all future requests using that password. If the user agrees, future requests on the same tty will be automatically allowed by the terminal. The editor or other program using this facility should ideally use a password randomly generated at startup, such as a UUID4. However, terminals may implement permanent/stored passwords. Users can then configure terminal programs they trust to use these password. The password and the human name are encoded using the ``pw`` and ``name`` keys in the metadata. The values are UTF-8 strings that are base64 encoded. Specifying a password without a human friendly name is equivalent to not specifying a password and the terminal must treat the request as though it had no password. Allowing terminal applications to respond to paste events -------------------------------------------------------------- .. versionadded:: 0.44.1 paste events via the 5522 mode If a TUI application wants to handle paste events (like the user pressing the paste key shortcut used by the terminal or selecting paste from a terminal UI menu) it can enable the *paste events* private mode (5522), as described in this `ancillary specification `__. When that mode is set, the terminal will send the application a list of MIME types on the clipboard every time the user triggers a paste action. The application is then free to request whatever MIME data it wants from the list of types. The mode can be enabled using the standard DECSET or DECRST control sequences. ``CSI ? 5522 h`` to enable the mode. ``CSI ? 5522 l`` to disable the mode. The terminal *should* send a one time password with the list of mime types, as the ``pw`` key (base64 encoded). The application can then use this password to request data from the clipboard without needing a permission prompt. The human name *should* be set to ``Paste event`` (base64 encoded) when the application uses this one time password. Detecting support for this protocol ----------------------------------------- Applications can detect if a terminal supports this protocol with a standard DECRQM query: .. code:: CSI ? 5522 $ p To which the terminal will respond with a DECRPM response: .. code:: CSI ? 5522 ; Ps $ y A Ps value of 0 or 4 means the mode is not supported. 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 other's clipboard requests. This is fundamentally unavoidable since the system clipboard is a single global shared resource. However, there is an additional complication where responses from 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. When the terminal sends an unsolicited paste event because the user triggered a paste and the 5522 mode is enabled, there will be no associated id. In this case, the multiplexer must forward the event to the currently active window. ================================================ FILE: docs/color-stack.rst ================================================ Color 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 (])``. ```` is either ``0x07 (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..7 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 sends 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 ================================================ FILE: docs/conf.py ================================================ #!/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 from pygments.lexer import bygroups as untyped_bygroups from pygments.token import Comment, Error, Keyword, Literal, Name, Number, String, Whitespace 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.session import save_as_session_message, save_as_session_options with open('generated/save-as-session.rst', 'w') as f: f.write(option_spec_as_rst( appname='save_as_session', ospec=save_as_session_options, heading_char='^', usage='[path-to-save-session-file-at]', message=save_as_session_message, )) 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.fast_data_types import all_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 all_color_names(): 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 string_replacements.items(): src = src.replace(k, v) source[0] = src # }}} # config file docs {{{ def bygroups(*args: Any) -> Any: return untyped_bygroups(*args) # type: ignore[no-untyped-call] class ConfLexer(RegexLexer): 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: 'ConfLexer', 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): 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 figure from docutils.writers.manpage import Translator from sphinx.writers.manpage import ManualPageTranslator # 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: figure) -> None: pass def depart_image(self: ManualPageTranslator, node: figure) -> None: pass def depart_figure(self: ManualPageTranslator, node: figure) -> 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 = getattr(ManualPageTranslator, 'astext') def astext(self: ManualPageTranslator) -> 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) setattr(self, 'body', b) return orig_astext(self) setattr(ManualPageTranslator, '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.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) ================================================ FILE: docs/conf.rst ================================================ kitty.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 .. 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 wide-gamut-colors ================================================ FILE: docs/deccara.rst ================================================ Setting 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 ================================================ FILE: docs/desktop-notifications.rst ================================================ .. _notifications_on_the_desktop: 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, let's 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 occasions 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 at least ``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 unique, like an UUID. ``i`` :ref:`identifier` ``unset`` Identifier for the notification. Make these globally unique, 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. ================================================ FILE: docs/extract-rst-targets.py ================================================ #!/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)) ================================================ FILE: docs/faq.rst ================================================ Frequently 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 escape code described in :doc:`color-stack` to set colors in a single window. Examples of using OSC escape codes to set colors:: Change the default foreground color: printf '\x1b]21;foreground=#ff0000\x1b\\' Change the default background color: printf '\x1b]21;background=blue\x1b\\' Change the cursor color: printf '\x1b]21;cursor=blue\x1b\\' Change the selection background color: printf '\x1b]21;selection_background=blue\x1b\\' Change the selection foreground color: printf '\x1b]21;selection_foreground=blue\x1b\\' Change the nth color (0 - 255): printf '\x1b]21;n=green\x1b\\' See :doc:`color-stack` for details on the syntax for specifying colors and how to query current colors. 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. The contents of the file are assumed to be the command line to pass to kitty in shell syntax, for example:: --single-instance --override background=red 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.4.0 symbol_map U+e000-U+e00a,U+e0a0-U+e0a2,U+e0a3,U+e0b0-U+e0b3,U+e0b4-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+ea60-U+ec1e,U+ed00-U+efce,U+f000-U+f2ff,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? ----------------------------------------------------------------------------- Use the :ref:`panel kitten `, this allows you to use kitty as a quick access Quake like terminal and even to use kitty as the desktop background, if so desired. 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 .. image:: https://github.com/mtklr/kitty-nyan-icon/raw/main/kitty-nyan.svg :target: https://github.com/mtklr/kitty-nyan-icon :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: .. tab:: macOS #. 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:: rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock .. tab:: Linux #. Copy :file:`kitty.desktop` from the installation location (usually :file:`/usr/share/applications` to :file:`~/.local/share/applications` #. Edit the copied desktop file changing the ``Icon`` line to have the absolute path to your desired icon. 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` and, most importantly, ``PATH`` to locate binaries. The simplest way to fix this is to have kitty load the environment variables from your shell configuration at startup using the :opt:`env` directive, adding the following to :file:`kitty.conf`:: env read_from_shell=PATH LANG LC_* XDG_* EDITOR VISUAL This works for POSIX compliant shells and the fish shell. Note that it does add significantly to kitty startup time, so use only if really necessary. This feature was added in version ``0.43.2``. 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/zellij 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:`variable sized text `, :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`. ================================================ FILE: docs/file-transfer-protocol.rst ================================================ File 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 waits 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. ================================================ FILE: docs/glossary.rst ================================================ :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:: KITTY_SI_RUN_COMMAND_AT_STARTUP Set this to an expression that the kitty shell integration scripts will ``eval`` after the shell is started. Note that this environment variable is ignored when present in the environment in which kitty itself is launched in. It is most useful with the ``--env`` flag for the :doc:`launch ` action. .. 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. ================================================ FILE: docs/graphics-protocol.rst ================================================ Terminal 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 * `blackcat `_ - a modern compatible cat with image 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 * `kitty-graphics.el `_ - Images in emacs * `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 `_ * `iTerm2 `_ * `xterm.js `_ 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:: POSIX sh .. code-block:: sh #!/bin/sh read rows cols </dev/null) [ "$char" = "t" ] && break response="${response}${char}" done command stty "$oldstty" h=$(echo "$response" | cut -d';' -f2) w=$(echo "$response" | cut -d';' -f3) printf "number of rows: %d number of columns: %d" "$rows" "$cols" printf " screen width: %d screen height: %d\n" "$w" "$h" 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 more precise version of this escape code, which is however supported in less terminals is ``[16t`` which causes the terminal to reply with the pixel dimensions of a single cell. A minimal example ------------------ Some minimal code to display PNG images in kitty, using the most basic features of the graphics protocol: .. tab:: POSIX sh .. code-block:: sh #!/bin/sh send_chunked() { first="y" while IFS= read -r chunk; do metadata=""; [ "$first" = "y" ] && { metadata="a=T,f=100,"; first="n"; } printf "\033_G%sm=1;%s\033\\" "${metadata}" "${chunk}" done [ "$first" = "n" ] && { printf "\033_Gm=0;\033\\"; return 0; } return 1 } transmit_png() { # Different systems have different or missing base64 executables. # The sed command below adds a trailing newline which openssl # base64 does not produce and is needed for reading via read -r { command base64 -w 4096 "$1" 2>/dev/null | send_chunked; } || \ { command base64 -b 4096 "$1" 2>/dev/null | send_chunked; } || \ { command openssl base64 -e -A -in "$1" | command sed '$a\' | command fold -b -w 4096 | send_chunked; } } transmit_png "$1" .. tab:: Python .. code-block:: python #!/usr/bin/env python import sys from base64 import standard_b64encode first, eof, buf = True, False, memoryview(bytearray(3 * 4096 // 4)) w = sys.stdout.buffer.write with open(sys.argv[-1], 'rb') as f: while not eof: p = buf[:] while p and not eof: n = f.readinto1(p) p, eof = p[n:], n == 0 encoded = standard_b64encode(buf[:len(buf)-len(p)]) metadata, first = "a=T,f=100," if first else "", False w(f'\x1b_G{metadata}m={0 if eof else 1};'.encode('ascii')) w(encoded) w(b'\x1b\\') 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. This 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, i.e. the placement will not get an id. 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. .. note:: When re-transmitting image data for a specific id, the existing image and all its placements must be deleted. The new data replaces the old image data but is not actually displayed until a placement for it is created. This is to avoid divergent behavior in the case when unrelated programs happen to re-use image ids in the same session. .. 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 (acts as though ``z`` was unspecified), ``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 (acts as though ``v`` was not specified), ``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. ================================================ FILE: docs/index.rst ================================================ kitty ========================================================== *If you live in the terminal, kitty is made for YOU!* The fast, feature-rich, GPU based terminal emulator. .. toctree:: :hidden: quickstart overview faq support sessions performance changelog integrations protocol-extensions press-mentions .. tab:: Fast * Uses GPU and SIMD vector CPU instructions for :doc:`best in class performance ` * 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 ================================================ FILE: docs/installer.sh ================================================ #!/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 "$@" ================================================ FILE: docs/integrations.rst ================================================ :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_bookokrat: `bookokrat `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal PDF/EPUB viewer .. _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_mcat: `mcat `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Display various types of files nicely formatted with images in the terminal `dawn `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A markdown editor that uses the text-sizing protocol for large headings and the graphics protocol for images. .. _tool_presentterm: `presenterm `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Show markdown based slides with images in your terminal, powered by the kitty graphics protocol. .. _tool_mdfried: `mdfried `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Markdown viewer that can render big headers with the text-sizing-protocol, and also render images with 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. .. code-block:: sh mpv --profile=sw-fast --vo=kitty --vo-kitty-use-shm=yes --really-quiet video.mkv .. _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. File managers ------------------- .. _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_far2l: `far2l `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Text-mode dual panel (orthodox) file manager and also terminal emulator, uses the kitty graphics and keyboard protocols (both as client and as terminal) 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 has support for the kitty graphics protocol, with its ``kittygd`` and ``kittycairo`` backends. .. _tool_k-nine: `k-nine `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A wrapper around the :code:`plotnine` library which lets you plot data from the command-line with bash one-liners. .. tool_tgutui: `tgutui `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 * `kitty-graphics.el `_ - view images in emacs 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. Desktop panels ------------------------- `kitty panel `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A system panel for Kitty terminal that displays real-time system metrics using terminal-based utilities. `pawbar `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A kitten-panel based desktop panel for your desktop Password managers --------------------- `1password `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allow injecting passwords from 1Password into kitty. `BitWarden `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Inject passwords from BitWarden into kitty Miscellaneous ------------------ .. tool_doom: DOOM ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Play the classic shooter DOOM in `kitty `__ or even inside `neovim inside kitty `__. .. 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 ================================================ FILE: docs/intro_vid.rst ================================================ .. 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
================================================ FILE: docs/invocation.rst ================================================ :orphan: The kitty command line interface ==================================== .. program:: kitty .. include:: generated/cli-kitty.rst .. include:: basic.rst See also ----------- See kitty.conf(5) ================================================ FILE: docs/keyboard-protocol.rst ================================================ Comprehensive 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 `foot terminal `__ * The `ghostty terminal `__ * The `iTerm2 terminal `__ * The `Microsoft terminal `__ * The `rio terminal `__ * The `TuiOS terminal (multiplexer) `__ * The `Warp terminal `__ * The `WezTerm terminal `__ * The `xterm.js terminal `__ Libraries implementing this protocol: * The `notcurses library `__ * The `crossterm library `__ * The `textual library `__ * The vaxis library `go `__ and `zig `__ * The `bubbletea library `__ 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 `Flow control 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, in the currently active keyboard layout. 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 alt+a -> CSI 0 ; ; 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). In the above example, the :kbd:`alt` modifier is consumed by the OS itself to produce the text å and not sent to the terminal emulator, which gets only a "text input" event and no information about modifiers, thus the event gets encoded with no modifiers. The exact behavior in these situations depends on the OS, keyboard layout, IME system in use and so on. In general, if the terminal emulator receives no key information, the key number 0 must be used to indicate a pure "text event". 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 usable 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, except that lock keys (such as :kbd:`Num lock` and :kbd:`Caps lock`) are not encoded as the legacy mode has no encoding for them. 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", "\t (9)", "ESC I", "ESC \t", "CSI 105; 6 u" "3", "3 (51)", "# (35)", "ESC 3", "ESC (27)", "ESC #", "ESC ESC", "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!! ================================================ FILE: docs/kittens/broadcast.rst ================================================ broadcast ================================================== .. 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 ================================================ FILE: docs/kittens/choose-files.rst ================================================ Selecting files, fast ======================== .. only:: man Overview -------------- .. versionadded:: 0.45.0 .. only:: not man .. figure:: /screenshots/choose-files.webp :alt: The choose files kitten, showing metadata and title from an e-book file :align: center :width: 100% The choose-files kitten is designed to allow you to select files, very fast, with just a few key strokes. It operates like `fzf `__ and similar fuzzy finders, except that it is specialised for finding files. As such it supports features such as filtering by file type, file type icons, content previews and so on, out of the box. It can be used as a drop in (but much more efficient and keyboard friendly) replacement for the :guilabel:`File open and save` dialog boxes common to GUI programs. On Linux, with the help of the :doc:`desktop-ui
` kitten, you can even convince most GUI programs on your computer to use this kitten instead of regular file dialogs. Simply run it as:: kitten choose-files to select a single file from the tree rooted at the current working directory. Type a few letters from the filename and once it becomes the top selection, press :kbd:`Enter`. You can change the current directory by selecting a directory and pressing the :kbd:`Tab` key. :kbd:`Shift+Tab` goes up one directory level. If you want to choose a file and insert it into your shell prompt at the current cursor position, press :sc:`insert_chosen_file` for files or :sc:`insert_chosen_directory` for directories. Similarly, to have a file chosen in a command line, use, for example:: some-command $(kitten choose-file) Note that the above may not work in a complicated pipeline as it performs terminal I/O and needs exclusive access to the tty device while choosing a file. .. note:: For content previews, this kitten uses some external programs. In particular `ffmpeg `__ is needed for video previews and `calibre `__ is needed for ebook metadata and cover preiews. Creating shortcuts to favorite/frequently used directories ------------------------------------------------------------ You can create keyboard shortcuts to quickly switch to any directory in :file:`choose-files.conf`. For example: .. code-block:: conf map ctrl+t cd /tmp map alt+p cd ~/my/project Selecting multiple files ----------------------------- When you wish to select multiple files, start the kitten with :option:`--mode `:code:`=files`. Then instead of pressing :kbd:`Enter`, press :kbd:`Shift+Enter` instead and the file will be added to the list of selections. You can also hold the :kbd:`Ctrl` key and click on files to add them to the selections. Similarly, you can hold the :kbd:`Alt` key and click to select ranges of files (similar to using :kbd:`Shift+click` in a GUI app). Press :kbd:`Enter` on the last selected file to finish. The list of selected files is displayed at the bottom of the kitten and you can click on them to deselect a file. Similarly, pressing :kbd:`Shift+Enter` will un-select a previously selected file. Hidden and ignored files -------------------------- By default, the kitten does not process hidden files and directories (whose names start with a period). This can be :opt:`changed in the configuration ` and also at runtime via the clickable link to the right of the search input. Similarly, the kitten respects both :file:`.gitignore` and :file:`.ignore` files, by default. This can also be changed both :opt:`in configuration ` or at runtime. Note that :file:`.gitignore` files are only respected if there is also a :file:`.git` directory present. The kitten also supports the global :file:`.gitignore` file, though it applies only inside git working trees. You can specify :opt:`global ignore patterns `, that apply everywhere in :file:`choose-files.conf`. Selecting non-existent files (save file names) ------------------------------------------------- This kitten can also be used to select non-existent files, that is a new file for a :guilabel:`Save file` type of dialog using :option:`--mode `:code:`=save-file`. Once you have changed to the directory you want the file to be in (using the :kbd:`Tab` key), press :kbd:`Ctrl+Enter` and you will be able to type in the file name. If you wish to modify an existing file name use :kbd:`Alt+Enter` to modify the filename of the current top match instead. Selecting directories --------------------------- This kitten can also be used to select directories, for an :guilabel:`Open directory` type of dialog using :option:`--mode `:code:`=dir`. Once you have changed to the directory you want, press :kbd:`Ctrl+Enter` to accept it. Or if you are in a parent directory you can select a descendant directory by pressing :kbd:`Enter`, the same as you would for selecting a file to open. Configuration ------------------------ You can configure various aspects of the kitten's operation by creating a :file:`choose-files.conf` in your :ref:`kitty config folder `. See below for the supported configuration directives. .. include:: /generated/conf-kitten-choose_files.rst .. include:: /generated/cli-kitten-choose_files.rst ================================================ FILE: docs/kittens/choose-fonts.rst ================================================ Changing 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, usually 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. Its value is used to set the value for the name. ================================================ FILE: docs/kittens/clipboard.rst ================================================ clipboard ================================================== .. 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 ================================================ FILE: docs/kittens/custom.rst ================================================ Custom 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. 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 `kitty-tab-switcher `__ Fuzzy finder for kitty tabs with previews `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. ================================================ FILE: docs/kittens/desktop-ui.rst ================================================ Using terminal programs to provide Linux desktop components =============================================================== .. only:: man Overview -------------- .. versionadded:: 0.43.0 Power users of terminals on Linux also often like to use bare bones window managers instead of full fledged desktop environments. This kitten helps provide parts of the desktop environment that are missing from such setups, and does so using keyboard friendly, terminal first, UI components. Some of its features are: * Replace the typical File Open/Save dialogs used in GUI programs with the fast and keyboard centric :doc:`choose-files ` kitten running in a semi-transparent kitty overlay. * Allow simple command line based management of the desktop light/dark modes. How to install ------------------- .. note:: This kitten relies on the :doc:`panel kitten ` under the hood to supply UI components. Check :ref:`the documentation ` of that kitten to see if your window manager works with it. First, run:: kitten desktop-ui enable-portal Then, set the following two environment variables, *system wide*, that means in :file:`/etc/environment` or the equivalent for your distribution:: QT_QPA_PLATFORMTHEME=xdgdesktopportal GTK_USE_PORTAL=1 Finally, reboot. Now, when you open a file dialog in most GUI applications, it should open the :doc:`choose-files kitten ` instead of a normal file open dialog. You can change the current light/dark mode of your desktop by running:: kitten desktop-ui set-color-scheme dark kitten desktop-ui set-color-scheme light Check the current value using:: dbus-send --session --print-reply --dest=org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read string:org.freedesktop.appearance string:color-scheme How it works ---------------- Modern Linux desktops have so called `portals `__ that were invented for sandboxed applications and provide various facilities to such applications over DBUS, including file open dialogs, common desktop settings, etc. This kitten works by implementing a backend for some of these services. Normal GUI applications can then be told to make use of these services, thereby allowing us to replace parts of the desktop experience as needed. There are multiple competing implementations of the backends. Each desktop environment like KDE or GNOME has it's own backend and many window managers provide implementations for some backends as well. Service discovery and configuring which backend to use happens via the :file:`xdg-desktop-portal` program, usually found at :file:`/usr/lib/xdg-desktop-portal`. It can be configured by files in :file:`~/.local/share/xdg-desktop-portal`. See `man portals.conf `__. The ``kitten desktop-ui enable-portal`` command takes care of the setup for you automatically. If you want to customize exactly which services to use this kitten for, run the command and then edit the conf file that the command says it has patched. Troubleshooting ------------------- First, ensure that DBUS is able to auto-start the kitten when it is needed. If the kitten is not already running, try the following command:: dbus-send --session --print-reply --dest=org.freedesktop.impl.portal.desktop.kitty \ /net/kovidgoyal/kitty/portal org.freedesktop.DBus.Properties.GetAll \ string:net.kovidgoyal.kitty.settings If DBUS is able to start the kitten or if it is already running it will print out the version property, otherwise it will fail with an error. If it fails, check the file :file:`~/.local/share/dbus-1/services/org.freedesktop.impl.portal.desktop.kitty.service` that should have been created by the ``enable-portal`` command. It's ``Exec`` key must point to the full path to the kitten executable. Next, check that the XDG portal system is actually using this kitten for its settings backend. Run:: dbus-send --session --print-reply --dest=org.freedesktop.portal.Desktop \ /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read \ string:net.kovidgoyal.kitty string:status If this returns a reply then the kitten is being used, as expected. If it returns a not found error, then some other backend is being used for settings. Read the ``portals.conf`` man page and run:: /usr/lib/xdg-desktop-portal -r v this will output a lot of debug information, which should tell you which backend is chosen for which service. Read the debug output carefully to determine why the kitten is not being selected. If some GUI applications are not using the choose-files kitten for their file select dialogs, then make sure the environment variables mentioned above are set, you can also try running the GUI application with them set explicitly, as:: QT_QPA_PLATFORMTHEME=xdgdesktopportal GTK_USE_PORTAL=1 my-gui-app Note that not all applications use portals, so if some particular application is failing to use the portal but others work, report the issue to that applications' developers. Configuration ------------------------ You can configure various aspects of the kitten's operation by creating a :file:`desktop-ui-portal.conf` in your :ref:`kitty config folder `. See below for the supported configuration directives. .. include:: /generated/conf-kitten-desktop_ui.rst ================================================ FILE: docs/kittens/developing-builtin-kittens.rst ================================================ Developing 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 ================================================ FILE: docs/kittens/diff.rst ================================================ kitty-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` 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`, :kbd:`Ctrl+F` Scroll to previous page :kbd:`PgUp`, :kbd:`Ctrl+B` Scroll down half page :kbd:`Ctrl+D` Scroll up half page :kbd:`Ctrl+U` 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 or exit :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 `. ================================================ FILE: docs/kittens/hints.rst ================================================ Hints ========== .. 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 ================================================ FILE: docs/kittens/hyperlinked_grep.rst ================================================ Hyperlinked 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. ================================================ FILE: docs/kittens/icat.rst ================================================ icat ======================================== .. 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 ================================================ FILE: docs/kittens/notify.rst ================================================ notify ================================================== .. 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 ================================================ FILE: docs/kittens/panel.rst ================================================ Draw a GPU accelerated dock panel on your desktop ==================================================================================================== .. highlight:: sh .. only:: man Overview -------------- .. include:: ../quake-screenshots.rst Draw the desktop wallpaper or docks and panels using arbitrary terminal programs, For example, have `btop `__ or `cava `__ be your desktop wallpaper. It is useful for showing status information or notifications on your desktop using terminal programs instead of GUI toolkits. The screenshot to the side shows some uses of the panel kitten to draw various desktop components such as the background, a quick access floating terminal and a dock panel showing system information (Linux only). .. versionadded:: 0.42.0 Support for macOS, see :ref:`compatibility matrix ` for details. and X11 (background and overlay). .. versionadded:: 0.34.0 Support for Wayland. See :ref:`below ` for which Wayland compositors work. Using this kitten is simple, for example:: 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 screenshots. If you are on Wayland or macOS, you can, for instance, run:: 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. There are projects that make use of this facility to implement generalised panels and desktop components: .. _panel_projects: * `kitty panel `__ * `pawbar `__ .. _remote_control_panel: Controlling panels via remote control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can control panels via the kitty :doc:`remote control
` facility. Create a panel with remote control enabled:: kitten panel -o allow_remote_control=socket-only --lines=2 \ --listen-on=unix:/tmp/panel kitten run-shell Now you can control this panel using remote control, for example to show/hide it, use:: kitten @ --to=unix:/tmp/panel resize-os-window --action=toggle-visibility To move the panel to the bottom of the screen and increase its height:: kitten @ --to=unix:/tmp/panel resize-os-window --action=os-panel \ --incremental edge=bottom lines=4 To create a new panel running the program top, in the same instance (like creating a new OS window):: kitten @ --to=unix:/tmp/panel launch --type=os-panel --os-panel edge=top \ --os-panel lines=8 top .. include:: ../generated/cli-kitten-panel.rst .. _quake_ss: How the screenshots were generated ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The system statistics in the background were created using:: kitten panel --edge=background -o background_opacity=0.2 -o background=black btop This creates a kitty background window and inside it runs the `btop `__ program to display the statistics. The floating quick access window was created by running:: kitten quick-access-terminal kitten run-shell \ zsh -c 'printf "\e]66;s=4;Quick access kitty in Hyprland\a\n\n\n\nAlso uses kitty to draw desktop background\n"' This starts the quick access window and inside it runs ``kitten run-shell``, which in turn first runs ``zsh`` to print out the message and then starts the users login shell. The Linux dock panel was:: wm bar This is a custom program I wrote for my personal use. It uses kitty's kitten infrastructure to implement the bar in a `few hundred lines of code `__. This was designed for my personal use only, but, there are :ref:`public projects implementing general purpose panels using kitty `. .. _panel_compat: Compatibility with various platforms ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. only:: man See the HTML documentation for the compatibility matrix. .. only:: not man Generated with the help of the :file:`panels.py` test script. .. tab:: Wayland Below is a list of the status of various Wayland compositors. The panel kitten relies on the `wlr layer shell protocol `__, which is technically supported by almost all Wayland compositors, but the implementation in some of them is quite buggy. 🟢 **Hyprland** Fully working, no known issues 🟢 **labwc** Fully working, no known issues 🟢 **niri** Fully working, no known issues 🟢 **river** Fully working, no known issues 🟢 **Xfce** Fully working, no known issues 🟠 **KDE** (kwin) Mostly working, except that clicks outside background panels cause kwin to :iss:`erroneously hide the panel <8715>`. KDE uses an `undocumented mapping `__ under Wayland to set the window type from the :code:`kitten panel --app-id` flag. You might want to use :code:`--app-id=dock` so that KDE treats the window as a dock panel, and disables window appearing/disappearing animations for it. 🟠 **Sway** Renders its configured background over the background window instead of under it. This is because it uses the wlr protocol for backgrounds itself. 🔴 **GNOME** (mutter) Does not implement the wlr protocol at all, nothing works. .. tab:: macOS Mostly everything works, with the notable exception that dock panels do not prevent other windows from covering them. This is because Apple does not provide and way to do this in their APIs. .. tab:: X11 Support is highly dependent on the quirks of individual window managers. See the matrix below: .. list-table:: Compatibility matrix :header-rows: 1 :stub-columns: 1 * - WM - Desktop - Dock - Quick - Notes * - KDE - 🟠 - 🟢 - 🟢 - transparency does not work for :option:`--edge=background <--edge>` * - GNOME - 🟢 - 🟢 - 🟢 - * - XFCE - 🟢 - 🟢 - 🟢 - * - i3 - 🔴 - 🟠 - 🔴 - only top and bottom dock panels, without transparency * - xmonad - 🔴 - 🔴 - 🔴 - doesn't support the needed NET_WM protocols ================================================ FILE: docs/kittens/query_terminal.rst ================================================ Query 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 ================================================ FILE: docs/kittens/quick-access-terminal.rst ================================================ .. _quake: Make a Quake like quick access terminal ==================================================================================================== .. highlight:: sh .. only:: man Overview -------------- .. include:: ../quake-screenshots.rst .. versionadded:: 0.42.0 See :ref:`here for what platforms it works on `. This kitten can be used to make a quick access terminal, that appears and disappears at a key press. To do so use the following command: .. code-block:: sh kitten quick-access-terminal Run this command in a terminal, and a quick access kitty window will show up at the top of your screen. Run it again, and the window will be hidden. To make the terminal appear and disappear at a key press: .. |macOs| replace:: :guilabel:`System Preferences->Keyboard->Keyboard Shortcuts->Services->General` .. only:: not man .. tab:: Linux Simply bind the above command to some key press in your window manager or desktop environment settings and then you have a quick access terminal at a single key press. .. tab:: macOS In kitty, run the above command to show the quick access window, then close it by running the command again or pressing :kbd:`ctrl+d`. Now go to |macOS| and set a shortcut for the :guilabel:`Quick access to kitty` entry. .. only:: man In Linux, simply assign the above command to a global shortcut in your window manager. In macOS, go to |macOS| and set a shortcut for the :guilabel:`Quick access to kitty` entry. Configuration ------------------------ You can configure the appearance and behavior of the quick access window by creating a :file:`quick-access-terminal.conf` file in your :ref:`kitty config folder `. In particular, you can use the :opt:`kitty_conf ` option to change various kitty settings, just for the quick access window. .. note:: This kitten uses the :doc:`panel kitten ` under the hood. You can use the :ref:`techniques described there ` for remote controlling the quick access window, remember to add ``kitty_override allow_remote_control=socket-only`` and ``kitty_override listen_on=unix:/tmp/whatever`` to :file:`quick-access-terminal.conf`. See below for the supported configuration directives: .. include:: /generated/conf-kitten-quick_access_terminal.rst .. include:: /generated/cli-kitten-quick_access_terminal.rst Sample quick-access-terminal.conf --------------------------------------- You can download a sample :file:`quick-access-terminal.conf` file with all default settings and comments describing each setting by clicking: :download:`sample quick-access-terminal.conf `. ================================================ FILE: docs/kittens/remote_file.rst ================================================ Remote 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. ================================================ FILE: docs/kittens/ssh.rst ================================================ Truly 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 ================================================ FILE: docs/kittens/themes.rst ================================================ Changing 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, and also all background image settings, 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 ================================================ FILE: docs/kittens/transfer.rst ================================================ Transfer 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 ================================================ FILE: docs/kittens/unicode_input.rst ================================================ Unicode 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 ================================================ FILE: docs/kittens_intro.rst ================================================ .. _kittens: Extend with kittens ----------------------- .. toctree:: :hidden: :glob: kittens/icat kittens/diff kittens/unicode_input kittens/themes kittens/choose-fonts kittens/hints kittens/quick-access-terminal kittens/choose-files kittens/panel 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:`Quick access terminal ` Get access to a quick access floating, semi-transparent kitty window with a single keypress. :doc:`Panel ` Draw the desktop wallpaper or docks and panels using arbitrary terminal programs. :doc:`Choose files ` Preview and select files at the speed of thought :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:`Clipboard ` Copy/paste to the clipboard from shell scripts, even over SSH. You can also :doc:`Learn to create your own kittens `. ================================================ FILE: docs/launch.rst ================================================ The :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 initialization/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 ... def on_tab_bar_dirty(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when any changes happen to the tab bar, such a new tabs being # created, tab titles changing, tabs moving, etc. Useful to display the # tab bar externally to kitty. This is called even if the tab bar is # hidden. Note that this is called only in *global watchers*, that is # watchers defined in kitty.conf or using the --watcher command line # flag. data contains tab_manager which is the object responsible for # managing all tabs in a single OS Window. ... def on_quit(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when kitty is about to quit. This is called in *global watchers* # only. It is called twice: once before the quit confirmation dialog is # shown (data['confirmed'] will be False) and once after the user has # confirmed quitting (data['confirmed'] will be True). Setting # data['aborted'] to True will abort the quit in both cases. ... 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 ================================================ FILE: docs/layouts.rst ================================================ Arrange 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 # Maximize the active window along the horizontal axis (fill full width), # keeping other windows visible in their vertical positions. Press again to # restore the original layout. map ctrl+shift+right layout_action maximize horizontal # Maximize the active window along the vertical axis (fill full height), # keeping other windows visible in their horizontal positions. Press again # to restore the original layout. map ctrl+shift+up layout_action maximize vertical 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``. The ``maximize`` action expands the active window to fill the maximum available space along a single axis while keeping the rest of the layout intact. Use ``maximize horizontal`` to fill the full width and ``maximize vertical`` to fill the full height. Calling it again restores the original split sizes. 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. The easiest method is to simply drag the window borders using a mouse, controlled by the option :opt:`window_drag_tolerance`. Note that technically this resizes layout slots not actual windows, so it does not work exactly like resizing OS Windows on your desktop. Instead, the layout is changed and potentially multiple windows get resized when dragging a single border. For keyboard friendly resizing, 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! ================================================ FILE: docs/mapping.rst ================================================ :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: Configuring a timeout ---------------------- You can also set a timeout for keyboard modes and multi-key mappings. If a timeout is set and you don't complete the key sequence or exit the mode within the specified time, the mode will be automatically cancelled. This is useful for multi-key mappings where you might accidentally press the first key and then change your mind. The timeout is specified in seconds and can be set globally using the :opt:`map_timeout` option or per-mode using ``--timeout``:: # Set a global 2 second timeout for all multi-key and modal mappings map_timeout 2.0 # This mode will have a 5 second timeout (overrides global setting) map --new-mode resize --timeout 5.0 kitty_mod+r map --mode resize h resize_window narrower map --mode resize l resize_window wider # ... more mappings # Multi-key mapping with the global timeout map ctrl+a>h new_window When a timeout occurs, the mode is exited and any buffered keys are discarded. A timeout value of zero disables the timeout. For multi-key sequences, the timeout is restarted after each valid key press in the sequence. 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 shown below: .. tab:: vim In :file:`~/.vimrc` add: .. code-block:: vim let &t_ti = &t_ti . "\033]1337;SetUserVar=in_editor=MQ==\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", "UIEnter" }, { group = vim.api.nvim_create_augroup("KittySetVarVimEnter", { clear = true }), callback = function() if vim.api.nvim_ui_send then vim.api.nvim_ui_send("\x1b]1337;SetUserVar=in_editor=MQ==\007") else io.stdout:write("\x1b]1337;SetUserVar=in_editor=MQ==\007") end end, }) vim.api.nvim_create_autocmd({ "VimLeave", "VimSuspend" }, { group = vim.api.nvim_create_augroup("KittyUnsetVarVimLeave", { clear = true }), callback = function() if vim.api.nvim_ui_send then vim.api.nvim_ui_send("\x1b]1337;SetUserVar=in_editor\007") else io.stdout:write("\x1b]1337;SetUserVar=in_editor\007") end 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. .. note:: When using multi-key mappings, of the form :kbd:`k1>k2` or similar, the condition applies to the first key and you can have only one condition per key, the last in kitty.conf wins. In particular, this means you cannot have multiple conditions applying to multi-key mappings with the same first key and you cannot have mappings with and without conditions applying to multi-keys with the same first key. 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 ... # Ignore 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 ================================================ FILE: docs/marks.rst ================================================ Mark 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 ================================================ FILE: docs/misc-protocol.rst ================================================ Miscellaneous 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. .. _mouse_leave_window: Reporting when the mouse leaves the window ---------------------------------------------- kitty extends the SGR Pixel mouse reporting protocol created by xterm to also report when the mouse leaves the window. This event is delivered encoded as a normal SGR pixel event except that the eight bit is set on the first number. Additionally, bit 5 is set to indicate this is a motion related event. The remaining bits 1-7 (except 5) are used to encode button and modifier information. When bit 8 is set it means the event is a mouse has left the window event, and all other bits should be ignored. The pixel position values must also be ignored as they may not be accurate. An escape code to move the contents of the screen into the scrollback ------------------------------------------------------------------------------------- The escape code is ``\x1b [ 22 J`` (ignoring spaces present for clarity). It moves all screen contents (text and images) into the scrollback leaving the screen in the same state as it would be if the standard screen clear escape code had been used ``\x1b [ 2 J``. 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). ================================================ FILE: docs/multiple-cursors-protocol.rst ================================================ The multiple cursors protocol ============================================== .. versionadded:: 0.43.0 Many editors support something called *multiple cursors* in which you can make the same changes at multiple locations in a file and the editor shows you cursors at each of the locations. In a terminal context editors typically implement this by showing some Unicode glyph at each location instead of the actual cursor. This is sub-optimal since actual cursors implemented by the terminal have many niceties like smooth animation [anim]_, auto adjust colors [rv]_, etc. To address this and other use cases, this protocol allows terminal programs to request that the terminal display multiple cursors at specific locations on the screen. Quickstart ---------------- An example, showing how to use the protocol: .. code-block:: sh # Show cursors of the same shape as the main cursor at y=4, x=5 printf "\e[>29;2:4:5 q" # Show more cursors on the seventh line, of various shapes, the underline shape is shown twice printf "\e[>1;2:7:1 q\e[>2;2:7:3 q\e[>3;2:7:5;2:7:7 q" The escape code to show a cursor has the following structure (ignore spaces they are present for readability only):: CSI > SHAPE;CO-ORD TYPE : CO-ORDINATES ; CO-ORD TYPE : CO-ORDINATES ... TRAILER Here ``CSI`` is the two bytes ESC (``0x1b``) and [ (``0x5b``). ``SHAPE`` can be one of: * ``0``: No cursor * ``1``: Block cursor * ``2``: Beam cursor * ``3``: Underline cursor * ``29``: Follow the shape of the main cursor * ``30``: Change the color of text under extra cursors * ``40``: Change the color of extra cursors * ``100``: Used for querying currently set cursors ``CO-ORD TYPE`` can be one of: * ``0``: This refers to the position of the main cursor and has no following co-ordinates. * ``2``: In this case the following co-ordinates are pairs of numbers pointing to cells in the form ``y:x`` with the origin in the top left corner at ``1,1``. There can be any number of pairs, the terminal must treat each pair as a new location to set a cursor. * ``4``: In this case the following co-ordinates are sets of four numbers that define a rectangle in the same co-ordinate system as above of the form: ``top:left:bottom:right``. The shape is set on every cell in the rectangle from the top left cell to the bottom right cell, inclusive. If no numbers are provided, the rectangle is the full screen. There can be any number of rectangles, the terminal must treat each set of four numbers as a new rectangle. The sequence of ``CO-ORD TYPE : CO-ORDINATES`` can be repeated any number of times separated by ``;``. The ``SHAPE`` will be set on the cells indicated by each such group. For example: ``-1;2:3:4;4:5:6:7:8`` will set the shape ``-1`` at the cell ``(3, 2)`` and in the rectangle ``(6, 5)`` to ``(8, 7)`` inclusive. Finally, the ``TRAILER`` terminates the sequence and is the bytes SPACE (``0x20``) and q (``0x71``). Terminals **must** ignore cells that fall outside the screen. That means, for rectangle co-ordinates only the intersection of the rectangle with the screen must be considered, and point co-ordinates that fall outside of the screen are simply ignored, with no effect. Terminals **must** ignore extra co-ordinates, that means if an odd number of co-ordinates are specified for type ``2`` the last co-ordinate is ignored. Similarly for type ``4`` if the number of co-ordinates is not a multiple of four, the last ``1 <= n <= 3`` co-ordinates are ignored, as if they were not specified. Querying for support ------------------------- A terminal program can query the terminal emulator for support of this protocol by sending the escape code:: CSI > TRAILER In this case a supporting terminal must reply with:: CSI > 1;2;3;29;30;40;100;101 TRAILER Here, the list of numbers indicates the cursor shapes and other operations the terminal supports and can be any subset of the above. No numbers indicates the protocol is not supported. To avoid having to wait with a timeout for a response from the terminal, the client should send this query code immediately followed by a request for the `primary device attributes `_. If the terminal responds with an answer for the device attributes without an answer for the *query* the terminal emulator does not support this protocol at all. Terminals **must** respond to these queries in FIFO order, so that multiplexers that split a single screen know which split to send responses too. Clearing previously set multi-cursors ------------------------------------------ The cursor at a cell is cleared by setting its shape to ``0``. The most common operation is to clear all previously set multi-cursors. This is easily done using the *rectangle* co-ordinate system above, like this:: CSI > 0;4 TRAILER For more precise control different co-ordinate types can be used. This is particularly important for multiplexers that split up the screen and therefore need to re-write these escape codes. .. _extra_cursor_color: Changing the color of extra cursors --------------------------------------- In order to visually distinguish extra cursors from the main cursor, it is possible to specify a color pair for extra cursors. Note that for performance reasons, there is only a single color pair that all extra cursors share. The color pair consists of the cursor color and the color for text in the cell the cursor is on. To change this color pair use an escape code of the form:: CSI > WHICH ; COLOR_SPACE : COLOR_PARAMETER1 : COLOR_PARAMETER2 : ... TRAILER Here, ``WHICH`` is ``30`` to set the color of text under the cursor and ``40`` to set the color of the cursor itself (these numbers mimic the SGR codes for foreground and background respectively). The ``COLOR_SPACE`` parameter sets the type of color, it can take values: ``0`` - unset color is same as for main cursor. No color parameters. ``1`` - *special* which typically means some kind of reverse video effect, see below ``2`` - sRGB color, with three color parameters, red, green and blue as numbers from 0 to 255 ``5`` - Indexed color with one color parameter which is an index into the color table from 0 to 255 When the cursor color is set to *special* via ``40`` it means the block cursor must be rendered with a reverse video effect where the cursor color becomes the foreground color of the cell under the cursor and the foreground color of the cell becomes its background color. Implementations are free to adjust these colors to ensure suitable contrast levels. In this case the text color set by ``30`` must be ignored. When the cursor color is not set to *special* but the text color via ``30`` is set to special, then that means the foreground color of the cell with the cursor must be changed to its background color for a partial reverse video effect. When unset, aka, set to ``0`` the cursors must be the same color as the main cursor. In particular if the main color is using a reverse video effect, the extra cursors must use the exact same colors as the main cursor, not the colors of the cells they are on. Querying for already set cursors -------------------------------------- Programs can ask the terminal what extra cursors are currently set, by sending the escape code:: CSI > 100 TRAILER The terminal must respond with **one** escape code:: CSI > 100; SHAPE:CO-ORDINATE TYPE:CO-ORDINATES ; ... TRAILER Here, the ``SHAPE:CO-ORDINATE TYPE:CO-ORDINATES`` block can be repeated any number of times, separated by ``;``. This response gives the set of shapes and positions currently active. If no cursors are currently active, there will be no blocks, just an empty response of the form:: CSI > 100 TRAILER Again, terminals **must** respond in FIFO order so that multiplexers know where to direct the responses. Querying for extra cursor colors ------------------------------------- Programs can ask the terminal what cursor colors are currently set, by sending escape code:: CSI > 101 TRAILER The terminal must respond with **one** escape code:: CSI > 101 ; 30 : COLOR_SPACE : COLOR_PARAMETERS ; 40 : COLOR_SPACE : COLOR_PARAMETERS TRAILER The number and type of ``COLOR_PARAMETERS`` depends on the preceding ``COLOR_SPACE`` and can be omitted for some ``COLOR_SPACE`` values. See the section :ref:`extra_cursor_color` for details. Interaction with other terminal controls and state ------------------------------------------------------- **The main cursor** The extra cursors must all have the same color and opacity and blink state as the main cursor. The main cursor's visibility must not affect the visibility of the extra cursors. Their visibility and shape are controlled only by this protocol. **Clearing the screen** The escape codes used to clear the screen (`ED `__) with parameters 2, 3 and 22 must remove all extra cursors, this is so that the clear command can be used by users to clear the screen of extra cursors. **Reset*** This must remove all extra cursors. **Alternate screen*** Switching between the main and alternate screens must remove all extra cursors. **Scrolling** The index (IND) and reverse index (RI) escape codes that cause screen contents to scroll into scrollback or off screen must not affect the extra cursors in any way. They remain at exactly the same position. It is up to applications to manage extra cursor positions when using these escape codes if needed. There are not a lot of use cases for scrolling extra cursors with screen content, since extra cursors are meant to be ephemeral and on screen only, not in scrollback. This allows terminals to avoid the extra overhead of adjusting positions of the extra cursors on every scroll. Footnotes ------------- .. [anim] kitty allows the cursor blink to be :opt:`animated ` using any CSS easing function. This cannot be implemented using fake cursors. .. [rv] kitty has a special "reverse video" color mode for cursors where the color of the cursor and the text under the cursor is adjusted based on the color of the cell under the cursor. This also cannot be implemented using fake cursors. ================================================ FILE: docs/notifications.py ================================================ #!/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 ================================================ FILE: docs/open_actions.rst ================================================ Scripting the mouse click ====================================================== |kitty| has support for :term:`terminal hyperlinks `. These are generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``, :ref:`tool_mcat`, 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 launch 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 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. Similarly on Linux, you can associate certain files types to open in 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())) ================================================ FILE: docs/overview.rst ================================================ Overview ============== 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 `. You can quickly browse all available mappable actions by pressing :sc:`command_palette`. .. 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, run ``kitten`` in kitty, or to browse some of the more prominent ones, see :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. Finally, there is remote control which allows you to control kitty from anywhere, even across a network! See below for more about remote control. .. 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 ------------------ 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`. You can also easily switch between sessions with a keypress. See :doc:`sessions` for details. 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 `. Additionally, various bits of the kitty UI itself work with the mouse. You can drag and drop tabs in the tab bar to re-order them or move them from one OS Window to another, or even pop them out into a new OS Window. You can drag window borders to resize windows. You can double click on empty regions of the tab bar to create new tabs or double click on an existing tab to rename it. .. 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. |kitty| displays an interactive :opt:`scrollbar` along the right edge of the window that shows your current position in the scrollback. You can click and drag the scrollbar to quickly navigate through the history. 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:`nvim` 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 ================================================ FILE: docs/performance.rst ================================================ Performance =================== 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 isn't 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. ================================================ FILE: docs/pipe.rst ================================================ :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. ================================================ FILE: docs/pointer-shapes.rst ================================================ Mouse 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. ================================================ FILE: docs/press-mentions.rst ================================================ Press 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 ================================================ FILE: docs/protocol-extensions.rst ================================================ Terminal 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 multiple-cursors-protocol file-transfer-protocol desktop-notifications pointer-shapes unscroll color-stack deccara clipboard misc-protocol ================================================ FILE: docs/quake-screenshots.rst ================================================ .. sidebar:: .. only:: not man **Screenshots** .. figure:: /screenshots/quake-macos.webp :alt: Screenshot, showing the kitty floating quick access terminal above the background which is the program btop, running inside kitty, on macOS :align: center :width: 100% macOS .. figure:: /screenshots/quake-hypr.webp :alt: Screenshot, showing the kitty floating quick access terminal above the background which is the program btop, running inside kitty, on Hyprland in Linux :align: center :width: 100% Linux .. figure:: /screenshots/panel.png :alt: Screenshot, showing a sample panel :align: center :width: 100% A sample panel on Linux How the screenshots :ref:`were generated `. ================================================ FILE: docs/quickstart.rst ================================================ .. _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`. ================================================ FILE: docs/rc_protocol.rst ================================================ The 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 ================================================ FILE: docs/remote-control.rst ================================================ Control 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-pass` 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 .. note:: The payloads for the different remote control commands are documented in the :doc:`remote control protocol specification `. .. _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. Relative paths to scripts are interpreted with respect to the kitty config directory. .. 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 ================================================ FILE: docs/requirements.txt ================================================ # sphinx-inline-tabs breaks with sphinx >= 9 sphinx <= 8.2.3 furo sphinx-copybutton sphinxext-opengraph sphinx-inline-tabs sphinx-autobuild matplotlib ================================================ FILE: docs/sessions.rst ================================================ .. _sessions: Sessions ============= kitty has robust support for sessions. A session is basically a simple text file where you can define kitty windows, tabs and what programs to run in them as well as how to layout the windows. kitty also supports actions to easily :ac:`create and switch between existing sessions `, so that you can move seamlessly from working on one project to another with a couple of keystrokes. Let's see a quick example to get a feel of how easy it is to create sessions. First, a session file to develop a project: .. code-block:: session # Set the layout for the current tab layout tall # Set the working directory for windows in the current tab cd ~/path/to/myproject # Create the "main" window and run an editor in it to edit the project files launch --title "Edit My Project" /usr/bin/nvim # Create a side window to run a shell to build or test project launch --title "Build My Project" # Create another side window to keep an eye on some useful log file launch --title "Log for my project" /usr/bin/tail -f /path/to/project/log/file Save this file as :file:`~/path/to/myproject/launch.kitty-session`. Now when you want to work on the project, simply run: .. code-block:: sh kitty --session ~/path/to/myproject/launch.kitty-session You can also set the session in :file:`kitty.conf` via :opt:`startup_session`. Thus, it is very easy to create sessions and work on projects. To learn how to create more complex sessions, see :ref:`complex_sessions`. .. _goto_session: Creating/Switching to sessions with a keypress ------------------------------------------------ If you like to manage multiple sessions within a single terminal and easily swap between them, kitty has you covered. You can use the :ac:`goto_session` action in kitty.conf, like this: .. code-block:: conf # Press F7 and then c to jump to the "cool" project map f7>c goto_session ~/path/to/cool/cool.kitty-session # Press F7 and then h to jump to the "hot" project map f7>h goto_session ~/path/to/hot/hot.kitty-session # Browse and select from the list of known projects defined via goto_session commands map f7>/ goto_session # Browse and select from the list of active projects defined via goto_session commands map f7>/ goto_session --active-only [=no] # Same as above, but the sessions are listed alphabetically instead of by most recent map f7>/ goto_session --sort-by=alphabetical # Browse session files inside a directory and pick one map f7>p goto_session ~/.local/share/kitty/sessions # Go to the previously active session (larger negative numbers jump further back in history) map f7>- goto_session -1 In this manner you can define as many projects/sessions as you like and easily switch between them with a keypress. When a directory path is supplied to :ac:`goto_session`, kitty scans it for files ending in ``.kitty-session``, ``.kitty_session`` or ``.session`` and presents an interactive list. The ``--sort-by`` option controls the ordering of that list just like it does for globally known sessions. You can also close sessions using the :ac:`close_session` action, which closes all windows in the session with a single keypress. Displaying the currently active session name ---------------------------------------------- You can display the name of the currently active session file in the kitty tab bar using :opt:`tab_title_template`. For example, using the value:: {session_name} {title} will show you the name of the session file the current tab was loaded from, as well as the normal tab title. Or alternatively, you can set the tab title directly to a project name in the session file itself when creating the tab, like this:: new_tab My Project Name .. _complex_sessions: More complex sessions ------------------------- If you want to create more complex sessions, with sophisticated layouts, such as :ref:`splits_layout`, the easiest way is to set up the state you want to save manually by first starting kitty like this: .. code-block:: sh kitty -o 'map f1 save_as_session --use-foreground-process --relocatable' Now create whatever splits and tabs you need and start whatever programs such as editors, REPLs, debuggers, etc. you want to start in each of them. Once kitty is the way you want it, press the :kbd:`F1` key, and you will be prompted for a path at which to save the session file. Specify the path and the session will be saved there with the exact setup you created. The saved file will even be opened in your editor for you to review, automatically. .. tip:: If you want session files to be saved to a specific directory regardless of your current working directory, use the ``--base-dir`` option. For example:: map f7>s save_as_session --use-foreground-process --base-dir ~/.local/share/kitty/sessions This is particularly useful when kitty is launched from system-wide shortcuts where the working directory might not be your home directory. Note that ``--relocatable`` is typically not used with ``--base-dir``, since relocatable is meant for session files that are co-located with their project directories. If instead, you want to create these by hand, see the example below which shows all the major keywords you can use in kitty session files: .. code-block:: session # Set the layout for the current tab layout tall # Set the working directory for windows in the current tab. Relative paths # are resolved with respect to the location of this session file. 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 --title for the new OS window os_window_title my fancy os window # 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 focus_os_window launch emacs # Create another tab new_tab logs launch tail -f /var/log/syslog # Focus the first tab (index 0) when the session loads # You can also use a match expression like: focus_tab title:logs focus_tab 0 # 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. For example: .. code-block:: sh launch --cwd=$THIS_IS_EXPANDED some-program $THIS_IS_NOT_EXPANDED Making newly created windows join an existing session --------------------------------------------------------- Normally, after activating a session, if you create new windows/tabs they don't belong to the session. If you would prefer to have them belong to the currently active session, you can use the :ac:`new_window_with_cwd` and :ac:`new_tab_with_cwd` actions instead, like this:: map kitty_mod+enter new_window_with_cwd map kitty_mod+t new_tab_with_cwd map kitty_mod+n new_os_window_with_cwd This will cause newly created windows and tabs to belong to the currently active session, if any. Note that adding a window to a session in this way is temporary, it does not edit the session file. If you wish to update the session file of the currently active session, you can use the following mapping for it:: map f5 save_as_session --relocatable --use-foreground-process --match=session:. . The two can be combined, using the :ac:`combine` action. For even more control of what session a window is added to use the :doc:`launch ` command with the :option:`launch --add-to-session` flag. Sessions with remote connections ------------------------------------- If you use the :doc:`ssh kitten ` to connect to remote computers, :ac:`save_as_session` is smart enough to save the ssh kitten invocation to your session file, preserving the remote working directory and even the currently running program on the remote host! Try it, run kitty with:: kitty -o 'map f1 save_as_session --use-foreground-process --relocatable' --session <(echo "layout vertical\nlaunch\nlaunch") Now in both windows, run:: kitten ssh localhost To connect them both to a remote computer (replace ``localhost`` with another computer if you like). In one window change the directory to /tmp and in the other start some program. Then press :kbd:`F1` to save the session file. When you run the session file in another kitty instance you will see both windows re-created, as expected with the correct working directories and running programs. Managing multi tab sessions in a single OS Window ---------------------------------------------------- The natural way to organise sessions in kitty is one per :term:`os_window`. However, if you prefer to manage multiple sessions in a single OS Window, you can configure the kitty tab bar to only show tabs that belong to the currently active session. To do so, use :opt:`tab_bar_filter` in :file:`kitty.conf` set:: tab_bar_filter session:~ or session:^$ This will restrict the tab bar to only showing tabs from the currently active session as well tabs that do not belong to any session. Furthermore, when you are in a window or tab that does not belong to any session, the tab bar will show the tabs from the most recent active session, to maintain context. Keyword reference --------------------- Below is the list of all supported keywords in session files along with documentation for them. ``cd [path]`` Change the working directory for all windows in the current tab to ``path``. Relative paths are resolved with respect to the directory containing the session file. ``focus`` Give keyboard focus to the window created by the previous launch command ``focus_matching_window`` Give keyboard focus to window that matches the specified expression. See :ref:`search_syntax` for the syntax for matching expressions. ``focus_os_window`` Give keyboard focus to the current OS Window. This is guaranteed to work only is some other OS Window in the current kitty process has focus, otherwise the window manager might block changing focus to prevent *focus stealing*. ``focus_tab [tab specifier]`` Set which tab should be active (focused) in the current OS Window. The tab specifier can be either a plain number (treated as a 0-based index) or a match expression. For example, ``focus_tab 0`` will focus the first tab, ``focus_tab 1`` the second tab, and ``focus_tab title:logs`` will focus the tab whose title matches "logs". See :ref:`search_syntax` for the full syntax of match expressions. This is useful for session files that create multiple tabs and want to ensure a specific tab is active when the session is loaded. ``enabled_layouts comma separated list of layout names`` Set the layouts allowed in the current tab. Same syntax as :opt:`enabled_layouts`. ``launch`` Create a new window running the specified command or the default shell if no command is specified. See :doc:`launch` for details. Note that creating tabs and OS Windows using launch is not supported in session files, use the dedicated keywords for these. ``layout name`` Set the layout for the current tab to the specified layout, including any specified options, see :doc:`layouts` for the available layouts and options. ``new_os_window`` Create a new OS Window. Any OS window related keywords specified before the first ``new_os_window`` will apply to the first OS Window. ``new_tab [tab title]`` Create a new tab with the specified title. If no title is specified, the title behaves just as for a regular tab in kitty. ``os_window_title`` Set the title for the current OS Window. The OS Window will then always have this title, it will not change based on the title of the currently active window inside the OS Window. ``os_window_class`` Set the class part of WM_CLASS or Wayland Application Id for the current OS Window ``os_window_name`` Set the name part of WM_CLASS or Wayland Window tag for the current OS Window ``os_window_size`` Set the size of the current OS Window, can be specified in pixels or cells. For example: 80c 24c is a window of width 80 cells by 24 cells. ``os_window_state`` Set the state of the current OS Window, can be: ``normal``, ``fullscreen``, ``maximized`` or ``minimized`` ``resize_window`` Resize the current window. See the :ac:`resize_window` action for details. For example: resize_window wider 2 ``set_layout_state`` This keyword is only used in session files generated by the :ac:`save_as_session` action, it's syntax is undocumented and for internal use only. ``title`` Set the title for the next window. Deprecated, use ``launch --title`` instead. .. _save_as_session: The save_as_session action ------------------------------ This action can be mapped to a key press in :file:`kitty.conf`. It will save the currently open OS Windows, tabs, windows, running programs, working directories, etc. into a session file. It is a convenient way to :ref:`complex_sessions`. The options this action takes are documented below. .. include:: generated/save-as-session.rst ================================================ FILE: docs/shell-integration.rst ================================================ .. _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|2`` 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. A value of ``1`` will cause the click events to have absolute y co-ordinates, a value of ``2`` will cause them to have y-coordinates relative to the top line of the current prompt. In relative mode, y is zero for cells on the top line of the current prompt. The current prompt here is either the secondary (PS2) or primary prompt. If the secondary prompt is on the same line or above the mouse position, then the reported y will be with respect to that, otherwise with respect to the primary prompt. The click event is encoded in the SGR encoding from xterm. 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). ================================================ FILE: docs/support.html ================================================ ================================================ FILE: docs/support.rst ================================================ Support 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 ================================================ FILE: docs/text-sizing-protocol.rst ================================================ The 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 (n < d). ``0`` - top, ``1`` - bottom, ``2`` - centered" "h", "Integer from 0 to 2", "0", "The horizontal alignment to use for fractionally scaled text (n < d). ``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 code points 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 render area at top, bottom or middle. Similarly, the ``h`` key does horizontal alignment — left, right or centered. Note that alignment here is not actual text alignment, it refers to how the fractionally scaled render area fits inside the full render area of size ``s * w`` by ``s`` cells. Thus, alignment only applies when ``n < d``. 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 a :ref:`complex algorithm, specified below `. 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 ``CR`` (carriage return) followed by ``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 the cursor moved by two cells, then the width part is supported and if the third response has the cursor moved by another two cells, then 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. .. _gseg: The algorithm for splitting text into cells ------------------------------------------------ .. note:: kitty comes with a utility to test terminal compliance with this algorithm. Install kitty and run: ``kitten __width_test__`` in any terminal to test it. This uses tests published by the Unicode consortium, `GraphemeBreakTest.txt `__. .. warning:: This algorithm is under public discussion in :iss:`8533`. If serious issues are brought to light in that discussion, there may be small changes to the algorithm to address them. Additionally, in the future if the Unicode standard changes in ways that affect this algorithm, it will be updated. Currently the algorithm is based on Unicode version 16. Here, we specify how a terminal must split up text into cells, where a cell is a width one unit in the character grid the terminal displays. The basis for the algorithm is the `Grapheme segmentation algorithm `__ from the Unicode standard. However, that algorithm alone is insufficient to fully specify text handling for terminals. The full algorithm is specified below. A terminal using this algorithm must decode the bytes they receive into Unicode scalar values (i.e., code points except surrogates) using UTF-8. When it encounters any UTF-8 ill-formed subsequences, it must be replace each `maximal subpart of the ill-formed subsequence `__ with a :code:`U+FFFD REPLACEMENT CHARACTER` (�). For each decoded code point: #. First check if the code point is an ASCII control code, and handle it appropriately. ASCII control codes are the code points less than :code:`U+0032` and the code point :code:`U+0127 DEL`. The code point :code:`U+0000 NUL` must be discarded. #. Next, check if the code point is *invalid*, and if it is, discard it and finish processing. Invalid code points are code points with Unicode category :code:`Cc or Cs` and 66 additional code points: :code:`[0xfdd0, 0xfdef]`, :code:`[0xfffe, 0x10ffff-1, 0x10000]` and :code:`[0xffff, 0x10ffff, 0x10000]`. #. Next, check if there is a previous cell before the current cursor position. This means either the cursor is at x > 0 in which case the previous cell is at x-1 on the same line, or the previous cell is the last cell of the previous line, provided there is no line break between the previous and current lines. #. Next, calculate the width in cells of the received code point, which can be 0, 1, or 2 depending on the code point properties in the Unicode standard. #. If there is no previous cell and the code point's width is zero, the code point is discarded and its processing is finished. #. If there is a previous cell, the `Grapheme segmentation algorithm UAX29-C1-1 `__ is used to determine if there is a grapheme boundary between the previous cell and the current code point. #. If there is no boundary, the current code point is added to the previous cell and processing of the code point is finished. See the :ref:`var_select` section below for handling of Unicode Variation selectors. #. If there is a boundary, but the width of the current code point is zero, it is added to the previous cell and processing is finished. #. The code point is added to the current cell and the cursor is moved forward (right) by either 1 or 2 cells depending on the width of the code point. It remains to specify how to calculate the width in cells of a code point. To do this, code points are divided into various classes, as described by the rules below, in order of decreasing priority: .. note:: Notation: :code:`[start, stop, step]` means the integers from :code:`start` to :code:`stop` in increments of :code:`step`. When the step is not specified, it defaults to one. #. *Regional indicators*: 26 code points starting at :code:`0x1F1E6`. These all have width 2 #. *Doublewidth*: Parse `EastAsianWidth.txt `__ from the Unicode standard. All code points marked :code:`W` or :code:`F` have width two. All code points in the following ranges have width two *unless* they are marked as :code:`A` in :code:`EastAsianWidth.txt`: :code:`[0x3400, 0x4DBF], [0x4E00, 0x9FFF], [0xF900, 0xFAFF], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD]` #. *Wide emoji*: Parse `emoji-sequences.txt `__ from the Unicode standard. All :code:`Basic_Emoji` have width two unless they are followed by :code:`FE0F` in the file. The leading codepoints in all :code:`RGI_Emoji_Modifier_Sequence` and :code:`RGI_Emoji_Tag_Sequence` have width two. All code points in :code:`RGI_Emoji_Flag_Sequence` have width two. #. *Marks*: These are all zero width code points. They are code points with Unicode categories whose first letter is :code:`M` or :code:`S`. Additionally, code points with Unicode category: :code:`Cf`. Finally, they include all modifier code points from :code:`RGI_Emoji_Modifier_Sequence` in the *Wide emoji* rule above. #. All remaining code points have a width of one cell. .. _var_select: Unicode variation selectors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are two code points (:code:`U+FE0E` and :code:`U+FE0F`) that can actually alter the width of the previous code point. When adding a code point to the previous cell these have to be handled specially. ``U+FE0E`` - Variation Selector 15 When the previous cell has width two and the last code point in the previous cell is one of the ``Basic_Emoji`` code points from the *Wide emoji* rule above that is *not* followed by ``FEOF`` then the width of the previous cell is decreased to one. ``U+FE0F`` - Variation Selector 16 When the previous cell has width one and the last code point in the previous cell is one of the ``Basic_Emoji`` code points from the *Wide emoji* rule above that is followed by ``FEOF`` then the width of the previous cell is increased to two. Note that the rule for ``U+FE0E`` is particularly problematic for terminals as it means that the width of a string cannot be determined without knowing the width of the screen it will be rendered on. This is because when there is only one cell left on the current line and a wide emoji is received it wraps onto the next line. If subsequently a ``U+FE0E`` is received, the emoji becomes one cell wide but it is *not* moved back to the previous line. To avoid this issue, it is recommended applications detect when ``U+FE0E`` is present and in such cases use the width part of the text sizing protocol to control rendering. ================================================ FILE: docs/underlines.rst ================================================ Colored 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. ================================================ FILE: docs/unscroll.rst ================================================ .. _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 `__ ================================================ FILE: docs/wide-gamut-colors.rst ================================================ Wide gamut color formats ========================= kitty supports modern wide gamut color formats for precise color specification. These formats can be used anywhere a color value is accepted in the configuration (foreground, background, color0-color255, etc.). OKLCH Colors ------------ OKLCH is a perceptually uniform color space, ideal for creating color themes. The format is:: foreground oklch(0.9 0.05 140) color1 oklch(0.7 0.25 25) Parameters: - **L** (Lightness): 0 to 1, where 0 is black and 1 is white - **C** (Chroma): 0 to approximately 0.4, represents color saturation - **H** (Hue): 0 to 360 degrees (0=red, 120=green, 240=blue) Benefits: - Perceptually uniform - equal changes produce equal perceived differences - Adjusting lightness preserves hue (unlike HSL) - Industry standard for modern color design Example:: foreground oklch(0.9 0.05 140) color1 oklch(0.65 0.25 29) # Vibrant red-orange color2 oklch(0.65 0.25 142) # Vibrant green color3 oklch(0.70 0.19 90) # Warm yellow CIE LAB Colors -------------- CIE LAB is a device-independent color space designed to approximate human vision. The format is:: background lab(20 5 -10) color4 lab(50 0 -50) Parameters: - **L**: Lightness, 0 to 100 (0 = black, 100 = white) - **a**: Green (-) to red (+), typically -100 to +100 - **b**: Blue (-) to yellow (+), typically -100 to +100 Example:: background lab(10 0 0) # Very dark neutral gray foreground lab(90 0 0) # Very light neutral gray color1 lab(50 60 40) # Red color4 lab(50 0 -50) # Blue Gamut Mapping ------------- When you specify colors in OKLCH or CIE LAB formats that are outside the sRGB color gamut, kitty automatically converts them using the CSS Color Module Level 4 gamut mapping algorithm: - Preserves the original lightness and hue as much as possible - Reduces chroma (saturation) until the color fits within the displayable range - Uses perceptual color difference (deltaE OK) to minimize visible changes - Maximizes color saturation while staying in gamut This ensures that wide gamut colors gracefully degrade on standard sRGB displays while taking full advantage of wide gamut displays when available. The mapping happens automatically - you don't need to do anything special. For example, :code:`oklch(0.7 0.4 25)` might be too saturated for sRGB but will be automatically adjusted to fit while preserving the perceived hue and lightness. References ---------- - `CSS Color Module Level 4 `_ - `OKLCH Color Space `_ ================================================ FILE: embeds.go ================================================ package kitty import ( _ "embed" "encoding/json" "fmt" ) var _ = fmt.Print //go:embed logo/kitty.png var KittyLogoAsPNGData []byte //go:embed kitty_tests/GraphemeBreakTest.json var grapheme_break_test_data []byte type GraphemeBreakTest struct { Data []string `json:"data"` Comment string `json:"comment"` } func LoadGraphemeBreakTests() (ans []GraphemeBreakTest, err error) { if err := json.Unmarshal(grapheme_break_test_data, &ans); err != nil { return nil, fmt.Errorf("Failed to parse GraphemeBreakTest JSON with error: %s", err) } return } ================================================ FILE: gen/README.rst ================================================ Scripts 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. ================================================ FILE: gen/__init__.py ================================================ ================================================ FILE: gen/__main__.py ================================================ #!/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) elif which == 'color-names': from gen.color_names import main main(args) else: raise SystemExit(f'Unknown which: {which}') if __name__ == '__main__': main() ================================================ FILE: gen/apc_parsers.py ================================================ #!/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']) ================================================ FILE: gen/bitfields.py ================================================ #!/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) ================================================ FILE: gen/color_names.py ================================================ #!./kitty/launcher/kitty +launch # License: GPLv3 Copyright: 2025, Kovid Goyal import os import subprocess import sys import tempfile 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 Color(r: int, g: int, b: int) -> int: return (r << 16) | (g << 8) | b # 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 }}} def main(args: list[str]=sys.argv) -> None: with tempfile.TemporaryFile(suffix='.gperf', mode='w+') as tf: print('struct Keyword { int name, value; };\n%%', file=tf) names = sorted(color_names) for name in names: print(f'{name}, {color_names[name]}', file=tf) print('%%', file=tf) tf.flush() tf.seek(0) with open('kitty/color-names.h', 'w') as header: subprocess.check_call( 'gperf -m 2000 --struct-type --includes --readonly-tables --lookup-function-name in_color_name_set' ' --global-table --null-strings --hash-function-name color_name_hash /dev/stdin' ' --word-array-name color_names --pic --compare-strncmp'.split(), stdout=header, stdin=tf) if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'color-names']) ================================================ FILE: gen/config.py ================================================ #!./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 = [] special_colors = [] for opt in definition.iter_all_options(): if callable(opt.parser_func): match opt.parser_func.__name__: case 'to_color': all_colors.append(opt.name) case 'macos_titlebar_color' | 'titlebar_color' | 'scrollbar_color': all_colors.append(opt.name) special_colors.append(opt.name) case 'to_color_or_none' | 'cursor_text_color': all_colors.append(opt.name) nullable_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) sc = ',\n '.join(f'{x!r}' for x in special_colors) write_output('kitty', definition, f'\nnullable_colors = frozenset({{\n {nc}\n}})\n' f'\nspecial_colors = frozenset({{\n {sc}\n}})\n' ) if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'config']) ================================================ FILE: gen/cursors.py ================================================ #!/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']) ================================================ FILE: gen/go_code.py ================================================ #!./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 ( GoOption, go_options_for_seq, ) from kitty.conf.generate import gen_go_code from kitty.conf.types import Definition from kitty.config import commented_out_default_config from kitty.fast_data_types import all_color_names 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.simple_cli_definitions import CompletionSpec, parse_option_spec, serialize_as_go_string 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',) mb('kittens/choose_files', 'CombinedScore', 'score 16', 'length 16', 'index 32') # }}} # 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_defn.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 "github.com/kovidgoyal/kitty/tools/cli"') print('import "github.com/kovidgoyal/kitty/tools/cmd/tool"') print('import "github.com/kovidgoyal/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 {{{ 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 "github.com/kovidgoyal/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 = [] ser = [] kcd = kitten_cli_docs(kitten) has_underscore = '_' in kitten print(f'package {kitten}') print('import "fmt"') print('import "github.com/kovidgoyal/kitty/tools/cli"') print('var _ = fmt.Sprintf') 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()) ser.append('\n'.join(opt.as_string_for_commandline())) 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('}') print('func (opts Options) AsCommandLine() (ans []string) {') if ser: print('\t sval := ""') print('\t _ = sval') for x in ser: print('\t' + x) print('return') 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 all_color_names() ) + '\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 from kitty.simple_cli_definitions import CONFIG_HELP 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}` const ConfigHelp = "{serialize_as_go_string(CONFIG_HELP)}" 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 float64 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 "github.com/kovidgoyal/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 "github.com/kovidgoyal/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']) # }}} ================================================ FILE: gen/key_constants.py ================================================ #!/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']) ================================================ FILE: gen/rowcolumn-diacritics.txt ================================================ # 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;;;;; ================================================ FILE: gen/srgb_lut.py ================================================ #!/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.8f}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']) ================================================ FILE: gen/wcwidth.py ================================================ #!/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, TypeVar, 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 fetch_url(url: str) -> str: 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) return data.decode() def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]: url = f'https://www.unicode.org/Public/{folder}/latest/{fname}' for line in fetch_url(url).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) gndata = fetch_url('https://raw.githubusercontent.com/ryanoasis/nerd-fonts/refs/heads/master/glyphnames.json') for name, val in json.loads(gndata).items(): if name != 'METADATA': codepoint = int(val['code'], 16) category, sep, name = name.rpartition('-') name = name or category name = name.replace('_', ' ') 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()}) grapheme_segmentation_tests.append({ 'data': (' ', '\xad', ' '), 'comment': '÷ [0.2] SPACE (Other) ÷ [0.4] SOFT HYPHEN ÷ [999.0] SPACE (Other) ÷ [0.3]' }) grapheme_segmentation_tests.append({ 'data': ('\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466',), 'comment': '÷ [0.2] MAN × [9.0] ZERO WIDTH JOINER × [11.0] WOMAN × [9.0] ZERO WIDTH JOINER × [11.0] GIRL × [9.0] ZERO WIDTH JOINER × [11.0] BOY ÷ [0.3]' }) # }}} 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) HashableType = TypeVar('HashableType', bound=Hashable) def splitbins(t: tuple[HashableType, ...], property_size: int, use_fixed_shift: int = 0) -> tuple[list[int], list[int], list[HashableType], 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[HashableType] = [] tmap: dict[HashableType, 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}') cen('// UCBDeclaration {{''{') cen(f'#define MAX_UNICODE ({sys.maxunicode}u)') generate_enum(cen, gp, 'GraphemeBreakProperty', *grapheme_segmentation_maps, prefix='GBP_') generate_enum(c, gp, 'IndicConjunctBreak', *incb_map, prefix='ICB_') 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']) ================================================ FILE: glad/generate.py ================================================ #!/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,GL_ARB_framebuffer_sRGB,GL_EXT_framebuffer_sRGB ' '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() ================================================ FILE: glfw/__init__.py ================================================ ================================================ FILE: glfw/backend_utils.c ================================================ /* * 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; } } 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; } static id_type watch_counter = 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; } static void mark_key_repeat_fd_ready(int fd UNUSED, int events UNUSED, void *data) { ((EventLoopData*)(data))->key_repeat_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; #ifdef HAS_TIMER_FD eld->key_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (eld->key_repeat_fd < 0) return false; const int key_repeat_fd = eld->key_repeat_fd; #else if (pipe2(eld->key_repeat_fds, O_CLOEXEC | O_NONBLOCK) != 0) return false; const int key_repeat_fd = eld->key_repeat_fds[0]; #endif #ifdef _GLFW_WAYLAND if (!addWatch(eld, "key_repeat", key_repeat_fd, POLLIN, 1, mark_key_repeat_fd_ready, eld)) return false; #else (void)key_repeat_fd; (void)mark_key_repeat_fd_ready; #endif 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 } static void closeFds(int *fds, size_t count) { while(count--) { if (*fds > 0) { close(*fds); *fds = -1; } fds++; } } void finalizePollData(EventLoopData *eld) { (void)closeFds; #ifdef HAS_EVENT_FD close(eld->wakeupFd); eld->wakeupFd = -1; #else closeFds(eld->wakeupFds, arraysz(eld->wakeupFds)); #endif #ifdef HAS_TIMER_FD close(eld->key_repeat_fd); eld->key_repeat_fd = -1; #else closeFds(eld->key_repeat_fds, arraysz(eld->key_repeat_fds)); #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; eld->key_repeat_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; } ================================================ FILE: glfw/backend_utils.h ================================================ //======================================================================== // 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 #if __has_include() #define HAS_TIMER_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; #define MAX_NUM_OF_WATCHED_FDS 64 typedef struct { struct pollfd fds[MAX_NUM_OF_WATCHED_FDS]; #ifdef HAS_EVENT_FD int wakeupFd; #else int wakeupFds[2]; #endif #ifdef HAS_TIMER_FD int key_repeat_fd; #else int key_repeat_fds[2]; #endif bool wakeup_data_read, wakeup_fd_ready, key_repeat_fd_ready; nfds_t watches_count, timers_count; Watch watches[MAX_NUM_OF_WATCHED_FDS]; 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); ================================================ FILE: glfw/cocoa_displaylink.m ================================================ /* * 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 #include #define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll) #define MAX_NUM_OF_DISPLAYS 256 typedef struct _GLFWDisplayLinkNS { CVDisplayLinkRef displayLink; CGDirectDisplayID displayID; monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at; bool pending_dispatch; } _GLFWDisplayLinkNS; static struct { _GLFWDisplayLinkNS entries[MAX_NUM_OF_DISPLAYS]; os_unfair_lock locks[MAX_NUM_OF_DISPLAYS]; bool locks_initialized[MAX_NUM_OF_DISPLAYS]; size_t count; } displayLinks = {0}; static inline size_t index_for_entry(_GLFWDisplayLinkNS *entry) { return (size_t)(entry - displayLinks.entries); } static inline os_unfair_lock* lock_for_entry(_GLFWDisplayLinkNS *entry) { return &displayLinks.locks[index_for_entry(entry)]; } 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++) { _GLFWDisplayLinkNS *entry = &displayLinks.entries[i]; os_unfair_lock *lock = &displayLinks.locks[i]; os_unfair_lock_lock(lock); CVDisplayLinkRef link = entry->displayLink; entry->displayLink = NULL; entry->displayID = (CGDirectDisplayID)0; entry->lastRenderFrameRequestedAt = 0; entry->first_unserviced_render_frame_request_at = 0; entry->pending_dispatch = false; os_unfair_lock_unlock(lock); if (link) { CVDisplayLinkStop(link); CVDisplayLinkRelease(link); } } displayLinks.count = 0; } static void _glfwDispatchRenderFrame(void *); static CVReturn displayLinkCallback( CVDisplayLinkRef displayLink UNUSED, const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED, CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) { _GLFWDisplayLinkNS *entry = (_GLFWDisplayLinkNS *)userInfo; if (entry) { os_unfair_lock *lock = lock_for_entry(entry); os_unfair_lock_lock(lock); const bool should_dispatch = entry->first_unserviced_render_frame_request_at && !entry->pending_dispatch; CGDirectDisplayID displayID = entry->displayID; if (should_dispatch) entry->pending_dispatch = true; os_unfair_lock_unlock(lock); if (should_dispatch) dispatch_async_f(dispatch_get_main_queue(), (void*)(uintptr_t)displayID, _glfwDispatchRenderFrame); } return kCVReturnSuccess; } static void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) { CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink); if (entry->displayLink) CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, entry); } unsigned _glfwCreateDisplayLink(CGDirectDisplayID displayID) { for (unsigned i = 0; i < displayLinks.count; i++) { os_unfair_lock *existing_lock = &displayLinks.locks[i]; os_unfair_lock_lock(existing_lock); const bool already_created = displayLinks.entries[i].displayID == displayID; os_unfair_lock_unlock(existing_lock); if (already_created) return i; } if (displayLinks.count >= MAX_NUM_OF_DISPLAYS) { _glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link"); return displayLinks.count; } unsigned idx = displayLinks.count; _GLFWDisplayLinkNS *entry = &displayLinks.entries[idx]; if (!displayLinks.locks_initialized[idx]) { displayLinks.locks[idx] = OS_UNFAIR_LOCK_INIT; displayLinks.locks_initialized[idx] = true; } os_unfair_lock *lock = &displayLinks.locks[idx]; os_unfair_lock_lock(lock); memset(entry, 0, sizeof(entry[0])); entry->displayID = displayID; displayLinks.count++; _glfw_create_cv_display_link(entry); os_unfair_lock_unlock(lock); return idx; } 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]; os_unfair_lock *lock = &displayLinks.locks[i]; os_unfair_lock_lock(lock); CVDisplayLinkRef link = dl->displayLink; if (link) CVDisplayLinkRetain(link); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; dl->pending_dispatch = false; os_unfair_lock_unlock(lock); if (link) { CVDisplayLinkStop(link); CVDisplayLinkRelease(link); } } } 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]; bool need_start = false, need_stop = false, need_recreate = false; bool retain_link = false; CVDisplayLinkRef link = NULL; CVDisplayLinkRef new_link = NULL; bool new_link_retained = false; os_unfair_lock *lock = &displayLinks.locks[i]; os_unfair_lock_lock(lock); link = dl->displayLink; if (dl->displayID == displayID) { found_display_link = true; monotonic_t first_unserviced = dl->first_unserviced_render_frame_request_at; dl->lastRenderFrameRequestedAt = now; if (!first_unserviced) { dl->first_unserviced_render_frame_request_at = now; first_unserviced = now; } dl->pending_dispatch = false; if (link) { if (!CVDisplayLinkIsRunning(link)) { need_start = true; retain_link = true; } else if (now - first_unserviced > s_to_monotonic_t(1ll)) { need_recreate = true; dl->first_unserviced_render_frame_request_at = now; dl->displayLink = NULL; } } } else if (link && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) { need_stop = true; retain_link = true; dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; dl->pending_dispatch = false; } if (retain_link && link) CVDisplayLinkRetain(link); os_unfair_lock_unlock(lock); if (need_recreate && link) { CVDisplayLinkStop(link); CVDisplayLinkRelease(link); os_unfair_lock_lock(lock); _glfw_create_cv_display_link(dl); new_link = dl->displayLink; if (new_link) { CVDisplayLinkRetain(new_link); new_link_retained = true; } dl->pending_dispatch = false; os_unfair_lock_unlock(lock); if (new_link) { if (!CVDisplayLinkIsRunning(new_link)) CVDisplayLinkStart(new_link); if (new_link_retained) CVDisplayLinkRelease(new_link); } _glfwInputError(GLFW_PLATFORM_ERROR, "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating."); } else { if (need_start && link) CVDisplayLinkStart(link); else if (need_stop && link) CVDisplayLinkStop(link); if (retain_link && link) CVDisplayLinkRelease(link); } } if (!found_display_link) { unsigned idx = _glfwCreateDisplayLink(displayID); if (idx < displayLinks.count) { dl = &displayLinks.entries[idx]; os_unfair_lock *lock = &displayLinks.locks[idx]; os_unfair_lock_lock(lock); dl->lastRenderFrameRequestedAt = now; dl->first_unserviced_render_frame_request_at = now; dl->pending_dispatch = false; CVDisplayLinkRef link = dl->displayLink; if (link) CVDisplayLinkRetain(link); os_unfair_lock_unlock(lock); if (link) { if (!CVDisplayLinkIsRunning(link)) CVDisplayLinkStart(link); CVDisplayLinkRelease(link); } } } } static void _glfwDispatchRenderFrame(void *passed_in_data) { CGDirectDisplayID displayID = (uintptr_t)passed_in_data; _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]; bool need_stop = false; CVDisplayLinkRef link = NULL; os_unfair_lock *lock = &displayLinks.locks[i]; os_unfair_lock_lock(lock); if (dl->displayID == displayID) { dl->first_unserviced_render_frame_request_at = 0; dl->pending_dispatch = false; bool any_pending_request = false; _GLFWwindow *window = _glfw.windowListHead; while (window) { if (window->ns.renderFrameRequested && displayID == displayIDForWindow(window)) { any_pending_request = true; break; } window = window->next; } link = dl->displayLink; if (!any_pending_request && link && CVDisplayLinkIsRunning(link)) { need_stop = true; CVDisplayLinkRetain(link); dl->lastRenderFrameRequestedAt = 0; } } os_unfair_lock_unlock(lock); if (need_stop && link) { CVDisplayLinkStop(link); CVDisplayLinkRelease(link); } } } ================================================ FILE: glfw/cocoa_init.m ================================================ //======================================================================== // 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 #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 - (void)handleAppearanceChange; @end @implementation GLFWApplicationDelegate - (void)applicationDidActivate:(NSNotification *)notification { NSRunningApplication *app = notification.userInfo[NSWorkspaceApplicationKey]; if (app && app.processIdentifier != getpid()) { _glfw.ns.previous_front_most_application = app.processIdentifier; debug_rendering("Front most application changed to: %s pid: %d\n", app.bundleIdentifier.UTF8String, app.processIdentifier) } } - (void)applicationDidBecomeActive:(NSNotification *)notification { (void)notification; // When the application becomes active after switching spaces (e.g., swiping // back from a fullscreen app on another space), macOS may not send // windowDidBecomeKey: for the already-key window. This leaves GLFW thinking // no window has focus (since windowDidResignKey: was sent when leaving). // Ensure GLFW's focus state is updated to match the actual key window. NSWindow *keyWindow = [NSApp keyWindow]; if (keyWindow && !_glfw.focusedWindowId) { for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) { if (window->ns.object == keyWindow) { if (_glfw.focusedWindowId != window->id) _glfwInputWindowFocus(window, true); break; } } } } - (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(false); } - (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); } } static void *AppearanceObservationContext = &AppearanceObservationContext; static NSDate *application_finished_launching_at = nil; - (void)applicationDidFinishLaunching:(NSNotification *)notification { (void)notification; [[NSApplication sharedApplication] addObserver:self forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:AppearanceObservationContext]; if (finish_launching_callback) finish_launching_callback(true); [NSApp stop:nil]; CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL); _glfwCocoaPostEmptyEvent(); application_finished_launching_at = [NSDate date]; } GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { (void)query_if_unintialized; int theme_type = GLFW_COLOR_SCHEME_NO_PREFERENCE; NSAppearance *changedAppearance = NSApp.effectiveAppearance; NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){ theme_type = GLFW_COLOR_SCHEME_DARK; } else { theme_type = GLFW_COLOR_SCHEME_LIGHT; } return theme_type; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == AppearanceObservationContext) { if ([keyPath isEqualToString:@"effectiveAppearance"]) { // The initial call (from NSKeyValueObservingOptionInitial) might happen on a background thread. // Dispatch to the main thread to be safe, especially if updating UI. __block __typeof__(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf handleAppearanceChange]; }); } } else { // If the context doesn't match, pass the notification to the superclass. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)handleAppearanceChange { static GLFWColorScheme previously_reported_appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE; GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true); if (new_appearance != previously_reported_appearance) { previously_reported_appearance = new_appearance; _glfwInputColorScheme(new_appearance, false); } } - (void)applicationWillTerminate:(NSNotification *)aNotification { (void)aNotification; CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL); @try { [[NSApplication sharedApplication] removeObserver:self forKeyPath:@"effectiveAppearance" context:AppearanceObservationContext]; } @catch (NSException * __unused exception) { // Ignore exceptions, which can happen if the observer was never added. } } - (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; @end @implementation GLFWApplication - (void)tick_callback { _glfwDispatchTickCallback(); } @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 */; } static bool has_apple_fn_global_shortcut(void) { NSDictionary *hitoolbox_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.HIToolbox"]; id obj = [hitoolbox_settings objectForKey:@"AppleFnUsageType"]; if (![obj isKindOfClass:[NSNumber class]]) return false; // Non-zero AppleFnUsageType means macOS has reserved Fn/Globe for a // system action such as input source switching, emoji picker, or dictation. return [obj integerValue] != 0; } static bool is_apple_fn_global_shortcut(NSEvent *event) { if ([event keyCode] != 0x3f /* kVK_Function */) return false; NSEventModifierFlags mods = USEFUL_MODS([event modifierFlags]); if (mods != 0 && mods != NSEventModifierFlagFunction) return false; return has_apple_fn_global_shortcut(); } 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]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:_glfw.ns.delegate selector:@selector(applicationDidActivate:) name:NSWorkspaceDidActivateApplicationNotification object:nil]; 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 && !_glfw.keyboard_grabbed) { // 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; if (!_glfw.ignoreOSKeyboardProcessing && !_glfw.keyboard_grabbed && is_apple_fn_global_shortcut(event)) { debug_key("flagsChanged triggered global fn shortcut ignoring\n"); return event; } // 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(); [[NSUserDefaults standardUserDefaults] registerDefaults:@{ // Press and Hold prevents some keys from emitting repeated characters @"ApplePressAndHoldEnabled": @NO, // Dont generate openFile events from command line arguments @"NSTreatUnknownArgumentsAsOpen": @"NO", // This Tahoe nonsense causes slowdowns in some situations, see for example: // https://issues.chromium.org/issues/452372350 it doesnt affect // autofill via Edit->Autofill @"NSAutoFillHeuristicControllerEnabled" : @NO, }]; 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 } static NSDate* get_process_start_time(pid_t pid) { struct kinfo_proc kp; size_t len = sizeof(kp); int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; if (sysctl(mib, 4, &kp, &len, NULL, 0) == 0 && len == sizeof(kp)) { struct timeval start_tv = kp.kp_proc.p_starttime; time_t start_sec = start_tv.tv_sec; suseconds_t start_usec = start_tv.tv_usec; NSTimeInterval t = (NSTimeInterval)start_sec + ((NSTimeInterval)start_usec / 1000000.0); return [NSDate dateWithTimeIntervalSince1970:t]; } return nil; } void _glfwPlatformTerminate(void) { @autoreleasepool { // Kill the AutoFill helper process that macOS Tahoe starts and fails to // shutdown on application exit, see https://github.com/kovidgoyal/kitty/issues/9299 // Only kill helpers that were launched within a few seconds of this process to // avoid killing helpers from other processes. This is obviously not robust // but since Apple cant design its way out of a paper bag, it's the best we // can do. if (application_finished_launching_at != nil) { for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications]) { if ([app.bundleIdentifier isEqualToString:@"com.apple.SafariPlatformSupport.Helper"] && [[app.localizedName lowercaseString] containsString:@"autofill (kitty)"]) { NSDate *st = get_process_start_time(app.processIdentifier); if (st != nil) { NSTimeInterval timeDifference = [application_finished_launching_at timeIntervalSinceDate:st]; [st release]; if (fabs(timeDifference) <= 5) [app forceTerminate]; } } } [application_finished_launching_at release]; application_finished_launching_at = nil; } _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) { } bool _glfwPlatformGrabKeyboard(bool grab UNUSED) { return true; /* directly uses _glfw.keyboard_grabbed */ } ================================================ FILE: glfw/cocoa_joystick.h ================================================ //======================================================================== // 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; ================================================ FILE: glfw/cocoa_joystick.m ================================================ //======================================================================== // 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); } } ================================================ FILE: glfw/cocoa_monitor.m ================================================ //======================================================================== // 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 #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; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); return monitor->ns.displayID; } ================================================ FILE: glfw/cocoa_platform.h ================================================ //======================================================================== // 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)(bool); 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 typedef struct _GLFWDropData { const char **mimes; // Original MIME list; strings are owned here, never reordered size_t mimes_count; const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[] size_t copy_mimes_count; // Accepted count after last callback bool drag_accepted; id pasteboard; id data_mapping; id file_promise_mapping; } _GLFWDropData; // 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 suppress_frame_constraints; id notch_cover_window; unsigned int notch_cover_color; float notch_cover_opacity; bool titlebar_hidden; unsigned long pre_full_screen_style_mask; CGRect pre_traditional_fullscreen_frame; // Cached window properties to filter out duplicate events int width, height; int fbWidth, fbHeight; float xscale, yscale; int blur_radius; bool live_resize_in_progress; struct { struct { CGFloat red, green, blue, alpha; bool was_set; } color; bool transparent; } last_applied_titlebar_settings; // 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; // Layer shell windows struct { bool is_active; GLFWLayerShellConfig config; } layer_shell; // 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; // Cached MIME types from drag enter (for move events) _GLFWDropData drop_data; } _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; pid_t previous_front_most_application; 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; // Active drag session (NSDraggingSession*) and view (NSView*) id drag_session, drag_view, drag_image; } _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 _glfwRequestRenderFrame(_GLFWwindow *w); // event loop void _glfwDispatchTickCallback(void); void _glfwCocoaPostEmptyEvent(void); uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode); ================================================ FILE: glfw/cocoa_window.m ================================================ //======================================================================== // 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 #include #include #define debug debug_rendering #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; UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released if (t != nil && !t.dynamic) return t.identifier; size_t sz = strlen(mime); NSMutableString *hex = [NSMutableString stringWithCapacity:sz * 2]; for (NSUInteger i = 0; i < sz; i++) [hex appendFormat:@"%02x", (unsigned char)mime[i]]; return [NSString stringWithFormat:@"%@%@", UTI_ROUNDTRIP_PREFIX, hex]; // auto-released } static char hexval(char c, bool *ok) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; *ok = false; return 0; } static const char* uti_to_mime(NSString *uti) { if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain"; if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) { NSString *hexPart = [uti substringFromIndex:UTI_ROUNDTRIP_PREFIX.length]; NSUInteger hexLen = hexPart.length; if (hexLen == 0 || hexLen % 2 != 0) { return ""; } const char *hex = [hexPart UTF8String]; static char buf[4096]; size_t i, j; for (i = 0, j = 0; i < hexLen && j < sizeof(buf)-1; i += 2, j++) { char hi = hex[i], lo = hex[i + 1]; bool ok = true; buf[j] = (hexval(hi, &ok) << 4) | hexval(lo, &ok); if (!ok) return ""; } buf[j] = 0; return buf; } if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithIdentifier:uti]; // auto-released if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String]; } return ""; } 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 bool forward_dictation_selector_to_app(SEL selector, id sender) { static SEL start_dictation_selector = NULL, stop_dictation_selector = NULL; if (start_dictation_selector == NULL) { start_dictation_selector = NSSelectorFromString(@"startDictation:"); stop_dictation_selector = NSSelectorFromString(@"stopDictation:"); } if (selector != start_dictation_selector && selector != stop_dictation_selector) return false; if ([NSApp respondsToSelector:selector]) { debug_key("Forwarding %s to NSApp\n", [NSStringFromSelector(selector) UTF8String]); [NSApp performSelector:selector withObject:sender]; return true; } return false; } 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; NSArray *_lastScreenStates; } - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - (void)request_delayed_cursor_update:(id)sender; @end static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWwindow*, bool, bool); static void _glfwUpdateNotchCover(_GLFWwindow*); @implementation GLFWWindowDelegate - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) { window = initWindow; _lastScreenStates = [self captureScreenStates]; window->ns.live_resize_in_progress = false; } return self; } - (NSArray *)captureScreenStates { NSMutableArray *states = [NSMutableArray array]; for (NSScreen *screen in [NSScreen screens]) { // Use the screen's deviceDescription, which contains a stable ID. [states addObject:screen.deviceDescription]; } return [states copy]; } - (void)cleanup { [_lastScreenStates release]; _lastScreenStates = nil; } - (BOOL)windowShouldClose:(id)sender { (void)sender; if (window == nil) return YES; _glfwInputWindowCloseRequest(window); return NO; } - (void)windowDidResize:(NSNotification *)notification { (void)notification; NSArray *currentScreenStates = [self captureScreenStates]; const bool is_screen_change = ![_lastScreenStates isEqualToArray:currentScreenStates]; NSWindowStyleMask sm = [window->ns.object styleMask]; const bool is_fullscreen = (sm & NSWindowStyleMaskFullScreen) != 0; NSRect frame = [window->ns.object frame]; debug_rendering( "windowDidResize() called, is_screen_change: %d is_fullscreen: %d live_resize_in_progress: %d frame: %.1fx%.1f@(%.1f, %.1f)\n", is_screen_change, is_fullscreen, window->ns.live_resize_in_progress, frame.size.width, frame.size.height, frame.origin.x, frame.origin.y); if (is_screen_change) { // This resize likely happened because a screen was added, removed, or changed resolution. [_lastScreenStates release]; _lastScreenStates = [currentScreenStates retain]; } [currentScreenStates release]; 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); } // Because of a bug in macOS Tahoe we cannot redraw the window in response // to a resize event that was caused by a screen change as the OpenGL // context is not ready yet. See: https://github.com/kovidgoyal/kitty/issues/8983 if (window->ns.resizeCallback && !is_screen_change && !is_fullscreen && window->ns.live_resize_in_progress) 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; if (window->ns.in_traditional_fullscreen) { // macOS finished its Cocoa exit (cleared NSWindowStyleMaskFullScreen). // Defer restoration to the next run loop iteration because calling // setStyleMask: inside a delegate callback can leave the window in // an intermediate state. setStyleMask: also triggers macOS's // constrainFrameRect:toScreen: and window tiling logic which can // asynchronously reposition the window, so suppress frame // constraints during the restoration (#9572). unsigned long long wid = window->id; NSWindowStyleMask savedMask = window->ns.pre_full_screen_style_mask; CGRect savedFrame = window->ns.pre_traditional_fullscreen_frame; window->ns.in_traditional_fullscreen = false; _glfwUpdateNotchCover(window); window->ns.suppress_frame_constraints = true; dispatch_async(dispatch_get_main_queue(), ^{ _GLFWwindow *w = NULL; for (_GLFWwindow *ww = _glfw.windowListHead; ww; ww = ww->next) { if (ww->id == wid) { w = ww; break; } } if (w) { NSWindow *nswindow = w->ns.object; [nswindow setStyleMask: savedMask]; [nswindow setFrame: savedFrame display:YES]; update_titlebar_button_visibility_after_fullscreen_transition(w, true, false); [nswindow makeFirstResponder:w->ns.view]; NSNotification *resize = [NSNotification notificationWithName:NSWindowDidResizeNotification object:nswindow]; [w->ns.delegate performSelector:@selector(windowDidResize:) withObject:resize afterDelay:0]; } // Lift the constraint guard after a delay, even if the window // was not found (destroyed), to keep the flag consistent (#9572). dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(500 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ _GLFWwindow *w2 = NULL; for (_GLFWwindow *ww = _glfw.windowListHead; ww; ww = ww->next) { if (ww->id == wid) { w2 = ww; break; } } if (w2) w2->ns.suppress_frame_constraints = 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]); if (forward_dictation_selector_to_app(selector, nil)) return; } @end // }}} // File Promise Provider Delegate for async drag data {{{ // Structure to hold async drag state @interface GLFWFilePromiseProviderDelegate : NSObject { GLFWid windowId, instanceId; char* mimeType; // MIME type for this provider NSFileHandle *file_handle; NSURL *file_url; void (^completion_handler)(NSError*); } - (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime instanceId:(GLFWid)iid; - (void)request_drag_data; - (void)end_transfer:(int)errorCode; - (void)end_transfer_with_error:(NSError*)err; - (bool)is_mimetype:(const char*)mime_type; @end @interface GLFWDraggingSource : NSObject { NSPoint start_point, current_point; } @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; GLFWDraggingSource *dragging_source; } - (void) removeGLFWWindow; - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - (GLFWDraggingSource*)draggingSource; @end @implementation GLFWContentView - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) { window = initWindow; trackingArea = nil; input_context = [[GLFWTextInputContext alloc] initWithClient:self]; dragging_source = [[GLFWDraggingSource alloc] init]; 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.identifier = @"kitty-content-view"; [self updateTrackingAreas]; char tab_mime[64]; snprintf(tab_mime, sizeof(tab_mime), "application/net.kovidgoyal.kitty-tab-%d", getpid()); NSMutableArray *types = [NSMutableArray arrayWithObjects: NSPasteboardTypeFileURL, NSPasteboardTypeString, NSPasteboardTypeURL, NSPasteboardTypeColor, NSPasteboardTypeFont, NSPasteboardTypeHTML, NSPasteboardTypePDF, NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeSound, NSPasteboardTypeTIFF, UTTypeData.identifier, UTTypeItem.identifier, UTTypeContent.identifier, mime_to_uti(tab_mime), nil]; // Add file promise types [types addObjectsFromArray:[NSFilePromiseReceiver readableDraggedTypes]]; [self registerForDraggedTypes:types]; } return self; } - (void)dealloc { [trackingArea release]; [markedText release]; [dragging_source release]; if (input_source_at_last_key_event) [input_source_at_last_key_event release]; [input_context release]; [super dealloc]; } - (void) removeGLFWWindow { window = NULL; } - (GLFWDraggingSource*)draggingSource { return dragging_source; } - (_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; window->ns.live_resize_in_progress = true; _glfwInputLiveResize(window, true); } - (void)viewDidEndLiveResize { if (!window) return; window->ns.live_resize_in_progress = false; _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)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 */ || (keycode == 0x04 /* h */ && mods == GLFW_MOD_CONTROL)) [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 { GLFWScrollEvent ev = { .keyboard_modifiers=translateFlags([event modifierFlags]), .unscaled.x = [event scrollingDeltaX], .unscaled.y = [event scrollingDeltaY]}; ev.x_offset = ev.unscaled.x; ev.y_offset = ev.unscaled.y; if ([event hasPreciseScrollingDeltas]) { ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES; float xscale = 1, yscale = 1; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); if (xscale > 0) ev.x_offset *= xscale; if (yscale > 0) ev.y_offset *= yscale; } switch([event momentumPhase]) { case NSEventPhaseBegan: ev.momentum_type = GLFW_MOMENTUM_PHASE_BEGAN; break; case NSEventPhaseStationary: ev.momentum_type = GLFW_MOMENTUM_PHASE_STATIONARY; break; case NSEventPhaseChanged: ev.momentum_type = GLFW_MOMENTUM_PHASE_ACTIVE; break; case NSEventPhaseEnded: ev.momentum_type = GLFW_MOMENTUM_PHASE_ENDED; break; case NSEventPhaseCancelled: ev.momentum_type = GLFW_MOMENTUM_PHASE_CANCELED; break; case NSEventPhaseMayBegin: ev.momentum_type = GLFW_MOMENTUM_PHASE_MAY_BEGIN; break; case NSEventPhaseNone: break; } _glfwInputScroll(window, &ev); } // Drop implementation for drag and drop {{{ // Return YES to receive periodic dragging updates even when the mouse hasn't moved. // This allows the application to update acceptance status asynchronously. - (BOOL)wantsPeriodicDraggingUpdates { return YES; } static void free_drop_data(_GLFWwindow *window) { if (window->ns.drop_data.mimes) { for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free((void*)window->ns.drop_data.mimes[i]); free(window->ns.drop_data.mimes); } free(window->ns.drop_data.copy_mimes); // pointer array only; strings owned by mimes[] if (window->ns.drop_data.pasteboard) [window->ns.drop_data.pasteboard release]; if (window->ns.drop_data.data_mapping) [window->ns.drop_data.data_mapping release]; if (window->ns.drop_data.file_promise_mapping) { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; for (NSString *key in window->ns.drop_data.file_promise_mapping) { NSArray *pair = [window->ns.drop_data.file_promise_mapping objectForKey:key]; error = nil; if (pair[1] != [NSNull null]) [pair[1] closeAndReturnError:&error]; error = nil; [fileManager removeItemAtURL:pair[0] error:&error]; } [window->ns.drop_data.file_promise_mapping release]; } memset(&window->ns.drop_data, 0, sizeof(_GLFWDropData)); } static void update_drop_state(_GLFWwindow *window, size_t accepted_count) { _GLFWDropData *d = &window->ns.drop_data; d->copy_mimes_count = accepted_count; d->drag_accepted = accepted_count > 0; } // Reset the working copy of mimes so the next callback sees the full original // list. Returns false on allocation failure. static bool reset_drop_copy_mimes(_GLFWDropData *d) { if (d->mimes_count == 0) { d->copy_mimes_count = 0; return true; } if (!d->copy_mimes) { d->copy_mimes = malloc(d->mimes_count * sizeof(const char*)); if (!d->copy_mimes) return false; } memcpy(d->copy_mimes, d->mimes, d->mimes_count * sizeof(const char*)); d->copy_mimes_count = d->mimes_count; return true; } - (NSDragOperation)draggingEntered:(id )sender { const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [sender draggingLocation]; double xpos = pos.x; double ypos = contentRect.size.height - pos.y; free_drop_data(window); // Get MIME types from the dragging pasteboard NSPasteboard* pasteboard = [sender draggingPasteboard]; // Count total types across all pasteboard items plus 2 for uri-list and text/plain size_t max_types = 2; for (NSPasteboardItem* item in pasteboard.pasteboardItems) max_types += [item.types count]; NSArray *classes = @[[NSFilePromiseReceiver class]]; NSArray *receivers = [pasteboard readObjectsForClasses:classes options:@{}]; for (NSFilePromiseReceiver *receiver in receivers) max_types += [receiver.fileTypes count]; // Pre-allocate C array for MIME types const char** mime_array = (const char**)calloc(max_types, sizeof(const char*)); if (!mime_array) return NSDragOperationNone; size_t mime_count = 0; // Check for common types first (use _glfw_strdup since we need to own the strings) NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; if ([pasteboard canReadObjectForClasses:@[[NSURL class]] options:options]) { mime_array[mime_count++] = _glfw_strdup("text/uri-list"); } if ([pasteboard canReadObjectForClasses:@[[NSString class]] options:nil]) { mime_array[mime_count++] = _glfw_strdup("text/plain"); } #define add_mime(uti) { \ const char* mime = uti_to_mime(uti); \ if (mime && mime[0]) { \ bool duplicate = false; \ for (size_t i = 0; i < mime_count; i++) { \ if (strcmp(mime_array[i], mime) == 0) { \ duplicate = true; \ break; \ } \ } \ if (!duplicate) mime_array[mime_count++] = _glfw_strdup(mime); \ } \ } // Get file promise based types for (NSFilePromiseReceiver *receiver in receivers) { for (NSString *uti in receiver.fileTypes) { add_mime(uti); } } // Get additional types from pasteboard items for (NSPasteboardItem* item in pasteboard.pasteboardItems) { for (NSPasteboardType uti in item.types) { add_mime(uti); } } window->ns.drop_data.mimes = mime_array; window->ns.drop_data.mimes_count = mime_count; bool from_self = ([sender draggingSource] != nil); _GLFWDropData *d = &window->ns.drop_data; if (reset_drop_copy_mimes(d)) { size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self); update_drop_state(window, accepted_count); } return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone; } - (NSDragOperation)draggingUpdated:(id )sender { if (!window->ns.drop_data.drag_accepted) return NSDragOperationNone; const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [sender draggingLocation]; double xpos = pos.x; double ypos = contentRect.size.height - pos.y; bool from_self = ([sender draggingSource] != nil); _GLFWDropData *d = &window->ns.drop_data; if (reset_drop_copy_mimes(d)) { size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self); update_drop_state(window, accepted_count); } return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone; } - (void)draggingExited:(id )sender { bool from_self = ([sender draggingSource] != nil); _GLFWDropData *d = &window->ns.drop_data; if (reset_drop_copy_mimes(d)) { size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->copy_mimes, d->copy_mimes_count, from_self); update_drop_state(window, accepted_count); } free_drop_data(window); } - (BOOL)performDragOperation:(id )sender { if (!window->ns.drop_data.drag_accepted) return NO; const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [sender draggingLocation]; double xpos = pos.x; double ypos = contentRect.size.height - pos.y; bool from_self = ([sender draggingSource] != nil); _GLFWDropData *d = &window->ns.drop_data; if (!reset_drop_copy_mimes(d)) return NO; size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self); if (d->copy_mimes) { update_drop_state(window, num_accepted); window->ns.drop_data.pasteboard = [[sender draggingPasteboard] retain]; for (size_t i = 0; i < num_accepted; i++) _glfwPlatformRequestDropData(window, d->copy_mimes[i]); } return YES; } void _glfwPlatformRequestDropUpdate(_GLFWwindow* window UNUSED) { // No-op since macOS is calling the drop move callback periodically anyway // thanks to wantsPeriodicDraggingUpdates and we have no way to inform // macOS of any changes except in the cocoa callbacks. } static void send_data_available_event_on_next_event_loop_tick(GLFWid wid, const char *mime) { char *mt = _glfw_strdup(mime); dispatch_async(dispatch_get_main_queue(), ^{ _GLFWwindow *window = _glfwWindowForId(wid); if (window) { const char *mimes[1] = {mt}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); } free(mt); }); } int _glfwPlatformRequestDropData(_GLFWwindow *window UNUSED, const char *mime) { NSPasteboard* pasteboard = window->ns.drop_data.pasteboard; if (!pasteboard) return EINVAL; GLFWid wid = window->id; if (window->ns.drop_data.data_mapping == nil) window->ns.drop_data.data_mapping = [[NSMutableDictionary alloc] init]; NSArray *pair; if ((pair = window->ns.drop_data.data_mapping[@(mime)])) { window->ns.drop_data.data_mapping[@(mime)] = @[pair[0], @0]; send_data_available_event_on_next_event_loop_tick(wid, mime); return 0; } if (window->ns.drop_data.file_promise_mapping == nil) window->ns.drop_data.file_promise_mapping = [[NSMutableDictionary alloc] init]; if ((pair = window->ns.drop_data.file_promise_mapping[@(mime)])) { if (pair[0] == [NSNull null]) return 0; // waiting for promise if (pair[1] != [NSNull null]) { NSFileHandle *h = pair[1]; NSError *error = nil; [h seekToOffset:0 error:&error]; } send_data_available_event_on_next_event_loop_tick(wid, mime); return 0; } NSData* data = nil; NSFilePromiseReceiver *file_promise = nil; // Handle special MIME types if (strcmp(mime, "text/uri-list") == 0) { NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] options:options]; if (urls && [urls count] > 0) { NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; for (NSURL* url in urls) { if ([uri_list length] > 0) [uri_list appendString:@"\n"]; if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString]; else [uri_list appendString:url.absoluteString]; } data = [uri_list dataUsingEncoding:NSUTF8StringEncoding]; } } else if (strcmp(mime, "text/plain") == 0 || strcmp(mime, "text/plain;charset=utf-8") == 0) { NSArray* strings = [pasteboard readObjectsForClasses:@[[NSString class]] options:nil]; if (strings && [strings count] > 0) { NSString* str = strings[0]; data = [str dataUsingEncoding:NSUTF8StringEncoding]; } } if (data == nil) { // Try to read data for other MIME types using UTI NSString* uti = mime_to_uti(mime); if (uti) { NSPasteboardType pbType = [pasteboard availableTypeFromArray:@[uti]]; if (pbType) data = [pasteboard dataForType:pbType]; } if (data == nil) { // look in the file promise providers NSArray *receivers = [pasteboard readObjectsForClasses:@[[NSFilePromiseReceiver class]] options:@{}]; for (NSFilePromiseReceiver *receiver in receivers) { for (NSString *uti in receiver.fileTypes) { const char *q = uti_to_mime(uti); if (q && strcmp(q, mime) == 0) { file_promise = receiver; break; } } if (file_promise) break; } } } if (!data && !file_promise) return ENOENT; if (file_promise != nil) { window->ns.drop_data.file_promise_mapping[@(mime)] = @[[NSNull null], [NSNull null], [NSNull null]]; char *mt = _glfw_strdup(mime); [file_promise receivePromisedFilesAtDestination:[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] options:@{} operationQueue:[NSOperationQueue mainQueue] reader:^(NSURL *fileURL, NSError *errorOrNil) { _GLFWwindow *window = _glfwWindowForId(wid); if (!window || !window->ns.drop_data.file_promise_mapping) return; id null = [NSNull null]; if (errorOrNil) { NSLog(@"Error receiving file: %@: %@", fileURL, errorOrNil); window->ns.drop_data.file_promise_mapping[@(mt)] = @[fileURL, null, errorOrNil]; } else { NSError *err = nil; NSFileHandle *file_handle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&err]; window->ns.drop_data.file_promise_mapping[@(mt)] = err ? @[fileURL, null, err] : @[fileURL, file_handle, null]; } const char *mimes[1] = {mt}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); free(mt); }]; } else { window->ns.drop_data.data_mapping[@(mime)] = @[data, @0]; const char *mimes[1] = {mime}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); } return 0; } ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t capacity) { _GLFWwindow *window = (_GLFWwindow*)w; const char *mime = ev->mimes[0]; NSArray *pair; if ((pair = window->ns.drop_data.data_mapping[@(mime)])) { NSData *data = pair[0]; size_t offset = [pair[1] unsignedIntegerValue]; NSUInteger dataLength = [data length]; if (offset >= dataLength) return 0; // EOF NSUInteger remaining = dataLength - offset; NSUInteger to_read = (remaining < capacity) ? remaining : capacity; [data getBytes:buffer range:NSMakeRange(offset, to_read)]; offset += to_read; window->ns.drop_data.data_mapping[@(mime)] = @[data, @(offset)]; if (to_read) send_data_available_event_on_next_event_loop_tick(window->id, mime); return (ssize_t)to_read; } if ((pair = window->ns.drop_data.file_promise_mapping[@(mime)])) { id null = [NSNull null]; if (pair[0] == null) { return -ENOENT; } if (pair[2] != null) { NSError *err = pair[2]; if ([err.domain isEqualToString:NSPOSIXErrorDomain]) return -err.code; NSError *underlyingError = err.userInfo[NSUnderlyingErrorKey]; if (underlyingError && [underlyingError.domain isEqualToString:NSPOSIXErrorDomain]) return -underlyingError.code; return -EIO; } NSFileHandle *h = pair[1]; int fd = h.fileDescriptor; ssize_t bytesRead; do { bytesRead = read(fd, buffer, capacity); } while (bytesRead == -1 && errno == EINTR); bytesRead = bytesRead < 0 ? -errno : bytesRead; if (bytesRead > 0) send_data_available_event_on_next_event_loop_tick(window->id, mime); return bytesRead; } return -ENOENT; } void _glfwPlatformEndDrop(GLFWwindow *w UNUSED, GLFWDragOperationType op UNUSED) { free_drop_data((_GLFWwindow*)w); } // }}} - (BOOL)hasMarkedText { return [markedText length] > 0; } - (NSRange)markedRange { if ([markedText length] > 0) return NSMakeRange(0, [markedText length] - 1); else return kEmptyRange; } - (NSRange)selectedRange { // Return position 0 with no selection to indicate text can be inserted. // This is required for macOS dictation to work - returning kEmptyRange // (NSNotFound, 0) causes dictation to fail because the system doesn't // know where to insert text. See https://github.com/kovidgoyal/kitty/issues/3732 return NSMakeRange(0, 0); } - (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]); if (forward_dictation_selector_to_app(selector, self)) return; } - (void)startDictation:(id)sender { forward_dictation_selector_to_app(_cmd, sender); } - (void)stopDictation:(id)sender { forward_dictation_selector_to_app(_cmd, sender); } - (BOOL)isAccessibilityElement { return YES; } - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { // Allow accessibility selectors needed for dictation and other accessibility features // See https://github.com/kovidgoyal/kitty/issues/3732 if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText) || selector == @selector(accessibilitySelectedTextRange) || selector == @selector(accessibilityNumberOfCharacters) || selector == @selector(accessibilityInsertionPointLineNumber) || selector == @selector(accessibilityValue) || selector == @selector(setAccessibilityValue:)) return YES; // Allow accessibility selectors needed for external window management tools // (e.g. Easy Move+Resize) to find and manipulate the window. // See https://github.com/kovidgoyal/kitty/issues/5561 if (selector == @selector(accessibilityWindow) || selector == @selector(accessibilityParent) || selector == @selector(accessibilityPosition) || selector == @selector(setAccessibilityPosition:) || selector == @selector(accessibilitySize) || selector == @selector(setAccessibilitySize:)) 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; } // Accessibility methods required for dictation support // See https://github.com/kovidgoyal/kitty/issues/3732 - (NSRange)accessibilitySelectedTextRange { // Return position 0 with no selection for dictation support return NSMakeRange(0, 0); } - (NSInteger)accessibilityNumberOfCharacters { // Terminal doesn't have a fixed text buffer, return 0 return 0; } - (NSInteger)accessibilityInsertionPointLineNumber { // Return line 0 as the insertion point return 0; } - (NSString *)accessibilityValue { // Terminal doesn't expose its buffer as an accessibility value return @""; } - (void)setAccessibilityValue:(NSString *)value { // When dictation or other accessibility features set text, insert it as keyboard input if (value && [value length] > 0 && window) { const char *utf8 = [value UTF8String]; debug_key("Inserting text via setAccessibilityValue: %s\n", utf8); GLFWkeyevent glfw_keyevent = {.text=utf8, .ime_state=GLFW_IME_COMMIT_TEXT}; _glfwInputKeyboard(window, &glfw_keyevent); } } // // 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; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(screenParametersDidChange:) name:NSApplicationDidChangeScreenParametersNotification object:nil]; } return self; } - (void)screenParametersDidChange:(NSNotification *)notification { if (!glfw_window || !glfw_window->ns.layer_shell.is_active) return; _glfwPlatformSetLayerShellConfig(glfw_window, NULL); } - (void) removeGLFWWindow { glfw_window = NULL; [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (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]; } - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(nullable NSScreen *)screen { if (glfw_window && glfw_window->ns.suppress_frame_constraints) return frameRect; return [super constrainFrameRect:frameRect toScreen:screen]; } - (BOOL)canBecomeKeyWindow { if (!glfw_window) return NO; if (glfw_window->ns.layer_shell.is_active) { if (glfw_window->ns.layer_shell.config.type == GLFW_LAYER_SHELL_BACKGROUND) return NO; switch(glfw_window->ns.layer_shell.config.focus_policy) { case GLFW_FOCUS_NOT_ALLOWED: return NO; case GLFW_FOCUS_EXCLUSIVE: return YES; case GLFW_FOCUS_ON_DEMAND: return YES; } } // Required for NSWindowStyleMaskBorderless windows // Also miniaturized windows should not become key return !_glfwPlatformWindowIconified(glfw_window); } - (BOOL)canBecomeMainWindow { return !glfw_window->ns.layer_shell.is_active || glfw_window->ns.layer_shell.config.type != GLFW_LAYER_SHELL_BACKGROUND; } static void apply_titlebar_color_settings(_GLFWwindow *window); 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]; } if (!made_fullscreen) apply_titlebar_color_settings(w); } - (void)toggleFullScreen:(nullable id)sender { if (glfw_window) { if (glfw_window->ns.in_fullscreen_transition) return; // Capture the windowed frame before any fullscreen transition begins. // This is more reliable than saving it inside _glfwPlatformToggleFullscreen // because setStyleMask: calls between cycles can reposition the window (#9572). if (!glfw_window->ns.in_traditional_fullscreen && !([self styleMask] & NSWindowStyleMaskFullScreen)) { glfw_window->ns.pre_traditional_fullscreen_frame = [self frame]; } 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, const GLFWLayerShellConfig *lsc) { window->ns.deadKeyState = 0; if (lsc) { window->ns.layer_shell.is_active = true; window->ns.layer_shell.config = *lsc; } else window->ns.layer_shell.is_active = false; 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) { GLFWWindow *w = window->ns.object; if (_glfw.ns.disabledCursorWindow == window) _glfw.ns.disabledCursorWindow = NULL; free_drop_data(window); if (window->ns.notch_cover_window) { [w removeChildWindow:window->ns.notch_cover_window]; [window->ns.notch_cover_window close]; [window->ns.notch_cover_window release]; window->ns.notch_cover_window = nil; } [w orderOut:nil]; if (window->monitor) releaseMonitor(window); if (window->context.destroy) window->context.destroy(window); [w setDelegate:nil]; [window->ns.delegate cleanup]; [window->ns.delegate release]; window->ns.delegate = nil; [window->ns.view removeGLFWWindow]; [window->ns.view release]; window->ns.view = nil; [w removeGLFWWindow]; // Workaround for macOS Tahoe where if the frame is not set to zero size // even after NSWindow::close the window remains on screen as an invisible // rectangle that intercepts mouse events and takes up space in mission // control. Sigh. NSRect frame = w.frame; frame.size.width = 0; frame.size.height = 0; [w setFrame:frame display:NO]; [w setHasShadow:NO]; [w close]; // sends a release to NSWindow so we dont release it window->ns.object = nil; } static NSScreen* screen_for_window_center(_GLFWwindow *window) { NSRect windowFrame = [window->ns.object frame]; NSPoint windowCenter = NSMakePoint(NSMidX(windowFrame), NSMidY(windowFrame)); for (NSScreen *screen in [NSScreen screens]) { if (NSPointInRect(windowCenter, [screen frame])) { return screen; } } return NSScreen.mainScreen; } static NSScreen* active_screen(void) { NSPoint mouseLocation = [NSEvent mouseLocation]; NSArray *screens = [NSScreen screens]; for (NSScreen *screen in screens) { if (NSPointInRect(mouseLocation, [screen frame])) { return screen; } } // As a fallback, return the main screen return [NSScreen mainScreen]; } static bool is_same_screen(NSScreen *screenA, NSScreen * screenB) { if (screenA == screenB) return true; NSDictionary *deviceDescriptionA = [screenA deviceDescription]; NSDictionary *deviceDescriptionB = [screenB deviceDescription]; NSNumber *screenNumberA = deviceDescriptionA[@"NSScreenNumber"]; NSNumber *screenNumberB = deviceDescriptionB[@"NSScreenNumber"]; return [screenNumberA isEqualToNumber:screenNumberB]; } static void move_window_to_screen(_GLFWwindow *window, NSScreen *target) { NSRect screenFrame = [target visibleFrame]; NSRect windowFrame = [window->ns.object frame]; CGFloat newX = NSMidX(screenFrame) - (windowFrame.size.width / 2.0); CGFloat newY = NSMidY(screenFrame) - (windowFrame.size.height / 2.0); NSRect newWindowFrame = NSMakeRect(newX, newY, windowFrame.size.width, windowFrame.size.height); [window->ns.object setFrame:newWindowFrame display:NO animate:NO]; if (window->ns.layer_shell.is_active) _glfwPlatformSetLayerShellConfig(window, NULL); } const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { return &window->ns.layer_shell.config; } static NSScreen* screen_for_name(const char *name) { int count = 0; GLFWmonitor **monitors = glfwGetMonitors(&count); for (int i = 0; i < count; i++) { const char *q = glfwGetMonitorName(monitors[i]); if (q && strcmp(q, name) == 0) return ((_GLFWmonitor*)monitors[i])->ns.screen; } return NULL; } bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { #define config window->ns.layer_shell.config #define nswindow window->ns.object window->resizable = false; if (value) config = *value; const bool is_transparent = _glfwPlatformFramebufferTransparent(window); int background_blur = config.related.background_blur; if (!is_transparent || config.related.background_opacity >= 1.f) { background_blur = 0; } [nswindow setBackgroundColor:[NSColor clearColor]]; _glfwPlatformSetWindowBlur(window, background_blur); window->ns.titlebar_hidden = true; window->decorated = false; [nswindow setTitlebarAppearsTransparent:false]; [nswindow setHasShadow:false]; [nswindow setTitleVisibility:NSWindowTitleHidden]; NSColorSpace *cs = nil; switch (config.related.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 } [nswindow setColorSpace:cs]; [[nswindow standardWindowButton: NSWindowCloseButton] setHidden:true]; [[nswindow standardWindowButton: NSWindowMiniaturizeButton] setHidden:true]; [[nswindow standardWindowButton: NSWindowZoomButton] setHidden:true]; [nswindow setStyleMask:NSWindowStyleMaskBorderless]; // HACK: Changing the style mask can cause the first responder to be cleared [nswindow makeFirstResponder:window->ns.view]; NSScreen *screen = screen_for_window_center(window); if (config.output_name[0]) { NSScreen *q = screen_for_name(config.output_name); if (q) screen = q; } unsigned cell_width, cell_height; double left_edge_spacing, top_edge_spacing, right_edge_spacing, bottom_edge_spacing; float xscale = (float)config.expected.xscale, yscale = (float)config.expected.yscale; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); config.size_callback((GLFWwindow*)window, xscale, yscale, &cell_width, &cell_height, &left_edge_spacing, &top_edge_spacing, &right_edge_spacing, &bottom_edge_spacing); double spacing_x = left_edge_spacing + right_edge_spacing; double spacing_y = top_edge_spacing + bottom_edge_spacing; const unsigned xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells); const unsigned ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells); CGFloat dock_height = NSMinY(screen.visibleFrame) - NSMinY(screen.frame); CGFloat menubar_height = NSHeight(screen.frame) - NSHeight(screen.visibleFrame) - dock_height; CGFloat x = NSMinX(screen.visibleFrame), y = NSMinY(screen.visibleFrame) - 1, width = NSWidth(screen.visibleFrame), height = NSHeight(screen.visibleFrame) + 2; if (config.type == GLFW_LAYER_SHELL_BACKGROUND || config.edge == GLFW_EDGE_CENTER) { x = NSMinX(screen.frame); height = NSHeight(screen.frame) - menubar_height + 1; y = NSMinY(screen.frame); width = NSWidth(screen.frame); } // Screen co-ordinate system is with origin in lower left and y increasing upwards and x increasing rightwards // NSLog(@"frame: %@ visibleFrame: %@\n", NSStringFromRect(screen.frame), NSStringFromRect(screen.visibleFrame)); NSWindowLevel level = NSPopUpMenuWindowLevel - 1; // so that popup menus from globalmenubar function NSWindowAnimationBehavior animation_behavior = NSWindowAnimationBehaviorUtilityWindow; switch (config.type) { case GLFW_LAYER_SHELL_BACKGROUND: animation_behavior = NSWindowAnimationBehaviorNone; // See: https://stackoverflow.com/questions/4982584/how-do-i-draw-the-desktop-on-mac-os-x/4982619#4982619 level = kCGDesktopWindowLevel; break; case GLFW_LAYER_SHELL_OVERLAY: case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_PANEL: level = NSNormalWindowLevel - 1; break; case GLFW_LAYER_SHELL_TOP: level--; break; } if (config.type != GLFW_LAYER_SHELL_BACKGROUND && config.edge != GLFW_EDGE_CENTER) { double panel_height = spacing_y + ysz / yscale, panel_width = spacing_x + xsz / xscale; switch (config.edge) { case GLFW_EDGE_BOTTOM: height = panel_height; break; case GLFW_EDGE_TOP: y += height - panel_height + 1.; height = panel_height; break; case GLFW_EDGE_LEFT: width = panel_width; break; case GLFW_EDGE_RIGHT: x += width - panel_width + 1.; width = panel_width; break; case GLFW_EDGE_CENTER_SIZED: x += (width - panel_width) / 2; y += (height - panel_height) / 2; width = panel_width; height = panel_height; break; default: // top left y += height - panel_height + 1.; height = panel_height; width = panel_width; break; } if (width < 1.) width = NSWidth(screen.visibleFrame); if (height < 1.) height = NSWidth(screen.visibleFrame); } if (config.edge != GLFW_EDGE_CENTER_SIZED) { x += config.requested_left_margin; width -= config.requested_left_margin + config.requested_right_margin; y += config.requested_bottom_margin; height -= config.requested_top_margin + config.requested_bottom_margin; } [nswindow setAnimationBehavior:animation_behavior]; [nswindow setLevel:level]; [nswindow setCollectionBehavior: (NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorIgnoresCycle)]; [nswindow setFrame:NSMakeRect(x, y, width, height) display:YES]; return true; #undef config #undef nswindow } 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->ns.layer_shell.is_active) return; 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, bool move_to_active_screen) { const bool is_background = window->ns.layer_shell.is_active && window->ns.layer_shell.config.type == GLFW_LAYER_SHELL_BACKGROUND; NSWindow *nw = window->ns.object; if (move_to_active_screen) { NSScreen *current_screen = screen_for_window_center(window); NSScreen *target_screen = active_screen(); if (!is_same_screen(current_screen, target_screen)) { debug_rendering("Moving OS window %llu to active screen\n", window->id); move_window_to_screen(window, target_screen); } } if (is_background) { [nw orderBack:nil]; } else { // Cocoa has a bug where when showing a hidden window after // fullscreening an application, the window does not get added // to the current space even though it has NSWindowCollectionBehaviorCanJoinAllSpaces // probably because it wasnt added to the temp space used for // fullscreen. So to work around that, we change the collection // behavior temporarily to NSWindowCollectionBehaviorMoveToActiveSpace // and then change it back asynchronously. // See https://github.com/kovidgoyal/kitty/issues/8740 NSWindowCollectionBehavior old = nw.collectionBehavior; nw.collectionBehavior = (old & !NSWindowCollectionBehaviorCanJoinAllSpaces) | NSWindowCollectionBehaviorMoveToActiveSpace; [nw orderFront:nil]; __block __typeof__(nw) weakSelf = nw; dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.collectionBehavior = old; }); } } void _glfwPlatformHideWindow(_GLFWwindow* window) { [window->ns.object orderOut:nil]; pid_t prev_app_pid = _glfw.ns.previous_front_most_application; _glfw.ns.previous_front_most_application = 0; NSRunningApplication *app; if (window->ns.layer_shell.is_active && prev_app_pid > 0 && (app = [NSRunningApplication runningApplicationWithProcessIdentifier:prev_app_pid])) { unsigned num_visible = 0; for (_GLFWwindow *w = _glfw.windowListHead; w; w = w->next) { if (_glfwPlatformWindowVisible(w)) num_visible++; } if (!num_visible) { // yieldActivationToApplication was introduced in macOS 14 (Sonoma) SEL selector = NSSelectorFromString(@"yieldActivationToApplication:"); if ([NSApp respondsToSelector:selector]) { [NSApp performSelector:selector withObject:app]; [app activateWithOptions:0]; } else { #define NSApplicationActivateIgnoringOtherApps 2 [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; #undef NSApplicationActivateIgnoringOtherApps } } } } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED) { [NSApp requestUserAttention:NSInformationalRequest]; } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { NSBeep(); return true; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { if (_glfwPlatformWindowIconified(window)) { // miniaturized windows return false in canBecomeKeyWindow therefore // unminiaturize first [window->ns.object deminiaturize:nil]; } if ([window->ns.object canBecomeKeyWindow]) { // Make us the active application [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; } } } static void _glfwUpdateNotchCover(_GLFWwindow* w) { NSWindow *window = w->ns.object; if (w->ns.notch_cover_window) { [window removeChildWindow:w->ns.notch_cover_window]; [w->ns.notch_cover_window close]; [w->ns.notch_cover_window release]; w->ns.notch_cover_window = nil; } if (!w->ns.in_traditional_fullscreen) return; if (@available(macOS 12.0, *)) { CGFloat insetTop = window.screen.safeAreaInsets.top; if (insetTop <= 0) return; NSRect sf = [window.screen frame]; NSWindow *bg_window = [[NSWindow alloc] initWithContentRect:sf styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]; [bg_window setBackgroundColor:[NSColor clearColor]]; [bg_window setHasShadow:NO]; [bg_window setOpaque:NO]; [bg_window setIgnoresMouseEvents:YES]; [bg_window setReleasedWhenClosed:NO]; [bg_window setColorSpace:[window colorSpace]]; // Add a colored subview only in the notch strip area NSView *notchView = [[NSView alloc] initWithFrame:NSMakeRect(0, sf.size.height - insetTop, sf.size.width, insetTop)]; notchView.wantsLayer = YES; unsigned int c = w->ns.notch_cover_color; float a = w->ns.notch_cover_opacity; notchView.layer.backgroundColor = [NSColor colorWithSRGBRed:((c >> 16) & 0xFF) / 255.0 green:((c >> 8) & 0xFF) / 255.0 blue:(c & 0xFF) / 255.0 alpha:a].CGColor; [bg_window.contentView addSubview:notchView]; // must be above otherwise shadow of main window is rendered over bg_window [window addChildWindow:bg_window ordered:NSWindowAbove]; w->ns.notch_cover_window = bg_window; [notchView release]; } } 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) { // Apple throws NSGenericException if setStyleMask: clears // NSWindowStyleMaskFullScreen outside a transition (see #9572). // Split View sets this flag via the system, so fall back to // Cocoa fullscreen toggle instead of the traditional path. if (sm & NSWindowStyleMaskFullScreen) { [window toggleFullScreen:nil]; return false; } w->ns.pre_full_screen_style_mask = sm; w->ns.pre_traditional_fullscreen_frame = [window frame]; [window setStyleMask: NSWindowStyleMaskBorderless]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; NSRect screenFrame = [window.screen frame]; if (@available(macOS 12.0, *)) { screenFrame.size.height -= window.screen.safeAreaInsets.top; } [window setFrame:screenFrame display:YES]; w->ns.in_traditional_fullscreen = true; _glfwUpdateNotchCover(w); } else { made_fullscreen = false; if (sm & NSWindowStyleMaskFullScreen) { // Split View added NSWindowStyleMaskFullScreen on top of our // traditional fullscreen. We can't clear that flag directly // (NSGenericException), so trigger a Cocoa exit and defer the // traditional fullscreen cleanup to windowDidExitFullScreen: // which fires after macOS finishes its async transition (#9572). // Return true to prevent the caller from setting the window // frame during the Cocoa exit animation. [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; [window toggleFullScreen:nil]; return true; } else { [window setStyleMask: w->ns.pre_full_screen_style_mask]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; w->ns.in_traditional_fullscreen = false; _glfwUpdateNotchCover(w); } } } 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 {{{ 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; assert(window != NULL); _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; } @implementation NSView (FindByIdentifier) - (NSArray *)viewsWithIdentifier:(NSUserInterfaceItemIdentifier)identifier { NSMutableArray *result = [NSMutableArray array]; if ([self.identifier isEqual:identifier]) { [result addObject:self]; } for (NSView *sub in self.subviews) { [result addObjectsFromArray:[sub viewsWithIdentifier:identifier]]; } return result; } @end static void clear_title_bar_background_views(NSWindow *window) { #define tag @"kitty-for-transparent-titlebar" NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil; if (titlebarContainer) { for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview]; } } static void set_title_bar_background(NSWindow *window, NSColor *backgroundColor) { // add an extra view that just renders the background color under the transparent titlebar NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil; if (!titlebarContainer) return; for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview]; if (!backgroundColor) return; NSButton *b = [window standardWindowButton:NSWindowCloseButton]; if (b) { NSView *titlebarView = b.superview; NSView *bgView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, titlebarView.bounds.size.width, titlebarView.bounds.size.height)]; bgView.translatesAutoresizingMaskIntoConstraints = NO; bgView.wantsLayer = YES; bgView.layer.backgroundColor = backgroundColor.CGColor; bgView.identifier = tag; [titlebarView addSubview:bgView positioned:NSWindowBelow relativeTo:titlebarView.subviews[0]]; [NSLayoutConstraint activateConstraints:@[ // Pin to the top of the content view. [bgView.topAnchor constraintEqualToAnchor:titlebarView.topAnchor], // Pin to the leading edge of the content view. [bgView.leadingAnchor constraintEqualToAnchor:titlebarView.leadingAnchor], // Pin to the trailing edge of the content view. [bgView.trailingAnchor constraintEqualToAnchor:titlebarView.trailingAnchor], // Give it a fixed height [bgView.bottomAnchor constraintEqualToAnchor:titlebarView.bottomAnchor] ]]; [bgView release]; return; } NSView *bgView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, titlebarContainer.bounds.size.width, 32)]; bgView.translatesAutoresizingMaskIntoConstraints = NO; bgView.wantsLayer = YES; bgView.layer.backgroundColor = backgroundColor.CGColor; bgView.identifier = tag; // position the background view above the content view but below the titlebar view [titlebarContainer addSubview:bgView positioned:NSWindowAbove relativeTo:contentView]; // for (NSView *subview in titlebarContainer.subviews) NSLog(@"sv: %@", subview.identifier); [NSLayoutConstraint activateConstraints:@[ // Pin to the top of the content view. [bgView.topAnchor constraintEqualToAnchor:titlebarContainer.topAnchor], // Pin to the leading edge of the content view. [bgView.leadingAnchor constraintEqualToAnchor:titlebarContainer.leadingAnchor], // Pin to the trailing edge of the content view. [bgView.trailingAnchor constraintEqualToAnchor:titlebarContainer.trailingAnchor], // Give it a fixed height [bgView.bottomAnchor constraintEqualToAnchor:contentView.topAnchor] ]]; [bgView release]; #undef tag } static void apply_titlebar_color_settings(_GLFWwindow *window) { #define tc window->ns.last_applied_titlebar_settings.color GLFWWindow *nsw = window->ns.object; if (!window->ns.titlebar_hidden && window->decorated && tc.was_set && window->ns.last_applied_titlebar_settings.transparent) { NSColor *titlebar_color = [NSColor colorWithSRGBRed:tc.red green:tc.green blue:tc.blue alpha:tc.alpha]; set_title_bar_background(nsw, titlebar_color); } else clear_title_bar_background_views(nsw); #undef tc } 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; if (window->ns.layer_shell.is_active) return; GLFWWindow *nsw = window->ns.object; const bool is_transparent = _glfwPlatformFramebufferTransparent(window); if (!is_transparent) { background_opacity = 1.0; background_blur = 0; } NSColor *window_background = [NSColor windowBackgroundColor]; if (background_opacity < 1.0) { // use a clear color (fully transparent) so that the final color is just the color from the surface. // prevent blurring of shadows at window corners with desktop background by setting a low alpha background window_background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor]; } NSAppearance *appearance = nil; #define tc window->ns.last_applied_titlebar_settings.color tc.was_set = false; window->ns.last_applied_titlebar_settings.transparent = false; const NSWindowStyleMask current_style_mask = [nsw 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) { switch (system_color) { case 1: appearance = light_appearance; break; case 2: appearance = dark_appearance; break; } } else { tc.red = ((color >> 16) & 0xFF) / 255.0; tc.green = ((color >> 8) & 0xFF) / 255.0; tc.blue = (color & 0xFF) / 255.0; tc.alpha = background_opacity; tc.was_set = true; double luma = 0.2126 * tc.red + 0.7152 * tc.green + 0.0722 * tc.blue; appearance = luma < 0.5 ? dark_appearance : light_appearance; window->ns.last_applied_titlebar_settings.transparent = true; } [nsw setBackgroundColor:window_background]; [nsw 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; window->ns.last_applied_titlebar_settings.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; [nsw setTitlebarAppearsTransparent:window->ns.last_applied_titlebar_settings.transparent]; [nsw setHasShadow:has_shadow]; [nsw 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 titlebar_color_set: %d title_visibility: %d hidden: %d buttons_hidden: %d" "\n", window_background ? [window_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, window->ns.last_applied_titlebar_settings.transparent, tc.was_set, show_text_in_titlebar, window->ns.titlebar_hidden, hide_titlebar_buttons ); [nsw setColorSpace:cs]; [[nsw standardWindowButton: NSWindowCloseButton] setHidden:hide_titlebar_buttons]; [[nsw standardWindowButton: NSWindowMiniaturizeButton] setHidden:hide_titlebar_buttons]; [[nsw 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); NSWindowStyleMask desired_mask; if (in_fullscreen && window->ns.in_traditional_fullscreen) { desired_mask = NSWindowStyleMaskBorderless; } else { desired_mask = window->ns.pre_full_screen_style_mask | fsmask; } // Only call setStyleMask: when the mask actually changes. Redundant // calls can trigger macOS to reposition the window (#9572). if (desired_mask != current_style_mask) { [nsw setStyleMask:desired_mask]; } #undef tc apply_titlebar_color_settings(window); // HACK: Changing the style mask can cause the first responder to be cleared [nsw makeFirstResponder:window->ns.view]; window->ns.notch_cover_color = color; window->ns.notch_cover_opacity = background_opacity; if (window->ns.notch_cover_window) _glfwUpdateNotchCover(window); }} 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); } GLFWAPI bool glfwIsLayerShellSupported(void) { return true; } GLFWAPI void glfwCocoaCycleThroughOSWindows(bool backwards) { NSArray *allWindows = [NSApp windows]; if (allWindows.count < 2) return; NSMutableArray *filteredWindows = [NSMutableArray array]; for (NSWindow *window in allWindows) { NSRect windowFrame = [window frame]; // Exclude zero size windows which are likely zombie windows from the Tahoe bug // if ([obj isMemberOfClass:[MyClass class]]) { if ( windowFrame.size.width > 0 && windowFrame.size.height > 0 && \ !window.isMiniaturized && window.isVisible && \ [window isMemberOfClass:[GLFWWindow class]] ) [filteredWindows addObject:window]; } if (filteredWindows.count < 2) return; NSWindow *keyWindow = [NSApp keyWindow]; NSUInteger index = [filteredWindows indexOfObject:keyWindow]; NSUInteger nextIndex = 0; if (index != NSNotFound) { if (backwards) { nextIndex = (index == 0) ? [filteredWindows count] - 1 : index - 1; } else nextIndex = (index + 1) % filteredWindows.count; } NSWindow *nextWindow = filteredWindows[nextIndex]; [nextWindow makeKeyAndOrderFront:nil]; } GLFWAPI void glfwCocoaRegisterMIMETypes(GLFWwindow *window, const char **mimes, size_t count) { _GLFWwindow *w = (_GLFWwindow*)window; NSArray *currentTypes = [w->ns.view registeredDraggedTypes]; NSMutableArray *updatedTypes = [NSMutableArray arrayWithArray:currentTypes]; for (size_t i = 0; i < count; i++) { NSString *uti = mime_to_uti(mimes[i]); if (![updatedTypes containsObject:uti]) [updatedTypes addObject:uti]; } [w->ns.view registerForDraggedTypes:updatedTypes]; } ////////////////////////////////////////////////////////////////////////// ////// 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]; } // Drag source implementation {{{ @implementation GLFWDraggingSource - (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context { (void)session; (void)context; // Return the operation based on the stored drag operations bitfield NSDragOperation ops = NSDragOperationCopy; int q = _glfw.drag.operations; if (q & GLFW_DRAG_OPERATION_COPY) ops |= NSDragOperationCopy; if (q & GLFW_DRAG_OPERATION_MOVE) ops |= NSDragOperationMove; if (q & GLFW_DRAG_OPERATION_GENERIC) ops |= NSDragOperationGeneric; return ops; } - (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint { (void)session; start_point = screenPoint; } - (void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint { (void)session; current_point = screenPoint; } - (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { (void)session; _glfwPlatformFreeDragSourceData(); _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {0}; switch(operation) { case NSDragOperationCopy: case NSDragOperationLink: ev.action = GLFW_DRAG_OPERATION_COPY; break; case NSDragOperationMove: case NSDragOperationDelete: ev.action = GLFW_DRAG_OPERATION_MOVE; break; case NSDragOperationNone: break; default: ev.action = GLFW_DRAG_OPERATION_GENERIC; break; } switch (operation) { case NSDragOperationDelete: ev.type = GLFW_DRAG_CANCELLED; break; case NSDragOperationNone: { NSEvent *currentEvent = [NSApp currentEvent]; if (currentEvent && currentEvent.type == NSEventTypeKeyDown && currentEvent.keyCode == 53) { ev.type = GLFW_DRAG_CANCELLED; } else ev.type = GLFW_DRAG_DROPPED; } break; default: ev.type = GLFW_DRAG_DROPPED; break; } _glfwInputDragSourceRequest(window, &ev); if (operation == NSDragOperationNone) _glfwFreeDragSourceData(); } } @end static NSMutableArray *file_promise_providers = nil; static int set_image_for_dragging_item(NSDraggingItem *draggingItem, const GLFWimage *thumbnail, NSWindow *window) { CGFloat scaleFactor = 1.0; [draggingItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil]; if (_glfw.ns.drag_image) [_glfw.ns.drag_image release]; _glfw.ns.drag_image = nil; if (thumbnail && thumbnail->pixels && window) { scaleFactor = [window backingScaleFactor]; if (scaleFactor == 0) scaleFactor = [NSScreen mainScreen].backingScaleFactor; unsigned height = thumbnail->height + 20; // add empty padding at bottom NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:thumbnail->width pixelsHigh:height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:thumbnail->width * 4 bitsPerPixel:32]; if (!imageRep) return ENOMEM; memcpy([imageRep bitmapData], thumbnail->pixels, thumbnail->width * thumbnail->height * 4); NSSize pointSize = NSMakeSize(thumbnail->width / scaleFactor, height / scaleFactor); [imageRep setSize:pointSize]; _glfw.ns.drag_image = [[NSImage alloc] initWithSize:pointSize]; if (!_glfw.ns.drag_image) { [imageRep release]; return ENOMEM; } [_glfw.ns.drag_image addRepresentation:imageRep]; [imageRep release]; [draggingItem setImageComponentsProvider:^NSArray * _Nonnull{ NSDraggingImageComponent *icon = [NSDraggingImageComponent draggingImageComponentWithKey:NSDraggingImageComponentIconKey]; NSImage *image = _glfw.ns.drag_image; icon.contents = image; icon.frame = NSMakeRect(pointSize.width, pointSize.height, pointSize.width, pointSize.height); return @[icon]; }]; } return 0; } int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autoreleasepool{ if (file_promise_providers) { for (NSInteger i = [file_promise_providers count] - 1; i >= 0; i--) { GLFWFilePromiseProviderDelegate* d = file_promise_providers[i]; [d end_transfer:EINVAL]; } } // Obtain the event and view early so we can position the drag image relative to the cursor NSEvent* event = [NSApp currentEvent]; if (!event || ([event type] != NSEventTypeLeftMouseDown && [event type] != NSEventTypeLeftMouseDragged)) { // Create a synthetic left mouse down event using stored cursor position // Convert window coordinates to screen coordinates NSRect contentRect = [window->ns.view frame]; NSPoint windowPos = NSMakePoint(window->virtualCursorPosX, contentRect.size.height - window->virtualCursorPosY); event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown location:windowPos modifierFlags:0 timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:[window->ns.object windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1.0]; } if (!event) return EIO; GLFWContentView *v = window->ns.view; NSMutableArray* dragItems = [[[NSMutableArray alloc] init] autorelease]; for (size_t i = 0; i < _glfw.drag.item_count; i++) { NSString* utiString = mime_to_uti(_glfw.drag.items[i].mime_type); id w; if (_glfw.drag.items[i].optional_data) { NSPasteboardItem *pbItem = [[[NSPasteboardItem alloc] init] autorelease]; NSData *data = [NSData dataWithBytes:_glfw.drag.items[i].optional_data length:_glfw.drag.items[i].data_size]; [pbItem setData:data forType:utiString]; w = pbItem; } else { // Create file promise provider with our delegate GLFWFilePromiseProviderDelegate* delegate = [[[GLFWFilePromiseProviderDelegate alloc] initWithWindow:window mimeType:_glfw.drag.items[i].mime_type instanceId:_glfw.drag.instance_id] autorelease]; NSFilePromiseProvider *provider = [[[NSFilePromiseProvider alloc] initWithFileType:utiString delegate:delegate] autorelease]; // Store the delegate in the provider's user info so it's retained provider.userInfo = delegate; w = provider; } NSDraggingItem* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:w] autorelease]; if (i == 0 && thumbnail && thumbnail->pixels) { int err = set_image_for_dragging_item(dragItem, thumbnail, window->ns.object); if (err) return err; } else { [dragItem setDraggingFrame:NSMakeRect(0, 0, 32, 32) contents:nil]; } [dragItems addObject:dragItem]; } NSDraggingSession *s = [v beginDraggingSessionWithItems:dragItems event:event source:[v draggingSource]]; _glfw.ns.drag_session = [s retain]; _glfw.ns.drag_view = [v retain]; return 0; }} @implementation GLFWFilePromiseProviderDelegate - (void)end_transfer_with_error:(NSError*)err { if (err && file_url) { NSError *error; NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager removeItemAtURL:file_url error:&error]; } if (file_handle) [file_handle release]; file_handle = nil; if (completion_handler) { completion_handler(err); Block_release(completion_handler); completion_handler = nil; } [file_promise_providers removeObject:self]; } - (void)end_transfer:(int)errorCode { [self end_transfer_with_error:errorCode ? [NSError errorWithDomain:NSPOSIXErrorDomain code:errorCode userInfo:nil] : nil]; } - (bool)is_mimetype:(const char*)q { return strcmp(q, mimeType) == 0; } - (void)request_drag_data { if (instanceId != _glfw.drag.instance_id) { [self end_transfer:EINVAL]; return; } _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (!window) { [self end_transfer:EINVAL]; return; } bool keep_going = true; while (keep_going) { GLFWDragEvent ev = {.type=GLFW_DRAG_DATA_REQUEST, .mime_type=mimeType}; _glfwInputDragSourceRequest(window, &ev); if (ev.err_num) { keep_going = false; if (ev.err_num != EAGAIN) [self end_transfer:ev.err_num]; } else { if (ev.data_sz) { NSData* nsData = [NSData dataWithBytes:ev.data length:ev.data_sz]; NSError* error = nil; if (![file_handle writeData:nsData error:&error]) { keep_going = false; [self end_transfer_with_error:error]; } } else { keep_going = false; [self end_transfer_with_error:nil]; } _glfwInputDragSourceRequest(window, &ev); } } } - (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime instanceId:(GLFWid) instance_id { self = [super init]; if (self) { windowId = initWindow ? initWindow->id : 0; mimeType = _glfw_strdup(mime); instanceId = instance_id; if (file_promise_providers == nil) file_promise_providers = [NSMutableArray array]; [file_promise_providers addObject:self]; } return self; } - (void)dealloc { free(mimeType); mimeType = NULL; if (file_url) [file_url release]; file_url = nil; [self end_transfer:EINVAL]; [super dealloc]; } - (NSString*)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider fileNameForType:(NSString*)fileType { (void)filePromiseProvider; (void)fileType; // Generate a unique filename based on the MIME type NSString* extension = @"data"; if (mimeType) { UTType *type = [UTType typeWithMIMEType:@(mimeType)]; extension = type.preferredFilenameExtension; } return [NSString stringWithFormat:@"kitty-drag-source-%@.%@", [[NSUUID UUID] UUIDString], extension]; } - (void)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider writePromiseToURL:(NSURL*)url completionHandler:(void (^)(NSError*))completionHandler { (void)filePromiseProvider; _GLFWwindow* window = _glfwWindowForId(windowId); if (!window || instanceId != _glfw.drag.instance_id) { completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]); return; } // Create the file NSError* error = nil; if (![[NSFileManager defaultManager] createFileAtPath:url.path contents:nil attributes:nil]) { error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil]; completionHandler(error); return; } NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingToURL:url error:&error]; if (!fileHandle) { completionHandler(error); NSError *error; NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager removeItemAtURL:url error:&error]; return; } file_handle = fileHandle; completion_handler = completionHandler; file_url = [url retain]; [self request_drag_data]; } @end void _glfwPlatformFreeDragSourceData(void) { if (_glfw.ns.drag_session) [_glfw.ns.drag_session release]; _glfw.ns.drag_session = nil; if (_glfw.ns.drag_view) [_glfw.ns.drag_view release]; _glfw.ns.drag_view = nil; if (_glfw.ns.drag_image) [_glfw.ns.drag_image release]; _glfw.ns.drag_image = nil; } int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) {@autoreleasepool{ if (!_glfw.ns.drag_session || !_glfw.ns.drag_view) return 0; _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); [((NSDraggingSession*)_glfw.ns.drag_session) enumerateDraggingItemsWithOptions:0 forView:(NSView*)_glfw.ns.drag_view classes:@[[NSPasteboardItem class]] searchOptions:@{} usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { if (idx == 0) { set_image_for_dragging_item(draggingItem, thumbnail, window->ns.object); *stop = YES; } }]; return 0; }} int _glfwPlatformDragDataReady(const char *mime_type) { if (!file_promise_providers) return 0; for (GLFWFilePromiseProviderDelegate *d in file_promise_providers) { if ([d is_mimetype:mime_type]) [d request_drag_data]; } return 0; } ================================================ FILE: glfw/context.c ================================================ //======================================================================== // 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); } ================================================ FILE: glfw/dbus_glfw.c ================================================ //======================================================================== // 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 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, &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, 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; } ================================================ FILE: glfw/dbus_glfw.h ================================================ //======================================================================== // 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 DBusError *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); ================================================ FILE: glfw/egl_context.c ================================================ //======================================================================== // 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); assert(window != NULL); 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); _glfw.egl.display = EGL_NO_DISPLAY; if (_glfw.egl.platform) { _glfw.egl.display = eglGetPlatformDisplayEXT(_glfw.egl.platform, _glfwPlatformGetEGLNativeDisplay(), attribs); } free(attribs); if (_glfw.egl.display == EGL_NO_DISPLAY) _glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay()); EGLint egl_err; if (_glfw.egl.display == EGL_NO_DISPLAY && (egl_err = eglGetError()) != EGL_SUCCESS) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to get EGL display: %s", getEGLErrorString(egl_err)); _glfwTerminateEGL(); return false; } if (!eglInitialize(_glfw.egl.display, &_glfw.egl.major, &_glfw.egl.minor)) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to initialize EGL: %s display_present: %d egl_platform_present: %d", getEGLErrorString(eglGetError()), _glfw.egl.display != EGL_NO_DISPLAY, _glfw.egl.platform != 0); _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->major != 1 || ctxconfig->minor != 0) { setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major); setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor); } if (ctxconfig->noerror) { if (_glfw.egl.KHR_create_context_no_error) setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, true); } 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; assert(window != NULL); _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; assert(window != NULL); _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; } ================================================ FILE: glfw/egl_context.h ================================================ //======================================================================== // 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*/ ================================================ FILE: glfw/glfw.py ================================================ #!/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) bool glfwCocoaRecreateGLDrawable(GLFWwindow *w) GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, GLFWcocoarenderframefun callback) void* glfwGetX11Display(void) unsigned long glfwGetX11Window(GLFWwindow* window) void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string) void glfwCocoaCycleThroughOSWindows(bool backwards) 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) void glfwCocoaRegisterMIMETypes(GLFWwindow *window, const char **mimes, size_t count) 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 glfwWaylandSetTitlebarHidden(GLFWwindow *handle, bool hidden) void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) bool glfwWaylandBeep(GLFWwindow *handle) pid_t glfwWaylandCompositorPID(void) void glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval) unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) '''.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)(bool); 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() ================================================ FILE: glfw/glfw3.h ================================================ /************************************************************************* * 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 /* Include for ssize_t on POSIX systems. On Windows we define it if needed. */ #if !defined(_WIN32) #include #else #if !defined(SSIZE_T_DEFINED) typedef intptr_t ssize_t; #define SSIZE_T_DEFINED #endif #endif #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; typedef enum GLFWMomentumType { GLFW_NO_MOMENTUM_DATA = 0, GLFW_MOMENTUM_PHASE_BEGAN = 1, GLFW_MOMENTUM_PHASE_STATIONARY = 2, GLFW_MOMENTUM_PHASE_ACTIVE = 3, GLFW_MOMENTUM_PHASE_ENDED = 4, GLFW_MOMENTUM_PHASE_CANCELED = 5, GLFW_MOMENTUM_PHASE_MAY_BEGIN = 6, } GLFWMomentumType; typedef enum GLFWOffsetType { GLFW_SCROLL_OFFSET_LINES = 0, GLFW_SCROLL_OFFEST_V120 = 1, GLFW_SCROLL_OFFEST_HIGHRES = 2, } GLFWOffsetType; typedef struct GLFWScrollEvent { double x_offset, y_offset; // offsets are scaled by the window scale for HIGHRES struct { double x, y; } unscaled; // unscaled offsets, aka logical pixels GLFWMomentumType momentum_type; GLFWOffsetType offset_type; int keyboard_modifiers; } GLFWScrollEvent; /*! @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_WAYLAND_WINDOW_TAG 0x00025003 /*! @} */ #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; /*! @brief Opaque drop data object. * * Opaque drop data object representing data from a drag and drop operation. * This object is passed to the drop callback and can be used to query * available MIME types and read the dropped data in chunks. * * @see @ref path_drop * @see @ref glfwGetDropMimeTypes * @see @ref glfwReadDropData * * @since Added in version 4.0. * * @ingroup input */ 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, GLFW_EDGE_CENTER_SIZED } GLFWEdge; typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy; typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; struct { GLFWEdge edge; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; } previous; bool was_toggled_to_fullscreen; char output_name[128]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; int requested_exclusive_zone, hide_on_focus_loss; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing); struct { float xscale, yscale; } expected; struct { float background_opacity; int background_blur, color_space; } related; } 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; typedef enum { GLFW_DROP_ENTER, GLFW_DROP_MOVE, GLFW_DROP_LEAVE, GLFW_DROP_DROP, GLFW_DROP_STATUS_UPDATE, GLFW_DROP_DATA_AVAILABLE } GLFWDropEventType; /*! @brief Drag operation types. * * These constants specify the type of drag operation (copy, move, or generic). * * @ingroup input */ typedef enum { GLFW_DRAG_OPERATION_NONE = 0, // no operation, drop was not accepted /*! Move the dragged data to the destination. */ GLFW_DRAG_OPERATION_MOVE = 1, /*! Copy the dragged data to the destination. */ GLFW_DRAG_OPERATION_COPY = 2, /*! Generic drag operation (platform decides semantics). */ GLFW_DRAG_OPERATION_GENERIC = 4 } GLFWDragOperationType; typedef struct GLFWDropEvent { GLFWDropEventType type; const char **mimes; size_t num_mimes; // Positions are only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE. // They are in window co-ordinates same as for mouse events double xpos, ypos; bool from_self; // Only valid upto GLFW_DROP_DROP ssize_t (*read_data)(GLFWwindow *w, struct GLFWDropEvent* ev, char *buffer, size_t sz); // Only valid for GLFW_DROP_DATA_AVAILABLE void (*finish_drop)(GLFWwindow *w, GLFWDragOperationType op); // Only valid for GLFW_DROP_DROP and GLFW_DROP_DATA_AVAILABLE } GLFWDropEvent; typedef void (* GLFWdropeventfun)(GLFWwindow*, GLFWDropEvent *event); /*! @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*,const GLFWScrollEvent*); /*! @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*); typedef enum { GLFW_DRAG_DATA_REQUEST, // request data for specified mime type GLFW_DRAG_CANCELLED, GLFW_DRAG_FINSHED, GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet } GLFWDragEventType; typedef struct GLFWDragSourceItem { const char *mime_type; // Can be on null to provide data when the drag is started should be used only when the data is relatively small const char *optional_data; size_t data_size; } GLFWDragSourceItem; typedef struct GLFWDragEvent { GLFWDragEventType type; // When the drag event callback is called with a mimetype and no data, the // application should set the data ans data_sz and err_num fields. // Once glfw is done reading the data the drag event callback will be // called with the data pointer unchanged. The application is now free // to delete the data, as needed. const char *mime_type; const char *data; size_t data_sz; int err_num; // POSIX error code indicating failure fetching data GLFWDragOperationType action; // can be 0 indicating no action } GLFWDragEvent; typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev); /*! @brief The function pointer type for drag event callbacks. * * This is the function pointer type for drag event callbacks. A drag event * callback function has the following signature: * @code * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count) * @endcode * * @param[in] window The window that received the drag event. * @param[in] event The drag event type: @ref GLFW_DRAG_ENTER, @ref GLFW_DRAG_MOVE, * or @ref GLFW_DRAG_LEAVE. * @param[in] xpos The x-coordinate of the drag position in window coordinates. * @param[in] ypos The y-coordinate of the drag position in window coordinates. * @param[in,out] mime_types A writable array of MIME type strings available from the drag source. * For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events this is non-NULL and contains all * available MIME types. The callback is responsible for sorting this list by priority and * keeping only the MIME types it wants to accept. The first MIME type in the sorted list * will be used for the drop operation. The strings are only valid for the duration of the * callback; if you need to store them, make copies. For @ref GLFW_DRAG_LEAVE events this * is `NULL`. * @param[in,out] mime_count Pointer to the number of MIME types in the array. The callback * should update this to reflect the new count after sorting and filtering. For * @ref GLFW_DRAG_LEAVE events this is `NULL`. * @return For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events, return non-zero * to accept the drag or zero to reject it. This allows the application to * dynamically accept or reject the drag based on the current position. * Return value is ignored for @ref GLFW_DRAG_LEAVE events. * * @sa @ref drag_events * @sa @ref glfwSetDragCallback * @sa @ref glfwUpdateDragState * * @since Added in version 4.0. * * @ingroup input */ typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int* mime_count); 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. */ const 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); GLFWAPI bool glfwIsLayerShellSupported(void); /*! @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); GLFWAPI const char* glfwGetMonitorDescription(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, const GLFWLayerShellConfig *lsc); GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window); GLFWAPI const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle); GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value); /*! @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, bool move_to_active_screen); /*! @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); GLFWAPI bool glfwGrabKeyboard(int grab); /*! @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); GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun callback); GLFWAPI GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow *window, GLFWdropeventfun callback); GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window); // ask for update before GLFW_DROP_DROP happens GLFWAPI int glfwRequestDropData(GLFWwindow *window, const char *mime); GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op); GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* window, GLFWdragsourcefun callback); // Start a drag. If called with operations == -1 indicates that previously // requested data via GLFW_DRAG_DATA_REQUEST is ready. operations == -2 means // that the drag image is changed. GLFWAPI int glfwStartDrag(GLFWwindow* window, const GLFWDragSourceItem *items, size_t mime_count, const GLFWimage* thumbnail, int operations, bool needs_toplevel_on_wayland); /*! @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_ */ ================================================ FILE: glfw/glx_context.c ================================================ //======================================================================== // 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; assert(window != NULL); _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; assert(window != NULL); _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; } ================================================ FILE: glfw/glx_context.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/ibus_glfw.c ================================================ //======================================================================== // 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 DBusError *err, void *data) { if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s: %s", err->name, err->message); 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 DBusError *err, 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 (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s: %s", err->name, err->message); 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; } ================================================ FILE: glfw/ibus_glfw.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/init.c ================================================ //======================================================================== // 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; } ================================================ FILE: glfw/input.c ================================================ //======================================================================== // 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 #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, const GLFWScrollEvent *ev) { if (window->callbacks.scroll) window->callbacks.scroll((GLFWwindow*) window, ev); } // 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 a drop event. // The caller is responsible for passing a mutable working-copy of the mimes // array (reset to the full original list before each call) so that the // callback can sort/filter in-place without touching the backend's canonical // storage. The return value is ev.num_mimes after the callback returns, // i.e. the number of accepted (possibly reordered) mimes starting at // mimes[0]. size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self) { if (!window->callbacks.drop_event) return 0; GLFWDropEvent ev = { .mimes=mimes, .type=type, .xpos=xpos, .ypos=ypos, .num_mimes=num_mimes, .from_self=from_self, .read_data=type == GLFW_DROP_DATA_AVAILABLE ? _glfwPlatformReadAvailableDropData : NULL, .finish_drop=type == GLFW_DROP_DATA_AVAILABLE || type == GLFW_DROP_DROP ? _glfwPlatformEndDrop : NULL, }; window->callbacks.drop_event((GLFWwindow*)window, &ev); return ev.num_mimes; } // Notifies shared code that the OS wants data for a MIME type from the drag source // void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev) { if (window->callbacks.drag_source) window->callbacks.drag_source((GLFWwindow*) window, ev); } // 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 int glfwRequestDropData(GLFWwindow *window, const char *mime) { return _glfwPlatformRequestDropData((_GLFWwindow*)window, mime); } GLFWAPI void glfwEndDrop(GLFWwindow *window, GLFWDragOperationType op) { _glfwPlatformEndDrop(window, op); } GLFWAPI void glfwRequestDropUpdate(GLFWwindow *window) { _glfwPlatformRequestDropUpdate((_GLFWwindow*)window); } GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void) { return _glfw.ignoreOSKeyboardProcessing; } GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) { _glfw.ignoreOSKeyboardProcessing = enabled; } GLFWAPI bool glfwGrabKeyboard(int grab) { if (grab == 0 || grab == 1) { if (_glfwPlatformGrabKeyboard(grab)) _glfw.keyboard_grabbed = grab; } return _glfw.keyboard_grabbed; } 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 GLFWdropeventfun glfwSetDropEventCallback(GLFWwindow* handle, GLFWdropeventfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.drop_event, cbfun); return cbfun; } GLFWAPI GLFWdragsourcefun glfwSetDragSourceCallback(GLFWwindow* handle, GLFWdragsourcefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.drag_source, cbfun); return cbfun; } void _glfwFreeDragSourceData(void) { _glfwPlatformFreeDragSourceData(); if (_glfw.drag.items) { for (size_t i = 0; i < _glfw.drag.item_count; i++) { free((void*)_glfw.drag.items[i].mime_type); free((void*)_glfw.drag.items[i].optional_data); } free(_glfw.drag.items); } GLFWid iid = _glfw.drag.instance_id; memset(&_glfw.drag, 0, sizeof(_glfw.drag)); _glfw.drag.instance_id = iid; } GLFWAPI int glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_count, const GLFWimage* thumbnail, int operations, bool needs_toplevel_on_wayland) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(EINVAL); if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type); if (operations == -2) return _glfwPlatformChangeDragImage(thumbnail); _glfwFreeDragSourceData(); _glfw.drag.instance_id++; if (!items || !item_count) return 0; _glfw.drag.items = calloc(item_count, sizeof(_glfw.drag.items[0])); if (!_glfw.drag.items) return ENOMEM; _glfw.drag.item_count = item_count; for (size_t i = 0; i < item_count; i++) { if (!items[i].mime_type || !items[i].mime_type[0]) { _glfwFreeDragSourceData(); return EINVAL; } _glfw.drag.items[i].mime_type = _glfw_strdup(items[i].mime_type); if (!_glfw.drag.items[i].mime_type) { _glfwFreeDragSourceData(); return ENOMEM; } if (items[i].optional_data) { _glfw.drag.items[i].optional_data = malloc(items[i].data_size); if (!_glfw.drag.items[i].optional_data) { _glfwFreeDragSourceData(); return ENOMEM; } memcpy((void*)_glfw.drag.items[i].optional_data, items[i].optional_data, items[i].data_size); } _glfw.drag.items[i].data_size = items[i].data_size; } _glfw.drag.window_id = window->id; _glfw.drag.operations = operations; _glfw.drag.needs_toplevel_on_wayland = needs_toplevel_on_wayland; int ans = _glfwPlatformStartDrag(window, thumbnail); if (ans != 0) _glfwFreeDragSourceData(); return ans; } 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(); } ================================================ FILE: glfw/internal.h ================================================ //======================================================================== // 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], windowTag[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; GLFWliveresizefun liveResize; GLFWdragsourcefun drag_source; GLFWdropeventfun drop_event; } callbacks; // This is defined in the window API's platform.h _GLFW_PLATFORM_WINDOW_STATE; }; // Monitor structure // struct _GLFWmonitor { const char *name, *description; 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, keyboard_grabbed; 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; struct { GLFWDragSourceItem *items; size_t item_count; GLFWid window_id, instance_id; int operations; bool needs_toplevel_on_wayland; } drag; }; // Global state shared between compilation units of GLFW // extern _GLFWlibrary _glfw; typedef struct GeometryRect { int x, y, width, height; } GeometryRect; typedef struct MonitorGeometry { GeometryRect full, workarea; } MonitorGeometry; ////////////////////////////////////////////////////////////////////////// ////// 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, const GLFWLayerShellConfig *lsc); void _glfwPlatformDestroyWindow(_GLFWwindow* window); void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value); const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow* window); 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, bool move_to_active_screen); 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, const GLFWScrollEvent *ev); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); // Platform functions for drop data reading void _glfwPlatformRequestDropUpdate(_GLFWwindow* window); size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self); ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz); void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op); int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime); // Platform functions for drag source int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail); void _glfwFreeDragSourceData(void); void _glfwPlatformFreeDragSourceData(void); void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev); int _glfwPlatformDragDataReady(const char *mime_type); int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail); 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); MonitorGeometry _glfwPlatformGetMonitorGeometry(_GLFWmonitor* monitor); bool _glfwPlatformGrabKeyboard(bool grab); void glfw_handle_scroll_event_for_momentum(_GLFWwindow *w, const GLFWScrollEvent *ev, bool stopped, bool is_finger_based); #define glfw_cancel_momentum_scroll() glfw_handle_scroll_event_for_momentum(NULL, NULL, false, false) #ifdef _GLFW_X11 #define momentum_scroll_gesture_detection_timeout_ms 50 #else #define momentum_scroll_gesture_detection_timeout_ms 0 #endif 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__); } #define safe_close(fd) do { errno = 0; close(fd); } while(errno == EINTR) ================================================ FILE: glfw/kwin-blur-v1.xml ================================================ ================================================ FILE: glfw/linux_desktop_settings.c ================================================ /* * 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 DBusError* err, void *data) { \ (void)data; \ if (err) { \ _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", #name_, err->name, err->message); \ return; \ } HANDLER(get_color_scheme_legacy) DBusMessageIter iter, variant_iter, variant_iter2; 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_VARIANT) { _glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a variant"); return; } dbus_message_iter_recurse(&variant_iter, &variant_iter2); if (type != DBUS_TYPE_VARIANT) { _glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a nested variant"); return; } uint32_t val; dbus_message_iter_get_basic(&variant_iter2, &val); if (val < 3) appearance = val; } static void get_color_scheme(DBusMessage *msg, const DBusError* err, void *data) { (void) data; if (err) { if (strcmp("org.freedesktop.DBus.Error.UnknownMethod", err->name) == 0) { 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, "Read", DBUS_TIMEOUT_USE_DEFAULT, get_color_scheme_legacy, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID); } return; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", "get_color_scheme", err->name, err->message); return; } } 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, "WARNING: Failed to read desktop settings, using defaults, 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); } } ================================================ FILE: glfw/linux_desktop_settings.h ================================================ /* * 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); ================================================ FILE: glfw/linux_joystick.c ================================================ //======================================================================== // 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) { } ================================================ FILE: glfw/linux_joystick.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/linux_notify.c ================================================ /* * 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 DBusError* err, void *data) { if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s: %s", err->name, err->message); 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 DBusError* err, void* data UNUSED) { if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s: %s", err->name, err->message); 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; } ================================================ FILE: glfw/linux_notify.h ================================================ /* * 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); ================================================ FILE: glfw/main_loop.h ================================================ /* * 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); } ================================================ FILE: glfw/mappings.h ================================================ //======================================================================== // 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 }; ================================================ FILE: glfw/memfd.h ================================================ /* * 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 ================================================ FILE: glfw/momentum-scroll.c ================================================ /* * momentum-scroll.c * Copyright (C) 2026 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "internal.h" #include typedef struct ScrollSample { double dx, dy; monotonic_t timestamp; } ScrollSample; #define DEQUE_DATA_TYPE ScrollSample #define DEQUE_NAME ScrollSamples #include "../kitty/fixed_size_deque.h" typedef enum ScrollerState { NONE, PHYSICAL_EVENT_IN_PROGRESS, MOMENTUM_IN_PROGRESS } ScrollerState; typedef struct MomentumScroller { double friction, // Deceleration inverse factor (0-1, higher = longer coast) min_velocity, // Minimum velocity before stopping max_velocity, // Maximum velocity to prevent runaway scrolling velocity_scale; // Scale factor for initial velocity monotonic_t timer_interval; // animation speed GLFWid timer_id, window_id; ScrollSamples samples; ScrollerState state; double scale; struct { double x, y; } velocity; int keyboard_modifiers; struct { monotonic_t start, duration; struct { double x, y; } displacement; } physical_event; } MomentumScroller; #define DEFAULTS { .friction = 0.96, .min_velocity = 0.5, .max_velocity = 100, .timer_interval = 10, } static const MomentumScroller defaults = DEFAULTS; static MomentumScroller s = DEFAULTS; #undef DEFAULTS GLFWAPI void glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval_ms) { s.timer_interval = timer_interval_ms ? ms_to_monotonic_t(timer_interval_ms) : defaults.timer_interval; s.friction = friction < 0 ? defaults.friction : MAX(0, MIN(friction, 1)); #define S(w) s.w = w >= 0 ? w : defaults.w S(min_velocity); S(max_velocity); #undef S } static void cancel_existing_scroll(bool reset_velocity) { if (s.timer_id) { glfwRemoveTimer(s.timer_id); s.timer_id = 0; } if (s.state == MOMENTUM_IN_PROGRESS) { _GLFWwindow *w = _glfwWindowForId(s.window_id); if (w) _glfwInputScroll( w, &(GLFWScrollEvent){.momentum_type=GLFW_MOMENTUM_PHASE_CANCELED, .keyboard_modifiers=s.keyboard_modifiers}); } s.window_id = 0; s.keyboard_modifiers = 0; deque_clear(&s.samples); s.state = NONE; if (reset_velocity) { s.velocity.x = 0; s.velocity.y = 0; } } static void add_sample(double dx, double dy, monotonic_t now) { deque_push_back(&s.samples, (ScrollSample){dx, dy, now}, NULL); } static void last_sample_delta(double *dx, double *dy) { const ScrollSample *ss; if ((ss = deque_peek_back(&s.samples))) { *dx = ss->dx; *dy = ss->dy; } else { *dx = 0; *dy = 0; } } static void trim_old_samples(monotonic_t now) { const ScrollSample *ss; while ((ss = deque_peek_front(&s.samples)) && (now - ss->timestamp) > ms_to_monotonic_t(150)) deque_pop_front(&s.samples, NULL); } static void add_velocity(double x, double y) { if (x == 0 || x * s.velocity.x >= 0) s.velocity.x += x; else s.velocity.x = x; if (y == 0 || y * s.velocity.y >= 0) s.velocity.y += y; else s.velocity.y = y; s.velocity.x = MAX(-s.max_velocity, MIN(s.velocity.x, s.max_velocity)); s.velocity.y = MAX(-s.max_velocity, MIN(s.velocity.y, s.max_velocity)); } static void set_velocity_from_samples(monotonic_t now) { s.timer_interval = ms_to_monotonic_t(8); trim_old_samples(now); ScrollSample ss; switch (deque_size(&s.samples)) { case 0: return; case 1: deque_pop_front(&s.samples, &ss); add_velocity(ss.dx, ss.dy); return; } // Use weighted average - more recent samples have higher weight double total_dx = 0.0, total_dy = 0.0, total_weight = 0.0; monotonic_t first_time = deque_peek_front(&s.samples)->timestamp; monotonic_t last_time = deque_peek_back(&s.samples)->timestamp; double time_span = MAX(1, last_time - first_time); for (size_t i = 0; i < deque_size(&s.samples); i++) { const ScrollSample *ss = deque_at(&s.samples, i); double weight = 1.0 + (ss->timestamp - first_time) / time_span; total_dx += ss->dx * weight; total_dy += ss->dy * weight; total_weight += weight; } deque_clear(&s.samples); if (total_weight <= 0) return; double dy = total_dy / total_weight, dx = total_dx / total_weight; add_velocity(dx, dy); if (false) timed_debug_print("momentum scroll: event velocity: %.1f final velocity: %.1f\n", dy, s.velocity.y); } static void send_momentum_event(bool is_start) { _GLFWwindow *w = _glfwWindowForId(s.window_id); if (!w || w != _glfwFocusedWindow()) { cancel_existing_scroll(true); return; } s.velocity.x *= s.friction; s.velocity.y *= s.friction; if (fabs(s.velocity.x) < s.min_velocity) s.velocity.x = 0; if (fabs(s.velocity.y) < s.min_velocity) s.velocity.y = 0; GLFWMomentumType m = is_start ? GLFW_MOMENTUM_PHASE_BEGAN : GLFW_MOMENTUM_PHASE_ACTIVE; if (s.velocity.x == 0 && s.velocity.y == 0 && !is_start) { m = GLFW_MOMENTUM_PHASE_ENDED; if (s.timer_id) glfwRemoveTimer(s.timer_id); s.timer_id = 0; s.state = NONE; } GLFWScrollEvent e = { .offset_type=GLFW_SCROLL_OFFEST_HIGHRES, .momentum_type=m, .unscaled.x=s.velocity.x, .unscaled.y=s.velocity.y, .x_offset=s.scale * s.velocity.x, .y_offset=s.scale * s.velocity.y, .keyboard_modifiers=s.keyboard_modifiers }; _glfwInputScroll(w, &e); } static void momentum_timer_fired(unsigned long long timer_id UNUSED, void *data UNUSED) { send_momentum_event(false); } static void start_momentum_scroll(monotonic_t now) { set_velocity_from_samples(now); send_momentum_event(true); s.timer_id = glfwAddTimer(s.timer_interval, true, momentum_timer_fired, NULL, NULL); } static bool is_suitable_for_momentum(void) { return ( MAX(fabs(s.physical_event.displacement.x), fabs(s.physical_event.displacement.y)) > 10 && s.physical_event.duration > ms_to_monotonic_t(2) ); } void glfw_handle_scroll_event_for_momentum( _GLFWwindow *w, const GLFWScrollEvent *ev, bool stopped, bool is_finger_based ) { const bool is_synthetic_momentum_start_event = stopped && momentum_scroll_gesture_detection_timeout_ms; if (!w) { cancel_existing_scroll(true); return; } if (!is_finger_based || ev->offset_type != GLFW_SCROLL_OFFEST_HIGHRES || s.friction < 0 || s.friction >= 1) { if (ev->x_offset != 0 || ev->y_offset != 0) _glfwInputScroll(w, ev); return; } monotonic_t now = monotonic(); if (is_synthetic_momentum_start_event) now -= ms_to_monotonic_t(momentum_scroll_gesture_detection_timeout_ms); if (s.state == PHYSICAL_EVENT_IN_PROGRESS) { s.physical_event.displacement.x += ev->unscaled.x; s.physical_event.displacement.y += ev->unscaled.y; if (stopped) { s.physical_event.duration = now - s.physical_event.start; s.physical_event.start = 0; } } else { memset(&s.physical_event, 0, sizeof(s.physical_event)); s.physical_event.start = now; } if (ev->unscaled.y > 0) s.scale = ev->y_offset / ev->unscaled.y; else if (ev->unscaled.x > 0) s.scale = ev->x_offset / ev->unscaled.x; if (s.window_id && s.window_id != w->id) cancel_existing_scroll(true); if (s.state != PHYSICAL_EVENT_IN_PROGRESS) cancel_existing_scroll(false); if (!is_synthetic_momentum_start_event) { // Check for change in direction double ldx, ldy; last_sample_delta(&ldx, &ldy); if (ldx * ev->x_offset < 0 || ldy * ev->y_offset < 0) cancel_existing_scroll(true); } s.window_id = w->id; s.keyboard_modifiers = ev->keyboard_modifiers; if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) { if (!is_synthetic_momentum_start_event) add_sample(ev->unscaled.x, ev->unscaled.y, now); if (stopped) s.state = is_suitable_for_momentum() ? MOMENTUM_IN_PROGRESS : NONE; else s.state = PHYSICAL_EVENT_IN_PROGRESS; } else { s.state = stopped ? NONE : PHYSICAL_EVENT_IN_PROGRESS; } if (s.state == MOMENTUM_IN_PROGRESS) start_momentum_scroll(now); else _glfwInputScroll(w, ev); } ================================================ FILE: glfw/monitor.c ================================================ //======================================================================== // 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((void*)monitor->name); free((void*)monitor->description); 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 ? monitor->name : ""; } GLFWAPI const char* glfwGetMonitorDescription(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return monitor->description ? monitor->description : ""; } 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); } ================================================ FILE: glfw/monotonic.c ================================================ /* * 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" ================================================ FILE: glfw/nsgl_context.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/nsgl_context.m ================================================ //======================================================================== // 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 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; } // We use a globally shared context across all OS Windows so that // if all OS Windows are closed the context does not have to be recreated // with all associated data lost. On Apple kitty can remain running with no // OS Windows. static NSOpenGLContext* globally_shared_context = nil; // Terminate OpenGL support // void _glfwTerminateNSGL(void) { if (globally_shared_context != nil) { [globally_shared_context release]; globally_shared_context = nil; } } // 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 = globally_shared_context; 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 (globally_shared_context == nil) { globally_shared_context = [window->context.nsgl.object retain]; } 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; assert(window != NULL); _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; } GLFWAPI bool glfwCocoaRecreateGLDrawable(GLFWwindow *w) { _GLFWwindow* window = (_GLFWwindow*)w; if (window->context.client == GLFW_NO_API) return false; @try { // Save current state NSOpenGLPixelFormat *pixelFormat = window->context.nsgl.pixelFormat; NSOpenGLContext *oldContext = window->context.nsgl.object; // Create a new context sharing resources with the old one NSOpenGLContext *newContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:oldContext]; if (newContext == nil) return false; // Copy settings from old context GLint opacity = 0; [oldContext getValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; [newContext setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; GLint interval = 0; [newContext setValues:&interval forParameter:NSOpenGLContextParameterSwapInterval]; // Detach old context [NSOpenGLContext clearCurrentContext]; [oldContext clearDrawable]; // Attach new context to the view [window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.retina]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [newContext setView:window->ns.view]; #pragma clang diagnostic pop [newContext makeCurrentContext]; [newContext update]; // Replace context window->context.nsgl.object = newContext; [oldContext release]; } @catch (NSException *e) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to recreate NSGL context: %s (%s)", [[e name] UTF8String], [[e reason] UTF8String]); return false; } return true; } ================================================ FILE: glfw/null_init.c ================================================ //======================================================================== // 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"; } ================================================ FILE: glfw/null_joystick.c ================================================ //======================================================================== // 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) { } ================================================ FILE: glfw/null_joystick.h ================================================ //======================================================================== // 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 "" ================================================ FILE: glfw/null_monitor.c ================================================ //======================================================================== // 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); } ================================================ FILE: glfw/null_platform.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/null_window.c ================================================ //======================================================================== // 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 #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 _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) { // No-op for null platform } int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) { (void)window; (void)thumbnail; return ENOTSUP; } void _glfwPlatformFreeDragSourceData(void) {} int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; } int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { (void)thumbnail; return 0; } const char** _glfwPlatformGetDropMimeTypes(GLFWDropData* drop UNUSED, int* count) { if (count) *count = 0; return NULL; } ssize_t _glfwPlatformReadDropData(GLFWDropData* drop UNUSED, const char* mime UNUSED, void* buffer UNUSED, size_t capacity UNUSED, monotonic_t timeout UNUSED) { return -ENOENT; } void _glfwPlatformFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation UNUSED, bool success UNUSED) { if (!drop) return; // Free the heap-allocated drop data structure free(drop); } 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; } ================================================ FILE: glfw/osmesa_context.c ================================================ //======================================================================== // 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; assert(window != NULL); _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; } ================================================ FILE: glfw/osmesa_context.h ================================================ //======================================================================== // 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); ================================================ FILE: glfw/posix_thread.c ================================================ //======================================================================== // 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); } ================================================ FILE: glfw/posix_thread.h ================================================ //======================================================================== // 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; ================================================ FILE: glfw/source-info.json ================================================ { "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", "unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml", "staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml", "staging/xdg-system-bell/xdg-system-bell-v1.xml", "staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml", "staging/ext-background-effect/ext-background-effect-v1.xml", "staging/xdg-toplevel-drag/xdg-toplevel-drag-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", "momentum-scroll.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", "momentum-scroll.c", "linux_notify.c" ] } } ================================================ FILE: glfw/vulkan.c ================================================ //======================================================================== // 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); } ================================================ FILE: glfw/window.c ================================================ //======================================================================== // 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, const GLFWLayerShellConfig *lsc) { _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, lsc)) { 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, false); #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; case GLFW_WAYLAND_WINDOW_TAG: strncpy(_glfw.hints.window.wl.windowTag, value, sizeof(_glfw.hints.window.wl.windowTag) - 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 const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); return _glfwPlatformGetLayerShellConfig(window); } GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); return _glfwPlatformSetLayerShellConfig(window, 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, bool move_to_active_screen) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->monitor) return; _glfwPlatformShowWindow(window, move_to_active_screen); #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(); } ================================================ FILE: glfw/wl_client_side_decorations.c ================================================ /* * 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; 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); free(st.data); st.data = malloc(sizeof(st.data[0]) * 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, (size_t)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, (size_t)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, (size_t)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, (size_t)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((size_t)factor * factor * height * src_width); if (big_src) { which(big_src, src_width * factor, height * factor); memset(src, 0, (size_t)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 size_t button_size = decs.titlebar.buffer.height; unsigned 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; const bool has_titlebar = !decs.titlebar_hidden; const int side_height = window->wl.height + (has_titlebar ? decs.metrics.visible_titlebar_height : 0); #define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, decs.for_window_state.fscale); if (has_titlebar) 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, side_height); bp(shadow_right, decs.metrics.width, side_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) if (has_titlebar) Q(titlebar); all_shadow_surfaces(Q); #undef Q wl_shm_pool_destroy(pool); if (has_titlebar) 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; } bool csd_should_window_be_decorated(_GLFWwindow *window) { return window_is_csd_capable(window) && window->monitor == NULL && (window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN) == 0; } static bool ensure_csd_resources(_GLFWwindow *window) { if (!window_is_csd_capable(window)) return false; const bool has_titlebar = !decs.titlebar_hidden; 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 titlebar_state_changed = (has_titlebar && !decs.titlebar.surface) || (!has_titlebar && decs.titlebar.surface); const bool needs_update = focus_changed || size_changed || titlebar_state_changed || 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 || titlebar_state_changed) { free_csd_buffers(window); if (!create_shm_buffers(window)) return false; decs.buffer_destroyed = false; } const int top_y = has_titlebar ? -(int)decs.metrics.visible_titlebar_height : 0; #define setup_surface(which, x, y) \ if (!decs.which.surface) create_csd_surfaces(window, &decs.which); \ position_csd_surface(&decs.which, x, y); if (has_titlebar) { setup_surface(titlebar, 0, -decs.metrics.visible_titlebar_height); } else { free_csd_surface(&decs.titlebar); if (decs.focus == CSD_titlebar) { decs.focus = CENTRAL_WINDOW; decs.dragging = false; } } setup_surface(shadow_top, 0, top_y - decs.metrics.width); setup_surface(shadow_bottom, 0, window->wl.height); setup_surface(shadow_left, -decs.metrics.width, top_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 (has_titlebar) { 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) { const bool include_space_for_csd = csd_should_window_be_decorated(window); const bool has_titlebar = include_space_for_csd && !decs.titlebar_hidden; 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_titlebar) *height += decs.metrics.visible_titlebar_height; } decs.geometry.x = 0; decs.geometry.y = 0; decs.geometry.width = *width; decs.geometry.height = *height; if (has_titlebar) { 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 ================================================ FILE: glfw/wl_client_side_decorations.h ================================================ /* * 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); bool csd_should_window_be_decorated(_GLFWwindow *window); void csd_set_visible(_GLFWwindow *window, bool visible); void csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface* surface); ================================================ FILE: glfw/wl_cursors.c ================================================ // 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; } ================================================ FILE: glfw/wl_cursors.h ================================================ // 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); ================================================ FILE: glfw/wl_init.c ================================================ //======================================================================== // 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_cancel_momentum_scroll(); _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; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: if (!info.y_start_time) info.y_start_time = ms_to_monotonic_t(time); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: if (!info.x_start_time) info.x_start_time = ms_to_monotonic_t(time); break; } 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; GLFWScrollEvent ev = {.keyboard_modifiers=_glfw.wl.xkb.states.modifiers}; bool found = false; if (info.discrete.y_axis_type != AXIS_EVENT_UNKNOWN) { ev.unscaled.y = info.discrete.y; if (info.discrete.y_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120; found = true; } else if (info.continuous.y_axis_type != AXIS_EVENT_UNKNOWN) { ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES; ev.unscaled.y = info.continuous.y; found = true; } if (info.discrete.x_axis_type != AXIS_EVENT_UNKNOWN) { ev.unscaled.x = info.discrete.x; if (info.discrete.x_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120; found = true; } else if (info.continuous.x_axis_type != AXIS_EVENT_UNKNOWN) { ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES; ev.unscaled.x = info.continuous.x; found = true; } bool stopped = info.y_stop_received || info.x_stop_received; if (!found && stopped) ev.offset_type = window->wl.prev_frame_offset_type; ev.unscaled.x *= -1; const double scale = ev.offset_type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfwWaylandWindowScale(window) : 1; ev.x_offset = scale * ev.unscaled.x; ev.y_offset = scale * ev.unscaled.y; glfw_handle_scroll_event_for_momentum( window, &ev, stopped, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER); window->wl.prev_frame_offset_type = ev.offset_type; /* clear pointer_curr_axis_info for next frame */ memset(&info, 0, sizeof(info)); } static void pointer_handle_axis_source(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t source) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; info.source_type = source; } static void pointer_handle_axis_stop(void *data UNUSED, struct wl_pointer *wl_pointer UNUSED, uint32_t time UNUSED, uint32_t axis) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: info.y_stop_received = true; info.y_stop_time = ms_to_monotonic_t(time); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: info.x_stop_received = true; info.x_stop_time = ms_to_monotonic_t(time); break; } } 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 bool needs_synthetic_key_repeat(void) { return _glfw.wl.keyboardRepeatRate > 0 && !_glfw.wl.has_key_repeat_events; } static void start_key_repeat_timer(bool initial) { #ifdef HAS_TIMER_FD (void)initial; struct itimerspec new_value = {.it_value={.tv_nsec = _glfw.wl.keyboardRepeatDelay}, .it_interval={.tv_nsec = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate)}}; if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime( _glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL); #else monotonic_t interval = _glfw.wl.keyboardRepeatDelay; if (!initial) interval = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate); changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, interval); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); #endif } static void stop_key_repeat_timer(void) { #ifdef HAS_TIMER_FD struct itimerspec new_value = {0}; if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime(_glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL); #else toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); #endif } #ifndef HAS_TIMER_FD static void send_key_repeat_timer_event(id_type timer_id UNUSED, void *data UNUSED) { char b = 1; b += write(_glfw.wl.eventLoopData.key_repeat_fds[1], &b, 1); if (needs_synthetic_key_repeat()) start_key_repeat_timer(false); } #endif 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) { if (needs_synthetic_key_repeat()) start_key_repeat_timer(true); 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); stop_key_repeat_timer(); } static void keyboardHandleKey(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, uint32_t time UNUSED, uint32_t key, uint32_t state) { glfw_cancel_momentum_scroll(); _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; int action = GLFW_PRESS; switch (state) { case WL_KEYBOARD_KEY_STATE_PRESSED: action = GLFW_PRESS; break; case WL_KEYBOARD_KEY_STATE_RELEASED: action = GLFW_RELEASE; break; #ifdef WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION case WL_KEYBOARD_KEY_STATE_REPEATED: action = GLFW_REPEAT; break; #endif } _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action); if (action == GLFW_PRESS && needs_synthetic_key_repeat() && glfw_xkb_should_repeat(&_glfw.wl.xkb, key)) { _glfw.wl.keyRepeatInfo.key = key; _glfw.wl.keyRepeatInfo.keyboardFocusId = window->id; start_key_repeat_timer(true); } else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) { _glfw.wl.keyRepeatInfo.key = 0; stop_key_repeat_timer(); } } 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; stop_key_repeat_timer(); } } 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 extBackgroundEffectHandleCapabilities(void* data UNUSED, struct ext_background_effect_manager_v1* manager UNUSED, uint32_t flags) { _glfw.wl.ext_background_effect_capabilities = flags; } static const struct ext_background_effect_manager_v1_listener extBackgroundEffectManagerListener = { extBackgroundEffectHandleCapabilities }; 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) { _glfw.wl.has_key_repeat_events = false; #if defined(WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION) _glfw.wl.seatVersion = MIN(WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION, (int)version); _glfw.wl.has_key_repeat_events = _glfw.wl.seatVersion >= WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION; #elif defined(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, 3); 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(ext_background_effect_manager_v1)) { _glfw.wl.ext_background_effect_manager_v1 = wl_registry_bind(registry, name, &ext_background_effect_manager_v1_interface, 1); ext_background_effect_manager_v1_add_listener(_glfw.wl.ext_background_effect_manager_v1, &extBackgroundEffectManagerListener, NULL); } 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(zwp_keyboard_shortcuts_inhibit_manager_v1)) { _glfw.wl.keyboard_shortcuts_inhibit_manager = wl_registry_bind(registry, name, &zwp_keyboard_shortcuts_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); } else if (is(xdg_system_bell_v1)) { _glfw.wl.xdg_system_bell_v1 = wl_registry_bind(registry, name, &xdg_system_bell_v1_interface, 1); } else if (is(xdg_toplevel_tag_manager_v1)) { _glfw.wl.xdg_toplevel_tag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_tag_manager_v1_interface, 1); } else if (is(xdg_toplevel_drag_manager_v1)) { _glfw.wl.xdg_toplevel_drag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_drag_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); if (!_glfw.wl.org_kde_kwin_blur_manager && !_glfw.wl.ext_background_effect_manager_v1) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "blur"); 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); C(bell, xdg_system_bell_v1); C(window-tag, xdg_toplevel_tag_manager_v1); C(keyboard_shortcuts_inhibit, keyboard_shortcuts_inhibit_manager); C(key-repeat, has_key_repeat_events); C(top_level_drag, xdg_toplevel_drag_manager_v1); #define P(x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", x); if (_glfw.wl.xdg_wm_base_version < 6) P("window-state-suspended"); if (_glfw.wl.xdg_wm_base_version < 5) P("window-capabilities"); #undef P #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(); #ifndef HAS_TIMER_FD _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, send_key_repeat_timer_event, NULL, NULL); #endif _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.untyped_data_offers); doi++) { if (_glfw.wl.untyped_data_offers[doi].id) { destroy_data_offer(&_glfw.wl.untyped_data_offers[doi]); } } if (_glfw.wl.primary_data_offer.id) destroy_data_offer(&_glfw.wl.primary_data_offer); if (_glfw.wl.clipboard_data_offer.id) destroy_data_offer(&_glfw.wl.clipboard_data_offer); if (_glfw.wl.drop_data_offer.id) destroy_data_offer(&_glfw.wl.drop_data_offer); 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.xdg_system_bell_v1) xdg_system_bell_v1_destroy(_glfw.wl.xdg_system_bell_v1); if (_glfw.wl.xdg_toplevel_tag_manager_v1) xdg_toplevel_tag_manager_v1_destroy(_glfw.wl.xdg_toplevel_tag_manager_v1); if (_glfw.wl.xdg_toplevel_drag_manager_v1) xdg_toplevel_drag_manager_v1_destroy(_glfw.wl.xdg_toplevel_drag_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.ext_background_effect_manager_v1) ext_background_effect_manager_v1_destroy(_glfw.wl.ext_background_effect_manager_v1); 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.keyboard_shortcuts_inhibit_manager) zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(_glfw.wl.keyboard_shortcuts_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 ; } ================================================ FILE: glfw/wl_monitor.c ================================================ //======================================================================== // 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 UNUSED, const char* model UNUSED, int32_t transform UNUSED) { struct _GLFWmonitor *monitor = data; monitor->wl.x = x; monitor->wl.y = y; monitor->widthMM = physicalWidth; monitor->heightMM = physicalHeight; } 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) { if (monitor->name) free((void*)monitor->name); monitor->name = _glfw_strdup(name); } } static void outputHandleDescription(void* data, struct wl_output* output UNUSED, const char* description) { struct _GLFWmonitor *monitor = data; if (description) { if (monitor->description) free((void*)monitor->description); monitor->description = _glfw_strdup(description); } } 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 handlers. monitor = _glfwAllocMonitor("unnamed", 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; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return monitor->wl.output; } ================================================ FILE: glfw/wl_platform.h ================================================ //======================================================================== // 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-ext-background-effect-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-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" #include "wayland-xdg-toplevel-icon-v1-client-protocol.h" #include "wayland-xdg-system-bell-v1-client-protocol.h" #include "wayland-xdg-toplevel-tag-v1-client-protocol.h" #include "wayland-xdg-toplevel-drag-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, TOPLEVEL_STATE_CONSTRAINED_LEFT = 512, TOPLEVEL_STATE_CONSTRAINED_RIGHT = 1024, TOPLEVEL_STATE_CONSTRAINED_TOP = 2048, TOPLEVEL_STATE_CONSTRAINED_BOTTOM = 4096, } 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, created; 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; struct ext_background_effect_surface_v1 *ext_background_effect_surface_v1; 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 */ bool x_stop_received, y_stop_received; uint32_t source_type; monotonic_t x_start_time, x_stop_time, y_stop_time, y_start_time; } pointer_curr_axis_info; GLFWOffsetType prev_frame_offset_type; _GLFWcursor* currentCursor; double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY; char* title; char appId[256], windowTag[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, titlebar_hidden; _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; struct zwp_keyboard_shortcuts_inhibitor_v1 *keyboard_shortcuts_inhibitor; } _GLFWwindowWayland; typedef struct _GLFWWaylandDataOffer { void *id; 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; const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[] size_t copy_mimes_count; // Count of entries in copy_mimes (accepted count after callback) bool drag_accepted, dropped; uint32_t serial; struct { id_type watch_id; int fd; char *mime; } *requested_drop_data; size_t dd_capacity, dd_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 xdg_system_bell_v1* xdg_system_bell_v1; struct xdg_toplevel_tag_manager_v1* xdg_toplevel_tag_manager_v1; struct xdg_toplevel_drag_manager_v1* xdg_toplevel_drag_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 ext_background_effect_manager_v1 *ext_background_effect_manager_v1; uint32_t ext_background_effect_capabilities; 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; struct zwp_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit_manager; int compositorVersion; int seatVersion; bool has_key_repeat_events; 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; _GLFWWaylandDataOffer untyped_data_offers[8]; _GLFWWaylandDataOffer clipboard_data_offer, primary_data_offer, drop_data_offer; bool has_preferred_buffer_scale; char *compositor_name; // Drag source state struct { struct wl_data_source* source; struct wl_surface *drag_icon; struct wp_viewport *drag_viewport; struct xdg_toplevel_drag_v1 *toplevel_drag; struct xdg_surface *toplevel_xdg_surface; struct xdg_toplevel *toplevel_xdg_toplevel; struct wl_buffer *toplevel_buffer; struct { const char *mime_type; int fd; GLFWid watch_id; char *pending_data; size_t sz, offset; } *data_requests; size_t count, capacity; GLFWDragOperationType action; } drag; } _GLFWlibraryWayland; // Wayland-specific per-monitor data // typedef struct _GLFWmonitorWayland { struct wl_output* output; uint32_t name; 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); ================================================ FILE: glfw/wl_text_input.c ================================================ /* * 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; } } ================================================ FILE: glfw/wl_text_input.h ================================================ /* * 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); ================================================ FILE: glfw/wl_window.c ================================================ //======================================================================== // 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 #include #include #include #define debug debug_rendering static bool is_layer_shell(_GLFWwindow *window) { return window->wl.layer_shell.config.type != GLFW_LAYER_SHELL_NONE; } static void inhibit_shortcuts_for(_GLFWwindow *window, bool inhibit) { if (inhibit) { if (window->wl.keyboard_shortcuts_inhibitor) return; window->wl.keyboard_shortcuts_inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(_glfw.wl.keyboard_shortcuts_inhibit_manager, window->wl.surface, _glfw.wl.seat); } else { if (!window->wl.keyboard_shortcuts_inhibitor) return; zwp_keyboard_shortcuts_inhibitor_v1_destroy(window->wl.keyboard_shortcuts_inhibitor); window->wl.keyboard_shortcuts_inhibitor = NULL; } } 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.ext_background_effect_manager_v1) { if (window->wl.has_blur && (_glfw.wl.ext_background_effect_capabilities & EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR)) { if (!window->wl.ext_background_effect_surface_v1) window->wl.ext_background_effect_surface_v1 = ext_background_effect_manager_v1_get_background_effect( _glfw.wl.ext_background_effect_manager_v1, window->wl.surface); if (window->wl.ext_background_effect_surface_v1) { struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor); if (region) { wl_region_add(region, 0, 0, window->wl.width, window->wl.height); ext_background_effect_surface_v1_set_blur_region(window->wl.ext_background_effect_surface_v1, region); wl_region_destroy(region); } } } else { if (window->wl.ext_background_effect_surface_v1) { ext_background_effect_surface_v1_set_blur_region(window->wl.ext_background_effect_surface_v1, NULL); } } } else 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, csd_should_window_be_decorated(window)); // 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 create_surface(_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.ext_background_effect_manager_v1 || _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); if (_glfw.keyboard_grabbed) inhibit_shortcuts_for(window, true); 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 #ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION C(TOPLEVEL_STATE_CONSTRAINED_LEFT); C(TOPLEVEL_STATE_CONSTRAINED_RIGHT); C(TOPLEVEL_STATE_CONSTRAINED_TOP); C(TOPLEVEL_STATE_CONSTRAINED_BOTTOM); #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; 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); // Force CSD when decorations are hidden or titlebar is hidden if (!window->decorated || window->wl.decorations.titlebar_hidden) has_server_side_decorations = false; 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, csd_should_window_be_decorated(window)); 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 = (size_t)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) { if (window->wl.decorations.titlebar_hidden) { window->wl.decorations.serverSide = false; zxdg_toplevel_decoration_v1_set_mode(window->wl.xdg.decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); csd_set_visible(window, csd_should_window_be_decorated(window)); } else { 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, csd_should_window_be_decorated(window)); } } 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->name, name) == 0) return m->wl.output; } return NULL; } static enum zwlr_layer_shell_v1_layer get_layer_shell_layer(const _GLFWwindow *window) { 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: 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; } return which_layer; } static void layer_set_properties(const _GLFWwindow *window, bool during_creation, uint32_t width, uint32_t height) { #define config window->wl.layer_shell.config #define surface window->wl.layer_shell.zwlr_layer_surface_v1 if (!surface) return; // cannot set properties till a surface is created 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 = config.requested_exclusive_zone; enum zwlr_layer_surface_v1_keyboard_interactivity focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; switch(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 (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 (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 = height; if (!config.override_exclusive_zone) exclusive_zone = 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 = height; if (!config.override_exclusive_zone) exclusive_zone = 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 = width; if (!config.override_exclusive_zone) exclusive_zone = 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 = width; if (!config.override_exclusive_zone) exclusive_zone = width; break; case GLFW_EDGE_CENTER: break; case GLFW_EDGE_CENTER_SIZED: which_anchor = 0; panel_width = width; panel_height = height; break; case GLFW_EDGE_NONE: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; panel_width = width; panel_height = height; break; } } zwlr_layer_surface_v1_set_size(surface, panel_width, panel_height); debug("Compositor will be informed that layer size: %dx%d viewport: %dx%d at next surface commit\n", panel_width, panel_height, width, 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, config.requested_top_margin, config.requested_right_margin, config.requested_bottom_margin, config.requested_left_margin); if (!during_creation) zwlr_layer_surface_v1_set_layer(surface, get_layer_shell_layer(window)); zwlr_layer_surface_v1_set_keyboard_interactivity(surface, focus_policy); #undef surface #undef config } static void calculate_layer_size(_GLFWwindow *window, uint32_t *width, uint32_t *height) { const GLFWLayerShellConfig *config = &window->wl.layer_shell.config; GLFWvidmode m = {0}; if (window->wl.monitorsCount) _glfwPlatformGetVideoMode(window->wl.monitors[0], &m); int monitor_width = m.width, monitor_height = m.height; const int y_margin = config->requested_bottom_margin + config->requested_top_margin, x_margin = config->requested_left_margin + config->requested_right_margin; monitor_width = monitor_width > x_margin ? monitor_width - x_margin : 0; monitor_height = monitor_height > y_margin ? monitor_height - y_margin : 0; float xscale = (float)config->expected.xscale, yscale = (float)config->expected.yscale; if (window->wl.window_fully_created) _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); unsigned cell_width, cell_height; double left_edge_spacing, top_edge_spacing, right_edge_spacing, bottom_edge_spacing; config->size_callback((GLFWwindow*)window, xscale, yscale, &cell_width, &cell_height, &left_edge_spacing, &top_edge_spacing, &right_edge_spacing, &bottom_edge_spacing); double spacing_x = left_edge_spacing + right_edge_spacing; double spacing_y = top_edge_spacing + bottom_edge_spacing; if (config->type == GLFW_LAYER_SHELL_BACKGROUND) { if (!*width) *width = monitor_width; if (!*height) *height = monitor_height; return; } const unsigned xsz = config->x_size_in_pixels ? (unsigned)(config->x_size_in_pixels * xscale) : (cell_width * config->x_size_in_cells); const unsigned ysz = config->y_size_in_pixels ? (unsigned)(config->y_size_in_pixels * yscale) : (cell_height * config->y_size_in_cells); debug("Calculating layer shell window size at scale: %f cell_size: %u %u sz: %u %u\n", xscale, cell_width, cell_height, xsz, ysz); if (config->edge == GLFW_EDGE_LEFT || config->edge == GLFW_EDGE_RIGHT) { if (!*height) *height = monitor_height; double spacing = spacing_x; spacing += xsz / 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 = spacing_y; spacing += ysz / yscale; *height = (uint32_t)(1. + spacing); } else if (config->edge == GLFW_EDGE_CENTER) { if (!*width) *width = monitor_width; if (!*height) *height = monitor_height; } else { spacing_x += xsz / xscale; spacing_y += ysz / yscale; *width = (uint32_t)(1. + spacing_x); *height = (uint32_t)(1. + spacing_y); } } 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); } calculate_layer_size(window, &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, false, window->wl.width, window->wl.height); if (window->wl.wp_viewport) wp_viewport_set_destination(window->wl.wp_viewport, window->wl.width, window->wl.height); } 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); #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, get_layer_shell_layer(window), window->wl.appId[0] ? window->wl.appId : "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, true, window->wl.width, window->wl.height); if (window->wl.wp_viewport) wp_viewport_set_destination(window->wl.wp_viewport, window->wl.width, window->wl.height); commit_window_surface(window); wl_display_roundtrip(_glfw.wl.display); window->wl.created = true; #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 (window->wl.appId[0]) xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId); if (window->wl.windowTag[0] && _glfw.wl.xdg_toplevel_tag_manager_v1) xdg_toplevel_tag_manager_v1_set_toplevel_tag(_glfw.wl.xdg_toplevel_tag_manager_v1, window->wl.xdg.toplevel, window->wl.windowTag); 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); window->wl.created = true; 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 handle_key_repeat_events(void) { uint64_t num_events = 0; #ifdef HAS_TIMER_FD if (read(_glfw.wl.eventLoopData.key_repeat_fd, &num_events, sizeof(num_events)) < (ssize_t)sizeof(num_events)) return; #else char buf[16]; while(1) { ssize_t num = read(_glfw.wl.eventLoopData.key_repeat_fds[0], buf, sizeof(buf)); if (num > 0) num_events += num; else if (num == 0 || errno != EINTR) break; } #endif if (_glfw.wl.keyRepeatInfo.keyboardFocusId != _glfw.wl.keyboardFocusId || _glfw.wl.keyboardRepeatRate == 0) return; _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window || !_glfw.wl.keyRepeatInfo.key) return; while (num_events--) glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT); } 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); if (_glfw.wl.eventLoopData.key_repeat_fd_ready) handle_key_repeat_events(); } 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 ////// ////////////////////////////////////////////////////////////////////////// static bool attach_opengl_context_to_window(_GLFWwindow *window, const _GLFWctxconfig *ctxconfig, const _GLFWfbconfig *fbconfig) { 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; } int _glfwPlatformCreateWindow( _GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, const GLFWLayerShellConfig *lsc ) { window->wl.layer_shell.config = lsc ? *lsc : (GLFWLayerShellConfig){0}; 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 (!create_surface(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.visible = false; window->wl.xdg.surface = NULL; window->wl.xdg.toplevel = NULL; window->wl.layer_shell.zwlr_layer_surface_v1 = NULL; } 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) attach_opengl_context_to_window(window, ctxconfig, fbconfig); 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.keyboard_shortcuts_inhibitor) zwp_keyboard_shortcuts_inhibitor_v1_destroy(window->wl.keyboard_shortcuts_inhibitor); 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->wl.ext_background_effect_surface_v1) ext_background_effect_surface_v1_destroy(window->wl.ext_background_effect_surface_v1); 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.layer_shell.zwlr_layer_surface_v1) zwlr_layer_surface_v1_destroy(window->wl.layer_shell.zwlr_layer_surface_v1); 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 (is_layer_shell(window)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot set window icon on layer shell surfaces"); return; } if (!window->wl.xdg.toplevel) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Ignoring attempt to set window icon on window without a toplevel"); return; } 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 += (size_t)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 = (size_t)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 (is_layer_shell(window)) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: Resizing of layer shell surfaces is not supported"); return; } 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, csd_should_window_be_decorated(window)); // 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.titlebar_hidden ? 0 : 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, bool move_to_active_screen UNUSED) { if (!window->wl.visible) { if (!window->wl.created) { create_window_desktop_surface(window); window->wl.visible = true; } else { // workaround for kwin layer shell bug: https://bugs.kde.org/show_bug.cgi?id=503121 if (is_layer_shell(window)) layer_set_properties(window, false, window->wl.width, window->wl.height); window->wl.visible = true; commit_window_surface(window); } debug("Window %llu mapped waiting for configure event from compositor\n", window->id); } } void _glfwPlatformHideWindow(_GLFWwindow* window) { if (!window->wl.visible) return; wl_surface_attach(window->wl.surface, NULL, 0, 0); window->wl.once.surface_configured = false; window->swaps_disallowed = true; window->wl.visible = false; commit_window_surface(window); debug("Window %llu unmapped\n", window->id); } bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { if (!is_layer_shell(window)) return false; if (value) window->wl.layer_shell.config = *value; uint32_t width, height; calculate_layer_size(window, &width, &height); layer_set_properties(window, false, width, height); commit_window_surface(window); return true; } 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 } 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 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, unsigned int 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, }; // Getting data from clipboard and drops {{{ static void destroy_drop_data(_GLFWWaylandDataOffer *offer) { for (size_t i = 0; i < offer->dd_count; i++) { if (offer->requested_drop_data[i].mime) free(offer->requested_drop_data[i].mime); if (offer->requested_drop_data[i].fd > -1) { safe_close(offer->requested_drop_data[i].fd); } if (offer->requested_drop_data[i].watch_id) removeWatch(&_glfw.wl.eventLoopData, offer->requested_drop_data[i].watch_id); } free(offer->requested_drop_data); } 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); } free(offer->copy_mimes); // pointer array only; strings are owned by mimes[] if (offer->requested_drop_data) destroy_drop_data(offer); memset(offer, 0, sizeof(offer[0])); } // Reset the working copy of mimes so the next callback sees the full original // list. Returns false on allocation failure. static bool reset_copy_mimes(_GLFWWaylandDataOffer *offer) { if (offer->mimes_count == 0) { offer->copy_mimes_count = 0; return true; } if (!offer->copy_mimes) { offer->copy_mimes = malloc(offer->mimes_count * sizeof(const char*)); if (!offer->copy_mimes) return false; } memcpy(offer->copy_mimes, offer->mimes, offer->mimes_count * sizeof(const char*)); offer->copy_mimes_count = offer->mimes_count; return true; } static void mark_data_offer(_GLFWWaylandDataOffer *ans, void *id) { if (ans->id) destroy_data_offer(ans); for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; if (offer->id == id) { *ans = *offer; memset(offer, 0, sizeof(offer[0])); break; } } } static void mark_selection_offer(void *data UNUSED, struct wl_data_device *data_device UNUSED, struct wl_data_offer *data_offer) { mark_data_offer(&_glfw.wl.clipboard_data_offer, data_offer); } 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) { mark_data_offer(&_glfw.wl.primary_data_offer, primary_selection_offer); } static void add_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime, bool is_self_offer) { if (is_self_offer) offer->is_self_offer = is_self_offer; if (strcmp(mime, clipboard_mime()) == 0) { offer->is_self_offer = true; } if (!offer->mimes || offer->mimes_count + 1 >= offer->mimes_capacity) { 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 _GLFWWaylandDataOffer* data_offer_for_id(void *id) { for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; if (offer->id == id) return offer; } if (_glfw.wl.clipboard_data_offer.id == id) return &_glfw.wl.clipboard_data_offer; if (_glfw.wl.primary_data_offer.id == id) return &_glfw.wl.primary_data_offer; if (_glfw.wl.drop_data_offer.id == id) return &_glfw.wl.drop_data_offer; return NULL; } static void add_generic_offer_mimetype(void *id, const char *mime, bool is_self_offer) { _GLFWWaylandDataOffer *offer = data_offer_for_id(id); if (offer) add_offer_mimetype(offer, mime, is_self_offer); } static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) { add_generic_offer_mimetype(id, mime, strcmp(mime, clipboard_mime()) == 0); } static void handle_primary_selection_offer_mimetype(void *data UNUSED, struct zwp_primary_selection_offer_v1* id, const char *mime) { add_generic_offer_mimetype(id, mime, strcmp(mime, clipboard_mime()) == 0); } static void data_offer_source_actions(void *data UNUSED, struct wl_data_offer* id, uint32_t actions) { _GLFWWaylandDataOffer *offer = data_offer_for_id(id); if (offer) offer->source_actions = actions; } static void data_offer_action(void *data UNUSED, struct wl_data_offer* id, uint32_t action) { _GLFWWaylandDataOffer *offer = data_offer_for_id(id); if (offer) offer->dnd_action = action; } 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 void handle_data_offer_generic(void *id, bool is_primary) { for (size_t i = 0; i < arraysz(_glfw.wl.untyped_data_offers); i++) { _GLFWWaylandDataOffer *offer = _glfw.wl.untyped_data_offers + i; if (offer->id == NULL) { offer->id = id; offer->is_primary = is_primary; return; } } if (is_primary) zwp_primary_selection_offer_v1_destroy(id); else wl_data_offer_destroy(id); _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: too many untyped data offers"); } 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); } // Helper function to update drop state from callback results static void update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t accepted_count) { d->copy_mimes_count = accepted_count; bool accepted = accepted_count > 0; bool acceptance_changed = (accepted != d->drag_accepted); // The first entry in the accepted (sorted) copy is the preferred MIME. const char* new_preferred_mime = (accepted && d->copy_mimes) ? d->copy_mimes[0] : NULL; bool mime_changed = false; // Check if the preferred MIME changed if (d->mime_for_drop == NULL && new_preferred_mime != NULL) { mime_changed = true; } else if (d->mime_for_drop != NULL && new_preferred_mime == NULL) { mime_changed = true; } else if (d->mime_for_drop != NULL && new_preferred_mime != NULL) { mime_changed = (strcmp(d->mime_for_drop, new_preferred_mime) != 0); } if (acceptance_changed || mime_changed) { d->drag_accepted = accepted; d->mime_for_drop = new_preferred_mime; wl_data_offer_accept(d->id, d->serial, d->mime_for_drop); } } 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, wl_fixed_t y, struct wl_data_offer *id) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; mark_data_offer(offer, id); if (!offer->id) return; offer->is_self_offer = (_glfw.drag.window_id != 0); offer->surface = surface; offer->serial = serial; offer->drag_accepted = false; offer->mime_for_drop = NULL; _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == surface) { double xpos = wl_fixed_to_double(x); double ypos = wl_fixed_to_double(y); if (reset_copy_mimes(offer)) { size_t mime_count = _glfwInputDropEvent( window, GLFW_DROP_ENTER, xpos, ypos, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer); update_drop_state(offer, window, mime_count); } break; } window = window->next; } } static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (offer->id) { _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == _glfw.wl.drop_data_offer.surface) { _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, NULL, 0, offer->is_self_offer); break; } window = window->next; } if (!offer->dropped) destroy_data_offer(offer); } } void _glfwPlatformEndDrop(GLFWwindow *w UNUSED, GLFWDragOperationType op UNUSED) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (offer->id) destroy_data_offer(offer); } ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz) { _GLFWwindow *window = (_GLFWwindow*)w; _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (!offer->id || offer->surface != window->wl.surface) return -ENOENT; int fd = (int)ev->xpos; for (size_t o = 0; o < offer->dd_count; o++) { if (offer->requested_drop_data[o].fd == fd) { ssize_t ret; do { ret = read(fd, buffer, sz); } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); if (ret <= 0) removeWatch(&_glfw.wl.eventLoopData, offer->requested_drop_data[o].watch_id); return ret < 0 ? -errno : ret; } } return -ENOENT; } static void drop_data_available(int fd, int events UNUSED, void *data UNUSED) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (!offer->id) return; for (size_t o = 0; o < offer->dd_count; o++) { if (offer->requested_drop_data[o].fd == fd) { _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == offer->surface) { const char *mimes[1] = {offer->requested_drop_data[o].mime}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, fd, 0, mimes, 1, offer->is_self_offer); return; } window = window->next; } destroy_data_offer(offer); } } } static int request_drop_data(_GLFWWaylandDataOffer *offer, const char *mime) { int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) != 0) return errno; wl_data_offer_receive(offer->id, mime, pipefd[1]); safe_close(pipefd[1]); // Flush to ensure the compositor processes the receive request wl_display_flush(_glfw.wl.display); id_type watch_id = addWatch(&_glfw.wl.eventLoopData, "drop_data", pipefd[0], POLLIN | POLLERR | POLLHUP, true, drop_data_available, NULL); if (!watch_id) { safe_close(pipefd[0]); return ERANGE; } char *mt = _glfw_strdup(mime); if (!mt) { safe_close(pipefd[0]); removeWatch(&_glfw.wl.eventLoopData, watch_id); return ENOMEM; } if (!offer->requested_drop_data || offer->dd_count + 1 >= offer->dd_capacity) { void *p = realloc(offer->requested_drop_data, sizeof(offer->requested_drop_data[0]) * (offer->dd_capacity + 8)); if (!p) { safe_close(pipefd[0]); removeWatch(&_glfw.wl.eventLoopData, watch_id); free(mt); return ENOMEM; } offer->requested_drop_data = p; offer->dd_capacity += 64; } offer->requested_drop_data[offer->dd_count].mime = mt; offer->requested_drop_data[offer->dd_count].watch_id = watch_id; offer->requested_drop_data[offer->dd_count].fd = pipefd[0]; offer->dd_count++; return 0; } int _glfwPlatformRequestDropData(_GLFWwindow *window UNUSED, const char *mime) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (offer->id) return request_drop_data(offer, mime); return EINVAL; } static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (!offer->id) return; offer->dropped = true; _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == offer->surface) { if (reset_copy_mimes(offer)) { size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer); if (!offer->copy_mimes) { destroy_data_offer(offer); return; } for (size_t i = 0; i < num_accepted; i++) request_drop_data(offer, offer->copy_mimes[i]); } break; } window = window->next; } } static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t time UNUSED, wl_fixed_t x, wl_fixed_t y) { _GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer; if (!offer->id) return; _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == offer->surface) { double xpos = wl_fixed_to_double(x); double ypos = wl_fixed_to_double(y); if (reset_copy_mimes(offer)) { size_t mime_count = _glfwInputDropEvent( window, GLFW_DROP_MOVE, xpos, ypos, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer); update_drop_state(offer, window, mime_count); } break; } window = window->next; } } void _glfwPlatformRequestDropUpdate(_GLFWwindow* window) { _GLFWWaylandDataOffer *d = &_glfw.wl.drop_data_offer; if (d->id && reset_copy_mimes(d)) { size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, d->copy_mimes, d->copy_mimes_count, d->is_self_offer); update_drop_state(d, window, mime_count); } } 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) { _GLFWWaylandDataOffer *d = clipboard_type == GLFW_PRIMARY_SELECTION ? &_glfw.wl.primary_data_offer : &_glfw.wl.clipboard_data_offer; if (!d->id) return; 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); } } 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; } bool _glfwPlatformGrabKeyboard(bool grab) { if (!_glfw.wl.keyboard_shortcuts_inhibit_manager) { _glfwInputError(GLFW_PLATFORM_ERROR, "The Wayland compositor does not implement inhibit-keyboard-shortcuts, cannot grab keyboard"); return false; } for (_GLFWwindow* window = _glfw.windowListHead; window; window = window->next) inhibit_shortcuts_for(window, grab); return true; } ////////////////////////////////////////////////////////////////////////// ////// 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; assert(window != NULL); _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 glfwWaylandSetTitlebarHidden(GLFWwindow *handle, bool hidden) { _GLFWwindow* window = (_GLFWwindow*) handle; if (window->wl.decorations.titlebar_hidden != hidden) { window->wl.decorations.titlebar_hidden = hidden; setXdgDecorations(window); inform_compositor_of_window_geometry(window, "SetTitlebarHidden"); commit_window_surface_if_safe(window); } } const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { return &window->wl.layer_shell.config; } GLFWAPI bool glfwIsLayerShellSupported(void) { return _glfw.wl.zwlr_layer_shell_v1 != NULL; } 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; } } GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) { if (!_glfw.wl.xdg_system_bell_v1) return false; _GLFWwindow *window = (_GLFWwindow*)handle; xdg_system_bell_v1_ring(_glfw.wl.xdg_system_bell_v1, window ? window->wl.surface : NULL); return true; } // Drag source {{{ static void drag_toplevel_xdg_surface_configure(void *data UNUSED, struct xdg_surface *surface, uint32_t serial) { xdg_surface_ack_configure(surface, serial); if (_glfw.wl.drag.toplevel_buffer) { wl_surface_attach(_glfw.wl.drag.drag_icon, _glfw.wl.drag.toplevel_buffer, 0, 0); wl_surface_damage(_glfw.wl.drag.drag_icon, 0, 0, INT32_MAX, INT32_MAX); wl_buffer_destroy(_glfw.wl.drag.toplevel_buffer); _glfw.wl.drag.toplevel_buffer = NULL; } if (_glfw.wl.drag.drag_icon) wl_surface_commit(_glfw.wl.drag.drag_icon); } static const struct xdg_surface_listener drag_toplevel_xdg_surface_listener = { .configure = drag_toplevel_xdg_surface_configure, }; static void drag_toplevel_configure(void *data UNUSED, struct xdg_toplevel *toplevel UNUSED, int32_t width UNUSED, int32_t height UNUSED, struct wl_array *states UNUSED) {} static void drag_toplevel_close(void *data UNUSED, struct xdg_toplevel *toplevel UNUSED) {} #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION static void drag_toplevel_configure_bounds(void *data UNUSED, struct xdg_toplevel *toplevel UNUSED, int32_t width UNUSED, int32_t height UNUSED) {} static void drag_toplevel_wm_capabilities(void *data UNUSED, struct xdg_toplevel *toplevel UNUSED, struct wl_array *caps UNUSED) {} #endif static const struct xdg_toplevel_listener drag_toplevel_listener = { .configure = drag_toplevel_configure, .close = drag_toplevel_close, #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION .configure_bounds = drag_toplevel_configure_bounds, .wm_capabilities = drag_toplevel_wm_capabilities, #endif }; static void cancel_drag(GLFWDragEventType type) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type=type}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); } #define dr _glfw.wl.drag.data_requests[i] static void finish_drag_write(size_t i) { if (dr.watch_id) removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; if (dr.fd > -1) safe_close(dr.fd); dr.fd = -1; free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0; free((void*)dr.mime_type); dr.mime_type = NULL; } static ssize_t write_as_much_as_possible(int fd, const char *data, size_t sz) { size_t ans = 0; while (ans < sz) { ssize_t ret = write(fd, data + ans, sz - ans); if (ret < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) return ans; return ret; } ans += ret; } return ans; } static void send_drag_data(_GLFWwindow *window, size_t i) { ssize_t ret; // Find the item matching the mime type for this data request. // We cannot use i directly to index _glfw.drag.items because the compositor // may call drag_source_send multiple times (once per target entered), making // data_requests grow independently of the items array. size_t item_idx = _glfw.drag.item_count; // sentinel: not found for (size_t j = 0; j < _glfw.drag.item_count; j++) { if (dr.mime_type && _glfw.drag.items[j].mime_type && strcmp(_glfw.drag.items[j].mime_type, dr.mime_type) == 0) { item_idx = j; break; } } if (item_idx == _glfw.drag.item_count) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: compositor requested data for unrecognised MIME type: %s", dr.mime_type ? dr.mime_type : "(null)"); } bool has_preset_data = item_idx < _glfw.drag.item_count && _glfw.drag.items[item_idx].data_size > 0; // On write error, only close this pipe; do NOT destroy the wl_data_source // since the compositor must send cancelled/dnd_finished before that is safe. #define on_fail _glfwInputError(\ GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \ finish_drag_write(i); return; if (dr.sz > dr.offset) { ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset); if (ret < 0) { on_fail; } else { dr.offset += ret; if (dr.offset >= dr.sz) { free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0; finish_drag_write(i); } } } else if (has_preset_data) { do { ret = write(dr.fd, _glfw.drag.items[item_idx].optional_data, _glfw.drag.items[item_idx].data_size); } while (ret < 0 && errno == EINTR); if (ret < 0) { on_fail; } else { if ((size_t)ret >= _glfw.drag.items[item_idx].data_size) { finish_drag_write(i); } else { void *pending = malloc(_glfw.drag.items[item_idx].data_size - ret); if (!pending) { errno = ENOMEM; on_fail; } else { dr.pending_data = pending; dr.sz = _glfw.drag.items[item_idx].data_size - ret; dr.offset = 0; } } } } else { GLFWDragEvent ev = {.type=GLFW_DRAG_DATA_REQUEST, .mime_type=dr.mime_type}; _glfwInputDragSourceRequest(window, &ev); if (ev.err_num) { if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; } else { finish_drag_write(i); return; } } else { if (ev.data_sz) { ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz); if (ret >= 0 && (size_t)ret < ev.data_sz) { void *pending = malloc(ev.data_sz - ret); if (!pending) { ret = -1; } else { dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0; memcpy(pending, ev.data + ret, dr.sz); } } _glfwInputDragSourceRequest(window, &ev); if (ret < 0) { on_fail; } else if ((size_t)ret >= ev.data_sz) { finish_drag_write(i); } } else finish_drag_write(i); } } #undef on_fail } static void ready_for_drag_data(int fd, int events UNUSED, void *data UNUSED) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (!window) { _glfwFreeDragSourceData(); return; } for (size_t i = 0; i < _glfw.wl.drag.count; i++) { if (dr.fd == fd) { send_drag_data(window, i); break; } } } static GLFWid add_drag_watch(int fd) { return addWatch( &_glfw.wl.eventLoopData, "drag_source", fd, POLLOUT | POLLERR | POLLHUP, true, ready_for_drag_data, NULL); } int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { if (!thumbnail || !thumbnail->pixels) return 0; struct wl_buffer* icon_buffer = createShmBuffer(thumbnail, false, true); if (!icon_buffer) return ENOMEM; _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (_glfw.wl.drag.drag_viewport) { double f_scale = window ? _glfwWaylandWindowScale(window) : 1.0; int logical_width = (int)(thumbnail->width / f_scale); int logical_height = (int)(thumbnail->height / f_scale); wp_viewport_set_destination(_glfw.wl.drag.drag_viewport, logical_width, logical_height); } else { int scale = window ? _glfwWaylandIntegerWindowScale(window) : 1; wl_surface_set_buffer_scale(_glfw.wl.drag.drag_icon, scale); } wl_surface_attach(_glfw.wl.drag.drag_icon, icon_buffer, 0, 0); wl_surface_damage(_glfw.wl.drag.drag_icon, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(_glfw.wl.drag.drag_icon); wl_buffer_destroy(icon_buffer); return 0; } int _glfwPlatformDragDataReady(const char *mime_type) { for (size_t i = 0; i < _glfw.wl.drag.count; i++) { if (strcmp(dr.mime_type, mime_type) == 0) { if (!dr.watch_id) dr.watch_id = add_drag_watch(dr.fd); } } return 0; } static void drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type, int fd) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); #define abort() safe_close(fd); cancel_drag(GLFW_DRAG_CANCELLED); return if (!window) { abort(); } mime_type = _glfw_strdup(mime_type); if (!mime_type) { abort(); } if (!_glfw.wl.drag.data_requests || _glfw.wl.drag.capacity <= _glfw.wl.drag.count + 1) { _glfw.wl.drag.capacity = MAX(8u, _glfw.wl.drag.capacity * 2); if (!(_glfw.wl.drag.data_requests = realloc(_glfw.wl.drag.data_requests, sizeof(_glfw.wl.drag.data_requests[0]) * _glfw.wl.drag.capacity))) { abort(); } } const size_t i = _glfw.wl.drag.count++; memset(&dr, 0, sizeof(_glfw.wl.drag.data_requests[0])); dr.mime_type = mime_type; dr.fd = fd; dr.watch_id = add_drag_watch(fd); #undef abort } #undef dr static void drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type=GLFW_DRAG_ACCEPTED, .mime_type=mime_type}; _glfwInputDragSourceRequest(window, &ev); } else cancel_drag(GLFW_DRAG_CANCELLED); } static void drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragOperationType op = GLFW_DRAG_OPERATION_GENERIC; switch (dnd_action) { case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: op = 0; break; case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: op = GLFW_DRAG_OPERATION_COPY; break; case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: op = GLFW_DRAG_OPERATION_MOVE; break; } _glfw.wl.drag.action = op; GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED, .action=op}; _glfwInputDragSourceRequest(window, &ev); } else cancel_drag(GLFW_DRAG_CANCELLED); } static void drag_source_dnd_drop_performed(void *data UNUSED, struct wl_data_source *source UNUSED) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type=GLFW_DRAG_DROPPED}; _glfwInputDragSourceRequest(window, &ev); } else cancel_drag(GLFW_DRAG_CANCELLED); } static void drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source UNUSED) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type=GLFW_DRAG_FINSHED, .action=_glfw.wl.drag.action}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); } static void drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) { // Uber competent Wayland people contravene their own spec and make it // impossible to distinguish between drag cancelled and dropped but not // accepted. https://gitlab.freedesktop.org/wayland/wayland/-/issues/140 // so we assume this is a drop unless we are in top-level mode. Sigh. cancel_drag(_glfw.wl.drag.toplevel_xdg_toplevel ? GLFW_DRAG_CANCELLED : GLFW_DRAG_DROPPED); } static const struct wl_data_source_listener drag_source_listener = { .send = drag_source_send, .cancelled = drag_source_cancelled, .target = drag_source_target, .action = drag_source_action, .dnd_drop_performed = drag_source_dnd_drop_performed, .dnd_finished = drag_source_dnd_finished, }; void _glfwPlatformFreeDragSourceData(void) { if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport); if (_glfw.wl.drag.toplevel_drag) xdg_toplevel_drag_v1_destroy(_glfw.wl.drag.toplevel_drag); if (_glfw.wl.drag.toplevel_buffer) wl_buffer_destroy(_glfw.wl.drag.toplevel_buffer); if (_glfw.wl.drag.toplevel_xdg_toplevel) xdg_toplevel_destroy(_glfw.wl.drag.toplevel_xdg_toplevel); if (_glfw.wl.drag.toplevel_xdg_surface) xdg_surface_destroy(_glfw.wl.drag.toplevel_xdg_surface); if (_glfw.wl.drag.drag_icon) wl_surface_destroy(_glfw.wl.drag.drag_icon); if (_glfw.wl.drag.source) wl_data_source_destroy(_glfw.wl.drag.source); if (_glfw.wl.drag.data_requests) { for (size_t i = 0; i < _glfw.wl.drag.count; i++) { free((void*)_glfw.wl.drag.data_requests[i].mime_type); free(_glfw.wl.drag.data_requests[i].pending_data); if (_glfw.wl.drag.data_requests[i].watch_id) removeWatch(&_glfw.wl.eventLoopData, _glfw.wl.drag.data_requests[i].watch_id); if (_glfw.wl.drag.data_requests[i].fd > -1) { safe_close(_glfw.wl.drag.data_requests[i].fd); } } free(_glfw.wl.drag.data_requests); } memset(&_glfw.wl.drag, 0, sizeof(_glfw.wl.drag)); } int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) { if (!_glfw.wl.dataDeviceManager) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device manager not available"); return ENOTSUP; } if (!_glfw.wl.dataDevice) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Data device not available"); return ENOTSUP; } // Create the data source _glfw.wl.drag.source = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager); if (!_glfw.wl.drag.source) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to create data source for drag"); return ENOMEM; } // Set the DND action based on operation type (bitfield) uint32_t wl_actions = 0; int operations = _glfw.drag.operations; if (operations & GLFW_DRAG_OPERATION_COPY) wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; if (operations & GLFW_DRAG_OPERATION_MOVE) wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; if (operations & GLFW_DRAG_OPERATION_GENERIC) wl_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; wl_data_source_set_actions(_glfw.wl.drag.source, wl_actions); for (size_t i = 0; i < _glfw.drag.item_count; i++) wl_data_source_offer(_glfw.wl.drag.source, _glfw.drag.items[i].mime_type); wl_data_source_add_listener(_glfw.wl.drag.source, &drag_source_listener, NULL); // Set up the drag icon surface if thumbnail is provided if (thumbnail && thumbnail->pixels) { _glfw.wl.drag.drag_icon = wl_compositor_create_surface(_glfw.wl.compositor); if (!_glfw.wl.drag.drag_icon) return ENOMEM; struct wl_buffer* icon_buffer = NULL; icon_buffer = createShmBuffer(thumbnail, false, true); if (!icon_buffer) return ENOMEM; if (_glfw.wl.wp_viewporter) { double f_scale = _glfwWaylandWindowScale(window); int logical_width = (int)(thumbnail->width / f_scale); int logical_height = (int)(thumbnail->height / f_scale); _glfw.wl.drag.drag_viewport = wp_viewporter_get_viewport( _glfw.wl.wp_viewporter, _glfw.wl.drag.drag_icon); wp_viewport_set_destination(_glfw.wl.drag.drag_viewport, logical_width, logical_height); } else { int scale = _glfwWaylandIntegerWindowScale(window); wl_surface_set_buffer_scale(_glfw.wl.drag.drag_icon, scale); } if (_glfw.drag.needs_toplevel_on_wayland && _glfw.wl.xdg_toplevel_drag_manager_v1) { _glfw.wl.drag.toplevel_drag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag( _glfw.wl.xdg_toplevel_drag_manager_v1, _glfw.wl.drag.source); if (!_glfw.wl.drag.toplevel_drag) return ENOMEM; _glfw.wl.drag.toplevel_xdg_surface = xdg_wm_base_get_xdg_surface( _glfw.wl.wmBase, _glfw.wl.drag.drag_icon); if (!_glfw.wl.drag.toplevel_xdg_surface) return ENOMEM; xdg_surface_add_listener(_glfw.wl.drag.toplevel_xdg_surface, &drag_toplevel_xdg_surface_listener, NULL); _glfw.wl.drag.toplevel_xdg_toplevel = xdg_surface_get_toplevel( _glfw.wl.drag.toplevel_xdg_surface); if (!_glfw.wl.drag.toplevel_xdg_toplevel) return ENOMEM; xdg_toplevel_add_listener(_glfw.wl.drag.toplevel_xdg_toplevel, &drag_toplevel_listener, NULL); _glfw.wl.drag.toplevel_buffer = icon_buffer; icon_buffer = NULL; xdg_toplevel_drag_v1_attach(_glfw.wl.drag.toplevel_drag, _glfw.wl.drag.toplevel_xdg_toplevel, 0, 0); } else wl_surface_attach(_glfw.wl.drag.drag_icon, icon_buffer, 0, 0); wl_surface_commit(_glfw.wl.drag.drag_icon); if (icon_buffer) wl_buffer_destroy(icon_buffer); } // Start the drag operation wl_data_device_start_drag( _glfw.wl.dataDevice, _glfw.wl.drag.source, window->wl.surface, _glfw.wl.xdg_toplevel_drag_manager_v1 ? NULL : _glfw.wl.drag.drag_icon, _glfw.wl.pointer_serial); return 0; } // }}} ================================================ FILE: glfw/wlr-layer-shell-unstable-v1.xml ================================================ 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. ================================================ FILE: glfw/x11_init.c ================================================ //======================================================================== // 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 Atom atom) { 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 #define ALL_ATOMS \ S(NET_WM_STATE) S(NET_WM_STATE_ABOVE) S(NET_WM_STATE_BELOW) S(NET_WM_STATE_FULLSCREEN) \ S(NET_WM_STATE_MAXIMIZED_VERT) S(NET_WM_STATE_MAXIMIZED_HORZ) S(NET_WM_STATE_DEMANDS_ATTENTION) \ S(NET_WM_STATE_SKIP_TASKBAR) S(NET_WM_STATE_SKIP_PAGER) S(NET_WM_STATE_STICKY) \ \ S(NET_WM_FULLSCREEN_MONITORS) S(NET_WM_STRUT_PARTIAL) \ \ S(NET_WM_WINDOW_TYPE) S(NET_WM_WINDOW_TYPE_NORMAL) S(NET_WM_WINDOW_TYPE_DOCK) S(NET_WM_WINDOW_TYPE_DESKTOP) \ S(NET_WM_WINDOW_TYPE_UTILITY) S(NET_WM_WINDOW_TYPE_SPLASH) S(NET_WM_WINDOW_TYPE_DIALOG) S(NET_WM_WINDOW_TYPE_MENU) \ S(NET_WM_WINDOW_TYPE_NOTIFICATION) \ \ S(NET_WORKAREA) S(NET_CURRENT_DESKTOP) S(NET_ACTIVE_WINDOW) S(NET_FRAME_EXTENTS) S(NET_REQUEST_FRAME_EXTENTS) \ \ S(NET_WM_ALLOWED_ACTIONS) S(NET_WM_ACTION_MOVE) S(NET_WM_ACTION_RESIZE) S(NET_WM_ACTION_MINIMIZE) \ S(NET_WM_ACTION_SHADE) S(NET_WM_ACTION_STICK) S(NET_WM_ACTION_MAXIMIZE_HORZ) S(NET_WM_ACTION_MAXIMIZE_VERT) \ S(NET_WM_ACTION_FULLSCREEN) S(NET_WM_ACTION_CHANGE_DESKTOP) S(NET_WM_ACTION_CLOSE) S(NET_WM_ACTION_ABOVE) \ S(NET_WM_ACTION_BELOW) S(NET_WM_ACTION_ABOVE_BELOW) static const char* atom_names[40] = { #define S(x) "_" #x, ALL_ATOMS }; #undef S Atom atoms[arraysz(atom_names)]; XInternAtoms(_glfw.x11.display, (char**)atom_names, arraysz(atom_names), False, atoms); unsigned i = 0; #define S(name) _glfw.x11.name = getAtomIfSupported(supportedAtoms, atomCount, atoms[i++]); ALL_ATOMS #undef S #undef ALL_ATOMS XFree(supportedAtoms); } void read_xi_scroll_devices(void) { #define xi _glfw.x11.xi xi.num_scroll_devices = 0; // Require XI2.1 or later for smooth scrolling if (!xi.available || xi.major < 2 || (xi.major == 2 && xi.minor < 1) || !xi.LIBINPUT_SCROLL_METHOD_ENABLED) return; #undef xi int deviceCount; XIDeviceInfo* devices = XIQueryDevice(_glfw.x11.display, XIAllDevices, &deviceCount); if (!devices) return; for (int i = 0; i < deviceCount; i++) { XIDeviceInfo* device = &devices[i]; if (device->use == XIMasterPointer) _glfw.x11.xi.master_pointer_id = device->deviceid; if (device->use != XISlavePointer || !device->enabled) continue; Atom actual_type; int actual_format; unsigned long nitems, bytes_after; unsigned char *data = NULL; bool is_highres = false; XIGetProperty(_glfw.x11.display, device->deviceid, _glfw.x11.xi.LIBINPUT_SCROLL_METHOD_ENABLED, 0, 2, False, XA_INTEGER, &actual_type, &actual_format, &nitems, &bytes_after, &data); if (data) { if (nitems > 1) is_highres = data[0] || data[1]; XFree(data); } // Detect if this is a finger-based device (touchpad/touchscreen) bool is_finger_based = false; // Method 1: Check for libinput tapping support (touchpads only) if (_glfw.x11.xi.LIBINPUT_TAPPING != None) { Atom tapping_type; int tapping_format; unsigned long tapping_nitems, tapping_bytes; unsigned char *tapping_data = NULL; if (XIGetProperty(_glfw.x11.display, device->deviceid, _glfw.x11.xi.LIBINPUT_TAPPING, 0, 1, False, AnyPropertyType, &tapping_type, &tapping_format, &tapping_nitems, &tapping_bytes, &tapping_data) == Success) { if (tapping_data) { if (tapping_nitems > 0) is_finger_based = true; XFree(tapping_data); } } } // Method 2: Check for touch class (touchscreens/touchpads) if (!is_finger_based) { for (int j = 0; j < device->num_classes; j++) { if (device->classes[j]->type == XITouchClass) { is_finger_based = true; break; } } } for (int j = 0; j < device->num_classes; j++) { if (device->classes[j]->type != XIScrollClass) continue; XIScrollClassInfo* scroll = (XIScrollClassInfo*)device->classes[j]; XIScrollDevice *d = NULL; for (unsigned k = 0; k < _glfw.x11.xi.num_scroll_devices; k++) { XIScrollDevice *t = _glfw.x11.xi.scroll_devices + k; if (t->deviceid == device->deviceid && t->sourceid == scroll->sourceid) { d = t; break; } } if (!d) { if (_glfw.x11.xi.num_scroll_devices >= arraysz(_glfw.x11.xi.scroll_devices)) continue; d = &_glfw.x11.xi.scroll_devices[_glfw.x11.xi.num_scroll_devices++]; *d = (XIScrollDevice){ .is_finger_based=is_finger_based, .deviceid=device->deviceid, .sourceid=scroll->sourceid, }; if (is_highres) { d->type_detected = true; d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES; } memcpy(d->name, device->name, MIN(sizeof(d->name)-1, strlen(device->name))); } if (d->num_valuators >= arraysz(d->valuators)) continue; XIScrollValuator *v = d->valuators + d->num_valuators++; *v = (XIScrollValuator){ .number=scroll->number, .is_vertical=scroll->scroll_type == XIScrollTypeVertical, .increment=scroll->increment, }; } for (int j = 0; j < device->num_classes; j++) { if (device->classes[j]->type != XIValuatorClass) continue; XIValuatorClassInfo* vi = (XIValuatorClassInfo*)device->classes[j]; XIScrollDevice *d = NULL; for (unsigned k = 0; k < _glfw.x11.xi.num_scroll_devices; k++) { XIScrollDevice *t = _glfw.x11.xi.scroll_devices + k; if (t->deviceid == device->deviceid && t->sourceid == vi->sourceid) { d = t; break; } } if (!d) continue; XIScrollValuator *v = NULL; for (unsigned k = 0; k < d->num_valuators; k++) { XIScrollValuator *t = d->valuators + k; if (t->number == vi->number) { v = t; break; } } if (!v) continue; v->value = vi->value; v->mode = vi->mode; v->resolution = vi->resolution; v->min = vi->min; v->max = vi->max; v->initialized = true; } } XIFreeDeviceInfo(devices); } // 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"); glfw_dlsym(_glfw.x11.xi.QueryDevice, _glfw.x11.xi.handle, "XIQueryDevice"); glfw_dlsym(_glfw.x11.xi.FreeDeviceInfo, _glfw.x11.xi.handle, "XIFreeDeviceInfo"); glfw_dlsym(_glfw.x11.xi.GetProperty, _glfw.x11.xi.handle, "XIGetProperty"); 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 = 1; 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.XdndActionMove = XInternAtom(_glfw.x11.display, "XdndActionMove", False); _glfw.x11.XdndActionLink = XInternAtom(_glfw.x11.display, "XdndActionLink", 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); _glfw.x11.XdndLeave = XInternAtom(_glfw.x11.display, "XdndLeave", False); _glfw.x11.XdndProxy = XInternAtom(_glfw.x11.display, "XdndProxy", 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); _glfw.x11.xi.LIBINPUT_SCROLL_METHOD_ENABLED = XInternAtom(_glfw.x11.display, "libinput Scroll Method Enabled", False); _glfw.x11.xi.LIBINPUT_TAPPING = XInternAtom(_glfw.x11.display, "libinput Tapping Enabled", False); read_xi_scroll_devices(); // Select XI_HierarchyChanged events to detect device add/remove if (_glfw.x11.xi.available) { XIEventMask em; unsigned char mask[XIMaskLen(XI_HierarchyChanged)] = { 0 }; em.deviceid = XIAllDevices; em.mask_len = sizeof(mask); em.mask = mask; XISetMask(mask, XI_HierarchyChanged); XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1); } // 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); } free_dnd_data(); 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" ================================================ FILE: glfw/x11_monitor.c ================================================ //======================================================================== // 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 #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; } MonitorGeometry _glfwPlatformGetMonitorGeometry(_GLFWmonitor *monitor) { MonitorGeometry ans = {0}; if (!monitor) return ans; 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); ans.full.x = ci->x; ans.full.y = ci->y; const XRRModeInfo* mi = getModeInfo(sr, ci->mode); if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { ans.full.width = mi->height; ans.full.height = mi->width; } else { ans.full.width = mi->width; ans.full.height = mi->height; } XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } else { ans.full.width = DisplayWidth(_glfw.x11.display, _glfw.x11.screen); ans.full.height = DisplayHeight(_glfw.x11.display, _glfw.x11.screen); } ans.workarea = *(&ans.full); 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 (ans.workarea.x < globalX) { ans.workarea.width -= globalX - ans.workarea.x; ans.workarea.x = globalX; } if (ans.workarea.y < globalY) { ans.workarea.height -= globalY - ans.workarea.y; ans.workarea.y = globalY; } if (ans.workarea.x + ans.workarea.width > globalX + globalWidth) ans.workarea.width = globalX - ans.workarea.x + globalWidth; if (ans.workarea.y + ans.workarea.height > globalY + globalHeight) ans.workarea.height = globalY - ans.workarea.y + globalHeight; } } if (extents) XFree(extents); if (desktop) XFree(desktop); } return ans; } void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height) { MonitorGeometry ans = _glfwPlatformGetMonitorGeometry(monitor); if (xpos) *xpos = ans.workarea.x; if (ypos) *ypos = ans.workarea.y; if (width) *width = ans.workarea.width; if (height) *height = ans.workarea.height; } 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; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(None); return monitor->x11.crtc; } GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(None); return monitor->x11.output; } ================================================ FILE: glfw/x11_platform.h ================================================ //======================================================================== // 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); typedef XIDeviceInfo* (* PFN_XIQueryDevice)(Display*,int,int*); typedef void (* PFN_XIFreeDeviceInfo)(XIDeviceInfo*); typedef Status (* PFN_XIGetProperty)(Display *dpy, int deviceid, Atom property, long offset, long length, Bool delete_property, Atom type, Atom *type_return, int *format_return, unsigned long *num_items_return, unsigned long *bytes_after_return, unsigned char **data); #define XIQueryVersion _glfw.x11.xi.QueryVersion #define XISelectEvents _glfw.x11.xi.SelectEvents #define XIQueryDevice _glfw.x11.xi.QueryDevice #define XIFreeDeviceInfo _glfw.x11.xi.FreeDeviceInfo #define XIGetProperty _glfw.x11.xi.GetProperty 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; // XI2 smooth scrolling - track valuator values per window struct { double verticalValue; double horizontalValue; } smoothScroll; struct { bool is_active; GLFWLayerShellConfig config; } layer_shell; } _GLFWwindowX11; typedef struct MimeAtom { Atom atom; const char* mime; } MimeAtom; typedef struct AtomArray { MimeAtom *array; size_t sz, capacity; } AtomArray; typedef struct XIScrollValuator { double increment, value, min, max; int number, resolution, mode; bool is_vertical, initialized; } XIScrollValuator; typedef struct XIScrollDevice { bool is_finger_based; bool type_detected; int deviceid, sourceid; XIScrollValuator valuators[8]; unsigned num_valuators; char name[32]; unsigned num_events; GLFWOffsetType offset_type; } XIScrollDevice; typedef struct XdndSelectionRequest { char *mime; bool inflight, got_data; unsigned char *data; size_t offset, size; } XdndSelectionRequest; // 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_ALLOWED_ACTIONS, NET_WM_ACTION_MOVE, NET_WM_ACTION_RESIZE, NET_WM_ACTION_MINIMIZE, NET_WM_ACTION_SHADE, NET_WM_ACTION_STICK, NET_WM_ACTION_MAXIMIZE_HORZ, NET_WM_ACTION_MAXIMIZE_VERT, NET_WM_ACTION_FULLSCREEN, NET_WM_ACTION_CHANGE_DESKTOP, NET_WM_ACTION_CLOSE, NET_WM_ACTION_ABOVE, NET_WM_ACTION_BELOW, NET_WM_ACTION_ABOVE_BELOW; Atom NET_WM_ICON_NAME; Atom NET_WM_ICON; Atom NET_WM_PID; Atom NET_WM_PING; Atom NET_WM_WINDOW_TYPE, NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_DOCK, NET_WM_WINDOW_TYPE_DESKTOP, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_NOTIFICATION; Atom NET_WM_STATE; Atom NET_WM_STATE_ABOVE; Atom NET_WM_STATE_BELOW; 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_STATE_SKIP_TASKBAR; Atom NET_WM_STATE_SKIP_PAGER; Atom NET_WM_STATE_STICKY; 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 XdndActionMove; Atom XdndActionLink; Atom XdndDrop; Atom XdndFinished; Atom XdndSelection; Atom XdndTypeList; Atom XdndLeave; Atom XdndProxy; // 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[256]; int format_priority; Window target_window; // For drag events: the window being dragged over const char** mimes; // Cached MIME types from drag enter (original, never reordered) size_t mimes_count; // Count of MIME types (full original list, never reduced) const char** copy_mimes; // Working copy passed to callbacks; pointers into mimes[] size_t copy_mimes_count; // Accepted count after last callback bool drag_accepted; // Whether the callback accepted at least one MIME type bool from_self, dropped; Time drop_time; XdndSelectionRequest *selection_requests; size_t selection_requests_count, selection_requests_capacity; } xdnd; // Drag source state struct { Window source_window; Atom* type_atoms; // Atoms for each MIME type size_t type_count; Atom action_atom; // XdndActionCopy, XdndActionMove, or XdndActionLink bool active; // Whether a drag is currently active Window current_target;// Current drop target window under cursor Window proxy_target; // Proxy target if current_target has XdndProxy int xdnd_version; // Xdnd version supported by current target bool waiting_for_status; // Waiting for XdndStatus from target bool accepted; // Whether target accepted the drag Atom accepted_action; // Action accepted by target struct { const char *mime_type; Window requestor; Atom property; Atom target; bool inflight; } *pending_requests; size_t pending_count; size_t pending_capacity; // Thumbnail window for drag icon Window thumbnail_window; Pixmap thumbnail_pixmap; GC thumbnail_gc; } drag; 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; PFN_XIQueryDevice QueryDevice; PFN_XIFreeDeviceInfo FreeDeviceInfo; PFN_XIGetProperty GetProperty; XIScrollDevice scroll_devices[16]; unsigned num_scroll_devices; int master_pointer_id; Atom LIBINPUT_SCROLL_METHOD_ENABLED, LIBINPUT_TAPPING; } 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); void read_xi_scroll_devices(void); void free_dnd_data(void); ================================================ FILE: glfw/x11_window.c ================================================ //======================================================================== // 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 "math.h" #include "backend_utils.h" #include "linux_notify.h" #include "../kitty/monotonic.h" #include #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 // X11 momentum scrolling timer state static struct { unsigned long long timer_id; GLFWid window_id; GLFWScrollEvent last_event; } x11_momentum_scroll_state = {0}; static void x11_scroll_stop_timer_callback(unsigned long long timer_id UNUSED, void *data UNUSED) { x11_momentum_scroll_state.timer_id = 0; _GLFWwindow *w = _glfwWindowForId(x11_momentum_scroll_state.window_id); if (w) { x11_momentum_scroll_state.last_event.y_offset = 0; x11_momentum_scroll_state.last_event.x_offset = 0; x11_momentum_scroll_state.last_event.unscaled.x = 0; x11_momentum_scroll_state.last_event.unscaled.y = 0; glfw_handle_scroll_event_for_momentum(w, &x11_momentum_scroll_state.last_event, true, true); } else { // Window no longer exists, cancel any ongoing momentum glfw_cancel_momentum_scroll(); } } static void x11_cancel_momentum_scroll_timer(void) { if (x11_momentum_scroll_state.timer_id) { glfwRemoveTimer(x11_momentum_scroll_state.timer_id); x11_momentum_scroll_state.timer_id = 0; } x11_momentum_scroll_state.window_id = 0; } // 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); // Forward declarations for drag source helper functions static void send_drag_data(const char *mime_type, const char *data, size_t data_sz, Window requestor, Atom property, Atom target); static bool add_pending_request(const char *mime_type, Window requestor, Atom property, Atom target); static void handle_drag_motion(int root_x, int root_y, Time timestamp); static void handle_drag_button_release(Time timestamp); static void handle_xdnd_status(const XClientMessageEvent *event); static void handle_xdnd_finished(const XClientMessageEvent *event); static bool create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y); static bool render_drag_thumbnail_to_pixmap(const GLFWimage* thumbnail); 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); } // Enable XI2 smooth scrolling events on a window // static void enableSmoothScrolling(_GLFWwindow* window) { if (!_glfw.x11.xi.num_scroll_devices) return; // Select XI_Motion events on the window XIEventMask em; unsigned char mask[XIMaskLen(XI_Motion)] = { 0 }; em.deviceid = XIAllDevices; em.mask_len = sizeof(mask); em.mask = mask; XISetMask(mask, XI_Motion); XISelectEvents(_glfw.x11.display, window->x11.handle, &em, 1); } static void resetScrollValuators(void) { for (unsigned i = 0; i < _glfw.x11.xi.num_scroll_devices; i++) { XIScrollDevice *d = &_glfw.x11.xi.scroll_devices[i]; for (unsigned k = 0; k < d->num_valuators; k++) d->valuators[k].initialized = false; } } // 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); } typedef unsigned long strut_type; typedef struct WindowGeometry { int x, y, width, height; bool needs_strut; strut_type struts[12]; } WindowGeometry; #define config (window->x11.layer_shell.config) static _GLFWmonitor* find_monitor_by_name(const char* name) { if (!name || !name[0]) return (_GLFWmonitor*)glfwGetPrimaryMonitor();; for (int i = 0; i < _glfw.monitorCount; i++) { _GLFWmonitor *m = _glfw.monitors[i]; if (strcmp(m->name, name) == 0) return m; } return (_GLFWmonitor*)glfwGetPrimaryMonitor();; } static WindowGeometry calculate_layer_geometry(_GLFWwindow *window) { _GLFWmonitor *monitor = find_monitor_by_name(config.output_name); MonitorGeometry mg = _glfwPlatformGetMonitorGeometry((_GLFWmonitor*)glfwGetPrimaryMonitor()); WindowGeometry ans = {0}; debug_rendering("Monitor: %s full: %dx%d@%dx%d workarea: %dx%d@%dx%d\n", monitor->name, mg.full.width, mg.full.height, mg.full.x, mg.full.y, mg.workarea.width, mg.workarea.height, mg.workarea.x, mg.workarea.y); ans.width = mg.full.width; ans.height = mg.full.height; ans.x = mg.full.x; ans.y = mg.full.y; ans.needs_strut = config.type == GLFW_LAYER_SHELL_PANEL; if (config.type == GLFW_LAYER_SHELL_BACKGROUND) { ans.x += config.requested_left_margin; ans.y += config.requested_top_margin; ans.width -= config.requested_left_margin + config.requested_right_margin; ans.height -= config.requested_top_margin + config.requested_bottom_margin; return ans; } float xscale = (float)config.expected.xscale, yscale = (float)config.expected.yscale; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); unsigned cell_width, cell_height; double left_edge_spacing, top_edge_spacing, right_edge_spacing, bottom_edge_spacing; config.size_callback((GLFWwindow*)window, xscale, yscale, &cell_width, &cell_height, &left_edge_spacing, &top_edge_spacing, &right_edge_spacing, &bottom_edge_spacing); double spacing_x = left_edge_spacing + right_edge_spacing; double spacing_y = top_edge_spacing + bottom_edge_spacing; double xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells); double ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells); ans.width = (int)(1. + spacing_x + xsz); ans.height = (int)(1. + spacing_y + ysz); GeometryRect m = config.type == GLFW_LAYER_SHELL_TOP || config.type == GLFW_LAYER_SHELL_OVERLAY ? mg.workarea : mg.full; static const struct { unsigned 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; } s = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; switch (config.edge) { case GLFW_EDGE_LEFT: ans.x = m.x + config.requested_left_margin; ans.y = m.y + config.requested_top_margin; ans.height = m.height - config.requested_bottom_margin - config.requested_top_margin; ans.struts[s.left] = ans.width; ans.struts[s.left_end_y] = ans.height; break; case GLFW_EDGE_RIGHT: ans.x = m.x + m.width - config.requested_right_margin - ans.width; ans.y = m.y + config.requested_top_margin; ans.height = m.height - config.requested_bottom_margin - config.requested_top_margin; ans.struts[s.right] = ans.width; ans.struts[s.right_end_y] = ans.height; break; case GLFW_EDGE_TOP: ans.x = m.x + config.requested_left_margin; ans.y = m.y + config.requested_top_margin; ans.width = m.width - config.requested_right_margin - config.requested_left_margin; ans.struts[s.top] = ans.height; ans.struts[s.top_end_x] = ans.width; break; case GLFW_EDGE_BOTTOM: ans.x = m.x + config.requested_left_margin; ans.y = m.height - config.requested_bottom_margin - ans.height; ans.width = m.width - config.requested_right_margin - config.requested_left_margin; ans.struts[s.bottom] = ans.height; ans.struts[s.bottom_end_x] = ans.width; break; case GLFW_EDGE_NONE: ans.needs_strut = false; ans.x = m.x + config.requested_left_margin; ans.y = m.y + config.requested_top_margin; break; case GLFW_EDGE_CENTER_SIZED: ans.needs_strut = false; ans.x = (m.width - ans.width) / 2; ans.y = (m.height - ans.height) / 2; break; default: ans.needs_strut = false; ans.x = m.x + config.requested_left_margin; ans.y = m.y + config.requested_top_margin; ans.height = m.height - config.requested_bottom_margin - config.requested_top_margin; ans.width = m.width - config.requested_right_margin - config.requested_left_margin; break; } debug_rendering("Calculating layer geometry at scale: %f cell size: (%u, %u) -> %dx%d@%dx%d needs_strut: %d\n", xscale, cell_width, cell_height, ans.width, ans.height, ans.x, ans.y, ans.needs_strut) return ans; } GLFWAPI bool glfwIsLayerShellSupported(void) { return _glfw.x11.NET_WM_WINDOW_TYPE != 0 && _glfw.x11.NET_WM_STATE != 0; } static bool update_wm_hints(_GLFWwindow *window, const WindowGeometry *wg, const _GLFWwndconfig *wndconfig) { XWMHints* hints = XAllocWMHints(); bool is_layer_shell = window->x11.layer_shell.is_active; bool ok = false; if (hints) { ok = true; hints->flags = StateHint | InputHint; hints->initial_state = NormalState; hints->input = true; if (is_layer_shell && config.focus_policy == GLFW_FOCUS_NOT_ALLOWED) hints->input = false; XSetWMHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); } else _glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate WM hints"); if (_glfw.x11.NET_WM_WINDOW_TYPE) { Atom type = 0; if (is_layer_shell) { const char *name = NULL; #define S(which) type = _glfw.x11.which; name = #which switch (config.type) { case GLFW_LAYER_SHELL_BACKGROUND: S(NET_WM_WINDOW_TYPE_DESKTOP); break; case GLFW_LAYER_SHELL_PANEL: S(NET_WM_WINDOW_TYPE_DOCK); break; default: S(NET_WM_WINDOW_TYPE_NORMAL); break; } #undef S if (!type) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Window manager does not support _%s", name); ok = false; } } else if (_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL) type = _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL; if (type) XChangeProperty( _glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &type, 1); } else if (is_layer_shell) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Window manager does not support _NET_WM_WINDOW_TYPE"); ok = false; } if (is_layer_shell) { if (_glfw.x11.NET_WM_STRUT_PARTIAL) { XChangeProperty( _glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)(wg->needs_strut ? wg->struts : (strut_type[12]){0}), 12); } else if (wg->needs_strut) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Window manager does not support _NET_WM_STRUT_PARTIAL"); ok = false; } } if (ok) { updateNormalHints(window, wg->width, wg->height); Atom states[8]; unsigned count = 0; if (is_layer_shell) { _glfwPlatformSetWindowDecorated(window, false); if (_glfw.x11.NET_WM_STATE_STICKY) states[count++] = _glfw.x11.NET_WM_STATE_STICKY; if (_glfw.x11.NET_WM_STATE_SKIP_PAGER) states[count++] = _glfw.x11.NET_WM_STATE_SKIP_PAGER; if (_glfw.x11.NET_WM_STATE_SKIP_TASKBAR) states[count++] = _glfw.x11.NET_WM_STATE_SKIP_TASKBAR; #define S(x) if (_glfw.x11.x) { states[count++] = _glfw.x11.x; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Window manager does not support _%s", #x); ok = false; } switch (config.type) { case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_BACKGROUND: S(NET_WM_STATE_BELOW); break; case GLFW_LAYER_SHELL_PANEL: // i3 does not support NET_WM_STATE_BELOW but panels work without it if (_glfw.x11.NET_WM_STATE_BELOW) { S(NET_WM_STATE_BELOW); } break; case GLFW_LAYER_SHELL_TOP: case GLFW_LAYER_SHELL_OVERLAY: S(NET_WM_STATE_ABOVE); break; } #undef S } else if (wndconfig) { if (!wndconfig->decorated) _glfwPlatformSetWindowDecorated(window, false); if (_glfw.x11.NET_WM_STATE && !window->monitor) { 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 && _glfw.x11.NET_WM_STATE) XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*) states, count); } if (!wndconfig && ok) { _glfwPlatformSetWindowPos(window, wg->x, wg->y); _glfwPlatformSetWindowSize(window, wg->width, wg->height); } return ok; #undef config } // Create the X11 window (and its colormap) // static bool createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, Visual* visual, int depth) { WindowGeometry wg = {.width=wndconfig->width, .height=wndconfig->height}; if (window->x11.layer_shell.is_active) { wg = calculate_layer_geometry(window); window->resizable = false; } // 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; debug_rendering("Creating window with geometry: %dx%d@%dx%d\n", wg.width, wg.height, wg.x, wg.y); window->x11.handle = XCreateWindow(_glfw.x11.display, _glfw.x11.root, wg.x, wg.y, // Position wg.width, wg.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); // 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 (!update_wm_hints(window, &wg, wndconfig)) return false; // without this floating window position is incorrect on KDE if (window->x11.layer_shell.is_active) _glfwPlatformSetWindowPos(window, wg.x, wg.y); // 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); // Enable XI2 smooth scrolling if available enableSmoothScrolling(window); 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.display = request->display; reply.xselection.requestor = request->requestor; reply.xselection.selection = request->selection; reply.xselection.target = request->target; reply.xselection.time = request->time; reply.xselection.property = None; // Handle XdndSelection (drag and drop) specially if (request->selection == _glfw.x11.XdndSelection && _glfw.drag.window_id) { // Handle TARGETS request for XdndSelection if (request->target == _glfw.x11.TARGETS) { // Return the list of supported MIME type atoms Atom *targets = calloc(_glfw.x11.drag.type_count + 2, sizeof(Atom)); if (targets) { targets[0] = _glfw.x11.TARGETS; targets[1] = _glfw.x11.MULTIPLE; for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) targets[i + 2] = _glfw.x11.drag.type_atoms[i]; XChangeProperty(_glfw.x11.display, request->requestor, request->property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, _glfw.x11.drag.type_count + 2); free(targets); reply.xselection.property = request->property; } } else { // Find the matching MIME type for the requested target const char* mime_type = NULL; for (size_t i = 0; i < _glfw.x11.drag.type_count; i++) { if (_glfw.x11.drag.type_atoms[i] == request->target) { mime_type = _glfw.drag.items[i].mime_type; break; } } if (mime_type) { // Check if we have preset data const char *data = NULL; size_t data_sz = 0; for (size_t i = 0; i < _glfw.drag.item_count; i++) { if (strcmp(_glfw.drag.items[i].mime_type, mime_type) == 0) { data = _glfw.drag.items[i].optional_data; data_sz = _glfw.drag.items[i].data_size; break; } } if (data && data_sz > 0) { // We have preset data, send it immediately send_drag_data(mime_type, data, data_sz, request->requestor, request->property, request->target); reply.xselection.property = request->property; } else { // Add to pending requests for on-demand data if (add_pending_request(mime_type, request->requestor, request->property, request->target)) { // Request data from application _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = { .type = GLFW_DRAG_DATA_REQUEST, .mime_type = mime_type, .data = NULL, .data_sz = 0, .err_num = 0 }; _glfwInputDragSourceRequest(window, &ev); if (ev.data && ev.data_sz > 0) { // Data provided synchronously send_drag_data(mime_type, ev.data, ev.data_sz, request->requestor, request->property, request->target); reply.xselection.property = request->property; } else { // Data will be provided asynchronously via _glfwPlatformDragDataReady // Don't send SelectionNotify yet - it will be sent when data is ready return; } } } } } } } else { // Handle regular clipboard/primary selection reply.xselection.property = writeTargetToProperty(request); } 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; } } } static void handle_mouse_move_event(_GLFWwindow *window, const int x, const int 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; } static bool number_has_fractional_part(double x) { return fabs(x - round(x)) >= 1e-6; } static void handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) { XIScrollDevice *d = NULL; bool scroll_valuator_found = false; for (unsigned i = 0; i < _glfw.x11.xi.num_scroll_devices; i++) { XIScrollDevice *t = &_glfw.x11.xi.scroll_devices[i]; if (t->deviceid == de->deviceid && t->sourceid == de->sourceid) { d = t; break; } } if (d && de->valuators.mask_len){ double xOffset = 0, yOffset = 0; // Process valuators to detect scroll events GLFWOffsetType type = GLFW_SCROLL_OFFEST_HIGHRES; unsigned vidx = 0; for (int i = 0; i < de->valuators.mask_len * 8; i++) { if (!XIMaskIsSet(de->valuators.mask, i)) continue; const double value = de->valuators.values[vidx++]; XIScrollValuator *v = NULL; for (unsigned k = 0; k < d->num_valuators; k++) { XIScrollValuator *t = d->valuators + k; if (t->number == i) { v = t; break; } } if (!v) continue; scroll_valuator_found = true; if (!v->initialized) { v->initialized = true; v->value = value; continue; } double delta = value - v->value; v->value = value; delta *= -1; double *off = v->is_vertical ? &yOffset : &xOffset; *off = delta; d->num_events++; if (!d->type_detected) { if (v->increment == 120.) { d->type_detected = true; d->offset_type = GLFW_SCROLL_OFFEST_V120; } else { bool delta_is_fractional = number_has_fractional_part(delta); if (delta_is_fractional) { if (fabs(delta * 120 - round(delta * 120)) < 0.01) { d->type_detected = d->num_events > 2; d->offset_type = GLFW_SCROLL_OFFEST_V120; } else { d->type_detected = true; d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES; } } else { d->type_detected = d->num_events > 2; d->offset_type = GLFW_SCROLL_OFFSET_LINES; } } } if (d->offset_type == GLFW_SCROLL_OFFSET_LINES) { if (v->increment != 0) *off /= v->increment; } } type = d->offset_type; if (xOffset != 0 || yOffset != 0) { // Get keyboard modifiers int mods = translateState(de->mods.effective); // Scale offsets by content scale GLFWScrollEvent ev = { .keyboard_modifiers = mods, .x_offset = xOffset * (type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfw.x11.contentScaleX : 1), .y_offset = yOffset * (type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfw.x11.contentScaleY : 1), .unscaled = {.x = xOffset, .y = yOffset}, .offset_type = type, }; // For high-resolution, finger-based scrolling, use timer-based momentum scrolling if (d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) { // Reset the timer on each scroll event x11_cancel_momentum_scroll_timer(); // Store the event for later use when timer fires x11_momentum_scroll_state.window_id = window->id; x11_momentum_scroll_state.last_event = ev; // Start timer x11_momentum_scroll_state.timer_id = glfwAddTimer( ms_to_monotonic_t(momentum_scroll_gesture_detection_timeout_ms), false, x11_scroll_stop_timer_callback, NULL, NULL); // Send the scroll event through momentum handler glfw_handle_scroll_event_for_momentum(window, &ev, false, true); } else { // Regular mouse wheel scrolling - no momentum _glfwInputScroll(window, &ev); } } } if (!scroll_valuator_found) { x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll(); handle_mouse_move_event(window, (int)de->event_x, (int)de->event_y); } } #define dnd _glfw.x11.xdnd // Dropping of data onto window {{{ static void end_drop(_GLFWwindow *window, GLFWDragOperationType op) { bool accepted = dnd.drag_accepted || dnd.dropped; XEvent reply = { ClientMessage }; reply.xclient.window = dnd.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] = accepted ? 1 : 0; reply.xclient.data.l[2] = None; if (dnd.version >= 5) { switch(op) { case GLFW_DRAG_OPERATION_COPY: reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; break; case GLFW_DRAG_OPERATION_MOVE: reply.xclient.data.l[2] = _glfw.x11.XdndActionMove; break; case GLFW_DRAG_OPERATION_GENERIC: reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; break; case GLFW_DRAG_OPERATION_NONE: reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; break; } } XSendEvent(_glfw.x11.display, dnd.source, False, NoEventMask, &reply); XFlush(_glfw.x11.display); } static void update_drop_state(_GLFWwindow* window, size_t accepted_count) { dnd.copy_mimes_count = accepted_count; bool accepted = accepted_count > 0; dnd.drag_accepted = accepted; // The first entry in the accepted (sorted) copy is the preferred MIME. const char* new_preferred_mime = (accepted && dnd.copy_mimes) ? dnd.copy_mimes[0] : NULL; // Check if the preferred MIME changed bool mime_changed = strncmp(new_preferred_mime ? new_preferred_mime : "", dnd.format, arraysz(dnd.format)) != 0; if (mime_changed) { if (new_preferred_mime) strncpy(dnd.format, new_preferred_mime, arraysz(dnd.format)-1); else dnd.format[0] = 0; } if (accepted) { XEvent reply = { ClientMessage }; reply.xclient.window = dnd.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 (dnd.format_priority > 0 && accepted) { // 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); } else { end_drop(window, GLFW_DRAG_OPERATION_GENERIC); } } static void free_dnd_mimes(void) { // Free any previously cached MIME types if (dnd.mimes) { for (size_t j = 0; j < dnd.mimes_count; j++) { if (dnd.mimes[j]) XFree((void*)dnd.mimes[j]); } free(dnd.mimes); dnd.mimes = NULL; dnd.mimes_count = 0; } free(dnd.copy_mimes); // pointer array only; strings are owned by mimes[] dnd.copy_mimes = NULL; dnd.copy_mimes_count = 0; } // Reset the working copy of mimes so the next callback sees the full original // list. Returns false on allocation failure. static bool reset_dnd_copy_mimes(void) { if (dnd.mimes_count == 0) { dnd.copy_mimes_count = 0; return true; } if (!dnd.copy_mimes) { dnd.copy_mimes = malloc(dnd.mimes_count * sizeof(const char*)); if (!dnd.copy_mimes) return false; } memcpy(dnd.copy_mimes, dnd.mimes, dnd.mimes_count * sizeof(const char*)); dnd.copy_mimes_count = dnd.mimes_count; return true; } void free_dnd_data(void) { dnd.source = None; dnd.target_window = None; dnd.drag_accepted = false; free_dnd_mimes(); if (dnd.selection_requests) { for (size_t i = 0; i < dnd.selection_requests_count; i++) { free(dnd.selection_requests[i].mime); if (dnd.selection_requests[i].data) XFree(dnd.selection_requests[i].data); } free(dnd.selection_requests); dnd.selection_requests = NULL; } dnd.selection_requests_count = 0; dnd.selection_requests_capacity = 0; dnd.format[0] = 0; } static void update_dnd_mimes(XEvent *event) { unsigned long i, count; Atom* formats = NULL; const bool list = event->xclient.data.l[1] & 1; free_dnd_mimes(); // Get the MIME types before calling the callback if (list) { count = _glfwGetWindowPropertyX11(dnd.source, _glfw.x11.XdndTypeList, XA_ATOM, (unsigned char**) &formats); } else { count = 3; formats = (Atom*) event->xclient.data.l + 2; } // Get atom names and store them in the xdnd structure char **atom_names = calloc(count, sizeof(char*)); int valid_mime_count = 0; if (atom_names) { get_atom_names(formats, count, atom_names); // Compact the array to only valid MIME types for (i = 0; i < count; i++) { if (atom_names[i]) { if (valid_mime_count != (int)i) { atom_names[valid_mime_count] = atom_names[i]; atom_names[i] = NULL; } valid_mime_count++; } } // Store the MIME types for later use dnd.mimes = (const char**)atom_names; dnd.mimes_count = valid_mime_count; } if (list && formats) XFree(formats); } void _glfwPlatformEndDrop(GLFWwindow *w, GLFWDragOperationType op) { end_drop((_GLFWwindow*)w, op); free_dnd_data(); } static void drop_start(_GLFWwindow *window, XEvent *event) { // A drag operation has entered the window if (dnd.version > _GLFW_XDND_VERSION) return; free_dnd_data(); dnd.source = event->xclient.data.l[0]; dnd.version = event->xclient.data.l[1] >> 24; dnd.target_window = window->x11.handle; dnd.format[0] = 0; dnd.format_priority = 0; update_dnd_mimes(event); dnd.from_self = _glfw.x11.drag.source_window != None && dnd.source == _glfw.x11.drag.source_window; // Position is not known yet at enter time, will be updated with XdndPosition if (reset_dnd_copy_mimes()) { size_t accepted_count = _glfwInputDropEvent( window, GLFW_DROP_ENTER, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self); update_drop_state(window, accepted_count); // Set format_priority when the callback accepted at least one MIME type. // dnd.format has already been updated by update_drop_state. if (accepted_count > 0) dnd.format_priority = 1; } } static void drop_leave(_GLFWwindow *window, XEvent *event UNUSED) { // The drag operation has left the window _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, NULL, 0, dnd.from_self); if (!dnd.dropped) { free_dnd_data(); } } static void drop_move(_GLFWwindow *window, XEvent *event) { // The drag operation has moved over the window if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; 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; _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); if (reset_dnd_copy_mimes()) { size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self); update_drop_state(window, mimes_count); } } void _glfwPlatformRequestDropUpdate(_GLFWwindow* window) { // Check if there's an active drag over this window if (dnd.source == None || dnd.target_window != window->x11.handle) return; // Call the drag callback with STATUS_UPDATE event to get updated state // Position values are not valid for this event type if (reset_dnd_copy_mimes()) { size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self); update_drop_state(window, mimes_count); } } static void drop(_GLFWwindow *window, XEvent *event) { // The drag operation has finished by dropping on the window if (dnd.version > _GLFW_XDND_VERSION || dnd.version < 2) return; dnd.dropped = true; dnd.drop_time = (unsigned long)event->xclient.data.l[2]; if (!reset_dnd_copy_mimes()) return; size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self); if (!dnd.copy_mimes) return; for (size_t i = 0; i < num_accepted; i++) _glfwPlatformRequestDropData(window, dnd.copy_mimes[i]); } static void request_selection(_GLFWwindow *window, XdndSelectionRequest *r) { Atom target_atom = XInternAtom(_glfw.x11.display, r->mime, False); XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, target_atom, _glfw.x11.XdndSelection, window->x11.handle, dnd.drop_time); XFlush(_glfw.x11.display); r->inflight = true; } typedef struct { char *mime; GLFWid window_id; } notify_data; static void free_notify_data(unsigned long long timer_id UNUSED, void *x) { notify_data *d = x; free(d->mime); free(x); } static void notify_drop_data_available(unsigned long long timer_id UNUSED, void *x) { notify_data *d = x; const char * mimes[1] = {d->mime}; _GLFWwindow *window = _glfwWindowForId(d->window_id); if (window) _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, dnd.from_self); } ssize_t _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t sz) { _GLFWwindow *window = (_GLFWwindow*)w; const char *mime = ev->mimes[0]; for (size_t i = 0; i < dnd.selection_requests_count; i++) { XdndSelectionRequest *r = dnd.selection_requests + i; if (strcmp(r->mime, mime) == 0) { if (!r->got_data) return -EINVAL; if (r->offset >= r->size) return 0; size_t to_read = MIN(sz, r->size - r->offset); memcpy(buffer, r->data, to_read); r->offset += to_read; if (to_read) { notify_data *d = malloc(sizeof(notify_data)); if (d) { d->mime = _glfw_strdup(mime); d->window_id = window->id; _glfwPlatformAddTimer(0, false, notify_drop_data_available, d, free_notify_data); } } return to_read; } } return -ENOENT; } static XdndSelectionRequest* inflight_selection_request(void) { for (size_t i = 0; i < dnd.selection_requests_count; i++) { XdndSelectionRequest *r = dnd.selection_requests + i; if (r->inflight) return r; } return NULL; } int _glfwPlatformRequestDropData(_GLFWwindow *window, const char *mime) { if (!dnd.mimes) return EINVAL; for (size_t i = 0; i < dnd.selection_requests_count; i++) { XdndSelectionRequest *r = dnd.selection_requests + i; if (strcmp(r->mime, mime) == 0) { if (r->got_data) { r->offset = 0; const char * mimes[1] = {mime}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, dnd.from_self); } return 0; } } if (!dnd.selection_requests || dnd.selection_requests_count + 1 >= dnd.selection_requests_capacity) { dnd.selection_requests_capacity = dnd.selection_requests_count + 8; dnd.selection_requests = realloc( dnd.selection_requests, sizeof(dnd.selection_requests[0]) * dnd.selection_requests_capacity); if (!dnd.selection_requests) return ENOMEM; } XdndSelectionRequest *r = dnd.selection_requests + dnd.selection_requests_count++; memset(r, 0, sizeof(r[0])); r->mime = _glfw_strdup(mime); if (!inflight_selection_request()) request_selection(window, r); return 0; } static void drop_selection_notify(_GLFWwindow *window, XEvent *event) { // requested drop data is available XdndSelectionRequest *r = inflight_selection_request(); if (!r) return; r->size = _glfwGetWindowPropertyX11( event->xselection.requestor, event->xselection.property, event->xselection.target, &r->data); r->inflight = false; r->got_data = true; const char * mimes[1] = {r->mime}; _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, dnd.from_self); for (size_t i = 0; i < dnd.selection_requests_count; i++) { XdndSelectionRequest *r = dnd.selection_requests + i; if (!r->got_data) { request_selection(window, r); break; } } } // }}} #undef dnd // 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 && event->xcookie.extension == _glfw.x11.xi.majorOpcode) { if (XGetEventData(_glfw.x11.display, &event->xcookie)) { // Handle XI_RawMotion for disabled cursor if (event->xcookie.evtype == XI_RawMotion) { _GLFWwindow* window = _glfw.x11.disabledCursorWindow; if (window && window->rawMouseMotion) { 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); } } } // Handle XI_Motion for smooth scrolling else if (event->xcookie.evtype == XI_Motion) { XIDeviceEvent* de = (XIDeviceEvent*)event->xcookie.data; if (de->deviceid != _glfw.x11.xi.master_pointer_id) { // Find the window for this event _GLFWwindow* window = NULL; if (XFindContext(_glfw.x11.display, de->event, _glfw.x11.context, (XPointer*)&window) == 0) handle_xi_motion_event(window, de); } } // Handle XI_HierarchyChanged for device hotplug else if (event->xcookie.evtype == XI_HierarchyChanged) { XIHierarchyEvent* he = (XIHierarchyEvent*)event->xcookie.data; // Check if any devices were added or removed for (int i = 0; i < he->num_info; i++) { if (he->info[i].flags & (XISlaveAdded | XISlaveRemoved | XIMasterAdded | XIMasterRemoved)) { // Re-read scroll devices when devices are added or removed read_xi_scroll_devices(); break; } } } 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; x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll(); glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_PRESS); return; } case KeyRelease: { UPDATE_KEYMAP_IF_NEEDED; x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll(); 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); #define cancel_momentum() x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll() if (event->xbutton.button == Button1) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); } else if (event->xbutton.button == Button2) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods); } else if (event->xbutton.button == Button3) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods); } // Modern X provides scroll events as mouse button presses // Only use these if smooth scrolling is not available else if (event->xbutton.button == Button4) { if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .y_offset=1, .unscaled.y=1}); } else if (event->xbutton.button == Button5) { if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .y_offset=-1, .unscaled.y=-1}); } else if (event->xbutton.button == Button6) { if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .x_offset=1, .unscaled.x=1}); } else if (event->xbutton.button == Button7) { if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .x_offset=-1, .unscaled.x=-1}); } else { cancel_momentum(); // 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); // Handle drag drop on button release if (_glfw.x11.drag.active && event->xbutton.button == Button1) { handle_drag_button_release(event->xbutton.time); return; } if (event->xbutton.button == Button1) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button2) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button3) { cancel_momentum(); _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, mods); } else if (event->xbutton.button > Button7) { cancel_momentum(); // 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; #undef cancel_momentum } 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: { resetScrollValuators(); _glfwInputCursorEnter(window, false); return; } case MotionNotify: { // Handle drag motion if (_glfw.x11.drag.active) { int root_x, root_y; Window child; XTranslateCoordinates(_glfw.x11.display, event->xmotion.window, _glfw.x11.root, event->xmotion.x, event->xmotion.y, &root_x, &root_y, &child); handle_drag_motion(root_x, root_y, event->xmotion.time); } x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll(); handle_mouse_move_event(window, event->xmotion.x, event->xmotion.y); return; } case ConfigureNotify: { if (event->xconfigure.width != window->x11.width || event->xconfigure.height != window->x11.height) { debug_rendering("Window resized to: %d %d from: %d %d\n", event->xconfigure.width, event->xconfigure.height, window->x11.width, 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) { debug_rendering("Window moved to: %d %d from: %d %d\n", xpos, ypos, window->x11.xpos, window->x11.xpos); _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) { drop_start(window, event); } else if (event->xclient.message_type == _glfw.x11.XdndDrop) { drop(window, event); } else if (event->xclient.message_type == _glfw.x11.XdndLeave) { drop_leave(window, event); } else if (event->xclient.message_type == _glfw.x11.XdndPosition) { drop_move(window, event); } else if (event->xclient.message_type == _glfw.x11.XdndStatus) { handle_xdnd_status(&event->xclient); } else if (event->xclient.message_type == _glfw.x11.XdndFinished) { handle_xdnd_finished(&event->xclient); } return; } case SelectionNotify: if (event->type == SelectionNotify && event->xselection.selection == _glfw.x11.XdndSelection && event->xselection.property == _glfw.x11.XdndSelection) drop_selection_notify(window, event); 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); resetScrollValuators(); _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, const GLFWLayerShellConfig *lsc) { Visual* visual = NULL; int depth; if (lsc) { window->x11.layer_shell.is_active = true; window->x11.layer_shell.config = *lsc; } else window->x11.layer_shell.is_active = false; 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, false); 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); } const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { return &window->x11.layer_shell.config; } bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { if (value) window->x11.layer_shell.config = *value; WindowGeometry wg = calculate_layer_geometry(window); update_wm_hints(window, &wg, NULL); return false; } 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++) { const 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, bool move_to_active_screen UNUSED) { if (_glfwPlatformWindowVisible(window)) return; XMapWindow(_glfw.x11.display, window->x11.handle); // without this floating window position is incorrect on KDE if (window->x11.layer_shell.is_active) { WindowGeometry wg = calculate_layer_geometry(window); _glfwPlatformSetWindowPos(window, wg.x, wg.y); } 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; } bool _glfwPlatformGrabKeyboard(bool grab) { int result; if (grab) { result = XGrabKeyboard(_glfw.x11.display, _glfw.x11.root, True, GrabModeAsync, GrabModeAsync, CurrentTime); } else { result = XUngrabKeyboard(_glfw.x11.display, CurrentTime); } return result == GrabSuccess; } ////////////////////////////////////////////////////////////////////////// ////// 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; assert(window != NULL); _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); } // Drag source {{{ // Helper function to check if a window supports XdndAware static bool window_supports_xdnd(Window win, int *version_out) { Atom actual_type; int actual_format; unsigned long count, bytes_after; unsigned char *data = NULL; bool supported = false; if (XGetWindowProperty(_glfw.x11.display, win, _glfw.x11.XdndAware, 0, 1, False, XA_ATOM, &actual_type, &actual_format, &count, &bytes_after, &data) == Success) { if (actual_type == XA_ATOM && actual_format == 32 && count > 0) { supported = true; if (version_out) { int version = *(int*)data; *version_out = (version < _GLFW_XDND_VERSION) ? version : _GLFW_XDND_VERSION; } } if (data) XFree(data); } return supported; } // Find the XdndAware window at the given coordinates static Window find_xdnd_aware_target(Window root, int root_x, int root_y, int *version_out) { Window target = None; Window child = root; Window parent = root; Window *children = NULL; unsigned int nchildren = 0; // Walk down the window tree to find the deepest window at these coordinates while (child != None) { target = child; int x, y; if (!XTranslateCoordinates(_glfw.x11.display, root, target, root_x, root_y, &x, &y, &child)) { break; } if (child == None) break; } // Walk up the tree to find an XdndAware window while (target != None && target != root) { if (window_supports_xdnd(target, version_out)) { // Check for XdndProxy Atom actual_type; int actual_format; unsigned long count, bytes_after; unsigned char *data = NULL; if (XGetWindowProperty(_glfw.x11.display, target, _glfw.x11.XdndProxy, 0, 1, False, XA_WINDOW, &actual_type, &actual_format, &count, &bytes_after, &data) == Success) { if (actual_type == XA_WINDOW && actual_format == 32 && count > 0) { Window proxy = *(Window*)data; XFree(data); // Verify the proxy is XdndAware if (window_supports_xdnd(proxy, NULL)) { return proxy; } } else if (data) { XFree(data); } } return target; } // Move to parent Window new_parent; if (XQueryTree(_glfw.x11.display, target, &parent, &new_parent, &children, &nchildren)) { if (children) XFree(children); target = new_parent; } else { break; } } return None; } // Send XdndEnter message to target window static void send_xdnd_enter(Window target, int version) { XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = target; event.xclient.message_type = _glfw.x11.XdndEnter; event.xclient.format = 32; event.xclient.data.l[0] = _glfw.x11.drag.source_window; event.xclient.data.l[1] = (version << 24); // If we have more than 3 types, set the type list flag if (_glfw.x11.drag.type_count > 3) { event.xclient.data.l[1] |= 1; // More than 3 types, use property // Set the XdndTypeList property on source window XChangeProperty(_glfw.x11.display, _glfw.x11.drag.source_window, _glfw.x11.XdndTypeList, XA_ATOM, 32, PropModeReplace, (unsigned char*)_glfw.x11.drag.type_atoms, _glfw.x11.drag.type_count); } else { // Embed up to 3 types in the message for (size_t i = 0; i < _glfw.x11.drag.type_count && i < 3; i++) { event.xclient.data.l[2 + i] = _glfw.x11.drag.type_atoms[i]; } } _glfwGrabErrorHandlerX11(); XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event); XFlush(_glfw.x11.display); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None; } // Send XdndPosition message to target window static void send_xdnd_position(Window target, int root_x, int root_y, Time timestamp) { XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = target; event.xclient.message_type = _glfw.x11.XdndPosition; event.xclient.format = 32; event.xclient.data.l[0] = _glfw.x11.drag.source_window; event.xclient.data.l[1] = 0; // Reserved event.xclient.data.l[2] = (root_x << 16) | root_y; event.xclient.data.l[3] = timestamp; event.xclient.data.l[4] = _glfw.x11.drag.action_atom; _glfwGrabErrorHandlerX11(); XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event); XFlush(_glfw.x11.display); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None; else _glfw.x11.drag.waiting_for_status = true; } // Send XdndLeave message to target window static void send_xdnd_leave(Window target) { XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = target; event.xclient.message_type = _glfw.x11.XdndLeave; event.xclient.format = 32; event.xclient.data.l[0] = _glfw.x11.drag.source_window; _glfwGrabErrorHandlerX11(); XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event); XFlush(_glfw.x11.display); _glfwReleaseErrorHandlerX11(); // BadWindow on leave is benign – the target window is already gone } // Send XdndDrop message to target window static void send_xdnd_drop(Window target, Time timestamp) { XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = target; event.xclient.message_type = _glfw.x11.XdndDrop; event.xclient.format = 32; event.xclient.data.l[0] = _glfw.x11.drag.source_window; event.xclient.data.l[1] = 0; // Reserved event.xclient.data.l[2] = timestamp; _glfwGrabErrorHandlerX11(); XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event); XFlush(_glfw.x11.display); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode == BadWindow) { // Target window was destroyed; cancel the drag gracefully _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type = GLFW_DRAG_CANCELLED}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); } } // Render thumbnail pixels into _glfw.x11.drag.thumbnail_pixmap / thumbnail_gc. // thumbnail_window must already exist. On success returns true. static bool render_drag_thumbnail_to_pixmap(const GLFWimage* thumbnail) { // Free any previous pixmap / GC if (_glfw.x11.drag.thumbnail_gc != None) { XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); _glfw.x11.drag.thumbnail_gc = None; } if (_glfw.x11.drag.thumbnail_pixmap != None) { XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_pixmap = None; } _glfw.x11.drag.thumbnail_pixmap = XCreatePixmap( _glfw.x11.display, _glfw.x11.drag.thumbnail_window, thumbnail->width, thumbnail->height, DefaultDepth(_glfw.x11.display, _glfw.x11.screen) ); if (!_glfw.x11.drag.thumbnail_pixmap) return false; _glfw.x11.drag.thumbnail_gc = XCreateGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, 0, NULL); if (!_glfw.x11.drag.thumbnail_gc) { XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_pixmap = None; return false; } Visual* visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); XImage* ximage = XCreateImage( _glfw.x11.display, visual, DefaultDepth(_glfw.x11.display, _glfw.x11.screen), ZPixmap, 0, NULL, thumbnail->width, thumbnail->height, 32, 0 ); if (!ximage) { XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_gc = None; _glfw.x11.drag.thumbnail_pixmap = None; return false; } int pixels_size = thumbnail->width * thumbnail->height * 4; unsigned char* ximage_data = malloc(pixels_size); if (!ximage_data) { XDestroyImage(ximage); XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_gc = None; _glfw.x11.drag.thumbnail_pixmap = None; return false; } ximage->data = (char*)ximage_data; const unsigned char* src = thumbnail->pixels; for (int i = 0; i < thumbnail->width * thumbnail->height; i++) { unsigned char r = src[i * 4 + 0]; unsigned char g = src[i * 4 + 1]; unsigned char b = src[i * 4 + 2]; unsigned char a = src[i * 4 + 3]; if (a < 255) { r = (r * a) / 255; g = (g * a) / 255; b = (b * a) / 255; } unsigned long pixel; if (ximage->byte_order == LSBFirst) pixel = ((unsigned long)a << 24) | ((unsigned long)r << 16) | ((unsigned long)g << 8) | b; else pixel = ((unsigned long)b << 24) | ((unsigned long)g << 16) | ((unsigned long)r << 8) | a; XPutPixel(ximage, i % thumbnail->width, i / thumbnail->width, pixel); } XPutImage(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap, _glfw.x11.drag.thumbnail_gc, ximage, 0, 0, 0, 0, thumbnail->width, thumbnail->height); XDestroyImage(ximage); XSetWindowBackgroundPixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, _glfw.x11.drag.thumbnail_pixmap); return true; } // Create thumbnail window for drag operation static bool create_drag_thumbnail(const GLFWimage* thumbnail, int x, int y) { if (!thumbnail || !thumbnail->pixels || thumbnail->width <= 0 || thumbnail->height <= 0) { return true; // No thumbnail is fine } // Create an override-redirect window for the drag icon XSetWindowAttributes attrs; attrs.override_redirect = True; attrs.background_pixel = 0; _glfw.x11.drag.thumbnail_window = XCreateWindow( _glfw.x11.display, _glfw.x11.root, x, y, thumbnail->width, thumbnail->height, 0, // border width CopyFromParent, // depth InputOutput, // class CopyFromParent, // visual CWOverrideRedirect | CWBackPixel, &attrs ); if (!_glfw.x11.drag.thumbnail_window) { return false; } if (!render_drag_thumbnail_to_pixmap(thumbnail)) { XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); _glfw.x11.drag.thumbnail_window = None; return false; } // Map the window to make it visible XMapRaised(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); XFlush(_glfw.x11.display); return true; } // Handle motion during drag static void handle_drag_motion(int root_x, int root_y, Time timestamp) { if (!_glfw.x11.drag.active) return; // Move thumbnail window to follow cursor if (_glfw.x11.drag.thumbnail_window != None) { XMoveWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, root_x + 10, root_y + 10); } int version = _GLFW_XDND_VERSION; Window new_target = find_xdnd_aware_target(_glfw.x11.root, root_x, root_y, &version); if (new_target != _glfw.x11.drag.current_target) { // Send leave to old target if (_glfw.x11.drag.current_target != None) { send_xdnd_leave(_glfw.x11.drag.current_target); } _glfw.x11.drag.current_target = new_target; _glfw.x11.drag.waiting_for_status = false; _glfw.x11.drag.accepted = false; // Send enter to new target if (new_target != None) { _glfw.x11.drag.xdnd_version = version; send_xdnd_enter(new_target, version); } } // Send position to current target if (_glfw.x11.drag.current_target != None && !_glfw.x11.drag.waiting_for_status) { send_xdnd_position(_glfw.x11.drag.current_target, root_x, root_y, timestamp); } } // Handle button release during drag (drop) static void handle_drag_button_release(Time timestamp) { if (!_glfw.x11.drag.active) return; if (_glfw.x11.drag.current_target != None && _glfw.x11.drag.accepted) { send_xdnd_drop(_glfw.x11.drag.current_target, timestamp); } else { // Drag was cancelled or not accepted _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { GLFWDragEvent ev = {.type = GLFW_DRAG_CANCELLED}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); } } // Handle XdndStatus message from drop target static void handle_xdnd_status(const XClientMessageEvent *event) { if (!_glfw.x11.drag.active) return; if (event->data.l[0] != (long)_glfw.x11.drag.current_target) return; _glfw.x11.drag.waiting_for_status = false; _glfw.x11.drag.accepted = (event->data.l[1] & 1) != 0; if (_glfw.x11.drag.accepted && event->data.l[4] != None) { _glfw.x11.drag.accepted_action = event->data.l[4]; } } // Handle XdndFinished message from drop target static void handle_xdnd_finished(const XClientMessageEvent *event) { if (!_glfw.x11.drag.active) return; if (event->data.l[0] != (long)_glfw.x11.drag.current_target) return; _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { bool accepted = (event->data.l[1] & 1) != 0; GLFWDragOperationType action = 0; if (accepted && event->data.l[2] != None) { Atom action_atom = event->data.l[2]; if (action_atom == _glfw.x11.XdndActionCopy) { action = GLFW_DRAG_OPERATION_COPY; } else if (action_atom == _glfw.x11.XdndActionMove) { action = GLFW_DRAG_OPERATION_MOVE; } else if (action_atom == _glfw.x11.XdndActionLink) { action = GLFW_DRAG_OPERATION_GENERIC; } } GLFWDragEvent ev = {.type = GLFW_DRAG_FINSHED, .action = action}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); } // Add a pending data request static bool add_pending_request(const char *mime_type, Window requestor, Atom property, Atom target) { if (_glfw.x11.drag.pending_count >= _glfw.x11.drag.pending_capacity) { size_t new_capacity = _glfw.x11.drag.pending_capacity == 0 ? 8 : _glfw.x11.drag.pending_capacity * 2; void *new_ptr = realloc(_glfw.x11.drag.pending_requests, new_capacity * sizeof(_glfw.x11.drag.pending_requests[0])); if (!new_ptr) return false; _glfw.x11.drag.pending_requests = new_ptr; _glfw.x11.drag.pending_capacity = new_capacity; } size_t idx = _glfw.x11.drag.pending_count++; _glfw.x11.drag.pending_requests[idx].mime_type = _glfw_strdup(mime_type); _glfw.x11.drag.pending_requests[idx].requestor = requestor; _glfw.x11.drag.pending_requests[idx].property = property; _glfw.x11.drag.pending_requests[idx].target = target; _glfw.x11.drag.pending_requests[idx].inflight = true; return _glfw.x11.drag.pending_requests[idx].mime_type != NULL; } // Send drag data via XChangeProperty static void send_drag_data(const char *mime_type, const char *data, size_t data_sz, Window requestor, Atom property, Atom target) { (void)mime_type; // Parameter kept for consistency with other platforms if (data && data_sz > 0) { XChangeProperty(_glfw.x11.display, requestor, property, target, 8, PropModeReplace, (unsigned char*)data, data_sz); } else { // Send empty property to indicate no data XChangeProperty(_glfw.x11.display, requestor, property, target, 8, PropModeReplace, (unsigned char*)"", 0); } } int _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) { // If a drag is already active, cancel it first if (_glfw.x11.drag.active) { _glfwFreeDragSourceData(); } // Convert MIME types to atoms _glfw.x11.drag.type_count = _glfw.drag.item_count; _glfw.x11.drag.type_atoms = calloc(_glfw.drag.item_count, sizeof(Atom)); if (!_glfw.x11.drag.type_atoms) { return ENOMEM; } for (size_t i = 0; i < _glfw.drag.item_count; i++) { _glfw.x11.drag.type_atoms[i] = XInternAtom(_glfw.x11.display, _glfw.drag.items[i].mime_type, False); } // Determine action atom based on operations if (_glfw.drag.operations & GLFW_DRAG_OPERATION_COPY) { _glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy; } else if (_glfw.drag.operations & GLFW_DRAG_OPERATION_MOVE) { _glfw.x11.drag.action_atom = _glfw.x11.XdndActionMove; } else { _glfw.x11.drag.action_atom = _glfw.x11.XdndActionCopy; } _glfw.x11.drag.source_window = window->x11.handle; _glfw.x11.drag.active = true; _glfw.x11.drag.current_target = None; _glfw.x11.drag.waiting_for_status = false; _glfw.x11.drag.accepted = false; // Initialize thumbnail fields _glfw.x11.drag.thumbnail_window = None; _glfw.x11.drag.thumbnail_pixmap = None; _glfw.x11.drag.thumbnail_gc = None; // Get current cursor position for thumbnail placement Window root_return, child_return; int root_x, root_y, win_x, win_y; unsigned int mask_return; XQueryPointer(_glfw.x11.display, _glfw.x11.root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return); // Create thumbnail window if thumbnail is provided if (!create_drag_thumbnail(thumbnail, root_x + 10, root_y + 10)) { // Thumbnail creation failed, but continue with drag operation _glfw.x11.drag.thumbnail_window = None; _glfw.x11.drag.thumbnail_pixmap = None; _glfw.x11.drag.thumbnail_gc = None; } // Grab the pointer to track drag motion int result = XGrabPointer(_glfw.x11.display, window->x11.handle, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); if (result != GrabSuccess) { free(_glfw.x11.drag.type_atoms); _glfw.x11.drag.type_atoms = NULL; _glfw.x11.drag.type_count = 0; _glfw.x11.drag.active = false; // Clean up thumbnail if it was created if (_glfw.x11.drag.thumbnail_gc != None) { XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); _glfw.x11.drag.thumbnail_gc = None; } if (_glfw.x11.drag.thumbnail_pixmap != None) { XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_pixmap = None; } if (_glfw.x11.drag.thumbnail_window != None) { XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); _glfw.x11.drag.thumbnail_window = None; } return EIO; } // Set ourselves as the XdndSelection owner XSetSelectionOwner(_glfw.x11.display, _glfw.x11.XdndSelection, window->x11.handle, CurrentTime); return 0; } void _glfwPlatformFreeDragSourceData(void) { if (_glfw.x11.drag.active) { // Send leave to current target if (_glfw.x11.drag.current_target != None) { send_xdnd_leave(_glfw.x11.drag.current_target); } // Ungrab the pointer XUngrabPointer(_glfw.x11.display, CurrentTime); _glfw.x11.drag.active = false; _glfw.x11.drag.current_target = None; } // Clean up thumbnail resources if (_glfw.x11.drag.thumbnail_gc != None) { XFreeGC(_glfw.x11.display, _glfw.x11.drag.thumbnail_gc); _glfw.x11.drag.thumbnail_gc = None; } if (_glfw.x11.drag.thumbnail_pixmap != None) { XFreePixmap(_glfw.x11.display, _glfw.x11.drag.thumbnail_pixmap); _glfw.x11.drag.thumbnail_pixmap = None; } if (_glfw.x11.drag.thumbnail_window != None) { XDestroyWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); _glfw.x11.drag.thumbnail_window = None; } // Free type atoms if (_glfw.x11.drag.type_atoms) { free(_glfw.x11.drag.type_atoms); _glfw.x11.drag.type_atoms = NULL; } _glfw.x11.drag.type_count = 0; // Free pending requests if (_glfw.x11.drag.pending_requests) { for (size_t i = 0; i < _glfw.x11.drag.pending_count; i++) { free((void*)_glfw.x11.drag.pending_requests[i].mime_type); } free(_glfw.x11.drag.pending_requests); _glfw.x11.drag.pending_requests = NULL; } _glfw.x11.drag.pending_count = 0; _glfw.x11.drag.pending_capacity = 0; } int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { if (!_glfw.x11.drag.active) return 0; if (!thumbnail || !thumbnail->pixels || thumbnail->width <= 0 || thumbnail->height <= 0) return 0; if (_glfw.x11.drag.thumbnail_window == None) { // No thumbnail window yet; query cursor position and create one Window root_return, child_return; int root_x, root_y, win_x, win_y; unsigned int mask_return; XQueryPointer(_glfw.x11.display, _glfw.x11.root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return); if (!create_drag_thumbnail(thumbnail, root_x + 10, root_y + 10)) return EIO; return 0; } // Resize the existing window to match the new thumbnail dimensions XResizeWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window, thumbnail->width, thumbnail->height); if (!render_drag_thumbnail_to_pixmap(thumbnail)) return EIO; XClearWindow(_glfw.x11.display, _glfw.x11.drag.thumbnail_window); XFlush(_glfw.x11.display); return 0; } int _glfwPlatformDragDataReady(const char *mime_type) { // Find the pending request for this MIME type for (size_t i = 0; i < _glfw.x11.drag.pending_count; i++) { if (_glfw.x11.drag.pending_requests[i].inflight && strcmp(_glfw.x11.drag.pending_requests[i].mime_type, mime_type) == 0) { // Get the drag event with data from the application _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (!window) return ENODEV; GLFWDragEvent ev = { .type = GLFW_DRAG_DATA_REQUEST, .mime_type = mime_type, .data = NULL, .data_sz = 0, .err_num = 0 }; _glfwInputDragSourceRequest(window, &ev); // Send the data send_drag_data(mime_type, ev.data, ev.data_sz, _glfw.x11.drag.pending_requests[i].requestor, _glfw.x11.drag.pending_requests[i].property, _glfw.x11.drag.pending_requests[i].target); // Send SelectionNotify XEvent reply; memset(&reply, 0, sizeof(reply)); reply.xselection.type = SelectionNotify; reply.xselection.display = _glfw.x11.display; reply.xselection.requestor = _glfw.x11.drag.pending_requests[i].requestor; reply.xselection.selection = _glfw.x11.XdndSelection; reply.xselection.target = _glfw.x11.drag.pending_requests[i].target; reply.xselection.property = _glfw.x11.drag.pending_requests[i].property; reply.xselection.time = CurrentTime; XSendEvent(_glfw.x11.display, _glfw.x11.drag.pending_requests[i].requestor, False, 0, &reply); _glfw.x11.drag.pending_requests[i].inflight = false; break; } } return 0; } // }}} ================================================ FILE: glfw/xkb-compat-shim.h ================================================ /* * 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; } ================================================ FILE: glfw/xkb_glfw.c ================================================ //======================================================================== // 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 if (!xkb->keymap) return false; 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) { if (!xkb->keymap) return; 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) { if (!xkb->keymap) return; 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); } } ================================================ FILE: glfw/xkb_glfw.h ================================================ //======================================================================== // 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); ================================================ FILE: go.mod ================================================ module github.com/kovidgoyal/kitty go 1.26.0 toolchain go1.26.1 require ( github.com/ALTree/bigfloat v0.2.0 github.com/alecthomas/chroma/v2 v2.23.1 github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/dlclark/regexp2 v1.11.5 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b github.com/klauspost/compress v1.18.4 github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 github.com/kovidgoyal/go-parallel v1.1.1 github.com/kovidgoyal/go-shm v1.0.0 github.com/kovidgoyal/imaging v1.8.20 github.com/nwaples/rardecode/v2 v2.2.2 github.com/seancfoley/ipaddress-go v1.7.1 github.com/shirou/gopsutil/v4 v4.26.2 github.com/ulikunitz/xz v0.5.15 github.com/zeebo/xxh3 v1.1.0 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b golang.org/x/image v0.36.0 golang.org/x/sys v0.42.0 golang.org/x/text v0.34.0 howett.net/plist v1.0.1 ) // Uncomment the following to use a local checkout of dbus // replace github.com/kovidgoyal/dbus => ../dbus // Uncomment the following to use a local checkout of imaging // replace github.com/kovidgoyal/imaging => ../imaging require ( github.com/ebitengine/purego v0.10.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/seancfoley/bintree v1.3.1 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect ) ================================================ FILE: go.sum ================================================ github.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.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/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/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 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/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 h1:rMY/hWfcVzBm6BLX6YLA+gLJEpuXBed/VP6YEkXt8R4= github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1/go.mod h1:RbNG3Q1g6GUy1/WzWVx+S24m7VKyvl57vV+cr2hpt50= github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811uiUXyI7ok= github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw= github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo= github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds= github.com/kovidgoyal/imaging v1.8.20 h1:74GZ7C2rIm3rqmGEjK1GvvPOOnJ0SS5iDOa6Flfo0b0= github.com/kovidgoyal/imaging v1.8.20/go.mod h1:d3phGYkTChGYkY4y++IjpHgUGhWGELDc2NEQAqxwZZg= 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/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU= github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= 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-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/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/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 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.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= 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.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= 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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= 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= ================================================ FILE: key_encoding.json ================================================ { "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" } ================================================ FILE: kittens/__init__.py ================================================ ================================================ FILE: kittens/ask/__init__.py ================================================ ================================================ FILE: kittens/ask/choices.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "fmt" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/kitty/tools/wcswidth" "io" "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 } ================================================ FILE: kittens/ask/get_line.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "fmt" "io" "os" "path/filepath" "time" "github.com/kovidgoyal/kitty/kittens/choose_files" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print func get_line(o *Options, complete_file_names bool) (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 complete_file_names { ropts.Completer = choose_files.FilePromptCompleter(nil) } 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 } ================================================ FILE: kittens/ask/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "errors" "fmt" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" ) var _ = fmt.Print type Response struct { Items []string `json:"items"` Response string `json:"response"` } func show_message(msg, title string) { if title != "" { fmt.Printf("%s", loop.EscapeCodeToSetWindowTitle(title)) } 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, o.Title) 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, o.Title) result.Response, err = get_line(o, false) if err != nil { return 1, err } case "file": show_message(o.Message, o.Title) result.Response, err = get_line(o, true) 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) } ================================================ FILE: kittens/ask/main.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from kitty.typing_compat import BossType, TypedDict from ..tui.handler import result_handler def option_text() -> str: return '''\ --type -t choices=line,yesno,choices,password,file 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' ================================================ FILE: kittens/broadcast/__init__.py ================================================ ================================================ FILE: kittens/broadcast/main.py ================================================ #!/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_compat 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' ================================================ FILE: kittens/choose_files/__init__.py ================================================ def syntax_aliases(x: str) -> dict[str, str]: ans = {} for x in x.split(): k, _, v = x.partition(':') ans[k] = v return ans ================================================ FILE: kittens/choose_files/archive.go ================================================ package choose_files import ( "archive/tar" "compress/bzip2" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "github.com/klauspost/compress/gzip" "github.com/klauspost/compress/zip" "github.com/klauspost/compress/zstd" "github.com/nwaples/rardecode/v2" "github.com/ulikunitz/xz" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print func IsSupportedArchiveFile(abspath string) bool { name := strings.ToLower(filepath.Base(abspath)) ext := filepath.Ext(name) switch ext { case ".zip", ".tgz", ".tbz2", ".tzst", ".txz", ".rar": return true case ".gz", ".bz2", ".zst", ".xz": name = name[:len(name)-len(ext)] ext = filepath.Ext(name) return ext == ".tar" default: return false } } type archive_preview struct { path string metadata fs.FileInfo WakeupMainThread func() bool ch chan *MessagePreview mp *MessagePreview } func displayFilename(s string) string { if isPrintableUTF8(s) { return s } return fmt.Sprintf("%X", utils.UnsafeStringToBytes(s)) } func isPrintableUTF8(s string) bool { for _, r := range s { if r == '\uFFFD' { // replacement char return false } } return true } func (p *archive_preview) render() { name := strings.ToLower(filepath.Base(p.path)) ext := filepath.Ext(name) names := []string{""} populate_tar := func(r io.Reader) { tr := tar.NewReader(r) for len(names) < 500 { hdr, err := tr.Next() if err != nil { break } names = append(names, displayFilename(hdr.Name)) } } switch ext { case ".zip": r, err := zip.OpenReader(p.path) if err == nil || errors.Is(err, zip.ErrInsecurePath) { defer r.Close() for _, f := range r.File { if f.NonUTF8 { names = append(names, displayFilename(f.Name)) } else { names = append(names, f.Name) } } } case ".rar": r, err := rardecode.OpenReader(p.path, rardecode.SkipCheck) if err == nil { defer r.Close() for len(names) < 500 { hdr, err := r.Next() if err != nil { break } names = append(names, displayFilename(hdr.Name)) } } case ".gz", ".tgz": if f, err := os.Open(p.path); err == nil { defer f.Close() if gz, err := gzip.NewReader(f); err == nil { defer gz.Close() populate_tar(gz) } } case ".xz", ".txz": if f, err := os.Open(p.path); err == nil { defer f.Close() if gz, err := xz.NewReader(f); err == nil { populate_tar(gz) } } case ".bz2", ".tbz2": if f, err := os.Open(p.path); err == nil { defer f.Close() populate_tar(bzip2.NewReader(f)) } case ".zst", ".tzst": if f, err := os.Open(p.path); err == nil { defer f.Close() if gz, err := zstd.NewReader(f); err == nil { defer gz.Close() populate_tar(gz) } } } mp := *p.mp mp.trailers = append(mp.trailers, names...) p.ch <- &mp p.WakeupMainThread() } func (p *archive_preview) IsReady() bool { return true } func (p *archive_preview) Unload() {} func (p *archive_preview) IsValidForColorScheme(light bool) bool { return true } func (p *archive_preview) String() string { return fmt.Sprintf("ArchivePreview{%s}", p.path) } func (p *archive_preview) Render(h *Handler, x, y, width, height int) { if p.ch != nil { select { case mp := <-p.ch: p.mp = mp close(p.ch) p.ch = nil default: } } p.mp.Render(h, x, y, width, height) } func NewArchivePeview( abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool, ) Preview { mp := NewFileMetadataPreview(abspath, metadata) ans := &archive_preview{ path: abspath, metadata: metadata, WakeupMainThread: WakeupMainThread, ch: make(chan *MessagePreview, 1), mp: mp, } go ans.render() return ans } ================================================ FILE: kittens/choose_files/calibre.go ================================================ package choose_files import ( "bufio" "encoding/json" "errors" "fmt" "io" "io/fs" "os" "os/exec" "path/filepath" "slices" "strings" "sync" "sync/atomic" "time" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/kitty/tools/utils/images" "golang.org/x/sys/unix" ) var _ = fmt.Print var calibre_needs_cleanup atomic.Bool var calibre_server_load_finished atomic.Bool type calibre_server_process struct { proc *exec.Cmd stdout io.ReadCloser stdin io.WriteCloser file_extensions *utils.Set[string] } type CalibreMetadata struct { Title string `json:"title"` Authors []string `json:"authors"` Series string `json:"series"` Series_index float64 `json:"series_index"` Tags []string `json:"tags"` Rating float64 `json:"rating"` Published time.Time `json:"pubdate"` Timestamp time.Time `json:"timestamp"` } type CalibreResponse struct { Path string `json:"path"` Filetypes []string `json:"filetypes"` Cover string `json:"cover"` Error string `json:"error"` Metadata CalibreMetadata `json:"metadata"` } func ReadLineWithTimeout(r io.Reader, timeout time.Duration) (string, error) { type result struct { line string err error } ch := make(chan result, 1) go func() { br := bufio.NewReader(r) line, err := br.ReadString('\n') ch <- result{strings.TrimRight(line, "\n"), err} }() select { case res := <-ch: return res.line, res.err case <-time.After(timeout): return "", os.ErrDeadlineExceeded } } var calibre_server = sync.OnceValues(func() (ans *calibre_server_process, err error) { defer func() { calibre_server_load_finished.Store(true) }() ans = &calibre_server_process{} ans.proc = exec.Command("calibre-debug", "-c", "from calibre.ebooks.metadata.cli import *; simple_metadata_server()") ans.proc.Stderr = nil if ans.stdout, err = ans.proc.StdoutPipe(); err != nil { return nil, err } if ans.stdin, err = ans.proc.StdinPipe(); err != nil { ans.stdout.Close() return nil, err } ans.proc.SysProcAttr = &unix.SysProcAttr{Setsid: true} if err = ans.proc.Start(); err != nil { err = fmt.Errorf("calibre-debug executable not found in PATH, you must install the calibre program to preview these file types: %w", err) return } calibre_needs_cleanup.Store(true) payload, _ := json.Marshal(map[string]string{"path": ""}) if _, err = ans.stdin.Write(append(payload, '\n')); err != nil { err = fmt.Errorf("error writing to calibre metadata server: %w", err) return } line, err := ReadLineWithTimeout(ans.stdout, 2*time.Second) if err != nil { if errors.Is(err, os.ErrDeadlineExceeded) { err = fmt.Errorf("timed out waiting for response from calibre metadata server, make sure you are using calibre version >= 8.16") } else { err = fmt.Errorf("error reading from calibre metadata server: %w", err) } return } var r CalibreResponse if err = json.Unmarshal([]byte(line), &r); err != nil { err = fmt.Errorf("unexpected response from calibre metadata server: %#v", line) return } ans.file_extensions = utils.NewSet[string](len(r.Filetypes)) for _, ft := range r.Filetypes { ans.file_extensions.Add("." + ft) } return }) func calibre_cleanup() { if !calibre_needs_cleanup.Load() { return } calibre_needs_cleanup.Store(false) calibre, _ := calibre_server() if calibre.stdin != nil { calibre.stdin.Close() } if calibre.stdout != nil { calibre.stdout.Close() } if calibre.proc != nil { calibre.proc.Wait() } } func IsSupportedByCalibre(path string) bool { ext := strings.ToLower(filepath.Ext(filepath.Base(path))) if len(ext) > 1 { if calibre_server_load_finished.Load() { if calibre, err := calibre_server(); err == nil { return calibre.file_extensions.Has(ext) } } else { // we dont want to delay the render loop waiting for data from // calibre-server as it causes flicker because the atomic update // timeout expires, so use a default set of extensions. go func() { _, _ = calibre_server() }() return slices.Contains([]string{"cb7", "azw3", "kepub", "zip", "htmlz", "rar", "lrf", "cbr", "tpz", "azw1", "mobi", "lit", "pdf", "updb", "pml", "pobi", "fbz", "azw", "fb2", "cbc", "rtf", "snb", "opf", "txt", "epub", "oebzip", "txtz", "imp", "docx", "odt", "pdb", "rb", "prc", "html", "chm", "pmlz", "cbz", "lrx", "azw4"}, ext[1:]) } } return false } const CALIBRE_METADATA_KEY = "calibre-metadata.json" type calibre_renderer int func (c calibre_renderer) Unmarshall(m map[string]string) (any, error) { data, err := os.ReadFile(m[CALIBRE_METADATA_KEY]) if err != nil { return nil, err } var ans CalibreResponse if err = json.Unmarshal(data, &ans); err != nil { return nil, err } return &ans, nil } func (c calibre_renderer) Render(path string) (m map[string][]byte, mi metadata, img *images.ImageData, err error) { cpath, err := os.CreateTemp("", "") if err != nil { return } defer func() { cpath.Close() os.Remove(cpath.Name()) }() calibre, err := calibre_server() if err != nil { return } payload, err := json.Marshal(map[string]string{"path": path, "cover": cpath.Name()}) if err != nil { return } calibre.stdin.Write(append(payload, '\n')) line, err := ReadLineWithTimeout(calibre.stdout, 30*time.Second) if err != nil { return } lb := []byte(line) var reply CalibreResponse if err = json.Unmarshal(lb, &reply); err != nil { return } if reply.Cover == cpath.Name() { var ip ImagePreviewRenderer if m, mi, img, err = ip.Render(cpath.Name()); err != nil { return } } else { m = make(map[string][]byte) } mi.custom = &reply m[CALIBRE_METADATA_KEY] = lb return } func (c calibre_renderer) ShowMetadata(h *Handler, s ShowData) (offset int) { w := func(text string, center bool) { if s.height > offset { offset += h.render_wrapped_text_in_region(text, s.x, s.y+offset, s.width, s.height-offset, center) } } ext := filepath.Ext(s.abspath) text := fmt.Sprintf("%s: %s", ext, humanize.Bytes(uint64(s.metadata.Size()))) icon := icons.IconForPath(s.abspath) w(icon+" "+text, true) r := s.custom_metadata.custom.(*CalibreResponse) w("Title: "+r.Metadata.Title, false) w("Authors: "+strings.Join(r.Metadata.Authors, " & "), false) if r.Metadata.Series != "" { w(fmt.Sprintf("Series: Book %g of %s", r.Metadata.Series_index, r.Metadata.Series), false) } if len(r.Metadata.Tags) > 0 { w("Tags: "+strings.Join(r.Metadata.Authors, ", "), false) } return } func (c calibre_renderer) String() string { return "Calibre" } func NewCalibrePreview( abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool, ) Preview { c := calibre_renderer(0) if ans, err := NewImagePreview(abspath, metadata, opts, WakeupMainThread, c); err == nil { return ans } else { return NewErrorPreview(err) } } ================================================ FILE: kittens/choose_files/cmd_preview.go ================================================ package choose_files import ( "bytes" "encoding/json" "fmt" "io/fs" "os" "os/exec" "path/filepath" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/kitty/tools/utils/images" "golang.org/x/sys/unix" ) var _ = fmt.Print const CMD_METADATA_KEY = "cmd-metadata.json" type cmd_renderer struct { cmdline []string } func (c cmd_renderer) String() string { return c.cmdline[0] } type CmdResult struct { Lines []string `json:"lines"` Image string `json:"image"` TitleExtra string `json:"title_extra"` } func (c cmd_renderer) Render(path string) (m map[string][]byte, mi metadata, img *images.ImageData, err error) { cmdline := append(c.cmdline, path) cmd := exec.Command(cmdline[0], cmdline[1:]...) cmd.Stdin = nil cmd.SysProcAttr = &unix.SysProcAttr{Setsid: true} var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err = cmd.Run(); err != nil { err = fmt.Errorf("failed to run %v to read metadata from %s with error: %w and stderr: %s", c.cmdline, path, err, stderr.String()) return } var md CmdResult if err = json.Unmarshal(stdout.Bytes(), &md); err != nil { err = fmt.Errorf("could not decode JSON response from %v for %s: %w", c.cmdline, path, err) } if md.Image != "" { var ip ImagePreviewRenderer if m, mi, img, err = ip.Render(md.Image); err != nil { return } } mi.custom = &md return } func (c cmd_renderer) Unmarshall(m map[string]string) (any, error) { data, err := os.ReadFile(m[CMD_METADATA_KEY]) if err != nil { return nil, err } var ans CmdResult if err = json.Unmarshal(data, &ans); err != nil { return nil, err } return &ans, nil } func (c cmd_renderer) ShowMetadata(h *Handler, s ShowData) (offset int) { w := func(text string, center bool) { if s.height > offset { offset += h.render_wrapped_text_in_region(text, s.x, s.y+offset, s.width, s.height-offset, center) } } ext := filepath.Ext(s.abspath) r := s.custom_metadata.custom.(*CmdResult) text := fmt.Sprintf("%s: %s%s", ext, humanize.Bytes(uint64(s.metadata.Size())), r.TitleExtra) icon := icons.IconForPath(s.abspath) w(icon+" "+text, true) for _, line := range r.Lines { w(line, false) } h.lp.QueueWriteString("\x1b[m") // reset SGR attributes return } func NewCmdPreview( abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool, p previewer, ) Preview { c := cmd_renderer{p.cmdline} if ans, err := NewImagePreview(abspath, metadata, opts, WakeupMainThread, c); err == nil { return ans } else { return NewErrorPreview(err) } } ================================================ FILE: kittens/choose_files/collection.go ================================================ package choose_files import ( "fmt" "io/fs" "slices" "sync" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print type CollectionIndex struct { Slice, Pos int } func (c CollectionIndex) Compare(o CollectionIndex) int { if c.Slice == o.Slice { return c.Pos - o.Pos } return c.Slice - o.Slice } func (c CollectionIndex) Less(o CollectionIndex) bool { return c.Slice < o.Slice || (c.Slice == o.Slice && c.Pos < o.Pos) } func (c *CollectionIndex) NextSlice() { c.Slice++ c.Pos = 0 } type ResultCollection struct { slices [][]ResultItem append_idx CollectionIndex batch_size int } func NewResultCollection(batch_size int) (ans *ResultCollection) { batch_size = max(1, batch_size) return &ResultCollection{ batch_size: batch_size, slices: [][]ResultItem{make([]ResultItem, batch_size)}, } } func (c *ResultCollection) Len() int { return c.batch_size*(len(c.slices)-1) + c.append_idx.Pos } func (c *ResultCollection) NextAppendPointer() (ans *ResultItem) { s := c.slices[c.append_idx.Slice] ans = &s[c.append_idx.Pos] if c.append_idx.Pos+1 < len(s) { c.append_idx.Pos++ } else if c.append_idx.Slice+1 < len(c.slices) { c.append_idx.NextSlice() } else { c.slices = append(c.slices, make([]ResultItem, 4096)) c.append_idx.NextSlice() } return } func (c *ResultCollection) Batch(offset *CollectionIndex) (ans []ResultItem) { if offset.Slice == c.append_idx.Slice { if offset.Pos < c.append_idx.Pos { ans = c.slices[offset.Slice][offset.Pos:c.append_idx.Pos] offset.Pos = c.append_idx.Pos } } else if offset.Slice < c.append_idx.Slice { ans = c.slices[offset.Slice][offset.Pos:] offset.NextSlice() } return } func (c *ResultCollection) NextDir(offset *CollectionIndex) (ans string, ignore_files []ignore_file_with_prefix) { for ans == "" && offset.Compare(c.append_idx) < 0 { if c.slices[offset.Slice][offset.Pos].ftype&fs.ModeDir != 0 { ans = c.slices[offset.Slice][offset.Pos].text ignore_files = c.slices[offset.Slice][offset.Pos].ignore_files } offset.Pos++ if offset.Pos >= len(c.slices[offset.Slice]) { offset.NextSlice() } } return } type SortedResults struct { slices [][]*ResultItem mutex sync.Mutex len int } func NewSortedResults() *SortedResults { return &SortedResults{} } func (s *SortedResults) lock() { s.mutex.Lock() } func (s *SortedResults) unlock() { s.mutex.Unlock() } func (s *SortedResults) Len() int { s.lock() defer s.unlock() return s.len } func (s *SortedResults) Clear() { s.lock() defer s.unlock() s.slices = nil s.len = 0 } func (s *SortedResults) At(pos CollectionIndex) (ans *ResultItem) { s.lock() defer s.unlock() if pos.Slice < len(s.slices) { s := s.slices[pos.Slice] if pos.Pos < len(s) { ans = s[pos.Pos] } } return } func (s *SortedResults) RenderedMatches(pos CollectionIndex, max_num int) (ans []*ResultItem) { s.lock() defer s.unlock() if pos.Slice >= len(s.slices) { return } if max_num < 0 { max_num = s.len } ans = make([]*ResultItem, 0, max_num) for ; pos.Slice < len(s.slices) && max_num > 0; pos.NextSlice() { sl := s.slices[pos.Slice] if pos.Pos >= len(sl) { continue } sl = sl[pos.Pos:min(len(sl), pos.Pos+max_num)] ans = append(ans, sl...) max_num -= len(sl) } return } func (s *SortedResults) merge_slice(idx int, sl []*ResultItem) { sz := len(s.slices[idx]) maxs := sl[len(sl)-1].score limit := idx + 1 for limit < len(s.slices) { q := s.slices[limit] if q[0].score > maxs { break } sz += len(q) limit++ } ans := make([]*ResultItem, 0, sz) a := 0 b := CollectionIndex{Slice: idx} ss := s.slices[b.Slice] for a < len(sl) { if sl[a].score <= ss[b.Pos].score { ans = append(ans, sl[a]) a++ } else { ans = append(ans, ss[b.Pos]) b.Pos++ if b.Pos >= len(ss) { b.NextSlice() if b.Slice >= limit { break } ss = s.slices[b.Slice] } } } ans = append(ans, sl[a:]...) for ; b.Slice < limit; b.NextSlice() { ans = append(ans, s.slices[b.Slice][b.Pos:]...) } s.slices = slices.Replace(s.slices, idx, limit, ans) } func (s *SortedResults) AddSortedSlice(sl []*ResultItem) { if len(sl) == 0 { return } s.lock() defer s.unlock() s.len += len(sl) if len(s.slices) == 0 { s.slices = append(s.slices, sl) return } sl_min, sl_max := sl[0].score, sl[len(sl)-1].score for i, q := range s.slices { switch { case sl_max <= q[0].score: s.slices = slices.Insert(s.slices, i, sl) return case sl_min >= q[len(q)-1].score: continue default: s.merge_slice(i, sl) return } } s.slices = append(s.slices, sl) } func (s *SortedResults) Apply(first, last CollectionIndex, action func(*ResultItem) (keep_going bool)) { s.lock() defer s.unlock() if first.Slice >= len(s.slices) || first.Pos >= len(s.slices[first.Slice]) { return } amt := utils.IfElse(first.Less(last), 1, -1) var did_wrap bool for { if !action(s.slices[first.Slice][first.Pos]) { break } if first.Compare(last) == 0 { break } first, did_wrap = s.increment_with_wrap_around(first, amt) if did_wrap { break } } } func (s *SortedResults) Closest(idx CollectionIndex, matches func(*ResultItem) bool) *CollectionIndex { s.lock() defer s.unlock() if idx.Slice >= len(s.slices) || idx.Pos >= len(s.slices[idx.Slice]) { return nil } type result struct { idx CollectionIndex count int } var a, b result iterate := func(idx CollectionIndex, amt int, result *result) { var did_wrap bool var count int result.count = -1 for { idx, did_wrap = s.increment_with_wrap_around(idx, amt) if did_wrap { break } count++ if matches(s.slices[idx.Slice][idx.Pos]) { result.idx = idx result.count = count break } } } go func() { iterate(idx, 1, &a) }() go func() { iterate(idx, -1, &a) }() if a.count < 0 && b.count < 0 { return nil } return utils.IfElse(a.count < b.count, &b.idx, &a.idx) } func (s *SortedResults) IncrementIndexWithWrapAroundAndCheck(idx CollectionIndex, amt int) (ans CollectionIndex, did_wrap bool) { s.lock() defer s.unlock() return s.increment_with_wrap_around(idx, amt) } func (s *SortedResults) IncrementIndexWithWrapAround(idx CollectionIndex, amt int) CollectionIndex { s.lock() defer s.unlock() ans, _ := s.increment_with_wrap_around(idx, amt) return ans } func (s *SortedResults) increment_with_wrap_around(idx CollectionIndex, amt int) (CollectionIndex, bool) { did_wrap := false if amt > 0 { for amt > 0 { if delta := min(amt, len(s.slices[idx.Slice])-1-idx.Pos); delta > 0 { idx.Pos += delta amt -= delta } else { idx.NextSlice() if idx.Slice >= len(s.slices) { idx = CollectionIndex{} // wraparound did_wrap = true } amt-- } } } else { // we use separate code for negative increment instead of doing // increment = len - increment as it is faster in the common case of // increment much smaller than len amt *= -1 for amt > 0 { if idx.Pos > 0 { delta := min(amt, idx.Pos) amt -= delta idx.Pos -= delta } else { if idx.Slice == 0 { idx = CollectionIndex{Slice: len(s.slices) - 1, Pos: len(s.slices[len(s.slices)-1]) - 1} did_wrap = true } else { idx.Slice-- idx.Pos = len(s.slices[idx.Slice]) - 1 } amt-- } } } return idx, did_wrap } // Return a - b func (s *SortedResults) SignedDistance(a, b CollectionIndex) (ans int) { s.lock() defer s.unlock() return s.signed_distance(a, b) } // Return a - b func (s *SortedResults) signed_distance(a, b CollectionIndex) (ans int) { mult := -1 if b.Less(a) { a, b = b, a mult = 1 } limit := min(b.Slice, len(s.slices)) for ; a.Slice < limit; a.NextSlice() { ans += len(s.slices[a.Slice]) - a.Pos } return mult * (ans + (b.Pos - a.Pos)) } // Return |a - b| func (s *SortedResults) distance(a, b CollectionIndex) (ans int) { if b.Less(a) { a, b = b, a } limit := min(b.Slice, len(s.slices)) for ; a.Slice < limit; a.NextSlice() { ans += len(s.slices[a.Slice]) - a.Pos } return ans + (b.Pos - a.Pos) } func (s *SortedResults) SplitIntoColumns(calc_num_cols func(int) int, num_per_column, num_before_current int, current CollectionIndex) (ans [][]*ResultItem, num_before int, first_idx CollectionIndex) { s.lock() defer s.unlock() num_cols := calc_num_cols(s.len) total := num_cols * num_per_column if total < 1 { return nil, 0, CollectionIndex{} } num_before = min(total-1, num_before_current) idx, did_wrap := s.increment_with_wrap_around(current, -num_before) last_slice := s.slices[len(s.slices)-1] last := CollectionIndex{Slice: len(s.slices) - 1, Pos: len(last_slice) - 1} if did_wrap { idx = CollectionIndex{} } else if s.distance(idx, last) < total-1 { if idx, did_wrap = s.increment_with_wrap_around(last, 1-total); did_wrap { idx = CollectionIndex{} } } first_idx = idx num_before = s.distance(idx, current) // fmt.Printf("111111 idx: %v current: %v num_before: %d\n", idx, current, num_before) ans = make([][]*ResultItem, num_cols) for colidx := range len(ans) { col := make([]*ResultItem, 0, num_per_column) for len(col) < num_per_column && idx.Slice < len(s.slices) { ss := s.slices[idx.Slice] limit := min(len(ss), idx.Pos+num_per_column-len(col)) col = append(col, ss[idx.Pos:limit]...) idx.Pos = limit if idx.Pos >= len(ss) { idx.NextSlice() if idx.Slice >= len(s.slices) { break } } } ans[colidx] = col } return } ================================================ FILE: kittens/choose_files/ffmpeg.go ================================================ package choose_files import ( "bytes" "encoding/json" "fmt" "io/fs" "os" "os/exec" "path/filepath" "strconv" "sync" "time" "github.com/hako/durafmt" "github.com/kovidgoyal/imaging/magick" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/kitty/tools/utils/images" "golang.org/x/sys/unix" ) var _ = fmt.Print const FFMPEG_METADATA_KEY = "ffmpeg-metadata.json" type ffmpeg_renderer int var ( video_width = 480 video_fps = 10 video_duration = 5.0 video_encoding_quality = 75 ) func ffmpeg_thumbnail_cmd(path, outpath string) *exec.Cmd { return exec.Command( "ffmpeg", "-loglevel", "fatal", "-y", "-an", "-sn", "-dn", "-i", path, "-t", fmt.Sprintf("%f", video_duration), "-vf", fmt.Sprintf("fps=%d,scale=%d:-1:flags=lanczos", video_fps, video_width), "-c:v", "libwebp", "-lossless", "0", "-compression_level", "0", "-q:v", fmt.Sprintf("%d", video_encoding_quality), "-loop", "0", "-f", "webp", outpath, ) } func ffmpeg_thumbnail(path, tempath string, wg *sync.WaitGroup) (ans *images.ImageData, err error) { defer wg.Done() cmd := ffmpeg_thumbnail_cmd(path, tempath) cmd.Stdin = nil cmd.SysProcAttr = &unix.SysProcAttr{Setsid: true} var stderr bytes.Buffer cmd.Stdout = nil cmd.Stderr = &stderr if err = cmd.Run(); err != nil { return ans, fmt.Errorf("failed to use ffmpeg to render video from %s with error: %w and stderr: %s", path, err, stderr.String()) } ans, err = images.OpenImageFromPath(tempath) return } type FFMpegFormat struct { Start_time string `json:"start_time"` Duration string `json:"duration"` Tags map[string]any `json:"tags"` } type FFMpegStream struct { Codec_type string `json:"codec_type"` Width int `json:"width"` Height int `json:"height"` } type FFMpegMetadata struct { Streams []FFMpegStream `json:"streams"` Format FFMpegFormat `json:"format"` } func ffmpeg_metadata_cmd(path string) *exec.Cmd { return exec.Command( "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", path, ) } func ffmpeg_metadata(path string, wg *sync.WaitGroup) (ans FFMpegMetadata, err error) { defer wg.Done() cmd := ffmpeg_metadata_cmd(path) cmd.Stdin = nil cmd.SysProcAttr = &unix.SysProcAttr{Setsid: true} var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err = cmd.Run(); err != nil { return ans, fmt.Errorf("failed to use ffprobe to read metadata from %s with error: %w and stderr: %s", path, err, stderr.String()) } if err = json.Unmarshal(stdout.Bytes(), &ans); err != nil { return ans, fmt.Errorf("could not decode JSON response from ffprobe for %s: %w", path, err) } return } func (c ffmpeg_renderer) Render(path string) (m map[string][]byte, mi metadata, img *images.ImageData, err error) { wg := sync.WaitGroup{} tempfile, err := os.CreateTemp(magick.TempDirInRAMIfPossible(), "kitty-choose-files-*.webp") if err != nil { return nil, mi, nil, err } defer func() { _ = os.Remove(tempfile.Name()); tempfile.Close() }() var metadata FFMpegMetadata var metadata_error error wg.Add(1) go func() { metadata, metadata_error = ffmpeg_metadata(path, &wg) }() wg.Add(1) go func() { img, err = ffmpeg_thumbnail(path, tempfile.Name(), &wg) }() wg.Wait() if metadata_error != nil { return nil, mi, nil, metadata_error } var ip ImagePreviewRenderer if m, mi, img, err = ip.Render(tempfile.Name()); err != nil { return } mi.custom = &metadata return } func (c ffmpeg_renderer) Unmarshall(m map[string]string) (any, error) { data, err := os.ReadFile(m[FFMPEG_METADATA_KEY]) if err != nil { return nil, err } var ans FFMpegMetadata if err = json.Unmarshal(data, &ans); err != nil { return nil, err } return &ans, nil } func (c ffmpeg_renderer) ShowMetadata(h *Handler, s ShowData) (offset int) { w := func(text string, center bool) { if s.height > offset { offset += h.render_wrapped_text_in_region(text, s.x, s.y+offset, s.width, s.height-offset, center) } } ext := filepath.Ext(s.abspath) text := fmt.Sprintf("%s: %s", ext, humanize.Bytes(uint64(s.metadata.Size()))) r := s.custom_metadata.custom.(*FFMpegMetadata) icon := icons.IconForPath(s.abspath) var width, height int for _, s := range r.Streams { if s.Width > 0 && s.Height > 0 { width, height = s.Width, s.Height break } } text += fmt.Sprintf(" %dx%d", width, height) w(icon+" "+text, true) st := humanize.Time(s.metadata.ModTime()) if d, perr := strconv.ParseFloat(r.Format.Duration, 64); perr == nil { duration := time.Duration(d * float64(time.Second)) st += fmt.Sprintf(", %s", durafmt.Parse(duration).LimitFirstN(1).String()) } w(st, true) return } func (c ffmpeg_renderer) String() string { return "FFMpeg" } func NewFFMpegPreview( abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool, ) Preview { c := ffmpeg_renderer(0) if ans, err := NewImagePreview(abspath, metadata, opts, WakeupMainThread, c); err == nil { return ans } else { return NewErrorPreview(err) } } ================================================ FILE: kittens/choose_files/filters.go ================================================ package choose_files import ( "fmt" "path/filepath" "strings" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print type Filter struct { Name, Type, Pattern string Match func(filename string) bool } func (f Filter) String() string { return fmt.Sprintf("%s:%s:%s", f.Type, f.Pattern, f.Name) } func (f Filter) Equal(other Filter) bool { return f.Type == other.Type && f.Pattern == other.Pattern } func NewFilter(spec string) (*Filter, error) { parts := strings.SplitN(spec, ":", 3) if len(parts) != 3 { return nil, fmt.Errorf("%#v is not a valid filter specifier, must have at least two colons", spec) } ans := &Filter{Name: parts[2], Pattern: parts[1], Type: parts[0]} if _, err := filepath.Match(ans.Pattern, "test"); err != nil { return nil, fmt.Errorf("%#v is not a valid glob pattern with error: %w", ans.Pattern, err) } if ans.Pattern != "*" && ans.Pattern != "" { switch ans.Type { case "glob": ans.Match = func(filename string) bool { m, _ := filepath.Match(ans.Pattern, filename) return m } case "mime": ans.Match = func(filename string) bool { mime := utils.GuessMimeType(filename) if mime == "" { return false } m, _ := filepath.Match(ans.Pattern, mime) return m } default: return nil, fmt.Errorf("%#v is not a valid filter type", ans.Type) } } return ans, nil } func CombinedFilter(filters ...Filter) Filter { if len(filters) == 0 { return Filter{} } for _, f := range filters { if f.Match == nil { return f } } ans := filters[0] matchers := utils.Map(func(f Filter) func(filename string) bool { return f.Match }, filters) ans.Match = func(filename string) bool { for _, m := range matchers { if m(filename) { return true } } return false } return ans } ================================================ FILE: kittens/choose_files/footer.go ================================================ package choose_files import ( "fmt" "path/filepath" "strings" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/kitty/tools/wcswidth" ) var _ = fmt.Print const HOVER_STYLE = "default fg=red" type single_line_region struct { x, width, y int id string callback func(string) error } func (h *Handler) draw_footer() (num_lines int, err error) { lines := []string{} screen_width := h.screen_size.width sctx := style.Context{AllowEscapeCodes: true} if h.state.screen == SAVE_FILE { m := h.state.filter_map h.state.filter_map = nil defer func() { h.state.filter_map = m }() } if len(h.state.filter_map)+len(h.state.selections) > 0 { buf := strings.Builder{} pos := 0 current_style := sctx.SprintFunc("italic fg=green intense") non_current_style := sctx.SprintFunc("dim") var crs []single_line_region w := func(presep, text string, sfunc func(...any) string, id string, cb func(string)) { sz := len(presep) if sz+pos >= screen_width { lines = append(lines, buf.String()) pos = 0 buf.Reset() } else { buf.WriteString(presep) pos += sz } sz = wcswidth.Stringwidth(text) if sz+pos >= screen_width { lines = append(lines, buf.String()) pos = 0 buf.Reset() } if sfunc != nil { text = sfunc(text) } buf.WriteString(text) if cb != nil { crs = append(crs, single_line_region{x: pos, width: sz - 1, y: len(lines), id: id, callback: func(filter string) error { cb(filter) h.state.redraw_needed = true return nil }}) } pos += sz } flush := func() { if s := buf.String(); s != "" { lines = append(lines, s) } pos = 0 buf.Reset() } if len(h.state.filter_map) > 0 { w("", "󰈲 Filter:", nil, "", nil) for _, name := range h.state.filter_names { var cb func(string) if name != h.state.current_filter { cb = func(filter string) { h.set_filter(filter) } } w(" ", name, utils.IfElse(name == h.state.current_filter, current_style, non_current_style), name, cb) } flush() } if len(h.state.selections) > 0 { before := len(lines) w("", " Selected:", nil, "", nil) for i, s := range h.state.selections { text := s if rel, rerr := filepath.Rel(h.state.CurrentDir(), s); rerr == nil { text = rel } w(" ", text, nil, s, func(abspath string) { h.state.ToggleSelection(abspath) }) if len(lines)-before > 2 && len(h.state.selections)-i-1 > 3 { w(" ", fmt.Sprintf("and %d more…", len(h.state.selections)-1-i), nil, "", nil) break } } flush() } offset := h.screen_size.height - len(lines) for _, cr := range crs { h.state.mouse_state.AddCellRegion(cr.id, cr.x, cr.y+offset, cr.x+cr.width, cr.y+offset, cr.callback).HoverStyle = HOVER_STYLE } } if len(lines) > 0 { h.lp.MoveCursorTo(1, h.screen_size.height-len(lines)+1) if h.state.screen == SAVE_FILE { h.lp.ClearToEndOfScreen() } h.lp.QueueWriteString(strings.Join(lines, "\r\n")) } return len(lines), err } ================================================ FILE: kittens/choose_files/graphics.go ================================================ package choose_files import ( "encoding/json" "fmt" "os" "path/filepath" "strconv" "strings" "sync/atomic" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" ) var _ = fmt.Print type placement struct { gc *graphics.GraphicsCommand x, y, x_offset int } func (p placement) equal(o placement) bool { return p.x == o.x && p.x_offset == o.x_offset && p.y == o.y } type GraphicsHandler struct { running_in_tmux bool image_id_counter, detection_file_id uint32 files_to_delete []string files_supported atomic.Bool last_rendered_image struct { p *ImagePreview width, height int image_width, image_height int } image_transmitted uint32 current_placement, last_transmitted_placement placement } func (self *GraphicsHandler) Cleanup() { for _, f := range self.files_to_delete { _ = os.Remove(f) } } func (self *GraphicsHandler) new_graphics_command() *graphics.GraphicsCommand { gc := graphics.GraphicsCommand{} if self.running_in_tmux { gc.WrapPrefix = "\033Ptmux;" gc.WrapSuffix = "\033\\" gc.EncodeSerializedDataFunc = func(x string) string { return strings.ReplaceAll(x, "\033", "\033\033") } } return &gc } func (self *GraphicsHandler) Initialize(lp *loop.Loop) error { tmux := tui.TmuxSocketAddress() if tmux != "" && tui.TmuxAllowPassthrough() == nil { self.running_in_tmux = true } if !self.running_in_tmux { g := func(t graphics.GRT_t, payload string) uint32 { self.image_id_counter++ g1 := self.new_graphics_command() g1.SetTransmission(t).SetAction(graphics.GRT_action_query).SetImageId(self.image_id_counter).SetDataWidth(1).SetDataHeight(1).SetFormat( graphics.GRT_format_rgb).SetDataSize(uint64(len(payload))) _ = g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload)) return self.image_id_counter } tf, err := images.CreateTempInRAM() if err == nil { if _, err = tf.Write([]byte{1, 2, 3}); err == nil { self.detection_file_id = g(graphics.GRT_transmission_tempfile, tf.Name()) self.files_to_delete = append(self.files_to_delete, tf.Name()) } tf.Close() } } self.image_id_counter++ return nil } func (self *GraphicsHandler) free_image_from_terminal(lp *loop.Loop) { if self.image_transmitted > 0 { self.new_graphics_command().SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_free_by_id).SetImageId(self.image_transmitted).WriteWithPayloadToLoop(lp, nil) self.image_transmitted = 0 } } func (self *GraphicsHandler) Finalize(lp *loop.Loop) { self.free_image_from_terminal(lp) } func (self *GraphicsHandler) ClearPlacements(lp *loop.Loop) { self.current_placement.gc = nil } func (self *GraphicsHandler) ApplyPlacements(lp *loop.Loop) { if self.current_placement.gc == nil { g := self.new_graphics_command() g.SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_delete_by_id).SetImageId(self.image_transmitted) _ = g.WriteWithPayloadToLoop(lp, nil) self.last_transmitted_placement.gc = nil } else { if self.last_transmitted_placement.gc == nil || !self.current_placement.equal(self.last_transmitted_placement) { lp.MoveCursorTo(self.current_placement.x, self.current_placement.y) _ = self.current_placement.gc.WriteWithPayloadToLoop(lp, nil) self.last_transmitted_placement = self.current_placement } } } func (self *GraphicsHandler) HandleGraphicsCommand(gc *graphics.GraphicsCommand) error { switch gc.ImageId() { case self.detection_file_id: if gc.ResponseMessage() == "OK" { self.files_supported.Store(true) } } return nil } func (self *GraphicsHandler) cache_resized_image(cdir, cache_key string, img *images.ImageData) (m *images.SerializableImageMetadata, cached_data map[string]string, err error) { s, frames := img.Serialize() sd, err := json.Marshal(s) if err != nil { return nil, nil, err } path := filepath.Join(cdir, fmt.Sprintf("rsz-%s-metadata.json", cache_key)) if err = os.WriteFile(path, sd, 0o600); err != nil { return nil, nil, fmt.Errorf("failed to write resized frame metadata to cache: %w", err) } cached_data = make(map[string]string, len(frames)+1) cached_data[IMAGE_METADATA_KEY] = path for i, f := range frames { path := filepath.Join(cdir, fmt.Sprintf("rsz-%s-%d", cache_key, i)) key := IMAGE_DATA_PREFIX + strconv.Itoa(i) if err = os.WriteFile(path, f, 0o600); err != nil { return nil, nil, fmt.Errorf("failed to write resized frame %d data to cache: %w", i, err) } cached_data[key] = path } m = &s return } func (self *GraphicsHandler) cached_resized_image(cdir, cache_key string) (m *images.SerializableImageMetadata, cached_data map[string]string) { path := filepath.Join(cdir, fmt.Sprintf("rsz-%s-metadata.json", cache_key)) b, err := os.ReadFile(path) if err != nil { return } var s images.SerializableImageMetadata if err = json.Unmarshal(b, &s); err != nil { return } m = &s cached_data = make(map[string]string, len(s.Frames)+1) cached_data[IMAGE_METADATA_KEY] = path for i := range len(s.Frames) { path := filepath.Join(cdir, fmt.Sprintf("rsz-%s-%d", cache_key, i)) key := IMAGE_DATA_PREFIX + strconv.Itoa(i) cached_data[key] = path } return } func transmit_by_escape_code(lp *loop.Loop, frame []byte, gc *graphics.GraphicsCommand) { atomic := lp.IsAtomicUpdateActive() lp.EndAtomicUpdate() gc.SetTransmission(graphics.GRT_transmission_direct) _ = gc.WriteWithPayloadToLoop(lp, frame) if atomic { lp.StartAtomicUpdate() } } func transmit_by_file(lp *loop.Loop, frame_path []byte, gc *graphics.GraphicsCommand) { gc.SetTransmission(graphics.GRT_transmission_file) _ = gc.WriteWithPayloadToLoop(lp, frame_path) } func (self *GraphicsHandler) transmit(lp *loop.Loop, img *images.ImageData, m *images.SerializableImageMetadata, cached_data map[string]string) { if m == nil { s := img.SerializeOnlyMetadata() m = &s } self.image_transmitted = self.image_id_counter self.last_transmitted_placement.gc = nil self.last_rendered_image.image_width = m.Width self.last_rendered_image.image_height = m.Height is_animated := len(m.Frames) > 0 frame_control_cmd := self.new_graphics_command() frame_control_cmd.SetAction(graphics.GRT_action_animate).SetImageId(self.image_transmitted) for frame_num, frame := range m.Frames { gc := self.new_graphics_command() gc.SetImageId(self.image_transmitted) gc.SetDataWidth(uint64(frame.Width)).SetDataHeight(uint64(frame.Height)) gc.SetFormat(utils.IfElse(frame.Is_opaque, graphics.GRT_format_rgb, graphics.GRT_format_rgba)) switch frame_num { case 0: gc.SetAction(graphics.GRT_action_transmit) gc.SetCursorMovement(graphics.GRT_cursor_static) default: gc.SetAction(graphics.GRT_action_frame) gc.SetGap(int32(frame.Delay_ms)) if frame.Replace { gc.SetCompositionMode(graphics.Overwrite) } if frame.Compose_onto > 0 { gc.SetOverlaidFrame(uint64(frame.Compose_onto)) } gc.SetLeftEdge(uint64(frame.Left)).SetTopEdge(uint64(frame.Top)) } if cached_data == nil { _, _, _, data := img.Frames[frame_num].Data() transmit_by_escape_code(lp, data, gc) } else { path := cached_data[IMAGE_DATA_PREFIX+strconv.Itoa(frame_num)] transmit_by_file(lp, utils.UnsafeStringToBytes(path), gc) } 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)) c.SetNumberOfLoops(1) _ = c.WriteWithPayloadToLoop(lp, nil) case 1: c := frame_control_cmd c.SetAnimationControl(2) // set animation to loading mode _ = c.WriteWithPayloadToLoop(lp, nil) } } } if is_animated { c := frame_control_cmd c.SetAnimationControl(3) // set animation to normal mode _ = c.WriteWithPayloadToLoop(lp, nil) } } func (self *GraphicsHandler) place_image(x, y, px_width, y_offset int, sz ScreenSize) { gc := self.new_graphics_command() gc.SetAction(graphics.GRT_action_display).SetImageId(self.image_transmitted).SetPlacementId(1).SetCursorMovement(graphics.GRT_cursor_static) if extra := px_width - self.last_rendered_image.image_width; extra > 1 { extra /= 2 x += extra / sz.cell_width self.current_placement.x_offset = extra % sz.cell_width gc.SetXOffset(uint64(self.current_placement.x_offset)) } gc.SetYOffset(uint64(y_offset)) self.current_placement.x, self.current_placement.y = x, y self.current_placement.gc = gc } func (self *GraphicsHandler) RenderImagePreview(h *Handler, p *ImagePreview, x, y, width, height int) { sz := h.screen_size px_width, px_height := width*sz.cell_width, height*sz.cell_height y_offset := sz.cell_height / 2 px_height -= y_offset var err error defer func() { self.last_rendered_image.p = p self.last_rendered_image.width, self.last_rendered_image.height = width, height if err != nil { NewErrorPreview(fmt.Errorf("Failed to render image: %w", err)).Render(h, x, y, width, height) } else if self.image_transmitted > 0 { self.place_image(x, y, px_width, y_offset, sz) } }() if self.last_rendered_image.p == p && self.last_rendered_image.width == width && self.last_rendered_image.height == height { return } files_supported := self.files_supported.Load() if p.custom_metadata.image.Width <= px_width && p.custom_metadata.image.Height <= px_height { if files_supported { self.transmit(h.lp, nil, p.custom_metadata.image, p.cached_data) } else { if err = p.ensure_source_image(); err != nil { return } self.transmit(h.lp, p.source_img, p.custom_metadata.image, nil) } return } cache_key := fmt.Sprintf("%d-%d-%p", width, height, p) img_metadata, cached_data := self.cached_resized_image(p.disk_cache.ResultsDir(), cache_key) var img *images.ImageData if len(cached_data) == 0 { if err = p.ensure_source_image(); err != nil { return } img = p.source_img final_width, final_height := images.FitImage(img.Width, img.Height, px_width, px_height) if final_width != img.Width || final_height != img.Height { x_frac, y_frac := float64(final_width)/float64(img.Width), float64(final_height)/float64(img.Height) img = img.Resize(x_frac, y_frac) } if img_metadata, cached_data, err = self.cache_resized_image(p.disk_cache.ResultsDir(), cache_key, img); err != nil { err = fmt.Errorf("failed to cache resized image: %w", err) return } } if files_supported { self.transmit(h.lp, img, img_metadata, cached_data) } else { if img == nil { if img, err = load_image(cached_data); err != nil { err = fmt.Errorf("failed to load resized image from cache: %w", err) return } } self.transmit(h.lp, img, nil, nil) } } ================================================ FILE: kittens/choose_files/image_preview.go ================================================ package choose_files import ( "encoding/json" "fmt" "io/fs" "os" "path/filepath" "strconv" "sync" "sync/atomic" "time" "github.com/hako/durafmt" "github.com/kovidgoyal/go-parallel" "github.com/kovidgoyal/kitty/tools/disk_cache" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/kitty/tools/utils/images" ) const IMAGE_METADATA_KEY = "image-metadata.json" const IMAGE_DATA_PREFIX = "image-data-" var dc_size atomic.Int64 var _ = fmt.Print var preview_cache = sync.OnceValues(func() (*disk_cache.DiskCache, error) { cdir := utils.CacheDir() cdir = filepath.Join(cdir, "choose-files") return disk_cache.NewDiskCache(cdir, dc_size.Load()) }) type ShowData struct { abspath string metadata fs.FileInfo x, y, width, height int cached_data map[string]string custom_metadata metadata } type PreviewRenderer interface { Unmarshall(map[string]string) (any, error) Render(string) (map[string][]byte, metadata, *images.ImageData, error) ShowMetadata(h *Handler, s ShowData) int String() string } type render_data struct { cached_data map[string]string img *images.ImageData metadata metadata err error } type metadata struct { image *images.SerializableImageMetadata custom any } type ImagePreview struct { abspath string metadata fs.FileInfo disk_cache *disk_cache.DiskCache cached_data map[string]string render_err Preview render_channel chan render_data ready atomic.Bool source_img *images.ImageData custom_metadata metadata renderer PreviewRenderer file_metadata_preview Preview WakeupMainThread func() bool } func (p *ImagePreview) String() string { return fmt.Sprintf("ImagePreview{%#v, ready: %v, renderer: %s}", p.abspath, p.ready.Load(), p.renderer.String()) } func (p *ImagePreview) IsValidForColorScheme(bool) bool { return true } func (p *ImagePreview) IsReady() bool { return p.ready.Load() || p.render_channel == nil } func (p *ImagePreview) Unload() { p.source_img = nil } func load_image(cached_data map[string]string) (img *images.ImageData, err error) { fp := cached_data[IMAGE_METADATA_KEY] if fp == "" { return nil, fmt.Errorf("missing cached image metadata") } b, err := os.ReadFile(fp) if err != nil { return nil, fmt.Errorf("failed to read cached image metadata: %w", err) } var m images.SerializableImageMetadata if err = json.Unmarshal(b, &m); err != nil { return nil, fmt.Errorf("failed to decode cached image metadata: %w", err) } frames := make([][]byte, len(m.Frames)) for i := range m.Frames { path := cached_data[IMAGE_DATA_PREFIX+strconv.Itoa(i)] if path == "" { return nil, fmt.Errorf("missing cached data for frame: %d", i) } d, e := os.ReadFile(path) if e != nil { return nil, fmt.Errorf("failed to read cached image frame %d data: %w", i, e) } m.Frames[i].Size = len(d) frames[i] = d } return images.ImageFromSerialized(m, frames) } func (p *ImagePreview) ensure_source_image() (err error) { if p.source_img != nil { return } defer func() { if err != nil { p.render_err = NewErrorPreview(err) } }() p.source_img, err = load_image(p.cached_data) return } func (p *ImagePreview) render_image(h *Handler, x, y, width, height int) { defer func() { if r := recover(); r != nil { h.err_chan <- parallel.Format_stacktrace_on_panic(r, 1) p.WakeupMainThread() } }() offset := p.renderer.ShowMetadata(h, ShowData{ abspath: p.abspath, metadata: p.metadata, x: x, y: y, width: width, height: height, cached_data: p.cached_data, custom_metadata: p.custom_metadata, }) if p.custom_metadata.image != nil { h.graphics_handler.RenderImagePreview(h, p, x, y+offset, width, height-offset) } } func (p *ImagePreview) Render(h *Handler, x, y, width, height int) { if p.render_channel == nil { if p.render_err == nil { p.render_image(h, x, y, width, height) } else { p.render_err.Render(h, x, y, width, height) } return } select { case hd := <-p.render_channel: p.render_channel = nil p.cached_data = hd.cached_data p.source_img = hd.img p.custom_metadata = hd.metadata if hd.err != nil { p.render_err = NewFileMetadataPreviewWithError(p.abspath, p.metadata, fmt.Errorf("Failed to render the preview with error: %w", hd.err)) } p.Render(h, x, y, width, height) return default: } if p.file_metadata_preview == nil { p.file_metadata_preview = NewFileMetadataPreview(p.abspath, p.metadata) m := p.file_metadata_preview.(*MessagePreview) m.trailers = append(m.trailers, "", "Rendering image preview, please wait…") } p.file_metadata_preview.Render(h, x, y, width, height) } func (p *ImagePreview) start_rendering() { defer func() { if r := recover(); r != nil { p.render_channel <- render_data{err: parallel.Format_stacktrace_on_panic(r, 1)} } close(p.render_channel) p.ready.Store(true) p.WakeupMainThread() }() key, ans, err := p.disk_cache.GetPath(p.abspath) if err != nil { p.render_channel <- render_data{err: err} return } if len(ans) > 0 { mi := metadata{} if mi.custom, err = p.renderer.Unmarshall(ans); err == nil { if d := ans[IMAGE_METADATA_KEY]; d != "" { if b, err := os.ReadFile(d); err == nil { var m images.SerializableImageMetadata if err = json.Unmarshal(b, &m); err == nil { mi.image = &m } } } } if mi.custom != nil || mi.image != nil { p.render_channel <- render_data{cached_data: ans, metadata: mi} return } } rdata, metadata, img, err := p.renderer.Render(p.abspath) if err != nil { p.render_channel <- render_data{err: err} } else { ans, err = p.disk_cache.AddPath(p.abspath, key, rdata) if err == nil { p.render_channel <- render_data{cached_data: ans, metadata: metadata, img: img} } else { p.render_channel <- render_data{err: err} } } } type ImagePreviewRenderer uint func (p ImagePreviewRenderer) String() string { return "ImagePreviewRenderer" } func (p ImagePreviewRenderer) Render(abspath string) (ans map[string][]byte, m metadata, img *images.ImageData, err error) { if img, err = images.OpenImageFromPath(abspath); err != nil { return nil, metadata{}, nil, err } im, data := img.Serialize() ans = make(map[string][]byte, len(data)+1) sm, err := json.Marshal(im) if err != nil { return nil, metadata{}, nil, err } ans[IMAGE_METADATA_KEY] = sm m = metadata{image: &im} for i, d := range data { key := IMAGE_DATA_PREFIX + strconv.Itoa(i) ans[key] = d } return } func (p ImagePreviewRenderer) Unmarshall(map[string]string) (any, error) { return nil, nil } func (p ImagePreviewRenderer) ShowMetadata(h *Handler, s ShowData) int { offset := 0 if m := s.custom_metadata.image; m != nil { text := fmt.Sprintf("%s: %dx%d %s", m.Format_uppercase, m.Width, m.Height, humanize.Bytes(uint64(s.metadata.Size()))) icon := icons.IconForPath("/a.gif") text = icon + " " + text offset += h.render_wrapped_text_in_region(text, s.x, s.y, s.width, s.height, true) } st := humanize.Time(s.metadata.ModTime()) if s.custom_metadata.image != nil && len(s.custom_metadata.image.Frames) > 0 { var d time.Duration for _, f := range s.custom_metadata.image.Frames { if f.Delay_ms > 0 { d += time.Duration(time.Duration(f.Delay_ms) * time.Millisecond) } } if d > 0 { st += fmt.Sprintf(", %s", durafmt.Parse(d).LimitFirstN(1).String()) } } offset += h.render_wrapped_text_in_region(st, s.x, s.y+offset, s.width, s.height-offset, true) return offset } func NewImagePreview( abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool, r PreviewRenderer, ) (Preview, error) { dc_size.Store(opts.DiskCacheSize()) ans := &ImagePreview{ abspath: abspath, metadata: metadata, render_channel: make(chan render_data, 1), WakeupMainThread: WakeupMainThread, renderer: r, } if dc, err := preview_cache(); err != nil { return nil, err } else { ans.disk_cache = dc } go ans.start_rendering() return ans, nil } ================================================ FILE: kittens/choose_files/main.go ================================================ package choose_files import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "slices" "strconv" "strings" "sync" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/ignorefiles" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/shlex" "golang.org/x/text/message" ) var _ = fmt.Print var debugprintln = tty.DebugPrintln type Screen int const ( NORMAL Screen = iota SAVE_FILE ) type Mode int const ( SELECT_SINGLE_FILE Mode = iota SELECT_MULTIPLE_FILES SELECT_SAVE_FILE SELECT_SAVE_FILES SELECT_SAVE_DIR SELECT_SINGLE_DIR SELECT_MULTIPLE_DIRS ) func (m Mode) CanSelectNonExistent() bool { switch m { case SELECT_SAVE_FILE, SELECT_SAVE_DIR, SELECT_SAVE_FILES: return true } return false } func (m Mode) AllowsMultipleSelection() bool { switch m { case SELECT_MULTIPLE_FILES, SELECT_MULTIPLE_DIRS, SELECT_SAVE_FILES: return true } return false } func (m Mode) OnlyDirs() bool { switch m { case SELECT_SINGLE_DIR, SELECT_MULTIPLE_DIRS, SELECT_SAVE_DIR: return true } return false } func (m Mode) SelectFiles() bool { switch m { case SELECT_SINGLE_FILE, SELECT_MULTIPLE_FILES, SELECT_SAVE_FILE, SELECT_SAVE_FILES: return true } return false } func (m Mode) WindowTitle() string { switch m { case SELECT_SINGLE_FILE: return "Choose an existing file" case SELECT_MULTIPLE_FILES: return "Choose one or more existing files" case SELECT_SAVE_FILE: return "Choose a file to save" case SELECT_SAVE_DIR: return "Choose a directory to save" case SELECT_SINGLE_DIR: return "Choose an existing directory" case SELECT_MULTIPLE_DIRS: return "Choose one or more directories" case SELECT_SAVE_FILES: return "Choose files to save" } return "" } type render_state struct { num_matches, num_of_slots, num_before, num_per_column, num_columns, num_shown, preview_width int first_idx CollectionIndex } type State struct { base_dir string current_dir string multiselect bool search_text string mode Mode suggested_save_file_name string suggested_save_file_path string window_title string screen Screen current_filter string filter_map map[string]Filter filter_names []string show_hidden bool show_preview bool respect_ignores bool sort_by_last_modified bool global_ignores ignorefiles.IgnoreFile keyboard_shortcuts []*config.KeyAction display_title bool pygments_style, dark_pygments_style string syntax_aliases map[string]string max_disk_cache_size int64 selections []string current_idx CollectionIndex last_render render_state mouse_state tui.MouseState redraw_needed bool } func (s State) DiskCacheSize() int64 { return s.max_disk_cache_size } func (s State) HighlightStyles() (string, string) { return s.pygments_style, s.dark_pygments_style } func (s State) SyntaxAliases() map[string]string { return s.syntax_aliases } func (s State) DisplayTitle() bool { return s.display_title } func (s State) ShowHidden() bool { return s.show_hidden } func (s State) ShowPreview() bool { return s.show_preview } func (s State) RespectIgnores() bool { return s.respect_ignores } func (s State) SortByLastModified() bool { return s.sort_by_last_modified } func (s State) GlobalIgnores() ignorefiles.IgnoreFile { return s.global_ignores } func (s State) BaseDir() string { return utils.IfElse(s.base_dir == "", default_cwd, s.base_dir) } func (s State) Filter() Filter { return s.filter_map[s.current_filter] } func (s State) Multiselect() bool { return s.multiselect } func (s State) String() string { return utils.Repr(s) } func (s State) SearchText() string { return s.search_text } func (s State) OnlyDirs() bool { return s.mode.OnlyDirs() } func (s *State) SetSearchText(val string) { if s.search_text != val { s.search_text = val s.current_idx = CollectionIndex{} } } func (s *State) SetCurrentDir(val string) { if q, err := filepath.Abs(val); err == nil { val = q } if s.CurrentDir() != val { s.search_text = "" s.current_idx = CollectionIndex{} s.current_dir = val } } func (s State) CurrentIndex() CollectionIndex { return s.current_idx } func (s *State) SetCurrentIndex(val CollectionIndex) { s.current_idx = val } func (s State) CurrentDir() string { return utils.IfElse(s.current_dir == "", s.BaseDir(), s.current_dir) } func (s State) WindowTitle() string { if s.window_title == "" { return s.mode.WindowTitle() } return s.window_title } func (s *State) AddSelection(abspath string) bool { if !slices.Contains(s.selections, abspath) { s.selections = append(s.selections, abspath) return true } return false } func (s *State) ToggleSelection(abspath string) (added bool) { before := len(s.selections) s.selections = slices.DeleteFunc(s.selections, func(x string) bool { return x == abspath }) if len(s.selections) == before { s.selections = append(s.selections, abspath) added = true } return } func (s *State) IsSelected(x *ResultItem) bool { if len(s.selections) == 0 { return false } q := filepath.Join(s.CurrentDir(), x.text) return slices.Contains(s.selections, q) } type ScreenSize struct { width, height, cell_width, cell_height, width_px, height_px int } type Handler struct { state State screen_size ScreenSize result_manager *ResultManager lp *loop.Loop rl *readline.Readline err_chan chan error shortcut_tracker config.ShortcutTracker msg_printer *message.Printer spinner *tui.Spinner preview_manager *PreviewManager last_rendered_preview Preview last_rendered_preview_abspath string prev_preview_for_smooth_transition Preview graphics_handler GraphicsHandler } func (self *Handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error { switch etype { case loop.APC: gc := graphics.GraphicsCommandFromAPC(payload) if gc != nil { return self.graphics_handler.HandleGraphicsCommand(gc) } } return nil } func (h *Handler) draw_screen() (err error) { h.state.redraw_needed = false h.lp.StartAtomicUpdate() defer func() { h.state.mouse_state.UpdateHoveredIds() h.state.mouse_state.ApplyHoverStyles(h.lp) h.graphics_handler.ApplyPlacements(h.lp) if h.state.screen == NORMAL { // so that the cursor ends up in the right place h.lp.MoveCursorTo(1, 1) if h.state.DisplayTitle() { h.lp.Println(h.state.WindowTitle()) h.draw_search_bar(1) } else { h.draw_search_bar(0) } } h.lp.EndAtomicUpdate() }() h.lp.ClearScreenButNotGraphics() h.graphics_handler.ClearPlacements(h.lp) h.state.mouse_state.ClearCellRegions() switch h.state.screen { case NORMAL: matches, is_complete := h.get_results() h.lp.SetWindowTitle(h.state.WindowTitle()) y := SEARCH_BAR_HEIGHT + utils.IfElse(h.state.DisplayTitle(), 1, 0) footer_height, err := h.draw_footer() if err != nil { return err } y += h.draw_results(y, footer_height, matches, !is_complete) case SAVE_FILE: err = h.draw_save_file_name_screen() } return } type previewer struct { cmdline []string pattern string use_mimetype bool } func (p previewer) matches(fname, mt string) bool { q := utils.IfElse(p.use_mimetype, mt, fname) matched, err := filepath.Match(p.pattern, q) return matched && err == nil } var previewers []previewer func load_previewers(cfg *Config) (err error) { for _, spec := range cfg.Previewer { parts, err := shlex.Split(spec) if err != nil { return fmt.Errorf("invalid previewer specification: %#v with error: %w", spec, err) } if len(parts) < 2 { return fmt.Errorf("invalid previewer specification: %#v with error: no command specified", spec) } prefix, pattern, found := strings.Cut(parts[0], ":") if !found { return fmt.Errorf("invalid previewer specification: %#v with error: no prefix in pattern specification", spec) } previewers = append(previewers, previewer{parts[1:], pattern, prefix == "mime"}) } return } func load_config(opts *Options) (ans *Config, err error) { ans = NewConfig() p := config.ConfigParser{LineHandler: ans.Parse} err = p.LoadConfig("choose-files.conf", opts.Config, opts.Override) if err != nil { return nil, err } ans.KeyboardShortcuts = config.ResolveShortcuts(ans.KeyboardShortcuts) parts, err := shlex.Split(ans.Video_preview) if err != nil { return nil, err } for _, x := range parts { k, v, found := strings.Cut(x, "=") if !found { return nil, fmt.Errorf("invalid value %s in video_preview", x) } var i uint64 switch k { case "duration": if video_duration, err = strconv.ParseFloat(v, 64); err != nil { return nil, fmt.Errorf("invalid %s in video_preview: %s", k, v) } case "fps": if i, err = strconv.ParseUint(v, 10, 0); err != nil { return nil, fmt.Errorf("invalid %s in video_preview: %s", k, v) } video_fps = int(i) case "width": if i, err = strconv.ParseUint(v, 10, 0); err != nil { return nil, fmt.Errorf("invalid %s in video_preview: %s", k, v) } video_width = int(i) default: return nil, fmt.Errorf("unrecognized key in video_preview: %s", k) } } return ans, nil } func (h *Handler) init_sizes(new_size loop.ScreenSize) { h.screen_size.width = int(new_size.WidthCells) h.screen_size.height = int(new_size.HeightCells) h.screen_size.cell_width = int(new_size.CellWidth) h.screen_size.cell_height = int(new_size.CellHeight) h.screen_size.width_px = int(new_size.WidthPx) h.screen_size.height_px = int(new_size.HeightPx) h.rl.ClearCachedScreenSize() } func (h *Handler) OnInitialize() (ans string, err error) { if sz, err := h.lp.ScreenSize(); err != nil { return "", err } else { h.init_sizes(sz) } h.lp.AllowLineWrapping(false) h.lp.SetCursorShape(loop.BAR_CURSOR, true) h.lp.StartBracketedPaste() if h.state.suggested_save_file_path != "" { switch h.state.mode { case SELECT_SAVE_FILE, SELECT_SAVE_DIR: if s, err := os.Stat(h.state.suggested_save_file_path); err == nil { if (s.IsDir() && h.state.mode != SELECT_SAVE_FILE) || (!s.IsDir() && h.state.mode == SELECT_SAVE_FILE) { h.state.SetCurrentDir(filepath.Dir(h.state.suggested_save_file_path)) h.state.SetSearchText(filepath.Base(h.state.suggested_save_file_name)) } } } } err = h.graphics_handler.Initialize(h.lp) h.result_manager.set_root_dir() h.draw_screen() h.lp.SendOverlayReady() return } func (h *Handler) current_abspath() string { matches, _ := h.get_results() if r := matches.At(h.state.CurrentIndex()); r != nil { return filepath.Join(h.state.CurrentDir(), r.text) } return "" } func (s *State) CanSelect(r *ResultItem) bool { return utils.IfElse(s.OnlyDirs(), r.ftype.IsDir(), !r.ftype.IsDir()) } func (h *Handler) toggle_selection_at(idx CollectionIndex) bool { matches, _ := h.get_results() if r := matches.At(idx); r != nil && h.state.CanSelect(r) { m := filepath.Join(h.state.CurrentDir(), r.text) if added := h.state.ToggleSelection(m); added { h.result_manager.last_click_anchor = &idx } else { h.result_manager.last_click_anchor = nil if len(h.state.selections) > 0 { x := utils.NewSetWithItems(h.state.selections...) cdir := h.state.CurrentDir() h.result_manager.last_click_anchor = matches.Closest(idx, func(q *ResultItem) bool { return x.Has(filepath.Join(cdir, q.text)) }) } } return true } return false } func (h *Handler) toggle_selection() bool { return h.toggle_selection_at(h.state.CurrentIndex()) } func (h *Handler) change_current_dir(dir string) { if dir != h.state.CurrentDir() { h.state.SetCurrentDir(dir) h.result_manager.set_root_dir() h.state.last_render = render_state{} } } func (h *Handler) set_query(q string) { if q != h.state.SearchText() { h.state.SetSearchText(q) h.result_manager.set_query() h.state.last_render = render_state{} } } func (h *Handler) set_filter(filter_name string) { if filter_name != h.state.current_filter { h.state.current_filter = filter_name h.result_manager.set_filter() h.state.last_render = render_state{} } } func (h *Handler) change_to_current_dir_if_possible() error { if m := h.current_abspath(); m != "" { if st, err := os.Stat(m); err == nil { if !st.IsDir() { m = filepath.Dir(m) } h.change_current_dir(m) return h.draw_screen() } } h.lp.Beep() return nil } func (h *Handler) finish_selection() error { if h.state.mode.CanSelectNonExistent() && len(h.state.selections) == 0 { return h.switch_to_save_file_name_mode() } h.lp.Quit(0) return nil } func (h *Handler) change_filter(delta int) bool { if len(h.state.filter_names) < 2 { return false } idx := slices.Index(h.state.filter_names, h.state.current_filter) idx += delta + len(h.state.filter_names) idx %= len(h.state.filter_names) h.set_filter(h.state.filter_names[idx]) return true } func (h *Handler) switch_to_save_file_name_mode() error { name := h.state.suggested_save_file_name if h.state.SearchText() != "" { name = h.state.SearchText() } if name == "" { name = h.current_abspath() if name != "" { if r, err := filepath.Rel(h.state.CurrentDir(), name); err == nil { name = r } } } h.initialize_save_file_name(name) return h.draw_screen() } func (h *Handler) switch_to_save_file_name_mode_based_on_existing() error { name := h.current_abspath() if name == "" { return h.switch_to_save_file_name_mode() } if r, err := filepath.Rel(h.state.CurrentDir(), name); err == nil { name = r } h.initialize_save_file_name(name) return h.draw_screen() } func (h *Handler) accept_idx(idx CollectionIndex) (accepted bool, err error) { matches, _ := h.get_results() if r := matches.At(idx); r != nil { m := filepath.Join(h.state.CurrentDir(), r.text) if h.state.mode.SelectFiles() { var s os.FileInfo if s, err = os.Stat(m); err != nil { return false, nil } if s.IsDir() { if h.state.mode.CanSelectNonExistent() { return true, h.switch_to_save_file_name_mode() } return false, nil } } h.state.AddSelection(m) h.result_manager.last_click_anchor = &idx if len(h.state.selections) > 0 { return true, h.finish_selection() } return true, h.draw_screen() } return } func (h *Handler) dispatch_action(name, args string) (err error) { switch name { case "quit": h.lp.Quit(1) case "next": if n, nerr := strconv.Atoi(args); nerr == nil { h.next_result(n) } else { switch args { case "": h.next_result(1) case "left": h.move_sideways(true) case "right": h.move_sideways(false) case "first": h.state.SetCurrentIndex(CollectionIndex{}) h.state.last_render.num_before = 0 case "last": matches, _ := h.get_results() h.state.SetCurrentIndex(matches.IncrementIndexWithWrapAround(CollectionIndex{}, -1)) h.state.last_render.num_before = 0 case "first_on_screen": h.state.SetCurrentIndex(h.state.last_render.first_idx) h.state.last_render.num_before = 0 case "last_on_screen": matches, _ := h.get_results() h.state.SetCurrentIndex(matches.IncrementIndexWithWrapAround(h.state.last_render.first_idx, h.state.last_render.num_shown-1)) h.state.last_render.num_before = h.state.last_render.num_shown - 1 } } return h.draw_screen() case "next_filter": if n, nerr := strconv.Atoi(args); nerr == nil { h.change_filter(n) return h.draw_screen() } h.lp.Beep() case "select": if !h.toggle_selection() { h.lp.Beep() } else { return h.draw_screen() } case "accept": accepted, aerr := h.accept_idx(h.state.CurrentIndex()) if aerr != nil { return aerr } if !accepted { h.lp.Beep() } case "typename": if !h.state.mode.CanSelectNonExistent() { if h.state.mode.OnlyDirs() { h.state.AddSelection(h.state.CurrentDir()) return h.finish_selection() } h.lp.Beep() } else { return h.switch_to_save_file_name_mode() } case "modifyname": if h.state.mode.CanSelectNonExistent() { return h.switch_to_save_file_name_mode_based_on_existing() } else { h.lp.Beep() } case "toggle": switch args { case "preview": h.state.show_preview = !h.state.show_preview return h.draw_screen() case "dotfiles": h.state.show_hidden = !h.state.show_hidden h.result_manager.set_show_hidden() return h.draw_screen() case "ignorefiles": h.state.respect_ignores = !h.state.respect_ignores h.result_manager.set_respect_ignores() return h.draw_screen() case "sort_by_dates": h.state.sort_by_last_modified = !h.state.sort_by_last_modified h.result_manager.set_sort_by_last_modified() return h.draw_screen() default: h.lp.Beep() } case "cd": switch args { case ".": return h.change_to_current_dir_if_possible() case "..": curr := h.state.CurrentDir() switch curr { case "/": case ".": if curr, err = os.Getwd(); err == nil && curr != "/" { h.change_current_dir(filepath.Dir(curr)) return h.draw_screen() } default: h.change_current_dir(filepath.Dir(curr)) return h.draw_screen() } h.lp.Beep() default: args = utils.Expanduser(args) if st, serr := os.Stat(args); serr != nil || !st.IsDir() { h.lp.Beep() return } if absp, err := filepath.Abs(args); err == nil { h.change_current_dir(absp) return h.draw_screen() } else { h.lp.Beep() return nil } } } return } func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) { switch h.state.screen { case NORMAL: if h.handle_edit_keys(ev) { ev.Handled = true h.draw_screen() } ac := h.shortcut_tracker.Match(ev, h.state.keyboard_shortcuts) if ac != nil { ev.Handled = true return h.dispatch_action(ac.Name, ac.Args) } case SAVE_FILE: err = h.save_file_name_handle_key(ev) } return } func (h *Handler) OnMouseEvent(event *loop.MouseEvent) (err error) { h.state.redraw_needed = h.state.mouse_state.UpdateState(event) if err = h.state.mouse_state.DispatchEventToHoveredRegions(event); err != nil { return } if h.state.redraw_needed { err = h.draw_screen() } return } func (h *Handler) OnText(text string, from_key_event, in_bracketed_paste bool) (err error) { switch h.state.screen { case NORMAL: h.set_query(h.state.SearchText() + text) return h.draw_screen() case SAVE_FILE: if err = h.rl.OnText(text, from_key_event, in_bracketed_paste); err == nil { err = h.draw_screen() } } return } type CachedValues struct { Show_hidden bool `json:"show_hidden"` Hide_preview bool `json:"hide_preview"` Respect_ignores bool `json:"respect_ignores"` Sort_by_last_modified bool `json:"sort_by_last_modified"` } const cache_filename = "choose-files.json" var cached_values = sync.OnceValue(func() *CachedValues { ans := CachedValues{Respect_ignores: true} fname := filepath.Join(utils.CacheDir(), cache_filename) if data, err := os.ReadFile(fname); err == nil { _ = json.Unmarshal(data, &ans) } return &ans }) func (s State) save_cached_values() { c := CachedValues{Show_hidden: s.show_hidden, Respect_ignores: s.respect_ignores, Sort_by_last_modified: s.sort_by_last_modified, Hide_preview: !s.show_preview} fname := filepath.Join(utils.CacheDir(), cache_filename) if data, err := json.Marshal(c); err == nil { _ = os.WriteFile(fname, data, 0600) } } func (h *Handler) set_state_from_config(conf *Config, opts *Options) (err error) { h.state = State{} switch opts.Mode { case "file": h.state.mode = SELECT_SINGLE_FILE case "files": h.state.mode = SELECT_MULTIPLE_FILES case "save-file": h.state.mode = SELECT_SAVE_FILE case "dir": h.state.mode = SELECT_SINGLE_DIR case "dirs": h.state.mode = SELECT_MULTIPLE_DIRS case "save-dir": h.state.mode = SELECT_SAVE_DIR case "save-files": h.state.mode = SELECT_SAVE_FILES default: h.state.mode = SELECT_SINGLE_FILE } h.state.suggested_save_file_name = opts.SuggestedSaveFileName h.state.suggested_save_file_path = opts.SuggestedSaveFilePath h.state.filter_map = nil h.state.current_filter = "" if len(opts.FileFilter) > 0 { opts.FileFilter = utils.Uniq(opts.FileFilter) has_all_files := false fmap := make(map[string][]Filter) seen := utils.NewSet[string](len(opts.FileFilter)) for _, x := range opts.FileFilter { f, ferr := NewFilter(x) if ferr != nil { return ferr } if f.Match == nil { has_all_files = true } if h.state.current_filter == "" { h.state.current_filter = f.Name } fmap[f.Name] = append(fmap[f.Name], *f) if !seen.Has(f.Name) { seen.Add(f.Name) h.state.filter_names = append(h.state.filter_names, f.Name) } } if !has_all_files { af, _ := NewFilter("glob:*:All files") fmap[af.Name] = append(fmap[af.Name], *af) if !seen.Has(af.Name) { h.state.filter_names = append(h.state.filter_names, af.Name) } } h.state.filter_map = make(map[string]Filter) for name, filters := range fmap { h.state.filter_map[name] = CombinedFilter(filters...) } } h.state.sort_by_last_modified = false h.state.respect_ignores = true h.state.show_hidden = false h.state.show_preview = true switch conf.Show_hidden { case Show_hidden_true, Show_hidden_y, Show_hidden_yes: h.state.show_hidden = true case Show_hidden_false, Show_hidden_n, Show_hidden_no: h.state.show_hidden = false case Show_hidden_last: h.state.show_hidden = cached_values().Show_hidden } switch conf.Respect_ignores { case Respect_ignores_true, Respect_ignores_y, Respect_ignores_yes: h.state.respect_ignores = true case Respect_ignores_false, Respect_ignores_n, Respect_ignores_no: h.state.respect_ignores = false case Respect_ignores_last: h.state.respect_ignores = cached_values().Respect_ignores } switch conf.Sort_by_last_modified { case Sort_by_last_modified_true, Sort_by_last_modified_y, Sort_by_last_modified_yes: h.state.sort_by_last_modified = true case Sort_by_last_modified_false, Sort_by_last_modified_n, Sort_by_last_modified_no: h.state.sort_by_last_modified = false case Sort_by_last_modified_last: h.state.sort_by_last_modified = cached_values().Sort_by_last_modified } switch conf.Show_preview { case Show_preview_true, Show_preview_y, Show_preview_yes: h.state.show_preview = true case Show_preview_false, Show_preview_n, Show_preview_no: h.state.show_preview = false case Show_preview_last: h.state.show_preview = !cached_values().Hide_preview } h.state.global_ignores = ignorefiles.NewGitignore() if err = h.state.global_ignores.LoadLines(conf.Ignore...); err != nil { return err } h.state.keyboard_shortcuts = conf.KeyboardShortcuts h.state.display_title = opts.DisplayTitle h.state.pygments_style = conf.Pygments_style h.state.dark_pygments_style = conf.Dark_pygments_style h.state.syntax_aliases = conf.Syntax_aliases h.state.max_disk_cache_size = int64(conf.Cache_size * (1024 * 1024 * 1024)) return } var default_cwd string var use_light_colors bool func quote_if_needed(x string) string { if s, err := shlex.Split(x); len(s) == 1 && err == nil && !strings.Contains(x, "$") { return x } return utils.QuoteStringForSH(x) } func for_shell_relative(x string) string { if rel, is_under, err := utils.RelativeIfUnder(default_cwd, x, false); err == nil && is_under { x = rel } return quote_if_needed(x) } func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if opts.ClearCache { c, err := preview_cache() if err != nil { return 1, err } if err = c.Clear(); err != nil { return 1, err } } write_output := func(selections []string, interrupted bool, current_filter string) { payload := make(map[string]any) if err != nil { if opts.WriteOutputTo != "" { m := fmt.Sprint(err) if opts.OutputFormat == "json" { payload["error"] = m b, _ := json.MarshalIndent(payload, "", " ") m = string(b) } os.WriteFile(opts.WriteOutputTo, []byte(m), 0600) } return } if interrupted { if opts.WriteOutputTo != "" { if opts.OutputFormat == "json" { payload["interrupted"] = true b, _ := json.MarshalIndent(payload, "", " ") os.WriteFile(opts.WriteOutputTo, b, 0600) } } return } payload["paths"] = selections if current_filter != "" { payload["current_filter"] = current_filter } if tui.RunningAsUI() { fmt.Println(tui.KittenOutputSerializer()(payload)) } else { var m string switch opts.OutputFormat { case "shell": m = strings.Join(utils.Map(quote_if_needed, selections), " ") case "shell-relative": m = strings.Join(utils.Map(for_shell_relative, selections), " ") case "text": m = strings.Join(selections, "\n") case "json": b, _ := json.MarshalIndent(payload, "", " ") m = string(b) } os.Stdout.Write(utils.UnsafeStringToBytes(m)) if opts.WriteOutputTo != "" { os.WriteFile(opts.WriteOutputTo, utils.UnsafeStringToBytes(m), 0600) } } } conf, err := load_config(opts) if err != nil { return 1, err } if err = load_previewers(conf); err != nil { return 1, err } lp, err := loop.New() if err != nil { return 1, err } lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) lp.ColorSchemeChangeNotifications() handler := Handler{lp: lp, err_chan: make(chan error, 8), msg_printer: message.NewPrinter(utils.LanguageTag()), spinner: tui.NewSpinner("dots")} defer handler.graphics_handler.Cleanup() defer calibre_cleanup() getcwd := func() string { ans := handler.state.CurrentDir() if ans == "" { var err error ans, err = os.Getwd() if err != nil { ans = "." } } return ans } handler.rl = readline.New(lp, readline.RlInit{ Prompt: "> ", ContinuationPrompt: ". ", Completer: FilePromptCompleter(getcwd), }) if err = handler.set_state_from_config(conf, opts); err != nil { return 1, err } handler.result_manager = NewResultManager(handler.err_chan, &handler.state, lp.WakeupMainThread) handler.preview_manager = NewPreviewManager(handler.err_chan, &handler.state, lp.WakeupMainThread) switch len(args) { case 0: if default_cwd, err = os.Getwd(); err != nil { return } case 1: default_cwd = args[0] default: return 1, fmt.Errorf("Can only specify one directory to search in") } default_cwd = utils.Expanduser(default_cwd) if default_cwd, err = filepath.Abs(default_cwd); err != nil { return } lp.OnInitialize = func() (string, error) { if opts.WritePidTo != "" { if err := utils.AtomicWriteFile(opts.WritePidTo, bytes.NewReader([]byte(strconv.Itoa(os.Getpid()))), 0600); err != nil { return "", err } } if opts.Title != "" { lp.SetWindowTitle(opts.Title) } lp.RequestCurrentColorScheme() return handler.OnInitialize() } lp.OnFinalize = func() string { handler.graphics_handler.Finalize(lp) return "" } lp.OnResize = func(old, new_size loop.ScreenSize) (err error) { handler.init_sizes(new_size) handler.clear_cached_previews() return handler.draw_screen() } lp.OnColorSchemeChange = func(p loop.ColorPreference) (err error) { new_val := p == loop.LIGHT_COLOR_PREFERENCE if new_val != use_light_colors { use_light_colors = new_val handler.preview_manager.invalidate_color_scheme_based_cached_items() return handler.draw_screen() } return } lp.OnKeyEvent = handler.OnKeyEvent lp.OnText = handler.OnText lp.OnMouseEvent = handler.OnMouseEvent lp.OnEscapeCode = handler.on_escape_code lp.OnWakeup = func() (err error) { select { case err = <-handler.err_chan: default: err = handler.draw_screen() } return } err = lp.Run() handler.state.save_cached_values() if err != nil { write_output(nil, false, "") return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() write_output(nil, true, "") return 1, nil } rc = lp.ExitCode() switch rc { case 0: write_output(handler.state.selections, false, handler.state.current_filter) default: write_output(nil, true, "") } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } ================================================ FILE: kittens/choose_files/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal import sys from typing import Any from kitty.conf.types import Definition from kitty.constants import appname from kitty.simple_cli_definitions import CONFIG_HELP, CompletionSpec from kitty.typing_compat import BossType from ..tui.handler import result_handler definition = Definition( '!kittens.choose_files', ) agr = definition.add_group egr = definition.end_group opt = definition.add_option map = definition.add_map mma = definition.add_mouse_map agr('scanning', 'Filesystem scanning') # {{{ opt('show_hidden', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text=''' Whether to show hidden files. The default value of :code:`last` means remember the last used value. This setting can be toggled within the program.''') opt('sort_by_last_modified', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text=''' Whether to sort the list of entries by last modified, instead of name. Note that sorting only applies before any query is entered. Once a query is entered entries are sorted by their matching score. The default value of :code:`last` means remember the last used value. This setting can be toggled within the program.''') opt('respect_ignores', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text=''' Whether to respect .gitignore and .ignore files and the :opt:`ignore` setting. The default value of :code:`last` means remember the last used value. This setting can be toggled within the program.''') opt('+ignore', '', add_to_default=False, long_text=''' An ignore pattern to ignore matched files. Uses the same sytax as :code:`.gitignore` files (see :code:`man gitignore`). Anchored patterns match with respect to whatever directory is currently being displayed. Can be specified multiple times to use multiple patterns. Note that every pattern has to be checked against every file, so use sparingly. ''') egr() # }}} agr('appearance', 'Appearance') # {{{ opt('show_preview', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text=''' Whether to show a preview of the current file/directory. The default value of :code:`last` means remember the last used value. This setting can be toggled within the program.''') opt('pygments_style', 'default', long_text=''' The pygments color scheme to use for syntax highlighting of file previews. See :link:`pygments builtin styles ` for a list of schemes. 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. This sets the colors used for dark color schemes, use :opt:`pygments_style` to change the colors for light color schemes.''') opt('cache_size', '0.5', option_type='positive_float', long_text=''' The maximum size of the disk cache, in gigabytes, used for previews. Zero or negative values mean no limit. ''') 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('video_preview', 'width=480 fps=10 duration=5', long_text=''' Control how videos are sampled for previwing. The width controls the size of the generated thumbnail from the video. Duration controls how long the generated thumbnail plays for, in seconds. Note that when changing these you should also use the :code:`--clear-cache` flag otherwise it will not affect already cached previews. ''') opt('+previewer', '', long_text=''' Specify an arbitrary program based preview generator. The syntax is:: pattern program arguments... Here, pattern can be used to match file names or mimetypes. For example: :code:`name:*.doc` matches files with the extension :code:`.doc`. Similarly, :code:`mime:image/*` matches all image files. :code:`program` can be any executable program in PATH. It will be run with the supplied arguments. The last argument will be the path to the file for which a preview must be generated. Can be specified multiple times to setup different previewers for different types of files. Note that previewers specified using this option take precedence over the builtin previewers. The command must output preview data to STDOUT, as a JSON object: .. code-block:: json { "lines": ["line1", "line2", "..."], "image": "absolute path to generated image preview", "title_extra": "some text to show on the first line", } The lines can contain SGR formatting escape codes and will be displayed as is at the top of the preview panel. The image is optional and must be in one of the JPEG, PNG, GIF, WEBP, APNG formats. ''') egr() # }}} agr('shortcuts', 'Keyboard shortcuts') # {{{ map('Quit', 'quit esc quit') map('Quit', 'quit ctrl+c quit') map('Accept current result', 'accept enter accept') map('Select current result', 'select shift+enter select', long_text=''' When selecting multiple files, this will add the current file to the list of selected files. You can also toggle the selected status of a file by holding down the :kbd:`Ctrl` key and clicking on it. Similarly, the :kbd:`Alt` key can be held to click and extend the range of selected files. ''') map('Type file name', 'typename ctrl+enter typename', long_text=''' Type a file name/path rather than filtering the list of existing files. Useful when specifying a file or directory name for saving that does not yet exist. When choosing existing directories, will accept the directory whoose contents are being currently displayed as the choice. Does not work when selecting files to open rather than to save. ''') map('Modify file name', 'modifyname alt+enter modifyname', long_text=''' Modify the name of an existing file and select it for saving. Useful when specifying a file or directory name for saving that does not yet exist, but is based on an existing file name. Does not work when selecting files to open rather than to save. ''') map('Next result', 'next_result down next 1') map('Previous result', 'prev_result up next -1') map('Left result', 'left_result left next left') map('Right result', 'right_result right next right') map('First result on screen', 'first_result_on_screen home next first_on_screen') map('Last result on screen', 'last_result_on_screen end next last_on_screen') map('First result', 'first_result_on_screen ctrl+home next first') map('Last result', 'last_result_on_screen ctrl+end next last') map('Change to currently selected dir', 'cd_current tab cd .') map('Change to parent directory', 'cd_parent shift+tab cd ..') map('Change to root directory', 'cd_root ctrl+/ cd /') map('Change to home directory', 'cd_home ctrl+~ cd ~') map('Change to home directory', 'cd_home ctrl+` cd ~') map('Change to home directory', 'cd_home ctrl+shift+` cd ~') map('Change to temp directory', 'cd_tmp ctrl+t cd /tmp') map('Next filter', 'next_filter ctrl+f 1') map('Previous filter', 'prev_filter alt+f -1') map('Toggle showing dotfiles', 'toggle_dotfiles alt+h toggle dotfiles') map('Toggle showing ignored files', 'toggle_ignorefiles alt+i toggle ignorefiles') map('Toggle sorting by dates', 'toggle_sort_by_dates alt+d toggle sort_by_dates') map('Toggle showing preview', 'toggle_preview alt+p toggle preview') egr() # }}} def main(args: list[str]) -> None: raise SystemExit('This must be run as kitten choose-files') def relative_path_if_possible(path: str, base: str) -> str: if not base or not path: return path from contextlib import suppress from pathlib import Path b = Path(base) q = Path(path) with suppress(ValueError): return str(q.relative_to(b)) return path @result_handler(has_ready_notification=True) def handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType) -> None: import shlex from kitty.utils import shlex_split paths: list[str] = data.get('paths', []) if not paths: boss.ring_bell_if_allowed() return w = boss.window_id_map.get(target_window_id) if w is None: boss.ring_bell_if_allowed() return cwd = w.cwd_of_child items = [] for path in paths: if cwd: path = relative_path_if_possible(path, cwd) if w.at_prompt and len(tuple(shlex_split(path))) > 1: path = shlex.quote(path) items.append(path) text = (' ' if w.at_prompt else '\n').join(items) w.paste_text(text) usage = '[directory to start choosing files in]' OPTIONS = ''' --mode type=choices choices=file,files,save-file,dir,save-dir,dirs,save-files default=file The type of object(s) to select --file-filter type=list A list of filters to restrict the displayed files. Can be either mimetypes, or glob style patterns. Can be specified multiple times. The syntax is :code:`type:expression:Descriptive Name`. For example: :code:`mime:image/png:Images` and :code:`mime:image/gif:Images` and :code:`glob:*.[tT][xX][Tt]:Text files`. Note that glob patterns are case-sensitive. The mimetype specification is treated as a glob expressions as well, so you can, for example, use :code:`mime:text/*` to match all text files. The first filter in the list will be applied by default. Use a filter such as :code:`glob:*:All` to match all files. Note that filtering only appies to files, not directories. --suggested-save-file-name A suggested name when picking a save file. --suggested-save-file-path Path to an existing file to use as the save file. --title Window title to use for this chooser --display-title type=bool-set Show the window title at the top, useful when this kitten is used in an OS window without a title bar. --override -o type=list Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. --config type=list completion=type:file ext:conf group:"Config files" kwds:none,NONE {config_help} --write-output-to Path to a file to which the output is written in addition to STDOUT. --output-format choices=text,json,shell,shell-relative default=text The format in which to write the output. The :code:`text` format is absolute paths separated by newlines. The :code:`shell` format is quoted absolute paths separated by spaces, quoting is done only if needed. The :code:`shell-relative` format is the same as :code:`shell` except it returns paths relative to the starting directory. Note that when invoked from a mapping, this option is ignored, and either text or shell format is used automatically based on whether the cursor is at a shell prompt or not. --write-pid-to Path to a file to which to write the process ID (PID) of this process to. --clear-cache type=bool-set Clear the caches used by this kitten. '''.format(config_help=CONFIG_HELP.format(conf_name='choose-files', appname=appname)).format help_text = '''\ Select one or more files, quickly, using fuzzy finding, by typing just a few characters from the file name. Browse matching files, using the arrow keys to navigate matches and press :kbd:`Enter` to select. The :kbd:`Tab` key can be used to change to a sub-folder. See the :doc:`online docs ` for full details. ''' 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'] = 'Choose files, fast' cd['args_completion'] = CompletionSpec.from_string('type:directory') elif __name__ == '__conf__': sys.options_definition = definition # type: ignore ================================================ FILE: kittens/choose_files/preview.go ================================================ package choose_files import ( "fmt" "io/fs" "maps" "os" "path/filepath" "slices" "strings" "sync" "sync/atomic" "unicode/utf8" "github.com/kovidgoyal/go-parallel" "github.com/kovidgoyal/kitty/tools/highlight" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/kitty/tools/wcswidth" ) var _ = fmt.Print type Preview interface { Render(h *Handler, x, y, width, height int) IsValidForColorScheme(light bool) bool Unload() IsReady() bool String() string } type PreviewManager struct { report_errors chan error settings Settings WakeupMainThread func() bool cache map[string]Preview lock sync.Mutex highlighter highlight.Highlighter } func NewPreviewManager(err_chan chan error, settings Settings, WakeupMainThread func() bool) (ans *PreviewManager) { defer func() { sanitize = ans.highlighter.Sanitize }() return &PreviewManager{ report_errors: err_chan, settings: settings, WakeupMainThread: WakeupMainThread, cache: make(map[string]Preview), highlighter: highlight.NewHighlighter(nil), } } func (pm *PreviewManager) cached_preview(path string) Preview { pm.lock.Lock() defer pm.lock.Unlock() return pm.cache[path] } func (pm *PreviewManager) set_cached_preview(path string, val Preview) { pm.lock.Lock() defer pm.lock.Unlock() pm.cache[path] = val } func (h *Handler) render_wrapped_text_in_region(text string, x, y, width, height int, centered bool) int { lines := style.WrapTextAsLines(text, width, style.WrapOptions{}) for i, line := range lines { extra := 0 if centered { extra = max(0, width-wcswidth.Stringwidth(line)) / 2 } h.lp.MoveCursorTo(x+extra, y+i) h.lp.QueueWriteString(line) if i >= height { break } } return len(lines) } type MessagePreview struct { title string msg string trailers []string } func (p MessagePreview) String() string { return fmt.Sprintf("MessagePreview{%#v}", p.title) } func (p MessagePreview) IsValidForColorScheme(bool) bool { return true } func (p MessagePreview) IsReady() bool { return true } func (p MessagePreview) Unload() {} func (p MessagePreview) Render(h *Handler, x, y, width, height int) { offset := 0 if p.title != "" { offset += h.render_wrapped_text_in_region(p.title, x, y, width, height, true) } offset += h.render_wrapped_text_in_region(p.msg, x, y+offset, width, height-offset, false) limit := height - offset if limit > 1 { for i, line := range p.trailers { text := wcswidth.TruncateToVisualLength(line, width-1) if len(text) < len(line) { text += "…" } h.lp.MoveCursorTo(x, y+offset+i-1) if i >= limit { h.lp.QueueWriteString("…") break } h.lp.QueueWriteString(text) } } } func NewErrorPreview(err error) Preview { sctx := style.Context{AllowEscapeCodes: true} text := fmt.Sprintf("%s: %s", sctx.SprintFunc("fg=red")("Error"), err) return &MessagePreview{msg: text} } var sanitize func(string) string func write_file_metadata(abspath string, metadata fs.FileInfo, entries []fs.DirEntry) (header string, trailers []string) { buf := strings.Builder{} buf.Grow(4096) add := func(key, val string) { fmt.Fprintf(&buf, "%s: %s\n", key, val) } ftype := metadata.Mode().Type() const file_icon = " " fmt.Fprintln(&buf, filepath.Base(abspath)) switch ftype { case 0: add("Size", humanize.Bytes(uint64(metadata.Size()))) case fs.ModeSymlink: if tgt, err := os.Readlink(abspath); err == nil { add("Target", sanitize(tgt)) } else { add("Target", err.Error()) } case fs.ModeDir: num_files, num_dirs := 0, 0 for _, e := range entries { if e.IsDir() { num_dirs++ } else { num_files++ } } add("Children", fmt.Sprintf("%d %s %d %s", num_dirs, icons.IconForFileWithMode("dir", fs.ModeDir, false), num_files, file_icon)) } add("Modified", humanize.Time(metadata.ModTime())) add("Mode", metadata.Mode().String()) if len(entries) > 0 { type entry struct { lname string ftype fs.FileMode } type_map := make(map[string]entry, len(entries)) for _, e := range entries { type_map[e.Name()] = entry{strings.ToLower(e.Name()), e.Type()} } names := utils.Map(func(e fs.DirEntry) string { return e.Name() }, entries) slices.SortFunc(names, func(a, b string) int { return strings.Compare(type_map[a].lname, type_map[b].lname) }) fmt.Fprintln(&buf, "Contents:") for _, n := range names { trailers = append(trailers, icons.IconForFileWithMode(n, type_map[n].ftype, false)+" "+sanitize(n)) } } return buf.String(), trailers } func NewDirectoryPreview(abspath string, metadata fs.FileInfo) Preview { entries, err := os.ReadDir(abspath) if err != nil { return NewErrorPreview(fmt.Errorf("failed to read the directory %s with error: %w", abspath, err)) } title := icons.IconForFileWithMode("dir", fs.ModeDir, false) + " Directory\n" header, extra := write_file_metadata(abspath, metadata, entries) return &MessagePreview{title: title, msg: header, trailers: extra} } func NewFileMetadataPreview(abspath string, metadata fs.FileInfo) *MessagePreview { ext := filepath.Ext(abspath) if ext == "" { ext = "File" } title := icons.IconForFileWithMode(filepath.Base(abspath), metadata.Mode().Type(), false) + " " + ext h, t := write_file_metadata(abspath, metadata, nil) return &MessagePreview{title: title, msg: h, trailers: t} } func NewFileMetadataPreviewWithError(abspath string, metadata fs.FileInfo, err error) *MessagePreview { ext := filepath.Ext(abspath) if ext == "" { ext = "File" } title := icons.IconForFileWithMode(filepath.Base(abspath), metadata.Mode().Type(), false) + " " + ext h, t := write_file_metadata(abspath, metadata, nil) ans := &MessagePreview{title: title, msg: h, trailers: t} lines := style.WrapTextAsLines(err.Error(), 30, style.WrapOptions{}) ans.trailers = append(ans.trailers, "") ans.trailers = append(ans.trailers, lines...) return ans } type highlighed_data struct { text string light bool err error } type TextFilePreview struct { plain_text, highlighted_text string highlighted_chan chan highlighed_data ready atomic.Bool light bool path string metadata fs.FileInfo } func (p *TextFilePreview) String() string { return fmt.Sprintf("TextFilePreview{%#v, ready: %v}", p.path, p.ready.Load()) } func (p *TextFilePreview) IsValidForColorScheme(light bool) bool { return p.light == light } func (p *TextFilePreview) Unload() {} func (p *TextFilePreview) IsReady() bool { return p.ready.Load() || p.highlighted_chan == nil } func (p *TextFilePreview) Render(h *Handler, x, y, width, height int) { if p.highlighted_chan != nil { select { case hd := <-p.highlighted_chan: p.highlighted_chan = nil if hd.err == nil { p.highlighted_text = hd.text } default: } } text := p.highlighted_text if text == "" { text = p.plain_text } s := utils.NewLineScanner(text) buf := strings.Builder{} buf.Grow(1024 * height) title := icons.IconForPath(p.path) + " " + filepath.Base(p.path) + fmt.Sprintf(" %s", humanize.Bytes(uint64(p.metadata.Size()))) for num := 1 + h.render_wrapped_text_in_region(title, x, y, width, height, true); s.Scan() && num < height; num++ { line := s.Text() truncated := wcswidth.TruncateToVisualLength(line, width) buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y+num, x)) buf.WriteString(truncated) if len(truncated) < len(line) { wcswidth.KeepOnlyCSI(line[len(truncated):], &buf) } } buf.WriteString("\x1b[m") // reset any highlight styles h.lp.QueueWriteString(buf.String()) } func NewTextFilePreview(abspath string, metadata fs.FileInfo, highlighted_chan chan highlighed_data, sanitize func(string) string) Preview { data, err := os.ReadFile(abspath) if err != nil { return NewFileMetadataPreview(abspath, metadata) } text := utils.UnsafeBytesToString(data) if !utf8.ValidString(text) { text = "Error: not valid utf-8 text" } return &TextFilePreview{ path: abspath, plain_text: sanitize(text), highlighted_chan: highlighted_chan, light: use_light_colors, metadata: metadata} } type style_resolver struct { light bool light_style, dark_style string syntax_aliases map[string]string } func (s style_resolver) StyleName() string { return utils.IfElse(s.light, s.light_style, s.dark_style) } func (s style_resolver) UseLightColors() bool { return s.light } func (s style_resolver) SyntaxAliases() map[string]string { return s.syntax_aliases } func (s style_resolver) TextForPath(path string) (string, error) { ans, err := os.ReadFile(path) if err == nil { return utils.UnsafeBytesToString(ans), nil } return "", err } func (pm *PreviewManager) highlight_file_async(path string, output chan highlighed_data, ready *atomic.Bool) { s := style_resolver{light: use_light_colors, syntax_aliases: pm.settings.SyntaxAliases()} s.light_style, s.dark_style = pm.settings.HighlightStyles() go func() { defer func() { if r := recover(); r != nil { err := parallel.Format_stacktrace_on_panic(r, 1) output <- highlighed_data{err: err, light: s.light} } close(output) ready.Store(true) pm.WakeupMainThread() }() highlighted, err := pm.highlighter.HighlightFile(path, &s) output <- highlighed_data{text: highlighted, err: err, light: s.light} }() } func (pm *PreviewManager) invalidate_color_scheme_based_cached_items() { pm.lock.Lock() defer pm.lock.Unlock() maps.DeleteFunc(pm.cache, func(key string, p Preview) bool { return !p.IsValidForColorScheme(use_light_colors) }) } func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Preview) { if ans = pm.cached_preview(abspath); ans != nil { return ans } defer func() { pm.set_cached_preview(abspath, ans) }() s, err := os.Lstat(abspath) if err != nil { return NewErrorPreview(err) } if s.IsDir() { return NewDirectoryPreview(abspath, s) } if ftype&fs.ModeSymlink != 0 && ftype&SymlinkToDir != 0 { s, err = os.Stat(abspath) if err != nil { return NewErrorPreview(err) } return NewDirectoryPreview(abspath, s) } fname := filepath.Base(abspath) mt := utils.GuessMimeType(fname) for _, q := range previewers { if q.matches(fname, mt) { return NewCmdPreview(abspath, s, pm.settings, pm.WakeupMainThread, q) } } const MAX_TEXT_FILE_SIZE = 16 * 1024 * 1024 if s.Size() <= MAX_TEXT_FILE_SIZE && (utils.KnownTextualMimes[mt] || strings.HasPrefix(mt, "text/")) { ch := make(chan highlighed_data, 2) ans := NewTextFilePreview(abspath, s, ch, pm.highlighter.Sanitize) if p, ok := ans.(*TextFilePreview); ok { pm.highlight_file_async(abspath, ch, &p.ready) } return ans } switch { case strings.HasPrefix(mt, "image/"): var r ImagePreviewRenderer if ans, err := NewImagePreview(abspath, s, pm.settings, pm.WakeupMainThread, r); err == nil { return ans } else { return NewErrorPreview(err) } case strings.HasPrefix(mt, "video/"): return NewFFMpegPreview(abspath, s, pm.settings, pm.WakeupMainThread) case IsSupportedArchiveFile(abspath): return NewArchivePeview(abspath, s, pm.settings, pm.WakeupMainThread) case IsSupportedByCalibre(abspath): return NewCalibrePreview(abspath, s, pm.settings, pm.WakeupMainThread) } return NewFileMetadataPreview(abspath, s) } func (h *Handler) clear_cached_previews() { if h.last_rendered_preview != nil { h.last_rendered_preview.Unload() h.last_rendered_preview = nil } if h.prev_preview_for_smooth_transition != nil { h.prev_preview_for_smooth_transition.Unload() h.prev_preview_for_smooth_transition = nil } } func (h *Handler) draw_preview_content(x, y, width, height int) { matches, _ := h.get_results() r := matches.At(h.state.CurrentIndex()) if r == nil { h.render_wrapped_text_in_region("No preview available", x, y, width, height, false) return } render := func() { p := h.last_rendered_preview if p.IsReady() || h.prev_preview_for_smooth_transition == nil { p.Render(h, x, y, width, height) if h.prev_preview_for_smooth_transition != nil { h.prev_preview_for_smooth_transition.Unload() h.prev_preview_for_smooth_transition = nil } } else { h.prev_preview_for_smooth_transition.Render(h, x, y, width, height) } } abspath := h.current_abspath() if h.last_rendered_preview != nil { if abspath == h.last_rendered_preview_abspath { render() return } if h.prev_preview_for_smooth_transition != nil { h.prev_preview_for_smooth_transition.Unload() } h.prev_preview_for_smooth_transition = h.last_rendered_preview h.last_rendered_preview = nil } if p := h.preview_manager.preview_for(abspath, r.ftype); p == nil { h.render_wrapped_text_in_region("No preview available", x, y, width, height, false) } else { h.last_rendered_preview = p h.last_rendered_preview_abspath = abspath render() } } ================================================ FILE: kittens/choose_files/results.go ================================================ package choose_files import ( "fmt" "io/fs" "math" "os" "path/filepath" "runtime" "strings" "github.com/kovidgoyal/kitty/tools/icons" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/kitty/tools/wcswidth" ) var _ = fmt.Print func (h *Handler) draw_results_title() { text := filepath.Clean(h.state.CurrentDir()) home := filepath.Clean(utils.Expanduser("~")) if strings.HasPrefix(text, home) { text = "~" + text[len(home):] } text = sanitize(text) available_width := h.screen_size.width - 9 if available_width < 2 { return } tt := wcswidth.TruncateToVisualLength(text, available_width) if len(tt) < len(text) { text = wcswidth.TruncateToVisualLength(text, available_width-1) } text = fmt.Sprintf(" %s %s ", h.lp.SprintStyled("fg=blue", icons.IconForFileWithMode(text, fs.ModeDir, false)+" "), h.lp.SprintStyled("fg=intense-white bold", text)) extra := available_width - wcswidth.Stringwidth(text) x := 3 if extra > 1 { x += extra / 2 } h.lp.MoveCursorHorizontally(x) h.lp.QueueWriteString(text) } func (h *Handler) draw_no_matches_message(in_progress bool) { text := "Scanning filesystem, please wait…" if !in_progress { text = utils.IfElse(h.state.SearchText() == "", "No files present in this folder", "No matches found") } for _, line := range style.WrapTextAsLines(text, h.screen_size.width-2, style.WrapOptions{}) { h.lp.QueueWriteString("\r") h.lp.MoveCursorHorizontally(1) h.lp.QueueWriteString(line) h.lp.MoveCursorVertically(1) } } const matching_position_style = "fg=green" const selected_style = "fg=magenta" const current_style = "fg=intense-white bold" type text_chunk struct { text string emphasize bool } func split_up_text(text string, add_ellipsis bool, positions []int) func(func(x text_chunk) bool) { return func(yield func(x text_chunk) bool) { if len(positions) == 0 { if !yield(text_chunk{text, false}) { return } } else { at := 0 runes := []rune(text) limit := len(runes) for _, p := range positions { if max(p, at) >= limit || p < at { break } if at < p && !yield(text_chunk{string(runes[at:p]), false}) { return } if !yield(text_chunk{string(runes[p]), true}) { return } at = p + 1 } if at < len(runes) { if !yield(text_chunk{string(runes[at:]), false}) { return } } } if add_ellipsis { yield(text_chunk{"…", false}) } } } func (h *Handler) render_match_with_positions(text string, add_ellipsis bool, positions []int, is_current bool) { prefix, suffix, _ := strings.Cut(h.lp.SprintStyled(matching_position_style, " "), " ") if is_current { p, s, _ := strings.Cut(h.lp.SprintStyled(current_style, " "), " ") h.lp.QueueWriteString(p) defer h.lp.QueueWriteString(s) suffix += p } for chunk := range split_up_text(text, add_ellipsis, positions) { if chunk.text != "" { if chunk.emphasize { h.lp.QueueWriteString(prefix + chunk.text + suffix) } else { h.lp.QueueWriteString(chunk.text) } } } } var icon_cache map[string]string func icon_for(path string, x os.FileMode) string { if icon_cache == nil { icon_cache = make(map[string]string, 512) } if ans := icon_cache[path]; ans != "" { return ans } var ans string if x&fs.ModeSymlink != 0 && x&SymlinkToDir != 0 { ans = string(icons.SYMLINK_TO_DIR) } else { ans = icons.IconForFileWithMode(path, x, true) } if wcswidth.Stringwidth(ans) == 1 { ans += " " } icon_cache[path] = ans return ans } func (h *Handler) draw_column_of_matches(matches ResultsType, current_idx int, x, y, available_width, colnum int) { root_dir := h.state.CurrentDir() for i, m := range matches { h.lp.QueueWriteString("\r") h.lp.MoveCursorHorizontally(x) is_selected := h.state.IsSelected(m) var icon string if is_selected { icon = "󰗠 " } else { icon = icon_for(filepath.Join(root_dir, m.text), m.ftype) } text := sanitize(m.text) add_ellipsis := false width := wcswidth.Stringwidth(text) if width > available_width-3 { text = wcswidth.TruncateToVisualLength(text, available_width-4) add_ellipsis = true width = available_width - 3 } is_current := i == current_idx if is_current { h.lp.QueueWriteString(h.lp.SprintStyled(matching_position_style, icon+" ")) } else { if is_selected { h.lp.QueueWriteString(h.lp.SprintStyled(selected_style, icon+" ")) } else { h.lp.QueueWriteString(icon + " ") } } h.render_match_with_positions(text, add_ellipsis, m.sorted_positions(), is_current) h.lp.MoveCursorVertically(1) cr := h.state.mouse_state.AddCellRegion(fmt.Sprintf("result-%d-%d", colnum, i), x, y-1+i, x+width+2, y-1+i) cr.HoverStyle = HOVER_STYLE var data struct { colnum, i int } data.colnum, data.i = colnum, i cr.OnClickEvent = func(id string, ev *loop.MouseEvent, cell_offset tui.Point) error { if ev.Buttons&loop.LEFT_MOUSE_BUTTON == 0 { return nil } ctrl_mod := utils.IfElse(runtime.GOOS == "darwin", loop.SUPER, loop.CTRL) mods := ev.Mods & (ctrl_mod | loop.ALT) // shift alone and ctrl+shift are used for kitty bindings matches, _ := h.get_results() num_before := h.state.last_render.num_of_slots*data.colnum + data.i idx, did_wrap := matches.IncrementIndexWithWrapAroundAndCheck(h.state.last_render.first_idx, num_before) if did_wrap { h.lp.Beep() return nil } d := matches.SignedDistance(idx, h.state.current_idx) h.state.SetCurrentIndex(idx) h.state.last_render.num_before = max(0, h.state.last_render.num_before+d) switch mods { case 0: h.dispatch_action("accept", "") case ctrl_mod, ctrl_mod | loop.ALT: h.dispatch_action("select", "") case loop.ALT: r := matches.At(idx) if (r != nil && h.state.IsSelected(r)) || h.result_manager.last_click_anchor == nil { h.dispatch_action("select", "") return nil } already_selected := utils.NewSetWithItems(h.state.selections...) cdir := h.state.CurrentDir() matches.Apply(idx, *h.result_manager.last_click_anchor, func(r *ResultItem) bool { m := filepath.Join(cdir, r.text) if !already_selected.Has(m) && h.state.CanSelect(r) { already_selected.Add(m) h.state.selections = append(h.state.selections, m) } return true }) return h.draw_screen() } return nil } } } func (h *Handler) draw_list_of_results(matches *SortedResults, y, height int) (num_cols, num_shown, preview_width int) { const BASE_COL_WIDTH = 40 available_width := h.screen_size.width - 2 show_preview := h.state.ShowPreview() if show_preview && available_width < BASE_COL_WIDTH+30 { show_preview = false } if show_preview { switch { case available_width < BASE_COL_WIDTH*2: preview_width = max(30, available_width/2) default: preview_width = BASE_COL_WIDTH } available_width -= preview_width } col_width := available_width num_cols = 1 calc_num_cols := func(num_matches int) int { if num_matches == 0 || height < 2 { return 0 } if num_matches > height { col_width = BASE_COL_WIDTH num_cols = max(1, available_width/col_width) for num_cols > 1 && height*(num_cols-1) >= num_matches { num_cols-- } col_width = available_width / num_cols } return num_cols } columns, num_before, first_idx := matches.SplitIntoColumns(calc_num_cols, height, h.state.last_render.num_before, h.state.CurrentIndex()) h.state.last_render.num_before = num_before h.state.last_render.num_per_column = height h.state.last_render.num_columns = num_cols h.state.last_render.first_idx = first_idx x := 1 for i, col := range columns { h.lp.MoveCursorTo(x, y) h.draw_column_of_matches(col, num_before, x, y, col_width-1, i) num_before -= height num_shown += len(col) x += col_width } return len(columns), num_shown, preview_width } func (h *Handler) draw_num_of_matches(num_shown, y int, in_progress bool) { m := "" switch h.state.last_render.num_matches { case 0: m = " no matches " default: m = fmt.Sprintf(" %d of %s matches ", min(num_shown, h.state.last_render.num_matches), h.msg_printer.Sprint(h.state.last_render.num_matches)) } w := int(math.Ceil(float64(wcswidth.Stringwidth(m)) / 2.0)) spinner := "" spinner_width := 0 if in_progress { spinner = h.spinner.Tick() spinner_width = 1 + wcswidth.Stringwidth(spinner) } h.lp.MoveCursorTo(h.screen_size.width-w-spinner_width-2, y) st := loop.SizedText{Subscale_denominator: 2, Subscale_numerator: 1, Vertical_alignment: 2, Width: 1} graphemes := wcswidth.SplitIntoGraphemes(m) for len(graphemes) > 0 { s := "" for w := 0; w < 2 && len(graphemes) > 0; { w += wcswidth.Stringwidth(graphemes[0]) s += graphemes[0] graphemes = graphemes[1:] } h.lp.DrawSizedText(s, st) } if spinner != "" { h.lp.QueueWriteString(spinner) } } func (h *Handler) draw_preview(y int) { x := h.screen_size.width - h.state.last_render.preview_width height := h.state.last_render.num_of_slots buf := strings.Builder{} buf.Grow(16 * height) buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y-1, x)) buf.WriteString("┬") for i := range height { buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y+i, x)) buf.WriteString("│") } buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y+height, x)) buf.WriteString("┴") h.lp.QueueWriteString(buf.String()) h.draw_preview_content(x+1, y, h.state.last_render.preview_width-1, h.state.last_render.num_of_slots) } func (h *Handler) draw_results(y, bottom_margin int, matches *SortedResults, in_progress bool) (height int) { height = h.screen_size.height - y - bottom_margin h.lp.MoveCursorTo(1, 1+y) h.draw_frame(h.screen_size.width, height, in_progress) h.lp.MoveCursorTo(1, 1+y) h.draw_results_title() y += 2 h.lp.MoveCursorTo(1, y) h.state.last_render.num_of_slots = height - 2 num_cols := 0 num := matches.Len() num_shown := 0 h.state.last_render.preview_width = 0 switch num { case 0: h.draw_no_matches_message(in_progress) default: num_cols, num_shown, h.state.last_render.preview_width = h.draw_list_of_results(matches, y, h.state.last_render.num_of_slots) } h.state.last_render.num_matches = num h.state.last_render.num_shown = num_shown h.draw_num_of_matches(h.state.last_render.num_of_slots*num_cols, y+height-2, in_progress) if h.state.last_render.preview_width > 0 { h.draw_preview(y) } return } func (h *Handler) next_result(amt int) { if h.state.last_render.num_matches > 0 { idx := h.state.CurrentIndex() idx = h.result_manager.scorer.sorted_results.IncrementIndexWithWrapAround(idx, amt) h.state.SetCurrentIndex(idx) h.state.last_render.num_before = max(0, h.state.last_render.num_before+amt) } } func (h *Handler) move_sideways(leftwards bool) { r := h.state.last_render if r.num_matches > 0 && r.num_per_column > 0 { cidx := h.state.CurrentIndex() slots := r.num_of_slots if leftwards { idx := h.result_manager.scorer.sorted_results.IncrementIndexWithWrapAround(cidx, -slots) if idx.Less(cidx) { h.state.SetCurrentIndex(idx) if r.num_columns > 1 && r.num_before >= r.num_per_column { h.state.last_render.num_before = max(0, h.state.last_render.num_before-slots) } } } else { idx := h.result_manager.scorer.sorted_results.IncrementIndexWithWrapAround(cidx, slots) if cidx.Less(idx) { h.state.SetCurrentIndex(idx) if r.num_columns > 1 && r.num_before < (r.num_columns-1)*r.num_per_column { h.state.last_render.num_before = max(0, h.state.last_render.num_before+slots) } } } } } ================================================ FILE: kittens/choose_files/results_test.go ================================================ package choose_files import ( "fmt" "testing" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestSplitWithPositions(t *testing.T) { for _, c := range []struct { src string positions []int expected []string }{ {"abc", nil, []string{"abc"}}, {"abc", []int{0}, []string{"a", "bc"}}, {"abc", []int{1}, []string{"a", "b", "c"}}, {"abc", []int{2}, []string{"ab", "c"}}, {"abc", []int{0, 1}, []string{"a", "b", "c"}}, {"abc", []int{0, 1, 2}, []string{"a", "b", "c"}}, {"abc", []int{0, 2}, []string{"a", "b", "c"}}, // invalid positions {"abc", []int{-1}, []string{"abc"}}, {"abc", []int{3}, []string{"abc"}}, {"abc", []int{0, 3}, []string{"a", "bc"}}, {"abc", []int{2, 0}, []string{"ab", "c"}}, {"abc", []int{2, 1}, []string{"ab", "c"}}, {"abc", []int{1, 0}, []string{"a", "b", "c"}}, } { actual := make([]string, 0, len(c.expected)) for ch := range split_up_text(c.src, false, c.positions) { actual = append(actual, ch.text) } if diff := cmp.Diff(c.expected, actual); diff != "" { t.Fatalf("Failed for src: %#v positions: %v\n%s", c.src, c.positions, diff) } } } ================================================ FILE: kittens/choose_files/save-file.go ================================================ package choose_files import ( "fmt" "os" "path/filepath" "strings" "github.com/kovidgoyal/go-parallel" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print func FilePromptCompleter(getcwd func() string) func(string, string) *cli.Completions { if getcwd == nil { getcwd = func() string { ans, err := os.Getwd() if err != nil { ans = "." } return ans } } return func(before_cursor, after_cursor string) (ans *cli.Completions) { defer func() { if r := recover(); r != nil { err := parallel.Format_stacktrace_on_panic(r, 1) debugprintln(err) } }() ans = cli.NewCompletions() path := before_cursor prefix := "" if idx := strings.Index(path, string(os.PathSeparator)); idx > -1 { d := filepath.Dir(path) prefix = d + utils.IfElse(strings.HasSuffix(d, string(os.PathSeparator)), "", string(os.PathSeparator)) } dir := "" if path == "" { path = getcwd() dir = path } else { if !filepath.IsAbs(path) { path = filepath.Join(getcwd(), path) } dir = filepath.Dir(path) if strings.HasSuffix(before_cursor, string(os.PathSeparator)) { dir = path } } entries, err := os.ReadDir(dir) if err != nil { return nil } dirs := ans.AddMatchGroup("Directories") dirs.IsFiles = true dirs.NoTrailingSpace = true files := ans.AddMatchGroup("Files") files.IsFiles = true files.NoTrailingSpace = true leading, _ := filepath.Rel(dir, path) if leading == "." { leading = "" } for _, e := range entries { word := e.Name() if leading == "" || strings.HasPrefix(word, leading) { collection := utils.IfElse(e.Type().IsDir(), dirs, files) if prefix != "" { word = prefix + word } collection.Matches = append(collection.Matches, &cli.Match{Word: word}) } } return ans } } func (h *Handler) current_save_file_path() string { ans := h.rl.AllText() if ans != "" { ans = utils.Expanduser(ans) if !filepath.IsAbs(ans) { ans = filepath.Join(h.state.CurrentDir(), ans) } } return ans } func (h *Handler) save_file_name_handle_key(ev *loop.KeyEvent) (err error) { ac := h.shortcut_tracker.Match(ev, h.state.keyboard_shortcuts) if ac != nil { switch ac.Name { case "accept": if p := h.current_save_file_path(); p != "" { h.state.selections = append(h.state.selections, p) h.lp.Quit(0) } else { h.lp.Beep() } ev.Handled = true return nil case "select": if p := h.current_save_file_path(); p != "" { h.state.selections = append(h.state.selections, p) } ev.Handled = true h.rl.ResetText() return h.draw_screen() case "quit": h.state.screen = NORMAL ev.Handled = true return h.draw_screen() } } if err = h.rl.OnKeyEvent(ev); err == nil { err = h.draw_screen() } return } func (h *Handler) initialize_save_file_name(fname string) { h.state.screen = SAVE_FILE h.rl.ResetText() if len(h.state.selections) > 0 && fname == "" { if q, err := filepath.Abs(h.state.selections[0]); err == nil { if s, err := os.Stat(q); err == nil { if s.IsDir() == h.state.mode.OnlyDirs() { if fname, err = filepath.Rel(h.state.CurrentDir(), q); err != nil { fname = q } } } } } h.rl.SetText(fname) } func (h *Handler) draw_save_file_name_screen() (err error) { h.lp.AllowLineWrapping(true) desc := utils.IfElse(h.state.mode == SELECT_SAVE_FILE, "file", "directory") if h.state.DisplayTitle() { h.lp.Println(h.state.WindowTitle()) } h.lp.Println("Enter the name of the", desc, "below, relative to:") h.lp.Println(h.lp.SprintStyled("fg=green", h.state.CurrentDir())) if h.state.mode.AllowsMultipleSelection() { h.lp.Println("Use shift+enter (or whatever you mapped the select action to) to enter multiple filenames") } h.lp.Println() h.rl.RedrawNonAtomic() h.lp.AllowLineWrapping(false) if len(h.state.selections) > 0 { h.lp.SaveCursorPosition() h.draw_footer() h.lp.RestoreCursorPosition() } return } ================================================ FILE: kittens/choose_files/scan.go ================================================ package choose_files import ( "bytes" "cmp" "encoding/binary" "fmt" "io/fs" "math" "os" "path/filepath" "slices" "sort" "strings" "sync" "sync/atomic" "time" "unicode" "unicode/utf8" "github.com/kovidgoyal/go-parallel" "github.com/kovidgoyal/kitty/tools/fzf" "github.com/kovidgoyal/kitty/tools/ignorefiles" "github.com/kovidgoyal/kitty/tools/utils" "golang.org/x/sys/unix" ) var _ = fmt.Print func (c CombinedScore) String() string { return fmt.Sprintf("{score: %d length: %d index: %d}", c.Score(), c.Length(), c.Index()) } type ignore_file_with_prefix struct { impl ignorefiles.IgnoreFile prefix string } func (i *ignore_file_with_prefix) is_ignored(name string, ftype fs.FileMode) (ans bool, was_match bool) { ans, linenum, _ := i.impl.IsIgnored(i.prefix+name, ftype) was_match = linenum > -1 return } type ResultItem struct { text string ftype fs.FileMode positions []int // may be nil score CombinedScore ignore_files []ignore_file_with_prefix } type ResultsType []*ResultItem func (r *ResultItem) SetScoreResult(x fzf.Result) { r.positions = x.Positions r.score.Set_score(uint16(math.MaxUint16 - uint16(x.Score))) } func (r ResultItem) IsMatching() bool { return r.score.Score() < uint16(math.MaxUint16) } func (r ResultItem) String() string { return fmt.Sprintf("{text: %#v, %s, positions: %#v}", r.text, r.score, r.positions) } func (r *ResultItem) sorted_positions() []int { if len(r.positions) > 1 { sort.Ints(r.positions) } return r.positions } type FileSystemScanner struct { listeners []chan bool in_progress, keep_going atomic.Bool root_dir string mutex sync.Mutex collection *ResultCollection dir_reader func(path string) ([]fs.DirEntry, error) file_reader func(path string) ([]byte, error) filter_func func(filename string) bool global_gitignore ignorefiles.IgnoreFile global_ignore ignorefiles.IgnoreFile respect_ignores, show_hidden bool sort_by_last_modified bool err error } func new_filesystem_scanner(root_dir string, notify chan bool, filter_func func(string) bool) (fss *FileSystemScanner) { ans := &FileSystemScanner{root_dir: root_dir, listeners: []chan bool{notify}, collection: NewResultCollection(4096)} ans.in_progress.Store(true) ans.keep_going.Store(true) ans.dir_reader = os.ReadDir ans.file_reader = os.ReadFile ans.filter_func = utils.IfElse(filter_func == nil, accept_all, filter_func) ans.global_gitignore = ignorefiles.NewGitignore() ans.global_ignore = ignorefiles.NewGitignore() ans.respect_ignores = true ans.show_hidden = false return ans } type Scanner interface { Start() Cancel() AddListener(chan bool) Len() int Batch(offset *CollectionIndex) []ResultItem Finished() bool Error() error } func (fss *FileSystemScanner) lock() { fss.mutex.Lock() } func (fss *FileSystemScanner) unlock() { fss.mutex.Unlock() } func (fss *FileSystemScanner) Error() error { fss.lock() defer fss.unlock() return fss.err } func (fss *FileSystemScanner) Start() { go fss.worker() } func (fss *FileSystemScanner) Cancel() { fss.keep_going.Store(false) } func (fss *FileSystemScanner) AddListener(x chan bool) { fss.lock() defer fss.unlock() if !fss.in_progress.Load() { close(x) } else { fss.listeners = append(fss.listeners, x) } } func (fss *FileSystemScanner) Len() int { fss.lock() defer fss.unlock() return fss.collection.Len() } func (fss *FileSystemScanner) Batch(offset *CollectionIndex) []ResultItem { fss.lock() defer fss.unlock() return fss.collection.Batch(offset) } func (fss *FileSystemScanner) Finished() bool { return !fss.in_progress.Load() } type sortable_dir_entry struct { name string ftype fs.FileMode sort_key []byte buf [unix.NAME_MAX + 1]byte } const SymlinkToDir = 1 // lowercase a string into a pre-existing byte buffer with speedups for ASCII func as_lower(s string, output []byte) int { limit := min(len(s), len(output)) found_non_ascii := false pos := 0 for i := range limit { c := s[i] if 'A' <= c && c <= 'Z' { c += 'a' - 'A' if pos < i { copy(output[pos:i], s[pos:i]) } output[i] = c pos = i + 1 } else if c >= utf8.RuneSelf { if pos < i { copy(output[pos:i], s[pos:i]) } found_non_ascii = true pos = i break } } if !found_non_ascii { if pos < limit { copy(output[pos:limit], s[pos:limit]) } return limit } buf := [4]byte{} var n int for _, r := range s[pos:] { o := output[pos:] r = unicode.ToLower(r) if len(o) > 3 { n = utf8.EncodeRune(o, r) } else { n = utf8.EncodeRune(buf[:], r) n = copy(o, buf[:n]) } pos += n if pos >= len(output) { break } } return pos } func accept_all(filename string) bool { return true } func (fss *FileSystemScanner) worker() { defer func() { fss.lock() defer fss.unlock() fss.in_progress.Store(false) if r := recover(); r != nil { qerr := parallel.Format_stacktrace_on_panic(r, 1) fss.err = qerr } for _, l := range fss.listeners { close(l) } }() root_dir, _ := filepath.Abs(fss.root_dir) if !strings.HasSuffix(root_dir, string(os.PathSeparator)) { root_dir += string(os.PathSeparator) } dir := root_dir var ignore_files []ignore_file_with_prefix base := "" pos := &CollectionIndex{} var arena []sortable_dir_entry var sortable []*sortable_dir_entry var ignoreable []*sortable_dir_entry var idx uint32 dot_git := os.Getenv("GIT_DIR") if dot_git == "" { dot_git = ".git" } // do a breadth first traversal of the filesystem is_root := true for dir != "" { if !fss.keep_going.Load() { break } entries, err := fss.dir_reader(dir) if err != nil { if is_root { fss.keep_going.Store(false) fss.lock() fss.err = err fss.unlock() } entries = nil } if cap(arena) < len(entries) { arena = make([]sortable_dir_entry, 0, max(1024, len(entries), 2*cap(arena))) sortable = make([]*sortable_dir_entry, 0, cap(arena)) ignoreable = make([]*sortable_dir_entry, 0, cap(arena)) } arena = arena[:len(entries)] sortable = sortable[:0] ignoreable = ignoreable[:0] ignore_files_copied := false add_ignore_file_from_impl := func(impl ignorefiles.IgnoreFile) { // we want ignore_files to be a copy as we dont want to // change the underlying array of ignore_files as it is // referenced by multiple ResultItems if !ignore_files_copied { ignore_files_copied = true n := make([]ignore_file_with_prefix, len(ignore_files), len(ignore_files)+4) copy(n, ignore_files) ignore_files = n } ignore_files = append(ignore_files, ignore_file_with_prefix{impl: impl}) } add_ignore_file := func(name string) { if data, rerr := fss.file_reader(dir + name); rerr == nil { impl := ignorefiles.NewGitignore() if rerr = impl.LoadString(utils.UnsafeBytesToString(data)); rerr == nil && impl.Len() > 0 { add_ignore_file_from_impl(impl) } } } entry_is_ignored := func(name string, ftype fs.FileMode) (is_ignored bool) { for _, ignore_file := range ignore_files { if iig, was_match := ignore_file.is_ignored(name, ftype); was_match { is_ignored = iig } } return } has_git_ignore, has_dot_git, has_dot_ignore := false, false, false sort_order := 1 for i, e := range entries { name := e.Name() ftype := e.Type() is_dir := ftype&fs.ModeDir != 0 if !is_dir { switch name { case ".ignore": has_dot_ignore = true case ".gitignore": has_git_ignore = true } if !fss.filter_func(name) { continue } } else { if name == dot_git { has_dot_git = true } } if !fss.show_hidden && name[0] == '.' { continue } arena[i].name = name if ftype&fs.ModeSymlink != 0 { if st, serr := os.Stat(dir + arena[i].name); serr == nil && st.IsDir() { ftype |= SymlinkToDir } } arena[i].ftype = ftype if is_dir { arena[i].buf[0] = '0' } else { arena[i].buf[0] = '1' } if fss.sort_by_last_modified { var ts time.Time if info, err := e.Info(); err == nil { ts = info.ModTime() } binary.BigEndian.PutUint64(arena[i].buf[1:], uint64(ts.UnixNano())) arena[i].sort_key = arena[i].buf[:1+8] sort_order = -1 } else { n := as_lower(arena[i].name, arena[i].buf[1:]) arena[i].sort_key = arena[i].buf[:1+n] } sortable = append(sortable, &arena[i]) } if fss.respect_ignores { if is_root && fss.global_ignore.Len() > 0 { add_ignore_file_from_impl(fss.global_ignore) } if has_dot_git { if fss.global_gitignore.Len() > 0 { add_ignore_file_from_impl(fss.global_gitignore) } add_ignore_file(filepath.Join(dot_git, "info", "exclude")) if has_git_ignore { add_ignore_file(".gitignore") } } if has_dot_ignore { add_ignore_file(".ignore") } } final_entries := sortable if len(ignore_files) > 0 { for _, e := range sortable { if !entry_is_ignored(e.name, e.ftype) { ignoreable = append(ignoreable, e) } } final_entries = ignoreable } slices.SortFunc(final_entries, func(a, b *sortable_dir_entry) int { return sort_order * bytes.Compare(a.sort_key, b.sort_key) }) fss.lock() for _, e := range final_entries { i := fss.collection.NextAppendPointer() i.ftype = e.ftype i.text = base + e.name i.score.Set_index(idx) i.ignore_files = ignore_files idx++ } listeners := fss.listeners fss.unlock() for _, l := range listeners { select { case l <- true: default: } } ignore_files = nil if relpath, ignf := fss.collection.NextDir(pos); relpath != "" { base = relpath + string(os.PathSeparator) dir = root_dir + base if len(ignf) != 0 { name := filepath.Base(relpath) + string(os.PathSeparator) ignore_files = utils.Map(func(ignore_file ignore_file_with_prefix) ignore_file_with_prefix { return ignore_file_with_prefix{impl: ignore_file.impl, prefix: ignore_file.prefix + name} }, ignf) } } else { dir = "" } is_root = false } } type FileSystemScorer struct { scanner Scanner keep_going, is_complete atomic.Bool root_dir, query string filter Filter only_dirs bool mutex sync.Mutex sorted_results *SortedResults on_results func(error, bool) current_worker_wait *sync.WaitGroup scorer *fzf.FuzzyMatcher dir_reader func(path string) ([]fs.DirEntry, error) file_reader func(path string) ([]byte, error) global_gitignore, global_ignore ignorefiles.IgnoreFile respect_ignores, show_hidden bool sort_by_last_modified bool } func NewFileSystemScorer(root_dir, query string, filter Filter, only_dirs bool, on_results func(error, bool)) (ans *FileSystemScorer) { return &FileSystemScorer{ query: query, root_dir: root_dir, only_dirs: only_dirs, filter: filter, on_results: on_results, scorer: fzf.NewFuzzyMatcher(fzf.PATH_SCHEME), sorted_results: NewSortedResults(), respect_ignores: true, } } func (fss *FileSystemScorer) lock() { fss.mutex.Lock() } func (fss *FileSystemScorer) unlock() { fss.mutex.Unlock() } func (fss *FileSystemScorer) Start() { on_results := make(chan bool) fss.is_complete.Store(false) fss.keep_going.Store(true) if fss.scanner == nil { sc := new_filesystem_scanner(fss.root_dir, on_results, fss.filter.Match) if fss.dir_reader != nil { sc.dir_reader = fss.dir_reader } if fss.file_reader != nil { sc.file_reader = fss.file_reader } if fss.global_gitignore != nil { sc.global_gitignore = fss.global_gitignore } else if ignore := ignorefiles.GlobalGitignore(); ignore != nil { sc.global_gitignore = ignore } if fss.global_ignore != nil { sc.global_ignore = fss.global_ignore } sc.show_hidden, sc.respect_ignores = fss.show_hidden, fss.respect_ignores sc.sort_by_last_modified = fss.sort_by_last_modified fss.scanner = sc fss.scanner.Start() } else { fss.scanner.AddListener(on_results) } fss.current_worker_wait = &sync.WaitGroup{} fss.current_worker_wait.Add(1) go fss.worker(on_results, fss.current_worker_wait) } func (fss *FileSystemScorer) Change_query(query string) { if fss.query == query { return } fss.keep_going.Store(false) if fss.current_worker_wait != nil { if fss.scanner != nil { fss.scanner.Cancel() } fss.current_worker_wait.Wait() } fss.lock() fss.query = query fss.sorted_results.Clear() fss.unlock() fss.Start() } func (fss *FileSystemScorer) change_scanner_setting(callback func()) { fss.keep_going.Store(false) if fss.current_worker_wait != nil { if fss.scanner != nil { fss.scanner.Cancel() } fss.current_worker_wait.Wait() } fss.lock() callback() fss.sorted_results.Clear() fss.scanner = nil fss.unlock() fss.Start() } func (fss *FileSystemScorer) Change_filter(filter Filter) { if !fss.filter.Equal(filter) { fss.change_scanner_setting(func() { fss.filter = filter }) } } func (fss *FileSystemScorer) Change_show_hidden(val bool) { if fss.show_hidden != val { fss.change_scanner_setting(func() { fss.show_hidden = val }) } } func (fss *FileSystemScorer) Change_respect_ignores(val bool) { if fss.respect_ignores != val { fss.change_scanner_setting(func() { fss.respect_ignores = val }) } } func (fss *FileSystemScorer) Change_sort_by_last_modified(val bool) { if fss.sort_by_last_modified != val { fss.change_scanner_setting(func() { fss.sort_by_last_modified = val }) } } func (fss *FileSystemScorer) worker(on_results chan bool, worker_wait *sync.WaitGroup) { defer func() { fss.is_complete.Store(true) defer worker_wait.Done() if r := recover(); r != nil { if fss.keep_going.Load() { qerr := parallel.Format_stacktrace_on_panic(r, 1) fss.on_results(qerr, true) } } else { if fss.keep_going.Load() { fss.on_results(fss.scanner.Error(), true) } } }() handle_batch := func(results []ResultItem) (err error) { if err = fss.scanner.Error(); err != nil { return } var rp []*ResultItem if fss.only_dirs { rp = make([]*ResultItem, 0, len(results)) for i, r := range results { if r.ftype.IsDir() { rp = append(rp, &results[i]) } } } else { if fss.filter.Match == nil { rp = make([]*ResultItem, len(results)) for i := range len(rp) { rp[i] = &results[i] } } else { rp = make([]*ResultItem, 0, len(results)) for i, r := range results { if r.ftype.IsDir() || fss.filter.Match(filepath.Base(r.text)) { rp = append(rp, &results[i]) } } } } if len(rp) > 0 { if fss.query != "" { scores, err := fss.scorer.Score(utils.Map(func(r *ResultItem) string { return r.text }, rp), fss.query) if err != nil { return err } for i, r := range rp { r.SetScoreResult(scores[i]) r.score.Set_length(uint16(len(r.text))) } rp = utils.Filter(rp, func(r *ResultItem) bool { return r.IsMatching() }) } else { for _, r := range rp { r.score &= 0b11111111111111111111111111111111 // only preserve index r.positions = nil } } } if len(rp) > 0 { slices.SortFunc(rp, func(a, b *ResultItem) int { return cmp.Compare(a.score, b.score) }) } fss.sorted_results.AddSortedSlice(rp) return } offset := &CollectionIndex{} for range on_results { if !fss.keep_going.Load() { break } results := fss.scanner.Batch(offset) if len(results) > 0 || fss.scanner.Error() != nil { fss.on_results(handle_batch(results), false) } } for fss.keep_going.Load() { b := fss.scanner.Batch(offset) if len(b) == 0 { break } fss.on_results(handle_batch(b), false) } } func (fss *FileSystemScorer) Results() (ans *SortedResults, is_finished bool) { fss.lock() defer fss.unlock() return fss.sorted_results, fss.is_complete.Load() } func (fss *FileSystemScorer) Cancel() { fss.keep_going.Store(false) fss.scanner.Cancel() } type Settings interface { OnlyDirs() bool CurrentDir() string SearchText() string ShowHidden() bool RespectIgnores() bool SortByLastModified() bool Filter() Filter GlobalIgnores() ignorefiles.IgnoreFile HighlightStyles() (string, string) SyntaxAliases() map[string]string DiskCacheSize() int64 } type ResultManager struct { report_errors chan error WakeupMainThread func() bool settings Settings scorer *FileSystemScorer mutex sync.Mutex last_wakeup_at time.Time last_click_anchor *CollectionIndex } func NewResultManager(err_chan chan error, settings Settings, WakeupMainThread func() bool) *ResultManager { ans := &ResultManager{ report_errors: err_chan, settings: settings, WakeupMainThread: WakeupMainThread, } return ans } func (m *ResultManager) new_scorer() { root_dir := m.current_root_dir() query := m.settings.SearchText() m.scorer = NewFileSystemScorer(root_dir, query, m.settings.Filter(), m.settings.OnlyDirs(), m.on_results) m.scorer.respect_ignores = m.settings.RespectIgnores() m.scorer.show_hidden = m.settings.ShowHidden() m.scorer.global_ignore = m.settings.GlobalIgnores() m.scorer.sort_by_last_modified = m.settings.SortByLastModified() m.last_click_anchor = nil } func (m *ResultManager) on_results(err error, is_finished bool) { if err != nil { m.report_errors <- err m.WakeupMainThread() return } m.mutex.Lock() defer m.mutex.Unlock() if is_finished || time.Since(m.last_wakeup_at) > time.Millisecond*50 { m.WakeupMainThread() m.last_wakeup_at = time.Now() } } func (m *ResultManager) current_root_dir() string { var err error root_dir := m.settings.CurrentDir() if root_dir == "" || root_dir == "." { if root_dir, err = os.Getwd(); err != nil { return "/" } } root_dir = utils.Expanduser(root_dir) if root_dir, err = filepath.Abs(root_dir); err != nil { return "/" } return root_dir } func (m *ResultManager) set_root_dir() { if m.scorer != nil { m.scorer.Cancel() } _ = os.Chdir(m.current_root_dir()) // this is so the terminal emulator can read the wd for launch --directory=current m.new_scorer() m.mutex.Lock() m.last_wakeup_at = time.Time{} m.mutex.Unlock() m.scorer.Start() } func (m *ResultManager) set_something(callback func()) { m.mutex.Lock() m.last_wakeup_at = time.Time{} m.mutex.Unlock() if m.scorer == nil { m.new_scorer() m.scorer.Start() } else { m.last_click_anchor = nil callback() } } func (m *ResultManager) set_query() { m.set_something(func() { m.scorer.Change_query(m.settings.SearchText()) }) } func (m *ResultManager) set_filter() { m.set_something(func() { m.scorer.Change_filter(m.settings.Filter()) }) } func (m *ResultManager) set_show_hidden() { m.set_something(func() { m.scorer.Change_show_hidden(m.settings.ShowHidden()) }) } func (m *ResultManager) set_respect_ignores() { m.set_something(func() { m.scorer.Change_respect_ignores(m.settings.RespectIgnores()) }) } func (m *ResultManager) set_sort_by_last_modified() { m.set_something(func() { m.scorer.Change_sort_by_last_modified(m.settings.SortByLastModified()) }) } func (h *Handler) get_results() (ans *SortedResults, is_complete bool) { if h.result_manager.scorer == nil { return } return h.result_manager.scorer.Results() } ================================================ FILE: kittens/choose_files/scan_test.go ================================================ package choose_files import ( "fmt" "io/fs" "math/rand" "os" "strconv" "strings" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/kovidgoyal/kitty/tools/ignorefiles" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print func TestAsLower(t *testing.T) { buf := [512]byte{} for _, q := range []string{ "abc", "aBc", "aBCCf83Dx", "mOoseÇa", "89ÇĞxxA", "", "23", "aIİBc", } { n := as_lower(q, buf[:]) actual := utils.UnsafeBytesToString(buf[:n]) if diff := cmp.Diff(strings.ToLower(q), actual); diff != "" { t.Fatalf("Failed to lowercase: %#v\n%s", q, diff) } } } type node struct { name string children map[string]*node data string } func (n node) Name() string { return n.name } func (n node) IsDir() bool { return n.children != nil } func (n node) String() string { return fmt.Sprintf("{name: %s num_children: %d}", n.name, len(n.children)) } func (n node) Type() fs.FileMode { if n.children == nil { return 0 } return fs.ModeDir } func (n node) Info() (fs.FileInfo, error) { return nil, fmt.Errorf("Info() not implemented") } func random_name(r *rand.Rand) string { length := 3 + r.Intn(23) bytes := make([]byte, length) for i := range length { bytes[i] = byte(r.Intn(26) + 'a') } return string(bytes) } func (n *node) generate_random_tree(depth, breadth int) { r := rand.New(rand.NewSource(111)) n.children = make(map[string]*node) for range breadth { c := &node{name: random_name(r)} n.children[c.name] = c if depth > 0 && r.Intn(10) < 3 { c.generate_random_tree(depth-1, breadth) } } } func (n node) dir_entries() []fs.DirEntry { entries := make([]fs.DirEntry, 0, len(n.children)) for _, v := range n.children { entries = append(entries, v) } return entries } func (n node) ReadFile(name string) ([]byte, error) { if name == string(os.PathSeparator) { return nil, fs.ErrNotExist } p := &n for x := range strings.SplitSeq(strings.Trim(name, string(os.PathSeparator)), string(os.PathSeparator)) { c, found := p.children[x] if !found { return nil, fs.ErrNotExist } p = c } return []byte(p.data), nil } func (n node) ReadDir(name string) ([]fs.DirEntry, error) { if name == string(os.PathSeparator) { return n.dir_entries(), nil } p := &n for x := range strings.SplitSeq(strings.Trim(name, string(os.PathSeparator)), string(os.PathSeparator)) { c, found := p.children[x] if !found { return nil, fs.ErrNotExist } if !c.IsDir() { return nil, fs.ErrExist } p = c } return p.dir_entries(), nil } func TestChooseFilesIgnore(t *testing.T) { root := node{name: string(os.PathSeparator), children: map[string]*node{ "a": {name: "a"}, "b": {name: "b"}, "c.png": {name: "c.png"}, ".ignore": {name: ".ignore", data: "a\nx/s/n"}, ".gitignore": {name: ".gitignore", data: "b"}, "x": {name: "x", children: map[string]*node{ "1": {name: "1"}, "2": {name: "2"}, "3": {name: "3"}, "s": {name: "s", children: map[string]*node{ "m": {name: "m"}, "n": {name: "n"}, }}, }}, "y": {name: "y", children: map[string]*node{ "3": {name: "3"}, "4": {name: "4"}, "5": {name: "5"}, "s": {name: "s", children: map[string]*node{ "3": {name: "3"}, "4": {name: "4"}, "5": {name: "5"}, "6": {name: "6"}, }}, ".gitignore": {name: ".gitignore", data: "/s/5"}, ".git": {name: ".git", children: map[string]*node{ "info": {name: "info", children: map[string]*node{ "exclude": {name: "exclude", data: "s/4"}, }}, }}, }}, }} r := func(respect_ignores bool, expected string) { ch := make(chan bool) s := new_filesystem_scanner("/", ch, nil) s.dir_reader = root.ReadDir s.file_reader = root.ReadFile s.global_gitignore = ignorefiles.NewGitignore() s.global_ignore = ignorefiles.NewGitignore() s.respect_ignores = respect_ignores if err := s.global_gitignore.LoadLines("*.png", "s/3"); err != nil { t.Fatal(err) } if err := s.global_ignore.LoadLines("x/3"); err != nil { t.Fatal(err) } s.Start() for range ch { } if s.err != nil { t.Fatal(s.err) } ci := CollectionIndex{} actual := utils.Map(func(x ResultItem) string { return x.text }, s.Batch(&ci)) if diff := cmp.Diff(strings.Split(expected, ` `), actual); diff != "" { t.Fatalf("Incorrect ignoring:\n%s", diff) } } r(true, `x y b c.png x/s x/1 x/2 y/s y/3 y/4 y/5 x/s/m y/s/6`) r(false, `x y a b c.png x/s x/1 x/2 x/3 y/s y/3 y/4 y/5 x/s/m x/s/n y/s/3 y/s/4 y/s/5 y/s/6`) } func TestChooseFilesScoring(t *testing.T) { root := node{name: string(os.PathSeparator), children: map[string]*node{ "b": {name: "b"}, "a": {name: "a"}, "c.png": {name: "c.png"}, "x": {name: "x", children: map[string]*node{ "1": {name: "1"}, "2": {name: "2"}, "3": {name: "3"}, "s": {name: "s", children: map[string]*node{ "m": {name: "m"}, "n": {name: "n"}, }}, }}, "y": {name: "y", children: map[string]*node{ "3": {name: "3"}, "4": {name: "4"}, "5": {name: "5"}, }}, }} wg := sync.WaitGroup{} wg.Add(1) s := NewFileSystemScorer(string(os.PathSeparator), "", Filter{}, false, func(err error, is_complete bool) { if is_complete { wg.Done() } }) s.dir_reader = root.ReadDir s.global_gitignore = ignorefiles.NewGitignore() s.Start() wg.Wait() results := func() (ans []string) { rr, _ := s.Results() for _, r := range rr.RenderedMatches(CollectionIndex{}, -1) { ans = append(ans, r.text) } return } ae := func(query string, expected ...string) { wg.Add(1) s.Change_query(query) wg.Wait() if diff := cmp.Diff(expected, results()); diff != "" { t.Fatalf("Query less scoring failed\n%s", diff) } } ae("a", "a") ae("3", "x/3", "y/3") ae("s", "x/s", "x/s/m", "x/s/n") ae("sn", "x/s/n") ae("", "x", "y", "a", "b", "c.png", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n") af := func(filter string, expected ...string) { f, _ := NewFilter(filter) wg.Add(1) s.Change_filter(*f) wg.Wait() if diff := cmp.Diff(expected, results()); diff != "" { t.Fatalf("filter %s failed\n%s", filter, diff) } } af("glob:a:A", "x", "y", "a", "x/s") af("glob:[ab]:A", "x", "y", "a", "b", "x/s") af("mime:image/png:A", "x", "y", "c.png", "x/s") af("mime:image/*:A", "x", "y", "c.png", "x/s") af("glob:*:All", "x", "y", "a", "b", "c.png", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n") } func TestSortedResults(t *testing.T) { r := NewSortedResults() idx := CollectionIndex{} m := func(items ...int) []*ResultItem { ans := make([]*ResultItem, len(items)) for i, x := range items { ans[i] = &ResultItem{text: strconv.Itoa(x), score: CombinedScore(x)} } return ans } v := func(slice, pos, num int) []int { if num == 0 { num = r.Len() } return utils.Map(func(r *ResultItem) int { return int(r.score) }, r.RenderedMatches(CollectionIndex{slice, pos}, num)) } tv := func(slice, pos, num int, expected ...int) { if diff := cmp.Diff(expected, v(slice, pos, num)); diff != "" { t.Fatalf("view failed for %v num:%d\n%s", CollectionIndex{slice, pos}, num, diff) } } tci := func(increment int, expected int) { orig := idx idx = r.IncrementIndexWithWrapAround(idx, increment) actual := int(r.At(idx).score) if actual != expected { t.Fatalf("increment: %d on %v failed\nexpected: %d actual: %d idx: %v", increment, orig, expected, actual, idx) } } dt := func(a, b CollectionIndex, expected int) { actual := r.distance(a, b) if expected != actual { t.Fatalf("distance on %v and %v failed\nexpected: %d actual: %d ", a, b, expected, actual) } if r.distance(b, a) != actual { t.Fatalf("distance on %v and %v not commutative %d != %d", a, b, actual, r.distance(b, a)) } } tc := func(num_before, expected_new_before int, ci CollectionIndex, expected ...[]int) { ac, new_num_before, _ := r.SplitIntoColumns(func(int) int { return 2 }, 2, num_before, ci) actual := make([][]int, len(ac)) for i, x := range ac { actual[i] = utils.Map(func(r *ResultItem) int { return int(r.score) }, x) } if expected_new_before != new_num_before { t.Fatalf("new_num_before not as expected for num_before: %d ci: %v\n%d != %d", num_before, ci, expected_new_before, new_num_before) } if diff := cmp.Diff(expected, actual); diff != "" { t.Fatalf("wrong columns for num_before: %d ci: %v\n%s", num_before, ci, diff) } } r.AddSortedSlice(m(10, 20, 30)) r.AddSortedSlice(m(40, 50, 60)) r.AddSortedSlice(m(70, 80, 90)) tc(0, 0, CollectionIndex{}, []int{10, 20}, []int{30, 40}) tc(1, 1, CollectionIndex{Pos: 1}, []int{10, 20}, []int{30, 40}) tc(1, 1, CollectionIndex{Pos: 2}, []int{20, 30}, []int{40, 50}) tc(2, 2, CollectionIndex{Pos: 2}, []int{10, 20}, []int{30, 40}) tc(20, 2, CollectionIndex{Pos: 2}, []int{10, 20}, []int{30, 40}) for num_before := range 4 { tc(num_before, 3, CollectionIndex{2, 2}, []int{60, 70}, []int{80, 90}) } tc(1, 1, CollectionIndex{1, 1}, []int{40, 50}, []int{60, 70}) dt(CollectionIndex{Pos: 0}, CollectionIndex{Pos: 2}, 2) dt(CollectionIndex{Pos: 0}, CollectionIndex{Slice: 1}, 3) dt(CollectionIndex{Pos: 0}, CollectionIndex{Slice: 1, Pos: 1}, 4) tv(0, 0, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90) tv(0, 2, 3, 30, 40, 50) tv(0, 3, 3, 40, 50, 60) tv(1, 0, 4, 40, 50, 60, 70) tci(1, 20) tci(3, 50) tci(-1, 40) tci(-3, 10) tci(-2, 80) tci(3, 20) tci(9, 20) tci(-9, 20) r.AddSortedSlice(m(100, 110, 120)) r.AddSortedSlice(m(41, 61, 71, 99)) tv(0, 0, 0, 10, 20, 30, 40, 41, 50, 60, 61, 70, 71, 80, 90, 99, 100, 110, 120) r.AddSortedSlice(m(1, 2, 3)) tv(0, 0, 0, 1, 2, 3, 10, 20, 30, 40, 41, 50, 60, 61, 70, 71, 80, 90, 99, 100, 110, 120) r.AddSortedSlice(m(1000, 2000)) tv(0, 0, 0, 1, 2, 3, 10, 20, 30, 40, 41, 50, 60, 61, 70, 71, 80, 90, 99, 100, 110, 120, 1000, 2000) } func run_scoring(b *testing.B, depth, breadth int, query string) { root := node{name: string(os.PathSeparator)} root.generate_random_tree(depth, breadth) for b.Loop() { b.StopTimer() wg := sync.WaitGroup{} wg.Add(1) s := NewFileSystemScorer(string(os.PathSeparator), query, Filter{}, false, func(err error, is_complete bool) { if is_complete { wg.Done() } }) s.dir_reader = root.ReadDir s.global_gitignore = ignorefiles.NewGitignore() b.StartTimer() s.scanner.Start() s.Start() wg.Wait() } fmt.Println("\nnumber of iterations: ", b.N) fmt.Println("time per iteration:", b.Elapsed()/time.Duration(b.N)) } // To run this benchmark with profiling use: // go test -bench=FileNameScoringWithoutQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe func BenchmarkFileNameScoringWithoutQuery(b *testing.B) { run_scoring(b, 5, 20, "") } // To run this benchmark with profiling use: // go test -bench=FileNameScoringWithQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe func BenchmarkFileNameScoringWithQuery(b *testing.B) { run_scoring(b, 5, 20, "abc") } ================================================ FILE: kittens/choose_files/search-bar.go ================================================ package choose_files import ( "fmt" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/wcswidth" ) var _ = fmt.Print func (h *Handler) draw_frame(width, height int, in_progress bool) { lp := h.lp prefix, suffix := "", "" if in_progress { x := h.lp.SprintStyled("fg=cyan", " ") prefix, suffix, _ = strings.Cut(x, " ") lp.QueueWriteString(prefix) } for i := range height { lp.SaveCursorPosition() switch i { case 0: lp.QueueWriteString("╭") lp.QueueWriteString(strings.Repeat("─", width-2)) lp.QueueWriteString("╮") case height - 1: lp.QueueWriteString("╰") lp.QueueWriteString(strings.Repeat("─", width-2)) lp.QueueWriteString("╯") default: lp.QueueWriteString("│") lp.MoveCursorHorizontally(width - 2) lp.QueueWriteString("│") } lp.RestoreCursorPosition() lp.MoveCursorVertically(1) } if suffix != "" { lp.QueueWriteString(suffix) } } func (h *Handler) draw_search_text(available_width int) { text := h.state.SearchText() available_width /= 2 if wcswidth.Stringwidth(text) > available_width { g := wcswidth.SplitIntoGraphemes(text) available_width -= 2 g = g[len(g)-available_width:] text = "…" + strings.Join(g, "") } h.lp.DrawSizedText(text+" ", loop.SizedText{Scale: 2}) h.lp.MoveCursorHorizontally(-2) } const SEARCH_BAR_HEIGHT = 4 func (h *Handler) draw_controls(y int) (max_width int) { type entry struct { text string callback func() width int } lines := make([]entry, 0, SEARCH_BAR_HEIGHT) add_control := func(icon, text string, callback func()) { line := icon + " " + text width := wcswidth.Stringwidth(line) max_width = max(max_width, width) lines = append(lines, entry{line, callback, width}) } add_control(utils.IfElse(h.state.ShowHidden(), " ", " "), utils.IfElse(h.state.ShowHidden(), "hide dotfiles", "show dotfiles"), func() { h.state.show_hidden = !h.state.show_hidden h.result_manager.set_show_hidden() }) add_control("󰑑 ", utils.IfElse(h.state.RespectIgnores(), "show ignored", "hide ignored"), func() { h.state.respect_ignores = !h.state.respect_ignores h.result_manager.set_respect_ignores() }) add_control(" ", utils.IfElse(h.state.ShowPreview(), "hide preview", "show preview"), func() { h.state.show_preview = !h.state.show_preview }) add_control(utils.IfElse(h.state.SortByLastModified(), " ", " "), utils.IfElse(h.state.SortByLastModified(), "sort names", "sort dates"), func() { h.state.sort_by_last_modified = !h.state.sort_by_last_modified h.result_manager.set_sort_by_last_modified() }) x := h.screen_size.width - max_width for i, e := range lines { h.lp.MoveCursorTo(x+1, y+i+1) h.lp.QueueWriteString(e.text) cb := e.callback h.state.mouse_state.AddCellRegion("rcontrol-"+strconv.Itoa(i), x, y+i, x+e.width, y+i, func(_ string) error { cb() h.state.redraw_needed = true return nil }).HoverStyle = HOVER_STYLE } return max_width + 1 } func (h *Handler) draw_search_bar(y int) { left_margin, right_margin := 0, h.draw_controls(y) h.lp.MoveCursorTo(1+left_margin, 1+y) available_width := h.screen_size.width - left_margin - right_margin h.draw_frame(available_width, SEARCH_BAR_HEIGHT, false) for y1 := y; y1 < y+4; y1++ { cr := h.state.mouse_state.AddCellRegion("search-bar", left_margin, y1, left_margin+available_width, y1) cr.PointerShape = loop.TEXT_POINTER cr.HoverStyle = "none" } h.lp.MoveCursorTo(1+left_margin+1, 2+y) h.draw_search_text(available_width - 2) } func (h *Handler) handle_edit_keys(ev *loop.KeyEvent) bool { switch { case ev.MatchesPressOrRepeat("backspace"): if h.state.SearchText() == "" { h.lp.Beep() } else { g := wcswidth.SplitIntoGraphemes(h.state.search_text) h.set_query(strings.Join(g[:len(g)-1], "")) return true } } return false } ================================================ FILE: kittens/choose_fonts/__init__.py ================================================ ================================================ FILE: kittens/choose_fonts/backend.go ================================================ package choose_fonts import ( "encoding/json" "fmt" "io" "os" "os/exec" "strings" "sync" "time" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/choose_fonts/backend.py ================================================ #!/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_compat 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']) ================================================ FILE: kittens/choose_fonts/face.go ================================================ package choose_fonts import ( "fmt" "maps" "math" "slices" "strconv" "strings" "sync" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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 := range num_of_cells { 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() } ================================================ FILE: kittens/choose_fonts/faces.go ================================================ package choose_fonts import ( "fmt" "math" "sync" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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() } ================================================ FILE: kittens/choose_fonts/family_list.go ================================================ package choose_fonts import ( "fmt" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/subseq" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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 "" } ================================================ FILE: kittens/choose_fonts/final.go ================================================ package choose_fonts import ( "fmt" "path/filepath" "strings" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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() } ================================================ FILE: kittens/choose_fonts/graphics.go ================================================ package choose_fonts import ( "fmt" "strings" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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\n\nNote that the choose-fonts kitten does not work over SSH as it is meant to select a locally available font to use in kitty.", 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()) } ================================================ FILE: kittens/choose_fonts/index_feature.go ================================================ package choose_fonts import ( "fmt" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/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, 0) 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.FormatUint(uint64(self.current_val), 10)) } return self.handler.draw_screen() } ================================================ FILE: kittens/choose_fonts/list.go ================================================ package choose_fonts import ( "fmt" "strings" "sync" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/choose_fonts/main.go ================================================ package choose_fonts import ( "fmt" "os" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/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" } ================================================ FILE: kittens/choose_fonts/main.py ================================================ if __name__ == '__main__': import os import sys from kitty.constants import kitten_exe os.execlp(kitten_exe(), 'kitten', *sys.argv) ================================================ FILE: kittens/choose_fonts/styles.go ================================================ package choose_fonts import ( "fmt" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/choose_fonts/types.go ================================================ package choose_fonts import ( "fmt" "maps" "strconv" "strings" "sync" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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("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 } ================================================ FILE: kittens/choose_fonts/ui.go ================================================ package choose_fonts import ( "fmt" "os" "strconv" "sync" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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 } // }}} ================================================ FILE: kittens/clipboard/__init__.py ================================================ ================================================ FILE: kittens/clipboard/legacy.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "bytes" "encoding/base64" "errors" "fmt" "io" "os" "strings" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/clipboard/main.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "fmt" "io" "os" "strconv" "strings" "unicode" "github.com/kovidgoyal/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 opts.Password != "" { if opts.HumanName == "" { return 1, fmt.Errorf("must specify --human-name when using a password") } ptype, val, found := strings.Cut(opts.Password, ":") if !found { return 1, fmt.Errorf("invalid password: %#v no password type specified", opts.Password) } switch ptype { case "text": opts.Password = val case "fd": if fd, err := strconv.Atoi(val); err == nil { if f := os.NewFile(uintptr(fd), "password-fd"); f == nil { return 1, fmt.Errorf("invalid file descriptor: %d", fd) } else { data, err := io.ReadAll(f) f.Close() if err != nil { return 1, fmt.Errorf("failed to read from file descriptor: %d with error: %w", fd, err) } opts.Password = strings.TrimRightFunc(string(data), unicode.IsSpace) } } else { return 1, fmt.Errorf("not a valid file descriptor number: %#v", val) } case "file": if data, err := os.ReadFile(val); err == nil { opts.Password = strings.TrimRightFunc(string(data), unicode.IsSpace) } else { return 1, fmt.Errorf("failed to read from file: %#v with error: %w", val, err) } } } if len(args) > 0 { return 0, run_mime_loop(opts, args) } if opts.Password != "" || opts.HumanName != "" { return 1, fmt.Errorf("cannot use --human-name or --password in filter mode") } return 0, run_plain_text_loop(opts) } func EntryPoint(parent *cli.Command) { create_cmd(parent, clipboard_main) } ================================================ FILE: kittens/clipboard/main.py ================================================ #!/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. --password A password to use when accessing the clipboard. If the user chooses to accept the password future invocations of the kitten will not have a permission prompt in this tty session. Does not work in filter mode. Must be of the form: text:actual-password or fd:integer (a file descriptor number to read the password from) or file:path-to-file (a file from which to read the password). Note that you must also specify a human friendly name using the :option:`--human-name` flag. --human-name A human friendly name to show the user when asking for permission to access the clipboard. '''.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.simple_cli_definitions 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') ================================================ FILE: kittens/clipboard/read.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "bytes" "encoding/base64" "fmt" "image" "io" "os" "path/filepath" "slices" "strings" "sync" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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.SplitSeq(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, ".")) if opts.Password != "" { basic_metadata["pw"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.Password)) } if opts.HumanName != "" { basic_metadata["name"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.HumanName)) } 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 } ================================================ FILE: kittens/clipboard/write.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "encoding/base64" "errors" "fmt" "io" "os" "path/filepath" "slices" "strings" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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 } return slices.ContainsFunc(self.extra_mime_types, predicate) } 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 } if ptype == "write" { if opts.Password != "" { ans["pw"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.Password)) } if opts.HumanName != "" { ans["name"] = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(opts.HumanName)) } } 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) } ================================================ FILE: kittens/command_palette/__init__.py ================================================ ================================================ FILE: kittens/command_palette/main.go ================================================ // License: GPLv3 Copyright: 2024, Kovid Goyal package command_palette import ( "encoding/json" "fmt" "io" "os" "sort" "strings" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/fzf" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/wcswidth" ) // JSON data structures matching Python collect_keys_data output type Binding struct { Key string `json:"key"` Action string `json:"action"` ActionDisplay string `json:"action_display"` Definition string `json:"definition"` Help string `json:"help"` LongHelp string `json:"long_help"` Category string Mode string IsMouse bool } type InputData struct { Modes map[string]map[string][]Binding `json:"modes"` Mouse []Binding `json:"mouse"` ModeOrder []string `json:"mode_order"` CategoryOrder map[string][]string `json:"category_order"` } // DisplayItem wraps a binding with its per-column search texts for FZF scoring type DisplayItem struct { binding Binding colTexts [3]string // [0]=key, [1]=action_display, [2]=category } // matchInfo stores which column matched and the matched character positions type matchInfo struct { colIdx int // which column matched: 0=key, 1=action_display, 2=category positions []int // rune positions in the matched column text } type displayLine struct { text string isHeader bool isModeHdr bool itemIdx int // index into filtered_idx, -1 for headers } const maxKeyDisplayWidth = 30 // unmappedLabel is shown in the key column for actions with no keyboard shortcut. const unmappedLabel = "(unmapped)" // truncateToWidth truncates s to fit within maxWidth cells, appending "..." if // truncated and maxWidth > 3. When maxWidth <= 3, the string is simply trimmed // to fit without appending ellipsis (no room for it). func truncateToWidth(s string, maxWidth int) string { if wcswidth.Stringwidth(s) <= maxWidth { return s } runes := []rune(s) if maxWidth <= 3 { // Not enough room for ellipsis; just trim to fit for len(runes) > 0 && wcswidth.Stringwidth(string(runes)) > maxWidth { runes = runes[:len(runes)-1] } return string(runes) } for len(runes) > 0 && wcswidth.Stringwidth(string(runes))+3 > maxWidth { runes = runes[:len(runes)-1] } return string(runes) + "..." } // CachedSettings holds persistent UI settings stored in command-palette.json. type CachedSettings struct { ShowUnmapped bool `json:"show_unmapped"` } type Handler struct { lp *loop.Loop screen_size loop.ScreenSize all_items []DisplayItem matcher *fzf.FuzzyMatcher filtered_idx []int // indices into all_items for current results match_infos []matchInfo // parallel to filtered_idx, valid when query != "" query string selected_idx int scroll_offset int input_data InputData result string // action definition to execute after exit display_lines []displayLine results_start_y int results_height int show_unmapped bool cv *utils.CachedValues[*CachedSettings] } func (h *Handler) initialize() (string, error) { sz, err := h.lp.ScreenSize() if err != nil { return "", err } h.screen_size = sz h.lp.SetCursorVisible(true) h.lp.SetCursorShape(loop.BAR_CURSOR, true) h.lp.AllowLineWrapping(false) h.lp.SetWindowTitle("Command Palette") // Initialize with ShowUnmapped: true as the default; Load() returns this // default when no cache file exists yet. h.cv = utils.NewCachedValues("command-palette", &CachedSettings{ShowUnmapped: true}) settings := h.cv.Load() h.show_unmapped = settings.ShowUnmapped if err := h.loadData(); err != nil { return "", err } h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME) h.updateFilter() h.draw_screen() h.lp.SendOverlayReady() return "", nil } func (h *Handler) loadData() error { data, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("failed to read stdin: %w", err) } if len(data) == 0 { return fmt.Errorf("no input data received on stdin; this kitten must be launched from kitty") } if err := json.Unmarshal(data, &h.input_data); err != nil { return fmt.Errorf("failed to parse input data: %w", err) } h.flattenBindings() return nil } // flattenBindings converts the hierarchical mode/category/binding data into // a flat list suitable for display and FZF scoring. Uses the explicit ordering // arrays from Python since Go maps do not preserve insertion order. func (h *Handler) flattenBindings() { // Use explicit mode ordering from Python, falling back to sorted keys modeNames := h.input_data.ModeOrder if len(modeNames) == 0 { modeNames = make([]string, 0, len(h.input_data.Modes)) for name := range h.input_data.Modes { modeNames = append(modeNames, name) } sort.Slice(modeNames, func(i, j int) bool { if modeNames[i] == "" { return true } if modeNames[j] == "" { return false } return modeNames[i] < modeNames[j] }) } for _, modeName := range modeNames { categories, ok := h.input_data.Modes[modeName] if !ok { continue } // Use explicit category ordering from Python, falling back to sorted keys catNames := h.input_data.CategoryOrder[modeName] if len(catNames) == 0 { catNames = make([]string, 0, len(categories)) for name := range categories { catNames = append(catNames, name) } sort.Strings(catNames) } for _, catName := range catNames { bindings, ok := categories[catName] if !ok { continue } for _, b := range bindings { b.Category = catName b.Mode = modeName b.IsMouse = false keyText := b.Key if keyText == "" { keyText = unmappedLabel } h.all_items = append(h.all_items, DisplayItem{ binding: b, colTexts: [3]string{keyText, b.ActionDisplay, catName}, }) } } } // Mouse bindings for _, b := range h.input_data.Mouse { b.Category = "Mouse actions" b.Mode = "" b.IsMouse = true h.all_items = append(h.all_items, DisplayItem{ binding: b, colTexts: [3]string{b.Key, b.ActionDisplay, "Mouse actions"}, }) } } func (h *Handler) updateFilter() { if h.query == "" { // Show all items in original order, respecting the show_unmapped toggle h.filtered_idx = make([]int, 0, len(h.all_items)) for i, item := range h.all_items { if !h.show_unmapped && item.binding.Key == "" { continue } h.filtered_idx = append(h.filtered_idx, i) } h.match_infos = nil h.selected_idx = 0 h.scroll_offset = 0 return } nItems := len(h.all_items) // Build per-column text slices for batch FZF scoring colSlices := [3][]string{ make([]string, nItems), make([]string, nItems), make([]string, nItems), } for i, item := range h.all_items { colSlices[0][i] = item.colTexts[0] colSlices[1][i] = item.colTexts[1] colSlices[2][i] = item.colTexts[2] } // Score each column independently colResults := [3][]fzf.Result{} for c := 0; c < 3; c++ { results, err := h.matcher.Score(colSlices[c], h.query) if err == nil { colResults[c] = results } } type scored struct { idx int score uint colIdx int positions []int } var matches []scored for i := range h.all_items { if !h.show_unmapped && h.all_items[i].binding.Key == "" { continue } bestScore := uint(0) bestCol := 0 var bestPositions []int for c := 0; c < 3; c++ { if colResults[c] != nil && i < len(colResults[c]) && colResults[c][i].Score > bestScore { bestScore = colResults[c][i].Score bestCol = c bestPositions = colResults[c][i].Positions } } if bestScore > 0 { matches = append(matches, scored{idx: i, score: bestScore, colIdx: bestCol, positions: bestPositions}) } } sort.Slice(matches, func(i, j int) bool { return matches[i].score > matches[j].score }) h.filtered_idx = make([]int, len(matches)) h.match_infos = make([]matchInfo, len(matches)) for i, m := range matches { h.filtered_idx[i] = m.idx h.match_infos[i] = matchInfo{colIdx: m.colIdx, positions: m.positions} } h.selected_idx = 0 h.scroll_offset = 0 } // highlightMatchedChars returns a string with characters at the given rune // positions rendered using matchStyle, and the rest rendered using baseStyle // (or unstyled if baseStyle is empty). func (h *Handler) highlightMatchedChars(text string, positions []int, baseStyle, matchStyle string) string { if len(positions) == 0 { if baseStyle != "" { return h.lp.SprintStyled(baseStyle, text) } return text } posSet := make(map[int]bool, len(positions)) for _, p := range positions { posSet[p] = true } runes := []rune(text) var sb strings.Builder for i, r := range runes { ch := string(r) if posSet[i] { sb.WriteString(h.lp.SprintStyled(matchStyle, ch)) } else if baseStyle != "" { sb.WriteString(h.lp.SprintStyled(baseStyle, ch)) } else { sb.WriteString(ch) } } return sb.String() } func (h *Handler) selectedBinding() *Binding { if h.selected_idx < 0 || h.selected_idx >= len(h.filtered_idx) { return nil } idx := h.filtered_idx[h.selected_idx] if idx < 0 || idx >= len(h.all_items) { return nil } return &h.all_items[idx].binding } func (h *Handler) draw_screen() { h.lp.StartAtomicUpdate() defer h.lp.EndAtomicUpdate() h.lp.ClearScreen() width := int(h.screen_size.WidthCells) height := int(h.screen_size.HeightCells) if width < 10 || height < 5 { return } // Layout: line 1 = search bar, lines 2..height-2 = results, // line height-1 = help text, line height = key hints searchBarY := 1 resultsStartY := 2 helpY := height - 1 hintsY := height resultsHeight := helpY - resultsStartY if resultsHeight < 1 { resultsHeight = 1 } h.results_start_y = resultsStartY h.results_height = resultsHeight // Draw search bar h.lp.MoveCursorTo(1, searchBarY) h.lp.QueueWriteString(h.lp.SprintStyled("fg=bright-yellow", "> ")) h.lp.QueueWriteString(h.query) // Draw results if h.query == "" { h.drawGroupedResults(resultsStartY, resultsHeight, width) } else { h.drawFlatResults(resultsStartY, resultsHeight, width) } // Draw help text for selected binding h.lp.MoveCursorTo(1, helpY) if b := h.selectedBinding(); b != nil && b.Help != "" { helpStr := b.Help maxLen := width - 2 if maxLen < 3 { maxLen = 3 } if wcswidth.Stringwidth(helpStr) > maxLen { // Truncate by runes to avoid breaking multi-byte characters runes := []rune(helpStr) for len(runes) > 0 && wcswidth.Stringwidth(string(runes))+3 > maxLen { runes = runes[:len(runes)-1] } helpStr = string(runes) + "..." } h.lp.QueueWriteString(h.lp.SprintStyled("dim italic", " "+helpStr)) } // Draw key hints footer h.lp.MoveCursorTo(1, hintsY) unmappedToggleLabel := "Show" if h.show_unmapped { unmappedToggleLabel = "Hide" } footer := h.lp.SprintStyled("fg=bright-yellow", "[Enter]") + " Run " + h.lp.SprintStyled("fg=bright-yellow", "[Esc]") + " Quit " + h.lp.SprintStyled("fg=bright-yellow", "\u2191\u2193") + " Navigate " + h.lp.SprintStyled("fg=bright-yellow", "[F12]") + " " + unmappedToggleLabel + " unmapped" matchInfo := "" if h.query != "" { matchInfo = fmt.Sprintf(" %d/%d", len(h.filtered_idx), len(h.all_items)) } h.lp.QueueWriteString(" " + footer + h.lp.SprintStyled("dim", matchInfo)) // Position cursor at end of search text for typing h.lp.MoveCursorTo(3+wcswidth.Stringwidth(h.query), searchBarY) } func (h *Handler) drawGroupedResults(startY, maxRows, width int) { var lines []displayLine lastMode := "" lastCategory := "" for fi, idx := range h.filtered_idx { item := &h.all_items[idx] b := &item.binding // Mode header when mode changes if b.Mode != lastMode { lastMode = b.Mode lastCategory = "" if b.Mode != "" { // Non-default mode: show "── Keyboard mode: name ──" header (purple), no category separators if len(lines) > 0 { lines = append(lines, displayLine{itemIdx: -1, isHeader: true}) } label := "Keyboard mode: " + b.Mode labelWidth := wcswidth.Stringwidth(label) sepLen := max(0, width-labelWidth-6) sep := strings.Repeat("\u2500", sepLen) lines = append(lines, displayLine{ text: fmt.Sprintf(" \u2500\u2500 %s %s", label, sep), isModeHdr: true, isHeader: true, itemIdx: -1, }) } } // Category header when category changes - only for the default mode ("") if b.Mode == "" && b.Category != lastCategory { lastCategory = b.Category if len(lines) > 0 && !lines[len(lines)-1].isHeader { lines = append(lines, displayLine{itemIdx: -1, isHeader: true}) } catWidth := wcswidth.Stringwidth(b.Category) sepLen := max(0, width-catWidth-6) sep := strings.Repeat("\u2500", sepLen) lines = append(lines, displayLine{ text: fmt.Sprintf(" \u2500\u2500 %s %s", b.Category, sep), isHeader: true, itemIdx: -1, }) } // Binding line — key column shows "(unmapped)" for actions with no shortcut keyDisplay := b.Key if keyDisplay == "" { keyDisplay = unmappedLabel } keyDisplay = truncateToWidth(keyDisplay, maxKeyDisplayWidth) lines = append(lines, displayLine{ text: fmt.Sprintf(" %-*s %s", maxKeyDisplayWidth, keyDisplay, b.ActionDisplay), itemIdx: fi, }) } h.display_lines = lines h.drawLines(lines, startY, maxRows, width) } func (h *Handler) drawFlatResults(startY, maxRows, width int) { if len(h.filtered_idx) == 0 { h.lp.MoveCursorTo(1, startY) h.lp.QueueWriteString(h.lp.SprintStyled("italic dim", " No matches found")) h.display_lines = []displayLine{} return } var lines []displayLine for fi, idx := range h.filtered_idx { item := &h.all_items[idx] b := &item.binding keyDisplay := b.Key if keyDisplay == "" { keyDisplay = unmappedLabel } keyDisplay = truncateToWidth(keyDisplay, maxKeyDisplayWidth) catSuffix := "" if b.Mode != "" { catSuffix = fmt.Sprintf(" [%s/%s]", b.Mode, b.Category) } else { catSuffix = fmt.Sprintf(" [%s]", b.Category) } lines = append(lines, displayLine{ text: fmt.Sprintf(" %-*s %-30s%s", maxKeyDisplayWidth, keyDisplay, b.ActionDisplay, catSuffix), itemIdx: fi, }) } h.display_lines = lines h.drawLines(lines, startY, maxRows, width) } func (h *Handler) drawLines(lines []displayLine, startY, maxRows, width int) { if maxRows <= 0 || len(lines) == 0 { return } // Adjust scroll to keep selected item visible selectedLineIdx := -1 for i, dl := range lines { if dl.itemIdx == h.selected_idx { selectedLineIdx = i break } } if selectedLineIdx >= 0 { if selectedLineIdx < h.scroll_offset { // Scroll up to show selected item; also reveal any header lines above it h.scroll_offset = selectedLineIdx for h.scroll_offset > 0 && lines[h.scroll_offset-1].isHeader { h.scroll_offset-- } } if selectedLineIdx >= h.scroll_offset+maxRows { h.scroll_offset = selectedLineIdx - maxRows + 1 } } h.scroll_offset = max(0, h.scroll_offset) h.scroll_offset = min(h.scroll_offset, max(0, len(lines)-maxRows)) end := min(h.scroll_offset+maxRows, len(lines)) for row, li := range lines[h.scroll_offset:end] { h.lp.MoveCursorTo(1, startY+row) text := li.text // Truncate at rune boundary to avoid breaking multi-byte characters if wcswidth.Stringwidth(text) > width { runes := []rune(text) for len(runes) > 0 && wcswidth.Stringwidth(string(runes)) > width { runes = runes[:len(runes)-1] } text = string(runes) } if li.isModeHdr { h.lp.QueueWriteString(h.lp.SprintStyled("bold fg=magenta", text)) } else if li.isHeader { h.lp.QueueWriteString(h.lp.SprintStyled("fg=bright-blue", text)) } else if li.itemIdx == h.selected_idx { // Selected item: highlight with reverse video padded := text textWidth := wcswidth.Stringwidth(text) if textWidth < width { padded += strings.Repeat(" ", width-textWidth) } h.lp.QueueWriteString(h.lp.SprintStyled("fg=black bg=white", padded)) } else { h.drawBindingLine(text, li.itemIdx, width) } } } func (h *Handler) drawBindingLine(text string, filteredIdx, width int) { if filteredIdx < 0 || filteredIdx >= len(h.filtered_idx) { h.lp.QueueWriteString(text) return } idx := h.filtered_idx[filteredIdx] if idx < 0 || idx >= len(h.all_items) { h.lp.QueueWriteString(text) return } b := &h.all_items[idx].binding // Build the key display (using unmappedLabel for items with no shortcut) rawKey := b.Key if rawKey == "" { rawKey = unmappedLabel } keyDisplay := truncateToWidth(rawKey, maxKeyDisplayWidth) // Determine match info for highlighting (only set when a query is active) var mi *matchInfo if h.query != "" && filteredIdx < len(h.match_infos) { mi = &h.match_infos[filteredIdx] } const matchStyle = "fg=bright-yellow" const keyStyle = "fg=green" const unmappedStyle = "dim fg=green" // Render key column (4-space indent + key padded to maxKeyDisplayWidth + space) paddingLen := max(0, maxKeyDisplayWidth-wcswidth.Stringwidth(keyDisplay)) if mi != nil && mi.colIdx == 0 { ks := keyStyle if b.Key == "" { ks = unmappedStyle } h.lp.QueueWriteString(" ") h.lp.QueueWriteString(h.highlightMatchedChars(keyDisplay, mi.positions, ks, matchStyle)) h.lp.QueueWriteString(strings.Repeat(" ", paddingLen) + " ") } else if b.Key == "" { h.lp.QueueWriteString(h.lp.SprintStyled(unmappedStyle, " "+keyDisplay+strings.Repeat(" ", paddingLen)+" ")) } else { h.lp.QueueWriteString(h.lp.SprintStyled(keyStyle, " "+keyDisplay+strings.Repeat(" ", paddingLen)+" ")) } // Render action display column if mi != nil && mi.colIdx == 1 { h.lp.QueueWriteString(h.highlightMatchedChars(b.ActionDisplay, mi.positions, "", matchStyle)) } else { h.lp.QueueWriteString(b.ActionDisplay) } // Render category suffix (only present in flat / search-results mode) if h.query != "" { if mi != nil && mi.colIdx == 2 { if b.Mode != "" { h.lp.QueueWriteString(fmt.Sprintf(" [%s/", b.Mode)) } else { h.lp.QueueWriteString(" [") } h.lp.QueueWriteString(h.highlightMatchedChars(b.Category, mi.positions, "", matchStyle)) h.lp.QueueWriteString("]") } else { if b.Mode != "" { h.lp.QueueWriteString(fmt.Sprintf(" [%s/%s]", b.Mode, b.Category)) } else { h.lp.QueueWriteString(fmt.Sprintf(" [%s]", b.Category)) } } } } // rowToFilteredIdx converts a 0-indexed cell Y coordinate to a filtered item // index, or -1 if the cell is not over a clickable item. Internally converts // to 1-indexed screen rows (matching the MoveCursorTo convention) to compare // against results_start_y. func (h *Handler) rowToFilteredIdx(cellY int) int { screenRow := cellY + 1 // convert 0-indexed cell to 1-indexed screen row if screenRow < h.results_start_y || screenRow >= h.results_start_y+h.results_height { return -1 } lineIdx := h.scroll_offset + (screenRow - h.results_start_y) if lineIdx < 0 || lineIdx >= len(h.display_lines) { return -1 } return h.display_lines[lineIdx].itemIdx } func (h *Handler) onMouseEvent(ev *loop.MouseEvent) error { switch ev.Event_type { case loop.MOUSE_CLICK: if ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 { fi := h.rowToFilteredIdx(ev.Cell.Y) if fi >= 0 { h.selected_idx = fi h.triggerSelected() } } case loop.MOUSE_MOVE: fi := h.rowToFilteredIdx(ev.Cell.Y) h.lp.ClearPointerShapes() if fi >= 0 { h.lp.PushPointerShape(loop.POINTER_POINTER) } } return nil } func (h *Handler) onKeyEvent(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("escape") { ev.Handled = true if h.query != "" { h.query = "" h.updateFilter() h.draw_screen() } else { h.lp.Quit(0) } return nil } if ev.MatchesPressOrRepeat("enter") { ev.Handled = true h.triggerSelected() return nil } if ev.MatchesPressOrRepeat("up") || ev.MatchesPressOrRepeat("ctrl+k") || ev.MatchesPressOrRepeat("ctrl+p") { ev.Handled = true h.moveSelection(-1) return nil } if ev.MatchesPressOrRepeat("down") || ev.MatchesPressOrRepeat("ctrl+j") || ev.MatchesPressOrRepeat("ctrl+n") { ev.Handled = true h.moveSelection(1) return nil } if ev.MatchesPressOrRepeat("page_up") { ev.Handled = true delta := max(1, int(h.screen_size.HeightCells)-4) h.moveSelection(-delta) return nil } if ev.MatchesPressOrRepeat("page_down") { ev.Handled = true delta := max(1, int(h.screen_size.HeightCells)-4) h.moveSelection(delta) return nil } if ev.MatchesPressOrRepeat("home") || ev.MatchesPressOrRepeat("ctrl+home") { ev.Handled = true h.selected_idx = 0 h.draw_screen() return nil } if ev.MatchesPressOrRepeat("end") || ev.MatchesPressOrRepeat("ctrl+end") { ev.Handled = true if len(h.filtered_idx) > 0 { h.selected_idx = len(h.filtered_idx) - 1 } h.draw_screen() return nil } if ev.MatchesPressOrRepeat("backspace") { ev.Handled = true if h.query != "" { g := wcswidth.SplitIntoGraphemes(h.query) h.query = strings.Join(g[:len(g)-1], "") h.updateFilter() h.draw_screen() } else { h.lp.Beep() } return nil } if ev.MatchesPressOrRepeat("f12") { ev.Handled = true h.show_unmapped = !h.show_unmapped if h.cv != nil { h.cv.Opts.ShowUnmapped = h.show_unmapped h.cv.Save() } h.updateFilter() h.draw_screen() return nil } return nil } func (h *Handler) onText(text string, from_key_event bool, in_bracketed_paste bool) error { h.query += text h.updateFilter() h.draw_screen() return nil } func (h *Handler) onResize(old, new_size loop.ScreenSize) error { h.screen_size = new_size h.draw_screen() return nil } func (h *Handler) moveSelection(delta int) { if len(h.filtered_idx) == 0 { return } h.selected_idx += delta h.selected_idx = max(0, h.selected_idx) h.selected_idx = min(h.selected_idx, len(h.filtered_idx)-1) h.draw_screen() } func (h *Handler) triggerSelected() { b := h.selectedBinding() if b == nil || b.IsMouse { h.lp.Beep() return } h.result = b.Definition h.lp.Quit(0) } func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { if tty.IsTerminal(os.Stdin.Fd()) { return 1, fmt.Errorf("This kitten must only be run via the command_palette action mapped to a shortcut in kitty.conf") } output := tui.KittenOutputSerializer() lp, err := loop.New() if err != nil { return 1, err } handler := &Handler{lp: lp} lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) lp.OnInitialize = func() (string, error) { return handler.initialize() } lp.OnFinalize = func() string { return "" } lp.OnKeyEvent = handler.onKeyEvent lp.OnText = handler.onText lp.OnResize = handler.onResize lp.OnMouseEvent = handler.onMouseEvent err = lp.Run() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal:", ds) lp.KillIfSignalled() return } rc = lp.ExitCode() if handler.result != "" { s, serr := output(map[string]string{"action": handler.result}) if serr == nil { fmt.Println(s) } } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } ================================================ FILE: kittens/command_palette/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import sys from functools import partial from typing import Any from kitty.fast_data_types import add_timer, get_boss from kitty.typing_compat import BossType from ..tui.handler import result_handler def collect_keys_data(opts: Any) -> dict[str, Any]: """Collect all keybinding data from options into a JSON-serializable dict.""" from kitty.actions import get_all_actions, groups from kitty.options.utils import KeyDefinition from kitty.types import Shortcut # Build action->group and action->help lookups action_to_group: dict[str, str] = {} action_to_help: dict[str, str] = {} action_to_long_help: dict[str, str] = {} for group_key, actions in get_all_actions().items(): for action in actions: action_to_group[action.name] = groups[group_key] action_to_help[action.name] = action.short_help action_to_long_help[action.name] = action.long_help modes: dict[str, dict[str, list[dict[str, str]]]] = {} def as_sc(k: 'Any', v: KeyDefinition) -> Shortcut: if v.is_sequence: return Shortcut((v.trigger,) + v.rest) return Shortcut((k,)) for mode_name, mode in opts.keyboard_modes.items(): categories: dict[str, list[dict[str, str]]] = {} for key, defns in mode.keymap.items(): # Use last non-duplicate definition seen: set[tuple[Any, ...]] = set() uniq: list[KeyDefinition] = [] for d in reversed(defns): uid = d.unique_identity_within_keymap if uid not in seen: seen.add(uid) uniq.append(d) for d in uniq: sc = as_sc(key, d) key_repr = sc.human_repr(opts.kitty_mod) action_repr = d.human_repr() # Determine category from first word of action definition action_name = d.definition.split()[0] if d.definition else 'no_op' category = action_to_group.get(action_name, 'Miscellaneous') help_text = action_to_help.get(action_name, '') long_help = action_to_long_help.get(action_name, '') categories.setdefault(category, []).append({ 'key': key_repr, 'action': action_name, 'action_display': action_repr, 'definition': d.definition or action_name, 'help': help_text, 'long_help': long_help, }) # Sort within categories for cat in categories: categories[cat].sort(key=lambda b: b['key']) # Order categories by the groups order ordered: dict[str, list[dict[str, str]]] = {} for group_title in groups.values(): if group_title in categories: ordered[group_title] = categories.pop(group_title) # Add any remaining for cat_name, binds in sorted(categories.items()): ordered[cat_name] = binds modes[mode_name] = ordered # Move push_keyboard_mode bindings from the default mode into the # respective keyboard mode's section so they appear alongside its shortcuts. if '' in modes: new_default_cats: dict[str, list[dict[str, str]]] = {} for cat_name, bindings in modes[''].items(): keep: list[dict[str, str]] = [] for b in bindings: if b['action'] == 'push_keyboard_mode': parts = b['definition'].split() target = parts[1] if len(parts) > 1 else '' if target and target in modes: if 'Enter mode' not in modes[target]: new_target: dict[str, list[dict[str, str]]] = {'Enter mode': [b]} new_target.update(modes[target]) modes[target] = new_target else: modes[target]['Enter mode'].append(b) continue keep.append(b) if keep: new_default_cats[cat_name] = keep modes[''] = new_default_cats # Add unmapped actions (actions with no keyboard shortcut). # Collect all action names that already appear in a binding. mapped_actions: set[str] = set() for mode_cats in modes.values(): for bindings in mode_cats.values(): for b in bindings: mapped_actions.add(b['action']) default_mode_cats = modes.setdefault('', {}) for group_key, actions in get_all_actions().items(): category = groups[group_key] for action in actions: if action.name not in mapped_actions: default_mode_cats.setdefault(category, []).append({ 'key': '', 'action': action.name, 'action_display': action.name, 'definition': action.name, 'help': action.short_help, 'long_help': action.long_help, }) # Re-sort each category: mapped entries (non-empty key) by key first, # then unmapped entries (empty key) sorted by action name. for cat in default_mode_cats: default_mode_cats[cat].sort(key=lambda b: (b['key'] == '', b['key'] or b['action'])) # Re-order default_mode_cats by groups ordering (adding unmapped actions may # have appended new categories at the end, breaking the established order). reordered: dict[str, list[dict[str, str]]] = {} for group_title in groups.values(): if group_title in default_mode_cats: reordered[group_title] = default_mode_cats[group_title] for cat_name, binds in default_mode_cats.items(): if cat_name not in reordered: reordered[cat_name] = binds modes[''] = reordered # Emit explicit mode and category ordering since JSON maps lose insertion order mode_order = list(modes.keys()) category_order: dict[str, list[str]] = {} for mode_name, cats in modes.items(): category_order[mode_name] = list(cats.keys()) # Mouse mappings mouse: list[dict[str, str]] = [] for event, action in opts.mousemap.items(): key_repr = event.human_repr(opts.kitty_mod) mouse.append({'key': key_repr, 'action': action, 'action_display': action, 'help': '', 'long_help': ''}) mouse.sort(key=lambda b: b['key']) return { 'modes': modes, 'mouse': mouse, 'mode_order': mode_order, 'category_order': category_order, } def main(args: list[str]) -> None: raise SystemExit('This kitten must be used only from a kitty.conf mapping') def callback(target_window_id: int, action: str, timer_id: int | None) -> None: boss = get_boss() w = boss.window_id_map.get(target_window_id) boss.combine(action, w) @result_handler(has_ready_notification=True) def handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType) -> None: if data and (action := data.get('action')): # run action after event loop tick so command palette overlay is closed add_timer(partial(callback, target_window_id, action), 0, False) help_text = 'Browse and trigger keyboard shortcuts and actions' usage = '' OPTIONS = r''' '''.format 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 ================================================ FILE: kittens/command_palette/main_test.go ================================================ package command_palette import ( "encoding/json" "fmt" "strings" "testing" "github.com/kovidgoyal/kitty/tools/fzf" ) func sampleInputJSON() string { return `{ "modes": { "": { "Copy/paste": [ {"key": "ctrl+shift+c", "action": "copy_to_clipboard", "action_display": "copy_to_clipboard", "definition": "copy_to_clipboard", "help": "Copy the selected text from the active window to the clipboard", "long_help": ""}, {"key": "ctrl+shift+v", "action": "paste_from_clipboard", "action_display": "paste_from_clipboard", "definition": "paste_from_clipboard", "help": "Paste from the clipboard to the active window", "long_help": ""} ], "Scrolling": [ {"key": "ctrl+shift+up", "action": "scroll_line_up", "action_display": "scroll_line_up", "definition": "scroll_line_up", "help": "Scroll up one line", "long_help": ""}, {"key": "ctrl+shift+down", "action": "scroll_line_down", "action_display": "scroll_line_down", "definition": "scroll_line_down", "help": "Scroll down one line", "long_help": ""} ], "Window management": [ {"key": "ctrl+shift+enter", "action": "new_window", "action_display": "new_window", "definition": "new_window", "help": "Open a new window", "long_help": ""} ] }, "mw": { "Miscellaneous": [ {"key": "left", "action": "neighboring_window", "action_display": "neighboring_window left", "definition": "neighboring_window left", "help": "Focus neighbor window", "long_help": ""}, {"key": "esc", "action": "pop_keyboard_mode", "action_display": "pop_keyboard_mode", "definition": "pop_keyboard_mode", "help": "Pop keyboard mode", "long_help": ""} ] } }, "mouse": [ {"key": "left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection normal", "definition": "mouse_selection normal", "help": "", "long_help": ""}, {"key": "ctrl+left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection rectangle", "definition": "mouse_selection rectangle", "help": "", "long_help": ""} ], "mode_order": ["", "mw"], "category_order": { "": ["Copy/paste", "Scrolling", "Window management"], "mw": ["Miscellaneous"] } }` } func newTestHandler() *Handler { h := &Handler{} if err := json.Unmarshal([]byte(sampleInputJSON()), &h.input_data); err != nil { panic("test data JSON is invalid: " + err.Error()) } h.flattenBindings() h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME) return h } func TestFlattenAllBindings(t *testing.T) { h := newTestHandler() // 5 default mode + 2 mw mode + 2 mouse = 9 if len(h.all_items) != 9 { t.Fatalf("Expected 9 items, got %d", len(h.all_items)) } } func TestDefaultModeComesFirst(t *testing.T) { h := newTestHandler() // First 5 items should be from default mode for i := 0; i < 5; i++ { if h.all_items[i].binding.Mode != "" { t.Fatalf("Item %d should be from default mode, got mode=%q", i, h.all_items[i].binding.Mode) } } } func TestCategoryOrderPreserved(t *testing.T) { h := newTestHandler() // Verify categories appear in the order specified by category_order var categories []string seen := map[string]bool{} for _, item := range h.all_items { if item.binding.Mode != "" || item.binding.IsMouse { continue } cat := item.binding.Category if !seen[cat] { categories = append(categories, cat) seen[cat] = true } } expected := []string{"Copy/paste", "Scrolling", "Window management"} if len(categories) != len(expected) { t.Fatalf("Expected %d categories, got %d: %v", len(expected), len(categories), categories) } for i, cat := range categories { if cat != expected[i] { t.Fatalf("Category %d: expected %q, got %q", i, expected[i], cat) } } } func TestCustomModePresent(t *testing.T) { h := newTestHandler() found := false for _, item := range h.all_items { if item.binding.Mode == "mw" { found = true break } } if !found { t.Fatal("Expected to find items from 'mw' mode") } } func TestMouseBindingsMarkedCorrectly(t *testing.T) { h := newTestHandler() mouseCount := 0 for _, item := range h.all_items { if item.binding.IsMouse { mouseCount++ if item.binding.Category != "Mouse actions" { t.Fatalf("Mouse binding should have category 'Mouse actions', got %q", item.binding.Category) } } } if mouseCount != 2 { t.Fatalf("Expected 2 mouse bindings, got %d", mouseCount) } } func TestFilterNoQueryReturnsAll(t *testing.T) { h := newTestHandler() h.show_unmapped = true // show all items including unmapped h.query = "" h.updateFilter() if len(h.filtered_idx) != len(h.all_items) { t.Fatalf("With no query and show_unmapped=true, expected %d items, got %d", len(h.all_items), len(h.filtered_idx)) } for i, idx := range h.filtered_idx { if idx != i { t.Fatalf("Expected sequential order, got index %d at position %d", idx, i) } } } func TestFilterMatchesSubset(t *testing.T) { h := newTestHandler() h.query = "clipboard" h.updateFilter() if len(h.filtered_idx) == 0 { t.Fatal("Expected matches for 'clipboard'") } if len(h.filtered_idx) >= len(h.all_items) { t.Fatal("Expected fewer matches than total items") } // Verify all returned items contain relevant text in at least one column for _, idx := range h.filtered_idx { item := &h.all_items[idx] found := false for _, col := range item.colTexts { if strings.Contains(strings.ToLower(col), "clipboard") { found = true break } } _ = found // FZF does fuzzy matching, so this is a soft check } } func TestFilterNonsenseReturnsEmpty(t *testing.T) { h := newTestHandler() h.query = "zzzznonexistent" h.updateFilter() if len(h.filtered_idx) != 0 { t.Fatalf("Expected no matches for nonsense, got %d", len(h.filtered_idx)) } } func TestFilterResetsSelectionAndScroll(t *testing.T) { h := newTestHandler() h.query = "" h.updateFilter() h.selected_idx = 3 h.scroll_offset = 5 h.query = "scroll" h.updateFilter() if h.selected_idx != 0 { t.Fatalf("Expected selection reset to 0, got %d", h.selected_idx) } if h.scroll_offset != 0 { t.Fatalf("Expected scroll offset reset to 0, got %d", h.scroll_offset) } } func TestSelectedBindingValid(t *testing.T) { h := newTestHandler() h.updateFilter() b := h.selectedBinding() if b == nil { t.Fatal("Expected non-nil binding") } if b.Key == "" || b.Action == "" { t.Fatal("Binding should have non-empty key and action") } } func TestSelectedBindingNilWhenEmpty(t *testing.T) { h := newTestHandler() h.query = "zzzznonexistent" h.updateFilter() if b := h.selectedBinding(); b != nil { t.Fatal("Expected nil binding when no matches") } } func TestSelectedBindingNilWhenNegativeIndex(t *testing.T) { h := newTestHandler() h.updateFilter() h.selected_idx = -1 if b := h.selectedBinding(); b != nil { t.Fatal("Expected nil binding for negative index") } } func TestSelectedBindingNilWhenOverflowIndex(t *testing.T) { h := newTestHandler() h.updateFilter() h.selected_idx = len(h.filtered_idx) + 10 if b := h.selectedBinding(); b != nil { t.Fatal("Expected nil binding for overflow index") } } func TestSearchTextContainsKeyAndAction(t *testing.T) { h := newTestHandler() for i, item := range h.all_items { // colTexts[0] = key (or unmappedLabel for empty key), [1] = action_display, [2] = category expectedKey := item.binding.Key if expectedKey == "" { expectedKey = unmappedLabel } if !strings.Contains(item.colTexts[0], expectedKey) { t.Fatalf("Item %d: colTexts[0] %q should contain key %q", i, item.colTexts[0], expectedKey) } if !strings.Contains(item.colTexts[1], item.binding.ActionDisplay) { t.Fatalf("Item %d: colTexts[1] %q should contain action %q", i, item.colTexts[1], item.binding.ActionDisplay) } } } func TestHelpTextPreserved(t *testing.T) { h := newTestHandler() helpCount := 0 for _, item := range h.all_items { if item.binding.Help != "" { helpCount++ } } if helpCount == 0 { t.Fatal("Expected at least some bindings to have help text") } // All keyboard bindings in our sample data have help text if helpCount < 7 { t.Fatalf("Expected at least 7 bindings with help text, got %d", helpCount) } } func TestEmptyInputData(t *testing.T) { h := &Handler{} emptyJSON := `{"modes": {}, "mouse": [], "mode_order": [], "category_order": {}}` if err := json.Unmarshal([]byte(emptyJSON), &h.input_data); err != nil { t.Fatal(err) } h.flattenBindings() h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME) h.updateFilter() if len(h.all_items) != 0 { t.Fatalf("Expected 0 items for empty data, got %d", len(h.all_items)) } if len(h.filtered_idx) != 0 { t.Fatalf("Expected 0 filtered items, got %d", len(h.filtered_idx)) } if b := h.selectedBinding(); b != nil { t.Fatal("Expected nil binding for empty data") } } func TestFallbackOrderingWithoutExplicitOrder(t *testing.T) { // Test that the kitten handles missing mode_order/category_order gracefully h := &Handler{} noOrderJSON := `{ "modes": { "": { "Scrolling": [{"key": "up", "action": "scroll", "action_display": "scroll", "help": "", "long_help": ""}], "Copy/paste": [{"key": "c", "action": "copy", "action_display": "copy", "help": "", "long_help": ""}] } }, "mouse": [] }` if err := json.Unmarshal([]byte(noOrderJSON), &h.input_data); err != nil { t.Fatal(err) } h.flattenBindings() if len(h.all_items) != 2 { t.Fatalf("Expected 2 items, got %d", len(h.all_items)) } // Without explicit order, categories should be sorted alphabetically cat0 := h.all_items[0].binding.Category cat1 := h.all_items[1].binding.Category if cat0 > cat1 { t.Fatalf("Expected alphabetical category order, got %q then %q", cat0, cat1) } } func TestTruncateToWidth(t *testing.T) { // Short string: no truncation s := "hello" got := truncateToWidth(s, 10) if got != s { t.Fatalf("Expected %q unchanged, got %q", s, got) } // Exact width: no truncation got = truncateToWidth("hello", 5) if got != "hello" { t.Fatalf("Expected %q unchanged at exact width, got %q", "hello", got) } // Over width: truncated with ellipsis got = truncateToWidth("hello world", 8) if !strings.HasSuffix(got, "...") { t.Fatalf("Expected truncated string to end with '...', got %q", got) } if len([]rune(got)) > 8 { t.Fatalf("Expected truncated string to be at most 8 runes, got %d in %q", len([]rune(got)), got) } // Long key like a mouse binding should be truncated longKey := "ctrl+shift+left press ungrabbed" got = truncateToWidth(longKey, maxKeyDisplayWidth) if len([]rune(got)) > maxKeyDisplayWidth { t.Fatalf("Key should be truncated to maxKeyDisplayWidth, got len=%d: %q", len([]rune(got)), got) } if !strings.HasSuffix(got, "...") { t.Fatalf("Truncated key should end with '...', got %q", got) } } func TestGroupedResultsModeHeaderFormat(t *testing.T) { h := newTestHandler() h.updateFilter() const testWidth = 80 // fixed width for testing // Build lines as drawGroupedResults would with the new separator format var lines []displayLine lastMode := "" lastCategory := "" for fi, idx := range h.filtered_idx { b := &h.all_items[idx].binding if b.Mode != lastMode { lastMode = b.Mode lastCategory = "" if b.Mode != "" { if len(lines) > 0 { lines = append(lines, displayLine{itemIdx: -1, isHeader: true}) } label := "Keyboard mode: " + b.Mode labelWidth := len([]rune(label)) sepLen := max(0, testWidth-labelWidth-6) sep := strings.Repeat("\u2500", sepLen) lines = append(lines, displayLine{ text: fmt.Sprintf(" \u2500\u2500 %s %s", label, sep), isModeHdr: true, isHeader: true, itemIdx: -1, }) } } if b.Mode == "" && b.Category != lastCategory { lastCategory = b.Category lines = append(lines, displayLine{isHeader: true, itemIdx: -1}) } lines = append(lines, displayLine{itemIdx: fi}) } // There should be a mode header for the "mw" mode found := false for _, l := range lines { if l.isModeHdr && strings.Contains(l.text, "Keyboard mode: mw") { found = true // Header should have ── separator characters if !strings.Contains(l.text, "\u2500\u2500") { t.Fatalf("Mode header should contain separator ── but got %q", l.text) } break } } if !found { t.Fatal("Expected to find 'Keyboard mode: mw' mode header") } } func TestGroupedResultsNoCategoryHeadersForNonDefaultMode(t *testing.T) { h := newTestHandler() h.updateFilter() // Build lines as drawGroupedResults would, tracking whether we are currently // inside a non-default keyboard-mode section. Category separators are only // valid for the default mode ("") and for the mouse-actions block; they must // NOT appear while we are still processing items for a non-default mode (e.g. // "mw"). Once we transition back to Mode=="" (e.g. for mouse bindings) the // section is over and category headers are allowed again. var lines []displayLine lastMode := "" lastCategory := "" for fi, idx := range h.filtered_idx { b := &h.all_items[idx].binding if b.Mode != lastMode { lastMode = b.Mode lastCategory = "" if b.Mode != "" { if len(lines) > 0 { lines = append(lines, displayLine{itemIdx: -1, isHeader: true}) } lines = append(lines, displayLine{ text: fmt.Sprintf(" Keyboard mode: %s", b.Mode), isModeHdr: true, isHeader: true, itemIdx: -1, }) } } // Category headers are only emitted for the default-mode block. if b.Mode == "" && b.Category != lastCategory { lastCategory = b.Category lines = append(lines, displayLine{ text: "category header", isHeader: true, itemIdx: -1, }) } lines = append(lines, displayLine{itemIdx: fi}) } // Verify: no "category header" line appears while we are still inside the // non-default keyboard-mode section. nonDefaultActive := false for _, l := range lines { if l.isModeHdr { nonDefaultActive = true continue } // A non-header item from Mode=="" exits the non-default section. if nonDefaultActive && !l.isHeader { if l.itemIdx >= 0 && l.itemIdx < len(h.filtered_idx) { idx := h.filtered_idx[l.itemIdx] if h.all_items[idx].binding.Mode == "" { nonDefaultActive = false } } } } } func TestRowToFilteredIdx(t *testing.T) { h := newTestHandler() h.updateFilter() h.results_start_y = 2 h.results_height = 20 // Populate display_lines with known structure h.display_lines = []displayLine{ {isHeader: true, itemIdx: -1}, // line 0: category header {itemIdx: 0}, // line 1: first item (filteredIdx=0) {itemIdx: 1}, // line 2: second item (filteredIdx=1) {isHeader: true, itemIdx: -1}, // line 3: blank header {itemIdx: 2}, // line 4: third item (filteredIdx=2) } h.scroll_offset = 0 // cellY=1 → screenRow=2 = results_start_y → lineIdx=0 = header → -1 if fi := h.rowToFilteredIdx(1); fi != -1 { t.Fatalf("Expected -1 for header row, got %d", fi) } // cellY=2 → screenRow=3 → lineIdx=1 = first item → filteredIdx=0 if fi := h.rowToFilteredIdx(2); fi != 0 { t.Fatalf("Expected filteredIdx=0 for first item row, got %d", fi) } // cellY=3 → screenRow=4 → lineIdx=2 = second item → filteredIdx=1 if fi := h.rowToFilteredIdx(3); fi != 1 { t.Fatalf("Expected filteredIdx=1 for second item row, got %d", fi) } // cellY=4 → screenRow=5 → lineIdx=3 = blank header → -1 if fi := h.rowToFilteredIdx(4); fi != -1 { t.Fatalf("Expected -1 for blank header row, got %d", fi) } // Click above results area (cellY=0 → screenRow=1 < results_start_y=2): should return -1 if fi := h.rowToFilteredIdx(0); fi != -1 { t.Fatalf("Expected -1 for row above results, got %d", fi) } // Click below results area (cellY=22 → screenRow=23 >= results_start_y+results_height=22): should return -1 if fi := h.rowToFilteredIdx(22); fi != -1 { t.Fatalf("Expected -1 for row below results, got %d", fi) } } func TestScrollAdjustRevealsSectionHeader(t *testing.T) { // When the selected item is scrolled into view from below, // any immediately preceding header lines should also be visible. lines := []displayLine{ {isHeader: true, itemIdx: -1}, // line 0: category header {itemIdx: 0}, // line 1: first item {itemIdx: 1}, // line 2: second item {isHeader: true, itemIdx: -1}, // line 3: blank {isHeader: true, itemIdx: -1}, // line 4: category header 2 {itemIdx: 2}, // line 5: third item } h := &Handler{} h.filtered_idx = []int{0, 1, 2} h.selected_idx = 0 // first item (at line 1) h.scroll_offset = 4 // currently scrolled past the first item // Call the scroll adjustment logic from drawLines selectedLineIdx := -1 for i, dl := range lines { if dl.itemIdx == h.selected_idx { selectedLineIdx = i break } } if selectedLineIdx != 1 { t.Fatalf("Expected selectedLineIdx=1, got %d", selectedLineIdx) } maxRows := 10 if selectedLineIdx < h.scroll_offset { h.scroll_offset = selectedLineIdx for h.scroll_offset > 0 && lines[h.scroll_offset-1].isHeader { h.scroll_offset-- } } if selectedLineIdx >= h.scroll_offset+maxRows { h.scroll_offset = selectedLineIdx - maxRows + 1 } h.scroll_offset = max(0, h.scroll_offset) h.scroll_offset = min(h.scroll_offset, max(0, len(lines)-maxRows)) // scroll_offset should be 0 so the category header at line 0 is visible if h.scroll_offset != 0 { t.Fatalf("Expected scroll_offset=0 to show category header, got %d", h.scroll_offset) } } func TestColTextsPopulated(t *testing.T) { h := newTestHandler() for i, item := range h.all_items { if item.binding.IsMouse { continue } expectedKey := item.binding.Key if expectedKey == "" { expectedKey = unmappedLabel } if item.colTexts[0] != expectedKey { t.Fatalf("Item %d: colTexts[0]=%q expected %q", i, item.colTexts[0], expectedKey) } if item.colTexts[1] != item.binding.ActionDisplay { t.Fatalf("Item %d: colTexts[1]=%q expected %q", i, item.colTexts[1], item.binding.ActionDisplay) } if item.colTexts[2] != item.binding.Category { t.Fatalf("Item %d: colTexts[2]=%q expected %q", i, item.colTexts[2], item.binding.Category) } } } func TestFilterSingleColumnMatch(t *testing.T) { // "scroll" is in action_display column only, not in key or category. // With per-column matching it should still match the action column. h := newTestHandler() h.query = "scroll" h.updateFilter() if len(h.filtered_idx) == 0 { t.Fatal("Expected matches for 'scroll' against action column") } // All matched items should have 'scroll' in exactly one column, not spread across columns for _, idx := range h.filtered_idx { item := &h.all_items[idx] colMatch := false for _, col := range item.colTexts { if strings.Contains(strings.ToLower(col), "scroll") { colMatch = true break } } _ = colMatch // FZF does fuzzy matching; at least the characters should appear in one column } } func TestFilterMatchInfosParallelToFilteredIdx(t *testing.T) { h := newTestHandler() h.query = "clipboard" h.updateFilter() if len(h.filtered_idx) == 0 { t.Fatal("Expected some matches") } if len(h.match_infos) != len(h.filtered_idx) { t.Fatalf("match_infos length %d != filtered_idx length %d", len(h.match_infos), len(h.filtered_idx)) } for i, mi := range h.match_infos { if mi.colIdx < 0 || mi.colIdx > 2 { t.Fatalf("match_infos[%d].colIdx=%d out of range [0,2]", i, mi.colIdx) } } } func TestFilterMatchInfosNilWhenNoQuery(t *testing.T) { h := newTestHandler() h.query = "" h.updateFilter() if h.match_infos != nil { t.Fatal("Expected match_infos to be nil when query is empty") } } func TestUnmappedActionDisplayed(t *testing.T) { // Inject an item with an empty key (unmapped action) and verify display h := &Handler{} unmappedJSON := `{ "modes": { "": { "Miscellaneous": [ {"key": "ctrl+n", "action": "new_window", "action_display": "new_window", "definition": "new_window", "help": "Open new window", "long_help": ""}, {"key": "", "action": "scroll_home", "action_display": "scroll_home", "definition": "scroll_home", "help": "Scroll to top", "long_help": ""} ] } }, "mouse": [], "mode_order": [""], "category_order": {"": ["Miscellaneous"]} }` if err := json.Unmarshal([]byte(unmappedJSON), &h.input_data); err != nil { t.Fatal(err) } h.flattenBindings() h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME) if len(h.all_items) != 2 { t.Fatalf("Expected 2 items, got %d", len(h.all_items)) } // Find the unmapped item var unmapped *DisplayItem for i := range h.all_items { if h.all_items[i].binding.Key == "" { unmapped = &h.all_items[i] break } } if unmapped == nil { t.Fatal("Expected to find unmapped item") } // colTexts[0] should be unmappedLabel, not empty if unmapped.colTexts[0] != unmappedLabel { t.Fatalf("Expected colTexts[0]=%q for unmapped item, got %q", unmappedLabel, unmapped.colTexts[0]) } // With show_unmapped=true, unmapped action should be searchable h.show_unmapped = true h.query = "scroll_home" h.updateFilter() if len(h.filtered_idx) == 0 { t.Fatal("Expected unmapped action to be found by action name search when show_unmapped=true") } // With show_unmapped=false, unmapped action should be hidden h.show_unmapped = false h.query = "" h.updateFilter() for _, idx := range h.filtered_idx { if h.all_items[idx].binding.Key == "" { t.Fatal("Expected unmapped action to be hidden when show_unmapped=false") } } } func TestShowUnmappedToggle(t *testing.T) { // TestShowUnmappedToggle creates a handler with both mapped and unmapped items // and verifies that the show_unmapped flag correctly filters the display. h := &Handler{} mixedJSON := `{ "modes": { "": { "Copy/paste": [ {"key": "ctrl+c", "action": "copy", "action_display": "copy", "definition": "copy", "help": "Copy", "long_help": ""}, {"key": "", "action": "paste_from_buffer", "action_display": "paste_from_buffer", "definition": "paste_from_buffer", "help": "Paste from buffer", "long_help": ""} ] } }, "mouse": [], "mode_order": [""], "category_order": {"": ["Copy/paste"]} }` if err := json.Unmarshal([]byte(mixedJSON), &h.input_data); err != nil { t.Fatal(err) } h.flattenBindings() h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME) if len(h.all_items) != 2 { t.Fatalf("Expected 2 items in all_items, got %d", len(h.all_items)) } // With show_unmapped=false, only mapped items should appear h.show_unmapped = false h.updateFilter() if len(h.filtered_idx) != 1 { t.Fatalf("With show_unmapped=false, expected 1 item, got %d", len(h.filtered_idx)) } if h.all_items[h.filtered_idx[0]].binding.Key == "" { t.Fatal("Filtered item should not be unmapped when show_unmapped=false") } // With show_unmapped=true, both items should appear h.show_unmapped = true h.updateFilter() if len(h.filtered_idx) != 2 { t.Fatalf("With show_unmapped=true, expected 2 items, got %d", len(h.filtered_idx)) } // Toggle back to false with a query active; unmapped should still be hidden h.show_unmapped = false h.query = "paste" h.updateFilter() for _, idx := range h.filtered_idx { if h.all_items[idx].binding.Key == "" { t.Fatal("Unmapped item should not appear in search results when show_unmapped=false") } } } ================================================ FILE: kittens/desktop_ui/__init__.py ================================================ ================================================ FILE: kittens/desktop_ui/main.go ================================================ package desktop_ui import ( "fmt" "strings" "github.com/kovidgoyal/dbus" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print type ServerOptions struct { Config []string Override []string } const server_conf_name = "desktop-ui-portal" func load_server_config(opts *ServerOptions) (ans *Config, err error) { ans = NewConfig() p := config.ConfigParser{LineHandler: ans.Parse} err = p.LoadConfig(server_conf_name+".conf", opts.Config, opts.Override) return } func run_server(opts *ServerOptions) (err error) { config, err := load_server_config(opts) if err == nil { portal, err := NewPortal(config, opts) if err == nil { err = portal.Start() if err == nil { defer portal.Cleanup() } } } if err != nil { return } c := make(chan string) <-c return } func specialize_command(parent *cli.Command) { parent.Run = func(cmd *cli.Command, args []string) (int, error) { cmd.ShowHelp() return 1, nil } parent.ShortDescription = "Implement various desktop components for use with lightweight compositors/window managers on Linux" rs := parent.AddSubCommand(&cli.Command{ Name: "run-server", ShortDescription: "Start the various servers used to integrate with the Linux desktop", HelpText: "This should be run very early in the startup sequence of your window manager, before any other programs are run.", Run: func(cmd *cli.Command, args []string) (rc int, err error) { opts := ServerOptions{} err = cmd.GetOptionValues(&opts) if err == nil { err = run_server(&opts) } return utils.IfElse(err == nil, 0, 1), err }, }) rs.Add(cli.OptionSpec{ Name: `--override -o`, Type: "list", Dest: `Override`, Help: "Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :italic:`-o color_scheme=dark`", }) rs.Add(cli.OptionSpec{ Name: `--config -c`, Type: "list", Dest: `Config`, Help: strings.ReplaceAll(strings.ReplaceAll(kitty.ConfigHelp, "{appname}", "kitty"), "{conf_name}", server_conf_name), }) parent.AddSubCommand(&cli.Command{ Name: "enable-portal", ShortDescription: "This will create or edit the various files needed so that the portal from this kitten is used by xdg-desktop-portal", HelpText: "Once you run this command, add :code:`kitten desktop-ui run-server` to your window manager startup sequence and reboot your computer (or logout and restart your session) and hopefully xdg-desktop-portal should now delegate to kitty for the portals implemented here. If it doesn't try running :code:`/usr/lib/xdg-desktop-portal -r -v` it will provide a lot of logging about why it is choosing different portal backends. That combined with a careful reading of :code:`man portals.conf` should be enough to learn how to convince xdg-desktop-portal to use kitty.\n\nYou can change the system color-scheme dynamically by running::\n\n:code:`kitten desktop-ui set-color-scheme dark`", Run: func(cmd *cli.Command, args []string) (rc int, err error) { err = enable_portal() return utils.IfElse(err == nil, 0, 1), err }, }) parent.AddSubCommand(&cli.Command{ Name: "set-color-scheme", ShortDescription: "Change the color scheme", ArgCompleter: cli.NamesCompleter("Choices for color-scheme", "no-preference", "light", "dark", "toggle"), Usage: " light|dark|no-preference|toggle", Run: func(cmd *cli.Command, args []string) (rc int, err error) { if len(args) != 1 { cmd.ShowHelp() return 1, fmt.Errorf("must specify the new color scheme value") } err = set_color_scheme(args[0]) return utils.IfElse(err == nil, 0, 1), err }, }) parent.AddSubCommand(&cli.Command{ Name: "set-accent-color", ShortDescription: "Change the accent color", Usage: " color_as_hex_or_name", Run: func(cmd *cli.Command, args []string) (rc int, err error) { if len(args) != 1 { cmd.ShowHelp() return 1, fmt.Errorf("must specify the new accent color value") } var v dbus.Variant if v, err = to_color(args[0]); err == nil { err = set_variant_setting(PORTAL_APPEARANCE_NAMESPACE, PORTAL_ACCENT_COLOR_KEY, v, false) } return utils.IfElse(err == nil, 0, 1), err }, }) parent.AddSubCommand(&cli.Command{ Name: "set-contrast", ShortDescription: "Change the contrast. Can be high or normal.", Usage: " high|normal", Run: func(cmd *cli.Command, args []string) (rc int, err error) { if len(args) != 1 { cmd.ShowHelp() return 1, fmt.Errorf("must specify the new contrast value") } var v dbus.Variant switch args[0] { case "normal": v = dbus.MakeVariant(uint32(0)) case "high": v = dbus.MakeVariant(uint32(1)) default: return 1, fmt.Errorf("%s is not a valid contrast value", args[0]) } err = set_variant_setting(PORTAL_APPEARANCE_NAMESPACE, PORTAL_CONTRAST_KEY, v, false) return utils.IfElse(err == nil, 0, 1), err }, }) st := parent.AddSubCommand(&cli.Command{ Name: "set-setting", ShortDescription: "Change an arbitrary setting", Usage: " key [value]", HelpText: "Set an arbitrary setting. If you want to set the color-scheme use the dedicated command for it. Use this command with care as it does no validation for the type of value. The syntax for specifying values is described at: :link:`the glib docs `. Leaving out the value or specifying an empty value, will delete the setting.", Run: func(cmd *cli.Command, args []string) (rc int, err error) { val := "" if len(args) < 1 { cmd.ShowHelp() return 1, fmt.Errorf("must specify the key") } if len(args) > 1 { val = args[1] } opts := SetOptions{} if err = cmd.GetOptionValues(&opts); err == nil { err = set_setting(args[0], val, &opts) } return utils.IfElse(err == nil, 0, 1), err }, }) st.Add(cli.OptionSpec{ Name: "--namespace -n", Help: "The namespace in which to change the setting.", Default: PORTAL_APPEARANCE_NAMESPACE, }) st.Add(cli.OptionSpec{ Name: "--data-type", Help: "The DBUS data type signature of the value. The default is to guess from the textual representation, see :link:`the glib docs ` for details.", }) ss := parent.AddSubCommand(&cli.Command{ Name: "show-settings", ShortDescription: "Print the current values of the desktop settings", Run: func(cmd *cli.Command, args []string) (rc int, err error) { if len(args) != 0 { cmd.ShowHelp() return 1, fmt.Errorf("no arguments allowed") } opts := ShowSettingsOptions{} err = cmd.GetOptionValues(&opts) if err == nil { err = show_settings(&opts) } return utils.IfElse(err == nil, 0, 1), err }, }) ss.Add(cli.OptionSpec{ Name: "--as-json", Help: "Show the settings as JSON for machine consumption", Type: "bool-set", }) ss.Add(cli.OptionSpec{ Name: "--in-namespace", Help: "Show only settings in the specified names. Can be specified multiple times. When unspecified all namespaces are returned.", Type: "list", }) ss.Add(cli.OptionSpec{ Name: "--allow-other-backends", Help: "Normally, after printing the settings, if the settings did not come from the desktop-ui kitten the command prints an error and exits. This prevents that.", Type: "bool-set", }) } func EntryPoint(root *cli.Command) { create_cmd(root, nil) } ================================================ FILE: kittens/desktop_ui/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal import sys from kitty.conf.types import Definition definition = Definition( '!kittens.choose_files', ) agr = definition.add_group egr = definition.end_group opt = definition.add_option map = definition.add_map mma = definition.add_mouse_map agr('Appearance') opt('color_scheme', 'no-preference', choices=('no-preference', 'dark', 'light'), long_text='''\ The color scheme for your system. This sets the initial value of the color scheme. It can be changed subsequently by using :code:`kitten desktop-ui color-scheme`. ''') opt('accent_color', 'cyan', long_text='The RGB accent color for your system, can be specified as a color name or in hex a decimal format.') opt('contrast', 'normal', choices=('normal', 'high'), long_text='The preferred contrast level.') opt('file_chooser_size', '', long_text=''' The size in lines and columns of the file chooser popup window. By default it is full screen. For example: :code:`file_chooser_size 25 80` will cause the popup to be of size 25 lines and 80 columns. Note that if you use this option, depending on the compositor you are running, the popup window may not be properly modal. ''') opt('+file_chooser_kitty_conf', '', long_text='Path to config file to use for kitty when drawing the file chooser window. Can be specified multiple times. By default, the' ' normal kitty.conf is used. Relative paths are resolved with respect to the kitty config directory.' ) opt('+file_chooser_kitty_override', '', long_text='Override individual kitty configuration options, for the file chooser window.' ' Can be specified multiple times. Syntax: :italic:`name=value`. For example: :code:`font_size=20`.' ) egr() def main(args: list[str]) -> None: raise SystemExit('This must be run as kitten desktop-ui') if __name__ == '__main__': main(sys.argv) elif __name__ == '__conf__': sys.options_definition = definition # type: ignore ================================================ FILE: kittens/desktop_ui/portal.go ================================================ package desktop_ui import ( "bytes" "encoding/json" "fmt" "log" "maps" "net/url" "os" "os/exec" "path/filepath" "runtime" "slices" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/kovidgoyal/dbus" "github.com/kovidgoyal/dbus/introspect" "github.com/kovidgoyal/dbus/prop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "golang.org/x/sys/unix" ) var _ = fmt.Print const PORTAL_APPEARANCE_NAMESPACE = "org.freedesktop.appearance" const PORTAL_COLOR_SCHEME_KEY = "color-scheme" const PORTAL_ACCENT_COLOR_KEY = "accent-color" const PORTAL_CONTRAST_KEY = "contrast" const PORTAL_BUS_NAME = "org.freedesktop.impl.portal.desktop.kitty" const DESKTOP_OBJECT_PATH = "/org/freedesktop/portal/desktop" const SETTINGS_INTERFACE = "org.freedesktop.impl.portal.Settings" const FILE_CHOOSER_INTERFACE = "org.freedesktop.impl.portal.FileChooser" const KITTY_OBJECT_PATH = "/net/kovidgoyal/kitty/portal" const CHANGE_SETTINGS_INTERFACE = "net.kovidgoyal.kitty.settings" const DESKTOP_PORTAL_NAME = "org.freedesktop.portal.Desktop" const REQUEST_INTERFACE = "org.freedesktop.impl.portal.Request" // Special portal setting used to check if we are being called by xdg-desktop-portal const SETTINGS_CANARY_NAMESPACE = "net.kovidgoyal.kitty" const SETTINGS_CANARY_KEY = "status" type ColorScheme uint32 const ( NO_PREFERENCE ColorScheme = iota DARK LIGHT ) const ( RESPONSE_SUCCESS uint32 = iota RESPONSE_CANCELED RESPONSE_ENDED ) type SettingsMap map[string]map[string]dbus.Variant type Portal struct { bus *dbus.Conn settings SettingsMap lock sync.Mutex opts *Config server_options *ServerOptions file_chooser_first_instance *exec.Cmd } func to_color(spec string) (v dbus.Variant, err error) { if col, err := style.ParseColor(spec); err == nil { return dbus.MakeVariant([]float64{float64(col.Red) / 255., float64(col.Green) / 255., float64(col.Blue) / 255.}), nil } return } func NewPortal(opts *Config, server_options *ServerOptions) (p *Portal, err error) { ans := Portal{opts: opts, server_options: server_options} ans.settings = SettingsMap{ SETTINGS_CANARY_NAMESPACE: map[string]dbus.Variant{ SETTINGS_CANARY_KEY: dbus.MakeVariant("running"), }, } ans.settings[PORTAL_APPEARANCE_NAMESPACE] = map[string]dbus.Variant{} switch opts.Color_scheme { case Color_scheme_dark: ans.settings[PORTAL_APPEARANCE_NAMESPACE][PORTAL_COLOR_SCHEME_KEY] = dbus.MakeVariant(uint32(DARK)) case Color_scheme_light: ans.settings[PORTAL_APPEARANCE_NAMESPACE][PORTAL_COLOR_SCHEME_KEY] = dbus.MakeVariant(uint32(LIGHT)) default: ans.settings[PORTAL_APPEARANCE_NAMESPACE][PORTAL_COLOR_SCHEME_KEY] = dbus.MakeVariant(uint32(NO_PREFERENCE)) } ans.settings[PORTAL_APPEARANCE_NAMESPACE][PORTAL_ACCENT_COLOR_KEY], err = to_color(opts.Accent_color) var contrast uint32 if opts.Contrast == Contrast_high { contrast = 1 } ans.settings[PORTAL_APPEARANCE_NAMESPACE][PORTAL_CONTRAST_KEY] = dbus.MakeVariant(contrast) return &ans, nil } type PropSpec map[string]*prop.Prop type SignalSpec map[string][]struct { Name, Type string } type MethodSpec map[string][]struct { Name, Type string Out bool } func ExportInterface(conn *dbus.Conn, object any, interface_name, object_path string, method_spec MethodSpec, prop_spec PropSpec, signal_spec SignalSpec) (err error) { op := dbus.ObjectPath(object_path) method_map := make(map[string]string, len(method_spec)) methods := []introspect.Method{} if len(method_spec) > 0 { for method_name, args := range method_spec { method_map[method_name] = method_name meth_args := make([]introspect.Arg, len(args)) for i, a := range args { meth_args[i] = introspect.Arg{ Name: a.Name, Type: a.Type, Direction: utils.IfElse(a.Out, "out", "in"), } } methods = append(methods, introspect.Method{ Name: method_name, Args: meth_args, }) } } if err = conn.ExportWithMap(object, method_map, op, interface_name); err != nil { return fmt.Errorf("failed to export interface: %s at object path: %s with error: %w", interface_name, object_path, err) } var properties []introspect.Property p := prop.Map{interface_name: prop_spec} if len(prop_spec) > 0 { if props, err := prop.Export(conn, op, p); err != nil { return fmt.Errorf("failed to export properties with error: %w", err) } else { properties = props.Introspection(interface_name) } } var signals []introspect.Signal if len(signal_spec) > 0 { for signal_name, args := range signal_spec { sig_args := make([]introspect.Arg, len(args)) for i, a := range args { sig_args[i] = introspect.Arg{ Name: a.Name, Type: a.Type, Direction: "out", } } signals = append(signals, introspect.Signal{ Name: signal_name, Args: sig_args, }) } } interface_data := introspect.Interface{ Name: interface_name, Methods: methods, Properties: properties, Signals: signals, } interfaces := []introspect.Interface{ introspect.IntrospectData, interface_data, } if len(properties) > 0 { interfaces = append(interfaces, prop.IntrospectData) } n := &introspect.Node{Name: object_path, Interfaces: interfaces} if err = conn.Export(introspect.NewIntrospectable(n), op, introspect.IntrospectData.Name); err != nil { return fmt.Errorf("failed to export introspected methods with error: %w", err) } return } func (self *Portal) Start() (err error) { if self.bus, err = dbus.SessionBus(); err != nil { return fmt.Errorf("could not connect to session D-Bus: %s", err) } reply, err := self.bus.RequestName(PORTAL_BUS_NAME, dbus.NameFlagDoNotQueue) if err != nil { return fmt.Errorf("failed to register dbus name: %v", err) } if reply != dbus.RequestNameReplyPrimaryOwner { return fmt.Errorf("can't register D-Bus name: name already taken") } props := PropSpec{ "version": {Value: uint32(1), Writable: false, Emit: prop.EmitFalse}, } signals := SignalSpec{ "SettingChanged": {{"namespace", "s"}, {"key", "s"}, {"value", "v"}}, } methods := MethodSpec{ "Read": {{"namespace", "s", false}, {"key", "s", false}, {"value", "v", true}}, "ReadAll": {{"namespaces", "as", false}, {"value", "a{sa{sv}}", true}}, } if err = ExportInterface(self.bus, self, SETTINGS_INTERFACE, DESKTOP_OBJECT_PATH, methods, props, signals); err != nil { return } methods = MethodSpec{ "OpenFile": {{"handle", "o", false}, {"app_id", "s", false}, {"parent_window", "s", false}, {"title", "s", false}, {"options", "a{sv}", false}, {"response", "u", true}, {"results", "a{sv}", false}, }, "SaveFile": {{"handle", "o", false}, {"app_id", "s", false}, {"parent_window", "s", false}, {"title", "s", false}, {"options", "a{sv}", false}, {"response", "u", true}, {"results", "a{sv}", false}, }, } if err = ExportInterface(self.bus, self, FILE_CHOOSER_INTERFACE, DESKTOP_OBJECT_PATH, methods, nil, nil); err != nil { return } methods = MethodSpec{ "ChangeSetting": {{"namespace", "s", false}, {"key", "s", false}, {"value", "v", false}}, "RemoveSetting": {{"namespace", "s", false}, {"key", "s", false}}, } props["version"].Value = uint32(1) if err = ExportInterface(self.bus, self, CHANGE_SETTINGS_INTERFACE, KITTY_OBJECT_PATH, methods, props, nil); err != nil { return } return } func ParseValueWithSignature(value, value_type_signature string) (v dbus.Variant, err error) { var s dbus.Signature if value_type_signature != "" { if value_type_signature[0] == '@' { value_type_signature = value_type_signature[1:] } s, err = dbus.ParseSignature(value_type_signature) if err != nil { return dbus.Variant{}, fmt.Errorf("%s is not a valid type signature: %w", value_type_signature, err) } } v, err = dbus.ParseVariant(value, s) if err != nil { if value_type_signature == "" { return dbus.Variant{}, fmt.Errorf("could not guess the data type of: %s with error: %w", value, err) } return dbus.Variant{}, fmt.Errorf("%s is not a valid value for signature: %#v with error: %w", value, value_type_signature, err) } return v, nil } func ParseValue(value string) (dbus.Variant, error) { return ParseValueWithSignature(value, "") } type ShowSettingsOptions struct { AsJson bool AllowOtherBackends bool InNamespace []string } func fetch_settings(conn *dbus.Conn, namespaces ...string) (ans ReadAllType, err error) { path := "/" + strings.ToLower(strings.ReplaceAll(DESKTOP_PORTAL_NAME, ".", "/")) obj := conn.Object(DESKTOP_PORTAL_NAME, dbus.ObjectPath(path)) interface_name := strings.ReplaceAll(DESKTOP_PORTAL_NAME, "Desktop", "Settings") if len(namespaces) == 0 { namespaces = append(namespaces, "") } call := obj.Call(interface_name+".ReadAll", dbus.FlagNoAutoStart, namespaces) if err = call.Store(&ans); err != nil { return nil, fmt.Errorf("Failed to read response from ReadAll with error: %w", err) } return } func show_settings(opts *ShowSettingsOptions) (err error) { conn, err := dbus.SessionBus() if err != nil { return fmt.Errorf("failed to connect to system bus with error: %w", err) } defer conn.Close() var response ReadAllType response, err = fetch_settings(conn, opts.InNamespace...) if opts.AsJson { unwrapped := make(map[string]map[string]any, len(response)) for ns, m := range response { w := make(map[string]any, len(m)) for k, a := range m { w[k] = a.Value() } unwrapped[ns] = w } j, err := json.MarshalIndent(unwrapped, "", " ") if err != nil { return fmt.Errorf("Failed to format the response as JSON: %w", err) } fmt.Println(string(j)) } else { for ns, m := range response { fmt.Println(ns + ":") for key, v := range m { fmt.Printf("\t%s: %s\n", key, v) } } } if !opts.AllowOtherBackends { is_running_self := false if m, found := response[SETTINGS_CANARY_NAMESPACE]; found { _, is_running_self = m[SETTINGS_CANARY_KEY] } if !is_running_self { err = fmt.Errorf("the settings did not come from the desktop-ui kitten. Some other portal backend is providing the service.") } } return } var DataDirs = sync.OnceValue(func() (ans []string) { // $XDG_DATA_DIRS defines the preference-ordered set of base directories // to search for data files **in addition to the $XDG_DATA_HOME** base // directory. The directories in $XDG_DATA_DIRS should be separated with // a colon ':'. // https://specifications.freedesktop.org/basedir-spec/0.8/#variables data_dirs := os.Getenv("XDG_DATA_DIRS") if data_dirs == "" { data_dirs = "/usr/local/share:/usr/share" } data_home := os.Getenv("XDG_DATA_HOME") if data_home == "" { data_home = utils.Expanduser("~/.local/share") } return utils.Uniq(append([]string{data_home}, strings.Split(data_dirs, ":")...)) }) func IsDir(x string) bool { s, err := os.Stat(x) return err == nil && s.IsDir() } var WritableDataDirs = sync.OnceValue(func() (ans []string) { for _, x := range DataDirs() { if err := os.MkdirAll(x, 0o755); err == nil && unix.Access(x, unix.W_OK) == nil { ans = append(ans, x) } } return }) var AllPortalInterfaces = sync.OnceValue(func() (ans []string) { return []string{SETTINGS_INTERFACE, FILE_CHOOSER_INTERFACE} }) // enable-portal {{{ func patch_portals_conf(text []byte) []byte { lines := []string{} in_preferred := false patched := false for _, line := range utils.Splitlines(utils.UnsafeBytesToString(text)) { sl := strings.TrimSpace(line) if strings.HasPrefix(sl, "[") { in_preferred = sl == "[preferred]" lines = append(lines, line) for _, iface := range AllPortalInterfaces() { lines = append(lines, iface+"=kitty;*") } patched = true } else if in_preferred { remove := false for _, iface := range AllPortalInterfaces() { if strings.HasPrefix(sl, iface) { remove = true break } } if !remove { lines = append(lines, line) } } } if !patched { // the file was empty or did not contain a section lines = append(lines, "[preferred]") for _, iface := range AllPortalInterfaces() { lines = append(lines, iface+"=kitty;*") } } return utils.UnsafeStringToBytes(strings.Join(lines, "\n")) } func enable_portal() (err error) { if len(WritableDataDirs()) == 0 { return fmt.Errorf("Could not find any writable data directories. Make sure XDG_DATA_DIRS is set and contains at least one directory for which you have write permission") } portals_dir := "" for _, x := range WritableDataDirs() { // Find-or-create the first available xdg-desktop-portals/portals directory q := filepath.Join(x, "xdg-desktop-portal", "portals") if (unix.Access(q, unix.W_OK) == nil && IsDir(q)) || (os.MkdirAll(q, 0o755) == nil) { portals_dir = q break } } if portals_dir == "" { return fmt.Errorf("Could not find any writable portals directories. Make sure XDG_DATA_HOME is set and point to a directory for which you have write permission.") } portals_defn := filepath.Join(portals_dir, "kitty.portal") if err = os.WriteFile(portals_defn, utils.UnsafeStringToBytes(fmt.Sprintf( `[portal] DBusName=%s Interfaces=%s; `, PORTAL_BUS_NAME, strings.Join(AllPortalInterfaces(), ";"))), 0o644); err != nil { return err } fmt.Println("Wrote kitty portal definition to:", portals_defn) dbus_service_dir := "" for _, x := range WritableDataDirs() { q := filepath.Join(x, "dbus-1", "services") if err := os.MkdirAll(q, 0o755); err == nil { dbus_service_dir = q break } } if dbus_service_dir == "" { return fmt.Errorf("Could not find any writable portals directories. Make sure XDG_DATA_HOME is set and point to a directory for which you have write permission.") } dbus_service_defn := filepath.Join(dbus_service_dir, PORTAL_BUS_NAME+".service") exe_path, eerr := os.Executable() if eerr != nil { exe_path = utils.Which("kitten") } if exe_path, err = filepath.Abs(exe_path); eerr != nil { return fmt.Errorf("failed to get path to kitten executable with error: %w", err) } if err = os.WriteFile(dbus_service_defn, utils.UnsafeStringToBytes(fmt.Sprintf( `[D-BUS Service] Name=%s Exec=%s desktop-ui run-server `, PORTAL_BUS_NAME, exe_path)), 0o644); err != nil { return err } fmt.Println("Wrote kitty DBUS activation service file to:", dbus_service_defn) d := os.Getenv("XDG_CURRENT_DESKTOP") cf := os.Getenv("XDG_CONFIG_HOME") if cf == "" { cf = utils.Expanduser("~/.config") } cf = filepath.Join(cf, "xdg-desktop-portal") if err = os.MkdirAll(cf, 0o755); err != nil { return fmt.Errorf("failed to create %s to store the portals.conf file with error: %w", cf, err) } patched_file := "" desktops := utils.Filter(strings.Split(d, ":"), func(x string) bool { return x != "" }) desktops = append(desktops, "") for x := range strings.SplitSeq(d, ":") { q := filepath.Join(cf, utils.IfElse(x == "", "portals.conf", fmt.Sprintf("%s-portals.conf", strings.ToLower(x)))) if text, err := os.ReadFile(q); err == nil { text := patch_portals_conf(text) if err = os.WriteFile(q, text, 0o644); err == nil { patched_file = q fmt.Printf("Patched %s to use the kitty portals\n", patched_file) break } } } if patched_file == "" { x := desktops[0] q := filepath.Join(cf, utils.IfElse(x == "", "portals.conf", fmt.Sprintf("%s-portals.conf", strings.ToLower(x)))) text := patch_portals_conf([]byte{}) if err = os.WriteFile(q, text, 0o644); err != nil { return err } patched_file = q fmt.Printf("Created %s to use the kitty portals\n", patched_file) } return } // }}} type SetOptions struct { Namespace, DataType string } func set_variant_setting(namespace, key string, v dbus.Variant, remove_setting bool) (err error) { conn, err := dbus.SessionBus() if err != nil { return fmt.Errorf("failed to connect to system bus with error: %w", err) } defer conn.Close() method := "ChangeSetting" var vals = []any{namespace, key} if remove_setting { method = "RemoveSetting" } else { vals = append(vals, v) } obj := conn.Object(PORTAL_BUS_NAME, dbus.ObjectPath(KITTY_OBJECT_PATH)) call := obj.Call(CHANGE_SETTINGS_INTERFACE+"."+method, dbus.FlagNoAutoStart, vals...) if err = call.Store(); err != nil { return fmt.Errorf("failed to call %s with error: %w", method, err) } return } func set_setting(key, value string, opts *SetOptions) (err error) { remove_setting := false var v dbus.Variant if value == "" { remove_setting = true } else { if v, err = ParseValueWithSignature(value, opts.DataType); err != nil { return err } } return set_variant_setting(opts.Namespace, key, v, remove_setting) } func set_color_scheme(which string) (err error) { conn, err := dbus.SessionBus() if err != nil { return fmt.Errorf("failed to connect to system bus with error: %w", err) } defer conn.Close() val := NO_PREFERENCE var res ReadAllType if res, err = fetch_settings(conn, PORTAL_APPEARANCE_NAMESPACE); err != nil { return fmt.Errorf("failed to read existing color scheme setting with error: %w", err) } if m, found := res[PORTAL_APPEARANCE_NAMESPACE]; found { if v, found := m[PORTAL_COLOR_SCHEME_KEY]; found { v.Store(&val) } } nval := val switch which { case "toggle": switch val { case LIGHT: nval = DARK case DARK: nval = LIGHT } case "no-preference": nval = NO_PREFERENCE case "light": nval = LIGHT case "dark": nval = DARK default: return fmt.Errorf("%s is not a valid value of the color-scheme", which) } if val == nval { return } obj := conn.Object(PORTAL_BUS_NAME, dbus.ObjectPath(KITTY_OBJECT_PATH)) call := obj.Call(CHANGE_SETTINGS_INTERFACE+".ChangeSetting", dbus.FlagNoAutoStart, PORTAL_APPEARANCE_NAMESPACE, PORTAL_COLOR_SCHEME_KEY, dbus.MakeVariant(nval)) if err = call.Store(); err != nil { return fmt.Errorf("failed to call ChangeSetting with error: %w", err) } return } func (self *Portal) ChangeSetting(namespace, key string, value dbus.Variant) *dbus.Error { self.lock.Lock() defer self.lock.Unlock() if self.settings[namespace] == nil { self.settings[namespace] = map[string]dbus.Variant{} } self.settings[namespace][key] = value if e := self.bus.Emit( DESKTOP_OBJECT_PATH, SETTINGS_INTERFACE+".SettingChanged", namespace, key, value, ); e != nil { log.Println("Couldn't emit signal:", e) } return nil } func (self *Portal) RemoveSetting(namespace, key string) *dbus.Error { self.lock.Lock() defer self.lock.Unlock() existed := false if m := self.settings[namespace]; m != nil { _, existed = m[key] } if !existed { return nil } delete(self.settings[namespace], key) return nil } func (self *Portal) Read(namespace, key string) (dbus.Variant, *dbus.Error) { self.lock.Lock() defer self.lock.Unlock() if m, found := self.settings[namespace]; found { if v, found := m[key]; found { return v, nil } } return dbus.Variant{}, dbus.NewError("org.freedesktop.portal.Error.NotFound", []any{fmt.Sprintf("the setting %s in the namespace %s is not supported", key, namespace)}) } type ReadAllType map[string]map[string]dbus.Variant func (self *Portal) ReadAll(namespaces []string) (ReadAllType, *dbus.Error) { self.lock.Lock() defer self.lock.Unlock() var matched_namespaces = SettingsMap{} if len(namespaces) == 0 { matched_namespaces = self.settings } else { for _, namespace := range namespaces { if namespace == "" { matched_namespaces = self.settings break } else { if strings.HasSuffix(namespace, ".*") { namespace = namespace[:len(namespace)-1] for candidate := range self.settings { if strings.HasPrefix(candidate, namespace) { matched_namespaces[candidate] = map[string]dbus.Variant{} } } } else if _, found := self.settings[namespace]; found { matched_namespaces[namespace] = map[string]dbus.Variant{} } } } } values := map[string]map[string]dbus.Variant{} for namespace := range matched_namespaces { values[namespace] = make(map[string]dbus.Variant, len(self.settings[namespace])) maps.Copy(values[namespace], self.settings[namespace]) } return values, nil } func (self *Portal) reload_portal_settings() { self.lock.Lock() defer self.lock.Unlock() if config, err := load_server_config(self.server_options); err == nil { self.opts = config } } type vmap map[string]dbus.Variant type Filter_expression struct { Ftype uint32 Val string } type Filter struct { Name string Expressions []Filter_expression } func (f Filter) Equal(o Filter) bool { return f.Name == o.Name && slices.Equal(f.Expressions, o.Expressions) } type ChooseFilesData struct { Title string Mode string Cwd string SuggestedSaveFileName, SuggestedSaveFilePath string Handle dbus.ObjectPath Filters []Filter } func (c *ChooseFilesData) set_filters(options vmap) { if v, found := options["filters"]; found { v.Store(&c.Filters) } if v, found := options["current_filter"]; found { var x Filter if err := v.Store(&x); err == nil { idx := slices.IndexFunc(c.Filters, func(q Filter) bool { return x.Equal(q) }) if idx > -1 { c.Filters = slices.Delete(c.Filters, idx, idx+1) } c.Filters = slices.Insert(c.Filters, 0, x) } } } func get_matching_filter(name string, all_filters []Filter) (dbus.Variant, bool) { for _, x := range all_filters { if x.Name == name { return dbus.MakeVariant(x), true } } return dbus.Variant{}, false } func (options vmap) get_bool(name string, defval bool) (ans bool) { if v, found := options[name]; found { if v.Store(&ans) == nil { return } } return defval } func (self *Portal) Cleanup() { self.lock.Lock() defer self.lock.Unlock() if self.file_chooser_first_instance != nil { self.file_chooser_first_instance.Process.Signal(unix.SIGTERM) ch := make(chan int) go func() { self.file_chooser_first_instance.Wait() ch <- 0 }() select { case <-ch: case <-time.After(time.Second): self.file_chooser_first_instance.Process.Kill() self.file_chooser_first_instance.Wait() } self.file_chooser_first_instance = nil } } type ChooserResponse struct { Paths []string `json:"paths"` Error string `json:"error"` Interrupted bool `json:"interrupted"` Current_filter string `json:"current_filter"` } func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, result_dict vmap) { self.reload_portal_settings() response = RESPONSE_ENDED tdir, err := os.MkdirTemp("", "kitty-cfd") if err != nil { log.Println("cannot run file chooser as failed to create a temporary directory with error: ", err) return } pid_path := filepath.Join(tdir, "pid") var close_requested, child_killed atomic.Bool Close := func() *dbus.Error { close_requested.Store(true) if !child_killed.Load() { if raw, err := os.ReadFile(pid_path); err == nil { if pid, err := strconv.Atoi(string(raw)); err == nil { child_killed.Store(true) unix.Kill(pid, unix.SIGTERM) } } } return nil } self.bus.ExportMethodTable(map[string]any{"Close": Close}, cfd.Handle, REQUEST_INTERFACE) defer func() { self.bus.ExportMethodTable(nil, cfd.Handle, REQUEST_INTERFACE) _ = os.RemoveAll(tdir) }() output_path := filepath.Join(tdir, "output.json") cmd := func() *exec.Cmd { self.lock.Lock() defer self.lock.Unlock() edge, lines, columns := `center`, ``, `` if self.opts.File_chooser_size != "" { l, c, _ := strings.Cut(strings.TrimSpace(self.opts.File_chooser_size), " ") l, c = strings.TrimSpace(l), strings.TrimSpace(c) if li, err := strconv.Atoi(l); err == nil { if ci, err := strconv.Atoi(c); err == nil { if li < 10 || ci < 40 { log.Printf("file chooser size %s too small, ignoring", self.opts.File_chooser_size) } else { edge, lines, columns = `center-sized`, l, c } } else { log.Printf("file chooser size %s invalid with error: %s\n", self.opts.File_chooser_size, err) } } else { log.Printf("file chooser size %s invalid with error: %s\n", self.opts.File_chooser_size, err) } } args := []string{ "+kitten", "panel", "--layer=overlay", "--edge=" + edge, "--focus-policy=exclusive", "-o", "background_opacity=0.85", "--wait-for-single-instance-window-close", "--grab-keyboard", "--single-instance", "--instance-group", "cfp-" + strconv.Itoa(os.Getpid()), } if edge == "center-sized" { args = append(args, "--lines="+lines, "--columns="+columns) } for _, x := range self.opts.File_chooser_kitty_conf { args = append(args, `-c`, x) } for _, x := range self.opts.File_chooser_kitty_override { args = append(args, `-o`, x) } if self.file_chooser_first_instance == nil { fifo_path := filepath.Join(tdir, "fifo") if err := unix.Mkfifo(fifo_path, 0600); err != nil { log.Println("cannot run file chooser as failed to create a fifo directory with error: ", err) return nil } fa := slices.Clone(args) fa = append(fa, "--start-as-hidden", "sh", "-c", "echo a > '"+fifo_path+"'; read") cmd := exec.Command(utils.KittyExe(), fa...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Start() ch := make(chan int) go func() { f, err := os.OpenFile(fifo_path, os.O_RDONLY, os.ModeNamedPipe) if err != nil { log.Println("cannot run file chooser as failed to open fifo for read with error: ", err) } b := []byte{'a', 'b', 'c', 'd'} f.Read(b) ch <- 0 }() select { case <-ch: self.file_chooser_first_instance = cmd case <-time.After(5 * time.Second): log.Println("cannot run file chooser as panel script timed out writing to fifo") return nil } } args = append(args, "kitten", `choose-files`, `--mode`, cfd.Mode, `--write-output-to`, output_path, `--output-format=json`, `--display-title`) if cfd.SuggestedSaveFileName != "" { args = append(args, `--suggested-save-file-name`, cfd.SuggestedSaveFileName) } if cfd.SuggestedSaveFilePath != "" { args = append(args, `--suggested-save-file-path`, cfd.SuggestedSaveFilePath) } if cfd.Title != "" { args = append(args, "--title", cfd.Title) } for _, fs := range cfd.Filters { for _, exp := range fs.Expressions { args = append(args, "--file-filter", fmt.Sprintf("%s:%s:%s", utils.IfElse(exp.Ftype == 0, "glob", "mime"), exp.Val, fs.Name)) } } args = append(args, "--write-pid-to", pid_path) args = append(args, utils.IfElse(cfd.Cwd == "", "~", cfd.Cwd)) cmd := exec.Command(utils.KittyExe(), args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd }() if cmd == nil || close_requested.Load() { return } log.Println("running file chooser with args:", cmd.Path, utils.Repr(cmd.Args)) if err := cmd.Run(); err != nil { log.Println("running file chooser failed with error: ", err) return } if close_requested.Load() { return } raw, err := os.ReadFile(output_path) if err != nil { log.Println("running file chooser failed, could not read from output file with error: ", err) return } if close_requested.Load() { return } var result ChooserResponse if err = json.Unmarshal(raw, &result); err != nil { log.Println("running file chooser failed, invalid JSON response with error: ", err) return } if result.Error != "" { log.Println("running file chooser failed, with error: ", result.Error) return } if result.Interrupted { response = RESPONSE_CANCELED log.Println("running file chooser failed, interrupted by user.") return } response = RESPONSE_SUCCESS prefix := "file://" + utils.IfElse(runtime.GOOS == "windows", "/", "") uris := utils.Map(func(path string) string { path = filepath.ToSlash(path) u := url.URL{Path: path} return prefix + u.EscapedPath() }, result.Paths) result_dict = vmap{"uris": dbus.MakeVariant(uris)} if result.Current_filter != "" { if v, found := get_matching_filter(result.Current_filter, cfd.Filters); found { result_dict["current_filter"] = v } } return } func (options vmap) get_bytearray(name string) string { if v, found := options[name]; found { var b []byte if v.Store(&b) == nil { // the FileChooser spec requires paths and filenames to be null // terminated, so remove trailing nulls. return string(bytes.TrimRight(b, "\x00")) } } return "" } func (self *Portal) OpenFile(handle dbus.ObjectPath, app_id string, parent_window string, title string, options vmap) (uint32, vmap, *dbus.Error) { cfd := ChooseFilesData{Title: title, Cwd: options.get_bytearray("current_folder"), Handle: handle} cfd.set_filters(options) dir_only := options.get_bool("directory", false) multiple := options.get_bool("multiple", false) if dir_only { cfd.Mode = utils.IfElse(multiple, "dirs", "dir") } else { cfd.Mode = utils.IfElse(multiple, "files", "file") } response, result := self.run_file_chooser(cfd) return response, result, nil } func (self *Portal) SaveFile(handle dbus.ObjectPath, app_id string, parent_window string, title string, options vmap) (uint32, vmap, *dbus.Error) { cfd := ChooseFilesData{ Title: title, Cwd: options.get_bytearray("current_folder"), Handle: handle, SuggestedSaveFileName: options.get_bytearray("current_name"), SuggestedSaveFilePath: options.get_bytearray("current_file")} multiple := options.get_bool("multiple", false) cfd.set_filters(options) cfd.Mode = utils.IfElse(multiple, "save-files", "save-file") response, result := self.run_file_chooser(cfd) return response, result, nil } ================================================ FILE: kittens/diff/__init__.py ================================================ def syntax_aliases(x: str) -> dict[str, str]: ans = {} for x in x.split(): k, _, v = x.partition(':') ans[k] = v return ans ================================================ FILE: kittens/diff/collect.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "crypto/md5" "fmt" "io/fs" "os" "path/filepath" "strings" "unicode/utf8" "github.com/kovidgoyal/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 }) } 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 sanitize(text string) string { return utils.ReplaceControlCodes(text, conf.Replace_tab_by, "\n") } 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 } ================================================ FILE: kittens/diff/collect_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/kovidgoyal/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) } } ================================================ FILE: kittens/diff/diff.go ================================================ // 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 := min(end.x-start.x, 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 := range n { 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 } ================================================ FILE: kittens/diff/highlight.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "os" "sync" "github.com/kovidgoyal/kitty/tools/highlight" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" ) var _ = fmt.Print var _ = os.WriteFile type prefer_light_colors bool func (s prefer_light_colors) StyleName() string { return utils.IfElse(bool(s), conf.Pygments_style, conf.Dark_pygments_style) } func (s prefer_light_colors) UseLightColors() bool { return bool(s) } func (s prefer_light_colors) SyntaxAliases() map[string]string { return conf.Syntax_aliases } func (s prefer_light_colors) TextForPath(path string) (string, error) { return data_for_path(path) } var highlighter = sync.OnceValue(func() highlight.Highlighter { return highlight.NewHighlighter(sanitize) }) func highlight_all(paths []string, light bool) { ctx := images.Context{} srd := prefer_light_colors(light) if err := ctx.SafeParallel(0, len(paths), func(nums <-chan int) { for i := range nums { path := paths[i] raw, err := highlighter().HighlightFile(path, &srd) 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)) } } }); err != nil { panic(err) } } ================================================ FILE: kittens/diff/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "archive/tar" "bytes" "fmt" "io" "io/fs" "os" "os/exec" "path/filepath" "strings" "github.com/kovidgoyal/kitty/kittens/ssh" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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 var temp_files []string func resolve_path(path string) (ans string, is_dir bool, err error) { var s fs.FileInfo if s, err = os.Stat(path); err != nil { return } else { if s.Mode()&fs.ModeNamedPipe != 0 { var src, dest *os.File if src, err = os.Open(path); err != nil { return } defer src.Close() if dest, err = os.CreateTemp("", fmt.Sprintf("*-pipe-%s", filepath.Base(path))); err != nil { return } defer dest.Close() temp_files = append(temp_files, dest.Name()) if _, err = io.Copy(dest, src); err != nil { return } return dest.Name(), false, nil } else { return path, s.IsDir(), 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 } defer func() { for _, path := range temp_files { os.Remove(path) } }() var left_is_dir, right_is_dir bool if left, left_is_dir, err = resolve_path(left); err != nil { return 1, err } if right, right_is_dir, err = resolve_path(right); err != nil { return 1, err } if left_is_dir != right_is_dir { 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.'") } 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) } ================================================ FILE: kittens/diff/main.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from functools import partial from kitty.conf.types import Definition from kitty.constants import appname from kitty.simple_cli_definitions import CONFIG_HELP, CompletionSpec 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 ''', ) opt('mark_moved_lines', 'yes', option_type='to_bool', long_text=''' Highlight lines that are moved, that is removed from the left and added to the right differently, using the :opt:`moved_bg` color.''') opt('word_diff_mode', 'words', choices=('words', 'central'), long_text=''' The algorithm to use for highlighting which parts of changed lines differ. When set to :code:`words`, changed words in each changed line are highlighted. When set to :code:`central`, the central changed region of each changed line is highlighted at the byte level. The :code:`words` mode is generally more useful as it shows exactly which words changed. Note that the :code:`words` mode only applies when a changed chunk has equal numbers of added and removed lines. ''' ) opt('word_regex', r'[^\s\p{P}]+', long_text=''' The regular expression used to define a word when :opt:`word_diff_mode` is :code:`words`. The default value matches any run of non-whitespace and non-punctuation characters. The expression must be a valid Go regular expression. If an invalid expression is provided, diff will fail with an error. ''' ) 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('moved_bg', '#fffde7', option_type='to_color', long_text='Moved text backgrounds (same text that was removed in one place and added in another)') opt('dark_moved_bg', '#003333', option_type='to_color') opt('moved_margin_bg', '#fff3b0', option_type='to_color') opt('dark_moved_margin_bg', '#00495b', 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 next page', 'scroll_page_down ctrl+f scroll_to next-page', ) map('Scroll to previous page', 'scroll_page_up page_up scroll_to prev-page', ) map('Scroll to previous page', 'scroll_page_up ctrl+b scroll_to prev-page', ) map('Scroll down half page', 'scroll_half_page_down ctrl+d scroll_to next-half-page', ) map('Scroll up half page', 'scroll_half_page_up ctrl+u scroll_to prev-half-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__': from kitty.guess_mime_type import text_mimes 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' mimes = ' '.join(f'mime:{x}' for x in ('text/*', 'image/*') + tuple(text_mimes)) cd['args_completion'] = CompletionSpec.from_string(f'type:file {mimes} group:"Text and image files"') elif __name__ == '__conf__': sys.options_definition = definition # type: ignore ================================================ FILE: kittens/diff/mouse.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "math" "strconv" "strings" "sync" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/wcswidth" ) var _ = fmt.Print type KittyOpts struct { Wheel_scroll_multiplier float64 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.ParseFloat(val, 64) 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 := int(math.Round(RelevantKittyOpts().Wheel_scroll_multiplier)) if amt == 0 { amt = 1 } 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) } ================================================ FILE: kittens/diff/patch.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "bytes" "errors" "fmt" "os/exec" "path/filepath" "regexp" "strconv" "strings" "sync" parallel "github.com/kovidgoyal/go-parallel" "github.com/kovidgoyal/kitty/tools/simdstring" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" "github.com/kovidgoyal/kitty/tools/utils/shlex" ) 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 } // Region represents a highlighted byte range within a line. type Region struct{ offset, size int } // Center holds the highlighted regions for the left (removed) and right (added) sides of a changed line pair. type Center struct { left_regions []Region right_regions []Region } 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++ } // changed_center computes the central changed region of left/right at the byte level. 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) offset := 0 for ; offset < ml && left[offset] == right[offset]; offset++ { } suffix_count := 0 for ; suffix_count < ml && left[ll-1-suffix_count] == right[rl-1-suffix_count]; suffix_count++ { } left_size := ll - suffix_count - offset right_size := rl - suffix_count - offset if left_size > 0 { ans.left_regions = []Region{{offset: offset, size: left_size}} } if right_size > 0 { ans.right_regions = []Region{{offset: offset, size: right_size}} } } return } var word_regexp = sync.OnceValues(func() (*regexp.Regexp, error) { pattern := `\S+` if conf != nil && conf.Word_regex != "" { pattern = conf.Word_regex } return regexp.Compile(pattern) }) // word_diff_center computes highlighted regions for changed words between left and right. func word_diff_center(left, right string, re *regexp.Regexp) Center { left_matches := re.FindAllStringIndex(left, -1) right_matches := re.FindAllStringIndex(right, -1) type word struct { text string offset int size int } left_words := make([]word, len(left_matches)) right_words := make([]word, len(right_matches)) for i, m := range left_matches { left_words[i] = word{text: left[m[0]:m[1]], offset: m[0], size: m[1] - m[0]} } for i, m := range right_matches { right_words[i] = word{text: right[m[0]:m[1]], offset: m[0], size: m[1] - m[0]} } // Strip common prefix and suffix words so LCS only runs on the differing middle. prefix := 0 for prefix < len(left_words) && prefix < len(right_words) && left_words[prefix].text == right_words[prefix].text { prefix++ } suffix := 0 for suffix < len(left_words)-prefix && suffix < len(right_words)-prefix && left_words[len(left_words)-1-suffix].text == right_words[len(right_words)-1-suffix].text { suffix++ } lw := left_words[prefix : len(left_words)-suffix] rw := right_words[prefix : len(right_words)-suffix] m, n := len(lw), len(rw) // LCS dynamic programming table dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { if lw[i-1].text == rw[j-1].text { dp[i][j] = dp[i-1][j-1] + 1 } else if dp[i-1][j] > dp[i][j-1] { dp[i][j] = dp[i-1][j] } else { dp[i][j] = dp[i][j-1] } } } // Backtrack to find changed words within the middle slice. left_changed := make([]bool, m) right_changed := make([]bool, n) i, j := m, n for i > 0 && j > 0 { if lw[i-1].text == rw[j-1].text { i-- j-- } else if dp[i-1][j] > dp[i][j-1] { left_changed[i-1] = true i-- } else { right_changed[j-1] = true j-- } } for i > 0 { left_changed[i-1] = true i-- } for j > 0 { right_changed[j-1] = true j-- } // Remap changed flags to absolute indices within the full words arrays. left_changed_abs := make([]bool, len(left_words)) right_changed_abs := make([]bool, len(right_words)) for k, c := range left_changed { left_changed_abs[prefix+k] = c } for k, c := range right_changed { right_changed_abs[prefix+k] = c } // Verify that every changed word at index i has a corresponding changed // word at the same index i on the other side. If any position is changed // on exactly one side, the lines differ in word count and it makes more // sense to highlight the single changed central region of the whole line. max_idx := max(len(left_words), len(right_words)) for idx := 0; idx < max_idx; idx++ { lc := idx < len(left_words) && left_changed_abs[idx] rc := idx < len(right_words) && right_changed_abs[idx] if lc != rc { return changed_center(left, right) } } // All changed words are positionally paired. Apply character-level // prefix/suffix trimming to each pair so only the differing central bytes // are highlighted. var ans Center for idx := range left_words { if !left_changed_abs[idx] { continue } lword := left_words[idx] rword := right_words[idx] lt := left[lword.offset : lword.offset+lword.size] rt := right[rword.offset : rword.offset+rword.size] ll, rl := len(lt), len(rt) ml := min(ll, rl) cpfx := 0 for cpfx < ml && lt[cpfx] == rt[cpfx] { cpfx++ } csfx := 0 for csfx < ml-cpfx && lt[ll-1-csfx] == rt[rl-1-csfx] { csfx++ } lsize := ll - cpfx - csfx rsize := rl - cpfx - csfx if lsize > 0 { ans.left_regions = append(ans.left_regions, Region{offset: lword.offset + cpfx, size: lsize}) } if rsize > 0 { ans.right_regions = append(ans.right_regions, Region{offset: rword.offset + cpfx, size: rsize}) } } return ans } func (self *Chunk) finalize(_ []string, _ []string) { // Center computation is performed in parallel by Patch.compute_centers } 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 left_moved_lines, right_moved_lines *utils.Set[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 (self *Patch) compute_centers(left_lines, right_lines []string) error { word_mode := conf != nil && conf.Word_diff_mode == Word_diff_mode_words var re *regexp.Regexp if word_mode { var err error re, err = word_regexp() if err != nil { return fmt.Errorf("Failed to compile word_regex %q: %w", conf.Word_regex, err) } } type pair struct { chunk *Chunk idx int } var pairs []pair for _, hunk := range self.all_hunks { for _, chunk := range hunk.chunks { if !chunk.is_context && chunk.left_count == chunk.right_count { for i := 0; i < chunk.left_count; i++ { pairs = append(pairs, pair{chunk, i}) } } } } if len(pairs) == 0 { return nil } centers := make([]Center, len(pairs)) if err := parallel.Run_in_parallel_over_range(0, func(start, end int) { for i := start; i < end; i++ { p := pairs[i] left := left_lines[p.chunk.left_start+p.idx] right := right_lines[p.chunk.right_start+p.idx] if word_mode { centers[i] = word_diff_center(left, right, re) } else { centers[i] = changed_center(left, right) } } }, 0, len(pairs)); err != nil { return err } ci := 0 for _, hunk := range self.all_hunks { for _, chunk := range hunk.chunks { if !chunk.is_context && chunk.left_count == chunk.right_count { chunk.centers = centers[ci : ci+chunk.left_count] ci += chunk.left_count } } } return nil } // Use SIMD to efficiently find non-blank lines: a line is non-blank if it // contains at least one character that is not a space or tab. func is_non_blank(text string) bool { return simdstring.NotIndexByte2String(text, ' ', '\t') >= 0 } func (self *Patch) detect_moved_lines(left_lines, right_lines []string) { // Build maps from line text to lists of line numbers for removed and added lines. removed := make(map[string][]int, len(left_lines)) // text -> left line numbers added := make(map[string][]int, len(right_lines)) // text -> right line numbers for _, hunk := range self.all_hunks { for _, chunk := range hunk.chunks { if !chunk.is_context { for i := 0; i < chunk.left_count; i++ { lnum := chunk.left_start + i text := left_lines[lnum] if is_non_blank(text) { removed[text] = append(removed[text], lnum) } } for i := 0; i < chunk.right_count; i++ { rnum := chunk.right_start + i text := right_lines[rnum] if is_non_blank(text) { added[text] = append(added[text], rnum) } } } } } // Lines that appear in both removed and added sets are moved lines. When a // line appears multiple times on each side, only min(left_count, // right_count) occurrences are marked as moved. self.left_moved_lines = utils.NewSet[int]() self.right_moved_lines = utils.NewSet[int]() for text, lnums := range removed { if rnums, ok := added[text]; ok { count := min(len(lnums), len(rnums)) for _, lnum := range lnums[:count] { self.left_moved_lines.Add(lnum) } for _, rnum := range rnums[:count] { self.right_moved_lines.Add(rnum) } } } } 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 } err = ans.compute_centers(left_lines, right_lines) if err == nil && conf.Mark_moved_lines { ans.detect_moved_lines(left_lines, right_lines) } 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)) if err := ctx.SafeParallel(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 } }); err != nil { panic(err) } close(results) for r := range results { if r.err != nil { return nil, r.err } ans[r.file1] = r.patch } return ans, nil } ================================================ FILE: kittens/diff/patch_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "regexp" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) var region_eq = cmpopts.EquateComparable(Region{}) func TestWordDiffCenter(t *testing.T) { re := regexp.MustCompile(`\S+`) type tc struct { left, right string left_regions []Region right_regions []Region } tests := []tc{ { // word count equal, single substitution at index 1 → positional pair // "quick" vs "slow": no common chars → full words left: "the quick brown fox", right: "the slow brown fox", left_regions: []Region{{4, 5}}, right_regions: []Region{{4, 4}}, }, { left: "hello world", right: "hello world", left_regions: nil, right_regions: nil, }, { // word count equal, single substitution at index 1 → positional pair // "bar" vs "qux": no common chars → full words left: "foo bar baz", right: "foo qux baz", left_regions: []Region{{4, 3}}, right_regions: []Region{{4, 3}}, }, { // left has 3 words, right has 4 → word counts differ with unmatched // changed words → fall back to changed_center // changed_center gives: offset=4 (common "aaa "), suffix="ccc"(4) // left_size=3 ("bbb"), right_size=7 ("xxx yyy") left: "aaa bbb ccc", right: "aaa xxx yyy ccc", left_regions: []Region{{4, 3}}, right_regions: []Region{{4, 7}}, }, { // word on left deleted: unmatched changed word → fall back to changed_center // changed_center: prefix="aaa "(4), suffix=" ccc ddd"(8) // left_size=3 ("bbb"), right_size=-1 → nil left: "aaa bbb ccc ddd", right: "aaa ccc ddd", left_regions: []Region{{4, 3}}, right_regions: nil, }, { // word counts equal, single substitution at index 3 → positional pair // "fox" vs "cat": no common chars → full words left: "the quick brown fox over the lazy dog", right: "the quick brown cat over the lazy dog", left_regions: []Region{{16, 3}}, right_regions: []Region{{16, 3}}, }, { // single word, positional pair with common char prefix "version" (7) left: "version1", right: "version2", left_regions: []Region{{7, 1}}, right_regions: []Region{{7, 1}}, }, { // positional pair at index 1, char prefix="prefix"(6), suffix="suffix"(6) // word at offset 7 → highlight offset 13, size 2 left: "update prefixABsuffix done", right: "update prefixCDsuffix done", left_regions: []Region{{13, 2}}, right_regions: []Region{{13, 2}}, }, { // equal word count, multiple positional pairs (all words changed) // each pair has no common chars → full-word regions left: "aaa bbb ccc", right: "xxx yyy zzz", left_regions: []Region{{0, 3}, {4, 3}, {8, 3}}, right_regions: []Region{{0, 3}, {4, 3}, {8, 3}}, }, { // pure insertion on right side → unmatched changed word → fall back to changed_center // changed_center finds common prefix "aaa bbb ccc" (11 bytes) and suffix ""; // left_size=0 (nil), right_size=4 (" ddd" inserted at the end) left: "aaa bbb ccc", right: "aaa bbb ccc ddd", left_regions: nil, right_regions: []Region{{11, 4}}, }, } for _, tc := range tests { c := word_diff_center(tc.left, tc.right, re) if diff := cmp.Diff(tc.left_regions, c.left_regions, region_eq); diff != "" { t.Errorf("word_diff_center(%q, %q) left_regions mismatch: %s", tc.left, tc.right, diff) } if diff := cmp.Diff(tc.right_regions, c.right_regions, region_eq); diff != "" { t.Errorf("word_diff_center(%q, %q) right_regions mismatch: %s", tc.left, tc.right, diff) } } } func TestChangedCenter(t *testing.T) { type tc struct { left, right string left_regions []Region right_regions []Region } tests := []tc{ { left: "the quick brown fox", right: "the slow brown fox", left_regions: []Region{{4, 5}}, right_regions: []Region{{4, 4}}, }, { left: "hello world", right: "hello world", left_regions: nil, right_regions: nil, }, } for _, tc := range tests { c := changed_center(tc.left, tc.right) if diff := cmp.Diff(tc.left_regions, c.left_regions, region_eq); diff != "" { t.Errorf("changed_center(%q, %q) left_regions mismatch: %s", tc.left, tc.right, diff) } if diff := cmp.Diff(tc.right_regions, c.right_regions, region_eq); diff != "" { t.Errorf("changed_center(%q, %q) right_regions mismatch: %s", tc.left, tc.right, diff) } } } ================================================ FILE: kittens/diff/render.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "errors" "fmt" "math" "os" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/sgr" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/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 is_moved 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 if sl.left.is_moved { left_margin = format_as_sgr.moved_margin + left_margin left_text = format_as_sgr.moved + 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 if sl.right.is_moved { right_margin = format_as_sgr.moved_margin + right_margin right_text = format_as_sgr.moved + 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, moved, moved_margin 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 Moved_bg style.RGBA Moved_margin_bg style.RGBA 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.Moved_bg = conf.Dark_moved_bg rc.Moved_margin_bg = conf.Dark_moved_margin_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.Moved_bg = conf.Moved_bg rc.Moved_margin_bg = conf.Moved_margin_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.moved = only_open("bg=" + rc.Moved_bg.AsRGBSharp()) format_as_sgr.moved_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Moved_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 left_moved_lines *utils.Set[int] right_moved_lines *utils.Set[int] } 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, is_moved bool, ans []HalfScreenLine) []HalfScreenLine { var regions []Region if ltype == "remove" { regions = center.left_regions } else { regions = center.right_regions } if len(regions) > 0 { spans := make([]*sgr.Span, len(regions)) for i, r := range regions { spans[i] = center_span(ltype, r.offset, r.size) } line = sgr.InsertFormatting(line, spans...) } 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, is_moved: is_moved}) 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 left_is_moved := data.left_moved_lines != nil && data.left_moved_lines.Has(left_lnum) ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, left_is_moved, ll) left_lnum++ } if i < chunk.right_count { right_lnum = chunk.right_start + i right_is_moved := data.right_moved_lines != nil && data.right_moved_lines.Has(right_lnum) rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, right_is_moved, 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, left_moved_lines: patch.left_moved_lines, right_moved_lines: patch.right_moved_lines, } 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{}, false, 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 } ================================================ FILE: kittens/diff/search.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "regexp" "slices" "strings" "sync" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" "github.com/kovidgoyal/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{} if err := ctx.SafeParallel(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}) } }) } }); err != nil { panic(err) } 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 } ================================================ FILE: kittens/diff/ui.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "regexp" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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() { self.lp.RecoverFromPanicInGoRoutine() 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() { self.lp.RecoverFromPanicInGoRoutine() 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() { self.lp.RecoverFromPanicInGoRoutine() 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() { self.lp.RecoverFromPanicInGoRoutine() 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() { self.lp.RecoverFromPanicInGoRoutine() 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 && self.terminal_capabilities_received { 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: if !self.terminal_capabilities_received { go func() { self.async_results <- r self.lp.WakeupMainThread() }() return nil } 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 && self.terminal_capabilities_received { 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, `half`) { amt = amt / 2 } 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 } ================================================ FILE: kittens/hints/__init__.py ================================================ ================================================ FILE: kittens/hints/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package hints import ( "encoding/json" "fmt" "io" "os" "strconv" "strings" "unicode" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/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 range len(full_line) { 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 { replaced_text := mark_text[:len(hint)] replaced_text = strings.ReplaceAll(replaced_text, "\r", "\n") if strings.Contains(replaced_text, "\n") { buf := strings.Builder{} buf.Grow(2 * len(hint)) h := hint parts := strings.Split(replaced_text, "\n") for i, x := range parts { if x != "" { buf.WriteString(h[:len(x)]) h = h[len(x):] } if i != len(parts)-1 { buf.WriteString("\n") } } if h != "" { buf.WriteString(h) } hint = buf.String() } 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) } ================================================ FILE: kittens/hints/main.py ================================================ #!/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_compat 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 allows 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,remote-control 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. :code:`remote-control` is like background but the program can use kitty remote control without needing to turn on remote control globally. 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': boss.run_background_process(cmd, cwd=data['cwd'], allow_remote_control=False) elif action == 'remote-control': boss.run_background_process(cmd, cwd=data['cwd'], allow_remote_control=True) 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 main(sys.argv) 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 # }}} ================================================ FILE: kittens/hints/marks.go ================================================ // 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" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/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.SplitSeq(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]{0,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 } ================================================ FILE: kittens/hints/marks_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package hints import ( "errors" "fmt" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/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(`2600:1901:0:b2bd::`, `2600:1901:0:b2bd::`) // ifconfig.me 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`) } ================================================ FILE: kittens/hyperlinked_grep/__init__.py ================================================ ================================================ FILE: kittens/hyperlinked_grep/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package hyperlinked_grep import ( "bytes" "errors" "fmt" "net/url" "os" "os/exec" "path/filepath" "regexp" "strings" "sync" "unicode" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/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.SplitSeq(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.SplitSeq(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) } ================================================ FILE: kittens/hyperlinked_grep/main.py ================================================ #!/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' ================================================ FILE: kittens/hyperlinked_grep/main_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package hyperlinked_grep import ( "fmt" "github.com/kovidgoyal/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") } ================================================ FILE: kittens/icat/__init__.py ================================================ ================================================ FILE: kittens/icat/detect.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package icat import ( "errors" "fmt" "os" "time" "github.com/kovidgoyal/go-shm" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" ) 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 } ================================================ FILE: kittens/icat/main.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "fmt" "math" "os" "runtime" "slices" "strconv" "strings" "sync/atomic" "time" "github.com/kovidgoyal/imaging" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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 *imaging.NRGBColor var flip, flop bool type transfer_mode int const ( unknown transfer_mode = iota unsupported supported ) type fit_t int const ( fit_none fit_t = iota fit_width fit_height fit_both ) 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 var fit_mode fit_t 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 = &imaging.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_fit() (err error) { switch strings.ToLower(opts.Fit) { case "width": fit_mode = fit_width case "height": fit_mode = fit_height case "none", "neither": fit_mode = fit_none case "both": fit_mode = fit_both default: return fmt.Errorf("unknown fit specification: %#v", opts.Fit) } return nil } 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 if err = parse_place(); err != nil { return 1, err } if err = parse_fit(); 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: %s", opts.UseWindowSize) } screen_size = &unix.Winsize{} var t uint64 if t, err = strconv.ParseUint(parts[0], 10, 16); 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.ParseUint(parts[1], 10, 16); 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.ParseUint(parts[2], 10, 16); 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.ParseUint(parts[3], 10, 16); 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 } } switch { case opts.ClearAll: cc := &graphics.GraphicsCommand{} cc.SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_free_by_range).SetLeftEdge(0).SetTopEdge(math.MaxUint32) if err = cc.WriteWithPayloadTo(os.Stdout, nil); err != nil { return 1, err } case 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 i, ia := range items { ia.index = i 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 range num_workers { 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) expecting_input_sequence_number := 0 pending := make([]*image_data, 0, num_of_items) do_one := func(imgd *image_data) { expecting_input_sequence_number++ 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 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) } } } for num_of_items > 0 { imgd := <-output_channel num_of_items-- if imgd.input_sequence_number == expecting_input_sequence_number { do_one(imgd) } else { index, _ := slices.BinarySearchFunc(pending, imgd.input_sequence_number, func(x *image_data, n int) int { return x.input_sequence_number - n }) pending = slices.Insert(pending, index, imgd) } for len(pending) > 0 && pending[0].input_sequence_number == expecting_input_sequence_number { do_one(pending[0]) pending = pending[1:] } } for _, x := range pending { do_one(x) } 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) } ================================================ FILE: kittens/icat/main.py ================================================ #!/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 Cause images that are smaller than the specified area to be scaled up to use as much of the specified area as possible. The specified area depends on either the :option:`--place` or the :option:`--fit` options. --fit choices=width,height,both,none default=width When not using :option:`--place`, control how the image is scaled relative to the screen. You can have it fit in the screen width or height or both or neither. --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. Note that this cannot work with terminal multiplexers such as tmux since only the multiplexer can know the position of the screen. --clear-all type=bool-set Remove all images from screen and scrollback. Note that with terminal multiplexers like tmux, this will move images from all panes. --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.simple_cli_definitions 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') ================================================ FILE: kittens/icat/process_images.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "bytes" "fmt" "image" "io" "io/fs" "math" "net/http" "net/url" "os" "path/filepath" "strings" "github.com/kovidgoyal/imaging" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" ) var _ = fmt.Print type input_arg struct { arg string value string is_http_url bool index int } 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.Reader bytes []byte path string } type image_frame struct { filename string in_memory_bytes []byte width, height, left, top int transmission_format graphics.GRT_f compose_onto int replace bool number int delay_ms int } type image_data struct { canvas_width, canvas_height int format_uppercase string available_width, available_height int needs_scaling bool 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 input_sequence_number int // for error reporting err error source_name string } const inf = math.MaxInt func set_basic_metadata(imgd *image_data) { if imgd.frames == nil { imgd.frames = make([]*image_frame, 0, 32) } 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) } else { switch fit_mode { case fit_none: imgd.available_width, imgd.available_height = inf, inf case fit_both: imgd.available_width = int(screen_size.Xpixel) imgd.available_height = int(screen_size.Ypixel) case fit_width: imgd.available_width = int(screen_size.Xpixel) imgd.available_height = inf case fit_height: imgd.available_width = inf imgd.available_height = int(screen_size.Ypixel) } } imgd.needs_scaling = imgd.canvas_width > imgd.available_width || imgd.canvas_height > imgd.available_height || opts.ScaleUp } 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) { 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 f.bytes != nil { frame.in_memory_bytes = f.bytes } else if f.path != "" { frame.filename = f.path } else { var err error if frame.in_memory_bytes, err = io.ReadAll(f.file); err != nil { panic(err) } } } func scale_up(width, height, maxWidth, maxHeight int) (newWidth, newHeight int) { if width == 0 || height == 0 { return 0, 0 } // Calculate the ratio to scale the width and the ratio to scale the height. // We use floating-point division for precision. widthRatio := float64(maxWidth) / float64(width) heightRatio := float64(maxHeight) / float64(height) // To preserve the aspect ratio and fit within the limits, we must use the // smaller of the two scaling ratios. var ratio float64 if widthRatio < heightRatio { ratio = widthRatio } else { ratio = heightRatio } // Calculate the new dimensions and convert them back to uints. newWidth = int(float64(width) * ratio) newHeight = int(float64(height) * ratio) return newWidth, newHeight } func scale_image(imgd *image_data) bool { if imgd.needs_scaling { width, height := imgd.canvas_width, imgd.canvas_height if opts.ScaleUp && (imgd.canvas_width < imgd.available_width || imgd.canvas_height < imgd.available_height) && (imgd.available_height != inf || imgd.available_width != inf) { imgd.canvas_width, imgd.canvas_height = scale_up(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height) } neww, newh := images.FitImage(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height) imgd.needs_scaling = false x := float64(neww) / float64(width) y := float64(newh) / float64(height) imgd.canvas_width = int(x * float64(width)) imgd.canvas_height = int(y * float64(height)) return true } return false } func add_frame(imgd *image_data, img image.Image, left, top int) *image_frame { const shm_template = "kitty-icat-*" num_channels := 4 var pix []byte if imaging.IsOpaque(img) { num_channels, pix = 3, imaging.AsRGBData8(img) } else { pix = imaging.AsRGBAData8(img) } b := img.Bounds() f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: left, top: top} f.transmission_format = utils.IfElse(num_channels == 3, graphics.GRT_format_rgb, graphics.GRT_format_rgba) f.in_memory_bytes = pix imgd.frames = append(imgd.frames, &f) return &f } 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.bytes = dest.Bytes() f.file = bytes.NewReader(f.bytes) } else if arg.value == "" { stdin, err := io.ReadAll(os.Stdin) if err != nil { report_error("", "Could not read from", err) return } f.bytes = stdin f.file = bytes.NewReader(f.bytes) } else { q, err := os.Open(arg.value) if err != nil { report_error(arg.value, "Could not open", err) return } f.file = q f.path = q.Name() defer q.Close() } var img *images.ImageData var dopts []imaging.DecodeOption needs_conversion := false if flip { dopts = append(dopts, imaging.Transform(imaging.FlipVTransform)) needs_conversion = true } if flop { dopts = append(dopts, imaging.Transform(imaging.FlipHTransform)) needs_conversion = true } if remove_alpha != nil { dopts = append(dopts, imaging.Background(*remove_alpha)) needs_conversion = true } switch opts.Engine { case "native", "builtin": dopts = append(dopts, imaging.Backends(imaging.GO_IMAGE)) case "magick": dopts = append(dopts, imaging.Backends(imaging.MAGICK_IMAGE)) } imgd := image_data{source_name: arg.value, input_sequence_number: arg.index} dopts = append(dopts, imaging.ResizeCallback(func(w, h int) (int, int) { imgd.canvas_width, imgd.canvas_height = w, h set_basic_metadata(&imgd) if scale_image(&imgd) { needs_conversion = true w, h = imgd.canvas_width, imgd.canvas_height } return w, h })) var err error if f.path != "" { img, err = images.OpenImageFromPath(f.path, dopts...) } else { img, f.file, err = images.OpenImageFromReader(f.file, dopts...) } if err != nil { report_error(arg.value, "Could not render image to RGB", err) return } if !keep_going.Load() { return } imgd.format_uppercase = img.Format_uppercase imgd.canvas_width, imgd.canvas_height = img.Width, img.Height if !needs_conversion && imgd.format_uppercase == "PNG" && len(img.Frames) == 1 { make_output_from_input(&imgd, &f) } else { for _, f := range img.Frames { frame := add_frame(&imgd, f.Img, f.Left, f.Top) frame.number, frame.compose_onto = int(f.Number), int(f.Compose_onto) frame.replace = f.Replace frame.delay_ms = int(f.Delay_ms) } } 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 } } } ================================================ FILE: kittens/icat/scaling_test.go ================================================ package icat import ( "fmt" "image" "testing" ) var _ = fmt.Print func TestScaling(t *testing.T) { for _, tc := range []struct { w, h, pw, ph, ew, eh int }{ {1000, 50, 800, 600, 800, 40}, {1000, 50, 800000, 600, 12000, 600}, {100, 50, 800, 600, 800, 400}, {1920, 1080, 800, 600, 800, 450}, {300, 900, 800, 600, 200, 600}, {400, 300, 800, 600, 800, 600}, } { aw, ah := scale_up(tc.w, tc.h, tc.pw, tc.ph) actual := image.Pt(aw, ah) expected := image.Pt(tc.ew, tc.eh) if actual != expected { t.Fatalf("want: %v got: %v", expected, actual) } } } ================================================ FILE: kittens/icat/transmit.go ================================================ // License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "bytes" "crypto/rand" "encoding/binary" "fmt" "github.com/kovidgoyal/kitty" "io" "math" not_rand "math/rand/v2" "os" "path/filepath" "strings" "github.com/kovidgoyal/go-shm" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/graphics" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/images" ) 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)) gc.SetCompositionMode(utils.IfElse(frame.replace, graphics.Overwrite, graphics.AlphaBlend)) if frame.compose_onto > 0 { gc.SetOverlaidFrame(uint64(frame.compose_onto)) } 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() if data_size, err = f.Seek(0, io.SeekEnd); err != nil { return fmt.Errorf("Failed to seek in image data output file: %s with error: %w", frame.filename, err) } if _, err = f.Seek(0, io.SeekStart); err != nil { return fmt.Errorf("Failed to seek in image data output file: %s with error: %w", frame.filename, err) } if mmap, err = shm.CreateTemp("icat-*", uint64(data_size)); err != nil { return fmt.Errorf("Failed to create a SHM file for transmission: %w", err) } if _, err = io.ReadFull(f, mmap.Slice()); err != nil { mmap.Close() mmap.Unlink() return fmt.Errorf("Failed to read data from image output data file: %w", err) } } else { data_size = int64(len(frame.in_memory_bytes)) if mmap, err = shm.CreateTemp("icat-*", uint64(data_size)); err != nil { return fmt.Errorf("Failed to create a SHM file for transmission: %w", err) } copy(mmap.Slice(), frame.in_memory_bytes) } defer mmap.Close() // terminal is responsible for unlink 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())) 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 { 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 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 { os.Remove(f.Name()) 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) gc.SetTransmission(utils.IfElse(is_temp, graphics.GRT_transmission_tempfile, 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 { data, err = os.ReadFile(frame.filename) if err != nil { return fmt.Errorf("Failed to read image data output file: %s with error: %w", frame.filename, 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) } 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 } } ================================================ FILE: kittens/notify/__init__.py ================================================ ================================================ FILE: kittens/notify/main.go ================================================ package notify import ( "bytes" "encoding/base64" "fmt" "image" "io" "os" "slices" "strconv" "strings" "time" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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.SplitSeq(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) } ================================================ FILE: kittens/notify/main.py ================================================ #!/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' ================================================ FILE: kittens/pager/__init__.py ================================================ ================================================ FILE: kittens/pager/file_input.go ================================================ // License: GPLv3 Copyright: 2024, Kovid Goyal, package pager import ( "bytes" "errors" "fmt" "io" "os" "strings" "time" "golang.org/x/sys/unix" "github.com/kovidgoyal/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 } } } } ================================================ FILE: kittens/pager/main.go ================================================ // 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" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/pager/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import sys from kitty.simple_cli_definitions 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"') ================================================ FILE: kittens/panel/__init__.py ================================================ ================================================ FILE: kittens/panel/main.go ================================================ package panel import ( "fmt" "os" "path/filepath" "runtime" "strings" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/utils" "golang.org/x/sys/unix" ) var _ = fmt.Print func complete_kitty_listen_on(completions *cli.Completions, word string, arg_num int) { if !strings.Contains(word, ":") { mg := completions.AddMatchGroup("Address family") mg.NoTrailingSpace = true for _, q := range []string{"unix:", "tcp:"} { if strings.HasPrefix(q, word) { mg.AddMatch(q) } } } else if strings.HasPrefix(word, "unix:") && !strings.HasPrefix(word, "unix:@") { cli.FnmatchCompleter("UNIX sockets", cli.CWD, "*")(completions, word[len("unix:"):], arg_num) completions.AddPrefixToAllMatches("unix:") } } var CompleteKittyListenOn = complete_kitty_listen_on func GetQuickAccessKittyExe() (kitty_exe string, err error) { if kitty_exe, err = filepath.EvalSymlinks(utils.KittyExe()); err != nil { return "", fmt.Errorf("Failed to find path to the kitty executable, this kitten requires the kitty executable to function. The kitty executable or a symlink to it must be placed in the same directory as the kitten executable. Error: %w", err) } if runtime.GOOS == "darwin" { q := filepath.Join(filepath.Dir(filepath.Dir(kitty_exe)), "kitty-quick-access.app", "Contents", "MacOS", "kitty-quick-access") if err := unix.Access(q, unix.X_OK); err == nil { kitty_exe = q } } return kitty_exe, nil } func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { kitty_exe, err := GetQuickAccessKittyExe() if err != nil { return 1, err } argv := []string{kitty_exe, "+kitten", "panel"} argv = append(argv, o.AsCommandLine()...) err = unix.Exec(kitty_exe, append(argv, args...), os.Environ()) rc = 1 return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } ================================================ FILE: kittens/panel/main.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import os import sys from contextlib import suppress from functools import partial from types import MappingProxyType from typing import Any, Iterable, Mapping, Sequence from kitty.cli import parse_args from kitty.cli_stub import PanelCLIOptions from kitty.constants import is_macos, kitten_exe from kitty.fast_data_types import ( GLFW_EDGE_BOTTOM, GLFW_EDGE_CENTER, GLFW_EDGE_CENTER_SIZED, 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, layer_shell_config_for_os_window, set_layer_shell_config, toggle_os_window_visibility, ) from kitty.simple_cli_definitions import panel_options_spec from kitty.types import LayerShellConfig, run_once from kitty.typing_compat import BossType from kitty.utils import log_error args = PanelCLIOptions() help_text = 'Use a command line program to draw a GPU accelerated panel on your desktop' usage = '[cmdline-to-run ...]' def panel_kitten_options_spec() -> str: if not hasattr(panel_kitten_options_spec, 'ans'): setattr(panel_kitten_options_spec, 'ans', panel_options_spec()) ans: str = getattr(panel_kitten_options_spec, 'ans') return ans def parse_panel_args(args: list[str], track_seen_options: dict[str, Any] | None = None) -> tuple[PanelCLIOptions, list[str]]: return parse_args( args, panel_kitten_options_spec, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions, track_seen_options=track_seen_options) def dual_distance(spec: str, min_cell_value_if_no_pixels: int = 0) -> tuple[int, int]: with suppress(Exception): return int(spec), 0 if spec.endswith('px'): return min_cell_value_if_no_pixels, int(spec[:-2]) if spec.endswith('c'): return int(spec[:-1]), 0 return min_cell_value_if_no_pixels, 0 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, 'center-sized': GLFW_EDGE_CENTER_SIZED, }.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) if opts.hide_on_focus_loss: focus_policy = GLFW_FOCUS_ON_DEMAND x, y = dual_distance(opts.columns, min_cell_value_if_no_pixels=1), dual_distance(opts.lines, min_cell_value_if_no_pixels=1) return LayerShellConfig(type=ltype, edge=edge, x_size_in_cells=x[0], x_size_in_pixels=x[1], y_size_in_cells=y[0], y_size_in_pixels=y[1], 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, hide_on_focus_loss=opts.hide_on_focus_loss, output_name=opts.output_name or '') @run_once def cli_option_to_lsc_configs_map() -> MappingProxyType[str, tuple[str, ...]]: return MappingProxyType({ 'lines': ('y_size_in_cells', 'y_size_in_pixels'), 'columns': ('x_size_in_cells', 'x_size_in_pixels'), 'margin_top': ('requested_top_margin',), 'margin_left': ('requested_left_margin',), 'margin_bottom': ('requested_bottom_margin',), 'margin_right': ('requested_right_margin',), 'edge': ('edge',), 'layer': ('type',), 'output_name': ('output_name',), 'focus_policy': ('focus_policy',), 'exclusive_zone': ('requested_exclusive_zone',), 'override_exclusive_zone': ('override_exclusive_zone',), 'hide_on_focus_loss': ('hide_on_focus_loss',) }) def incrementally_update_layer_shell_config(existing: dict[str, Any], cli_options: Iterable[str]) -> LayerShellConfig: seen_options: dict[str, Any] = {} cli_options = [('' if x.startswith('--') else '--') + x for x in cli_options] try: try: opts, _ = parse_panel_args(cli_options, track_seen_options=seen_options) except SystemExit as e: raise ValueError(str(e)) lsc = layer_shell_config(opts) except Exception as e: raise ValueError(f'Invalid panel options specified: {e}') lsc_cli_map = cli_option_to_lsc_configs_map() for option in seen_options: for config in lsc_cli_map.get(option, ()): existing[config] = getattr(lsc, config) if seen_options.get('edge') == 'background': existing['type'] = GLFW_LAYER_SHELL_BACKGROUND if existing['hide_on_focus_loss']: existing['focus_policy'] = GLFW_FOCUS_ON_DEMAND return LayerShellConfig(**existing) mtime_map: dict[str, float] = {} def have_config_files_been_updated(config_files: Iterable[str]) -> bool: ans = False for cf in config_files: try: mtime = os.path.getmtime(cf) except OSError: mtime = 0 if mtime_map.get(cf, 0) != mtime: ans = True mtime_map[cf] = mtime return ans def handle_single_instance_command(boss: BossType, sys_args: Sequence[str], environ: Mapping[str, str], notify_on_os_window_death: str | None = '') -> None: global args from kitty.cli import parse_override from kitty.tabs import SpecialWindow try: new_args, items = parse_panel_args(list(sys_args[1:])) except BaseException as e: log_error(f'Invalid arguments received over single instance socket: {sys_args} with error: {e}') return lsc = layer_shell_config(new_args) config_changed = have_config_files_been_updated(new_args.config) or args.config != new_args.config or args.override != new_args.override args = new_args if config_changed: boss.load_config_file(*args.config, overrides=tuple(map(parse_override, new_args.override))) if args.toggle_visibility and boss.os_window_map: for os_window_id in boss.os_window_map: existing = layer_shell_config_for_os_window(os_window_id) layer_shell_config_changed = not existing or any(f for f in lsc._fields if getattr(lsc, f) != existing.get(f)) toggle_os_window_visibility(os_window_id, move_to_active_screen=args.move_to_active_monitor) if layer_shell_config_changed: set_layer_shell_config(os_window_id, lsc) return items = items or [kitten_exe(), 'run-shell'] os_window_id = boss.add_os_panel(lsc, args.cls, args.name) if notify_on_os_window_death: boss.os_window_death_actions[os_window_id] = partial(boss.notify_on_os_window_death, notify_on_os_window_death) tm = boss.os_window_map[os_window_id] tm.new_tab(SpecialWindow(cmd=items, env=dict(environ))) def main(sys_args: list[str]) -> None: # run_kitten runs using runpy.run_module which does not import into # sys.modules, which means the module will be re-imported later, causing # global variables to be duplicated, so do it now. from kittens.panel.main import actual_main actual_main(sys_args) return def actual_main(sys_args: list[str]) -> None: global args args, items = parse_panel_args(sys_args[1:]) have_config_files_been_updated(args.config) sys.argv = ['kitty'] if args.debug_rendering: sys.argv.append('--debug-rendering') if args.debug_input: sys.argv.append('--debug-input') for config in args.config: sys.argv.extend(('--config', config)) if not is_macos: sys.argv.extend(('--class', args.cls)) if args.name: sys.argv.extend(('--name', args.name)) if args.start_as_hidden: sys.argv.append('--start-as=hidden') if args.grab_keyboard: sys.argv.append('--grab-keyboard') for override in args.override: sys.argv.extend(('--override', override)) sys.argv.append('--override=linux_display_server=auto') sys.argv.append('--override=macos_quit_when_last_window_closed=yes') sys.argv.append('--override=macos_hide_from_tasks=yes') sys.argv.append('--override=macos_window_resizable=no') if args.single_instance: sys.argv.append('--single-instance') if args.instance_group: sys.argv.append(f'--instance-group={args.instance_group}') if args.listen_on: sys.argv.append(f'--listen-on={args.listen_on}') 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) real_main(called_from_panel=True) if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd: dict = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = panel_kitten_options_spec cd['help_text'] = help_text cd['short_desc'] = help_text ================================================ FILE: kittens/query_terminal/__init__.py ================================================ ================================================ FILE: kittens/query_terminal/main.go ================================================ package query_terminal import ( "bytes" "fmt" "github.com/kovidgoyal/kitty" "os" "slices" "strings" "time" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/query_terminal/main.py ================================================ #!/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 | memoryview) -> 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' ================================================ FILE: kittens/quick_access_terminal/__init__.py ================================================ ================================================ FILE: kittens/quick_access_terminal/main.go ================================================ package quick_access_terminal import ( "fmt" "os" "path/filepath" "runtime" "github.com/kovidgoyal/kitty/kittens/panel" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/utils" "golang.org/x/sys/unix" ) var _ = fmt.Print var complete_kitty_listen_on = panel.CompleteKittyListenOn func load_config(opts *Options) (ans *Config, err error) { ans = NewConfig() p := config.ConfigParser{LineHandler: ans.Parse} err = p.LoadConfig("quick-access-terminal.conf", opts.Config, opts.Override) if err != nil { return nil, err } return ans, nil } func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { conf, err := load_config(opts) if err != nil { return 1, err } kitty_exe, err := panel.GetQuickAccessKittyExe() if err != nil { return 1, err } argv := []string{kitty_exe, "+kitten", "panel", "--toggle-visibility", "--exclusive-zone=0", "--override-exclusive-zone", "--layer=overlay", "--single-instance", "--move-to-active-monitor"} argv = append(argv, fmt.Sprintf("--lines=%s", conf.Lines)) argv = append(argv, fmt.Sprintf("--columns=%s", conf.Columns)) argv = append(argv, fmt.Sprintf("--edge=%s", conf.Edge)) if conf.Margin_top != 0 { argv = append(argv, fmt.Sprintf("--margin-top=%d", conf.Margin_top)) } if conf.Margin_bottom != 0 { argv = append(argv, fmt.Sprintf("--margin-bottom=%d", conf.Margin_bottom)) } if conf.Margin_left != 0 { argv = append(argv, fmt.Sprintf("--margin-left=%d", conf.Margin_left)) } if conf.Margin_right != 0 { argv = append(argv, fmt.Sprintf("--margin-right=%d", conf.Margin_right)) } if len(conf.Kitty_conf) > 0 { cdir := utils.ConfigDir() for _, c := range conf.Kitty_conf { if !filepath.IsAbs(c) { c = filepath.Join(cdir, c) } argv = append(argv, fmt.Sprintf("--config=%s", c)) } } if len(conf.Kitty_override) > 0 { for _, c := range conf.Kitty_override { argv = append(argv, fmt.Sprintf("--override=%s", c)) } } argv = append(argv, fmt.Sprintf("--override=background_opacity=%f", conf.Background_opacity)) if runtime.GOOS != "darwin" { argv = append(argv, fmt.Sprintf("--app-id=%s", conf.App_id)) } if conf.Output_name != "" { argv = append(argv, fmt.Sprintf("--output-name=%s", conf.Output_name)) } argv = append(argv, fmt.Sprintf("--focus-policy=%s", conf.Focus_policy)) if conf.Start_as_hidden { argv = append(argv, `--start-as-hidden`) } if conf.Grab_keyboard { argv = append(argv, `--grab-keyboard`) } if conf.Hide_on_focus_loss { argv = append(argv, `--hide-on-focus-loss`) } if opts.DebugRendering { argv = append(argv, `--debug-rendering`) } if opts.DebugInput { argv = append(argv, `--debug-input`) } if opts.Detach { argv = append(argv, `--detach`) } if opts.DetachedLog != "" { if dl, err := filepath.Abs(opts.DetachedLog); err != nil { return 1, err } else { argv = append(argv, dl) } } if opts.InstanceGroup != "" { argv = append(argv, fmt.Sprintf("--instance-group=%s", opts.InstanceGroup)) } argv = append(argv, args...) err = unix.Exec(kitty_exe, argv, os.Environ()) rc = 1 return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } ================================================ FILE: kittens/quick_access_terminal/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal import re import sys from kitty.conf.types import Definition from kitty.constants import appname from kitty.simple_cli_definitions import CONFIG_HELP, get_option_maps, grab_keyboard_docs, panel_options_spec, parse_option_spec help_text = 'A quick access terminal window that you can bring up instantly with a keypress or a command.' definition = Definition( '!kittens.quick_access_terminal', ) agr = definition.add_group egr = definition.end_group opt = definition.add_option panel_opts = get_option_maps(parse_option_spec(panel_options_spec())[0])[0] def migrate_help(x: str) -> str: def sub(m: re.Match[str]) -> str: return f':opt:`{m.group(1)}`' ans = re.sub(r':option:`--(\S+?)`', sub, x) return ans.replace('Use the special value :code:`list`', 'Run :code:`kitten panel --output-name list`') def help_of(x: str) -> str: return migrate_help(panel_opts[x].help) agr('qat', 'Window appearance') opt('lines', '25', long_text=panel_opts['lines'].help) opt('columns', '80', long_text=panel_opts['columns'].help) opt('edge', 'top', choices=panel_opts['edge'].choices, long_text=help_of('edge')) opt('background_opacity', '0.85', option_type='unit_float', long_text=''' The background opacity of the window. This works the same as the kitty option of the same name, it is present here as it has a different default value for the quick access terminal. ''') opt('hide_on_focus_loss', 'no', option_type='to_bool', long_text=''' Hide the window when it loses keyboard focus automatically. Using this option will force :opt:`focus_policy` to :code:`on-demand`. ''') opt('grab_keyboard', 'no', option_type='to_bool', long_text=grab_keyboard_docs) opt('margin_left', '0', option_type='int', long_text=help_of('margin_left')) opt('margin_right', '0', option_type='int', long_text=help_of('margin_right')) opt('margin_top', '0', option_type='int', long_text=help_of('margin_top')) opt('margin_bottom', '0', option_type='int', long_text=help_of('margin_bottom')) opt('+kitty_conf', '', long_text='Path to config file to use for kitty when drawing the window. Can be specified multiple times. By default, the' ' normal kitty.conf is used. Relative paths are resolved with respect to the kitty config directory.' ) opt('+kitty_override', '', long_text='Override individual kitty configuration options, can be specified multiple times.' ' Syntax: :italic:`kitty_override name=value`. For example: :code:`kitty_override font_size=20`.' ) opt('app_id', f'{appname}-quick-access', long_text='On Wayland set the :italic:`namespace` of the layer shell surface.' ' On X11 set the WM_CLASS assigned to the quick access window. (Linux only)') opt('output_name', '', long_text=help_of('output_name')) opt('start_as_hidden', 'no', option_type='to_bool', long_text='Whether to start the quick access terminal hidden. Useful if you are starting it as part of system startup.') opt('focus_policy', 'exclusive', choices=panel_opts['focus_policy'].choices, long_text=help_of('focus_policy')) def options_spec() -> str: return f''' --config -c type=list completion=type:file ext:conf group:"Config files" kwds:none,NONE {CONFIG_HELP.format(conf_name='quick-access-terminal', appname=appname)} --override -o type=list Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :italic:`-o lines=12` --detach type=bool-set Detach from the controlling terminal, if any, running in an independent child process, the parent process exits immediately. --detached-log Path to a log file to store STDOUT/STDERR when using :option:`--detach` --instance-group default=quick-access The unique name of this quick access terminal Use a different name if you want multiple such terminals. --debug-rendering type=bool-set For debugging interactions with the compositor/window manager. --debug-input type=bool-set For debugging interactions with the compositor/window manager. ''' def main(args: list[str]) -> None: raise SystemExit('This kitten should be run as: kitten quick-access-terminal') if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd: dict = sys.cli_docs # type: ignore cd['usage'] = '[cmdline-to-run ...]' cd['options'] = options_spec cd['help_text'] = help_text cd['short_desc'] = help_text elif __name__ == '__conf__': sys.options_definition = definition # type: ignore ================================================ FILE: kittens/remote_file/__init__.py ================================================ ================================================ FILE: kittens/remote_file/main.py ================================================ #!/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_compat 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) ================================================ FILE: kittens/resize_window/__init__.py ================================================ ================================================ FILE: kittens/resize_window/main.py ================================================ #!/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) ================================================ FILE: kittens/runner.py ================================================ #!/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_compat 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 m = import_kitten_main_module(config_dir, kitten) kitten = resolved_kitten(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] original_kitten_name = kitten 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, original_kitten_name) 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 kitten = original_kitten_name # 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): path = path_to_custom_kitten(config_dir, resolved_kitten(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') ================================================ FILE: kittens/show_key/__init__.py ================================================ ================================================ FILE: kittens/show_key/kitty.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "fmt" "strings" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/show_key/legacy.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "errors" "fmt" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/kitty/tools/tty" "io" "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 } ================================================ FILE: kittens/show_key/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "fmt" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/show_key/main.py ================================================ #!/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 run 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 ================================================ FILE: kittens/ssh/__init__.py ================================================ ================================================ FILE: kittens/ssh/askpass.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "crypto/hmac" "crypto/sha1" "encoding/base32" "encoding/binary" "encoding/json" "fmt" "os" "strings" "time" "github.com/kovidgoyal/go-shm" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" ) var _ = fmt.Print func fatal(err error) int { cli.ShowError(err) return 1 } func trigger_ask(name string) int { term, err := tty.OpenControllingTerm() if err != nil { return fatal(err) } defer term.Close() _, err = term.WriteString("\x1bP@kitty-ask|" + name + "\x1b\\") if err != nil { return fatal(err) } return 0 } func isPasswordPrompt(msg string) bool { q := strings.ToLower(msg) if strings.Contains(q, "passphrase") { return false } return strings.Contains(q, "password") } func isOTPPrompt(msg string) bool { q := strings.ToLower(msg) if strings.Contains(q, "passphrase") { return false } if strings.Contains(q, "verification code") || strings.Contains(q, "one-time password") || strings.Contains(q, "one time password") || strings.Contains(q, "authenticator code") || strings.Contains(q, "authentication code") || strings.Contains(q, "two-factor") || strings.Contains(q, "2fa") || strings.Contains(q, "otp") || strings.Contains(q, "passcode") { return true } return false } func generateTOTP(secret string, digits, period int64, t time.Time) (string, error) { s := strings.ToUpper(strings.TrimSpace(secret)) s = strings.ReplaceAll(s, " ", "") key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(s) if err != nil { return "", fmt.Errorf("invalid TOTP secret: %w", err) } counter := uint64(t.Unix() / period) var buf [8]byte binary.BigEndian.PutUint64(buf[:], counter) mac := hmac.New(sha1.New, key) _, _ = mac.Write(buf[:]) sum := mac.Sum(nil) off := sum[len(sum)-1] & 0x0f code := (uint32(sum[off])&0x7f)<<24 | (uint32(sum[off+1])&0xff)<<16 | (uint32(sum[off+2])&0xff)<<8 | (uint32(sum[off+3]) & 0xff) mod := uint32(1) for range digits { mod *= 10 } val := code % mod fmtstr := fmt.Sprintf("%%0%dd", digits) return fmt.Sprintf(fmtstr, val), nil } func RunSSHAskpass() int { 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])") // Auto-fill from ssh.conf if configured if !is_confirm && !is_fingerprint_check { host := os.Getenv("KITTY_SSH_ASKPASS_HOST") user := os.Getenv("KITTY_SSH_ASKPASS_USER") if host != "" { var overrides []string _ = json.Unmarshal([]byte(os.Getenv("KITTY_SSH_ASKPASS_OVERRIDES")), &overrides) if cfg, _, err := load_config(host, user, overrides); err == nil && cfg != nil { if err = resolve_secrets(cfg, false); err != nil { return fatal(err) } // Password autofill if isPasswordPrompt(msg) && cfg.Password != "" { fmt.Println(cfg.Password) return 0 } // OTP autofill if isOTPPrompt(msg) && cfg.Totp_secret != "" { code, err := generateTOTP(cfg.Totp_secret, int64(cfg.Totp_digits), int64(cfg.Totp_period), time.Now()) if err == nil { fmt.Println(code) return 0 } } } } } q := map[string]any{ "message": msg, "type": q_type, "is_password": !is_fingerprint_check, } data, err := json.Marshal(q) if err != nil { return fatal(err) } data_shm, err := shm.CreateTemp("askpass-*", uint64(len(data)+32)) if err != nil { return 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 { return fatal(fmt.Errorf("Failed to write to SHM file with error: %w", err)) } if err = data_shm.Flush(); err != nil { return 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 { return 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 { return 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 { return fatal(fmt.Errorf("Failed to parse response data: %#v with error: %w", string(data), err)) } if is_fingerprint_check { response = strings.ToLower(response) switch response { case "y": response = "yes" case "n": response = "no" } } } if response != "" { fmt.Println(response) } return 0 } ================================================ FILE: kittens/ssh/config.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "archive/tar" "encoding/json" "errors" "fmt" "os" "path" "path/filepath" "strings" "time" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/paths" "github.com/kovidgoyal/kitty/tools/utils/shlex" "github.com/bmatcuk/doublestar/v4" "golang.org/x/sys/unix" ) var _ = fmt.Print func resolve_secret(key, val string) (string, error) { v := strings.TrimSpace(val) if v == "" { return "", nil } if b, s, ok := strings.Cut(v, ":"); ok { b = strings.ToLower(strings.TrimSpace(b)) s = strings.TrimSpace(s) switch b { case "text": return s, nil default: return "", fmt.Errorf("Unsupported secret backend %s for %s. Supported backends: text", b, key) } } return "", fmt.Errorf("No secret backend specified for: %s", key) } func resolve_secrets(c *Config, only_syntax bool) error { _ = only_syntax // this will be useful when using backends that require user interaction if r, err := resolve_secret("password", c.Password); err != nil { return err } else { c.Password = r } if r, err := resolve_secret("totp_secret", c.Totp_secret); err != nil { return err } else { c.Totp_secret = r } return nil } 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.SplitSeq(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 } ================================================ FILE: kittens/ssh/config_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "fmt" "github.com/kovidgoyal/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 --exclude second --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", "second"}, 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) } } } ================================================ FILE: kittens/ssh/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "archive/tar" "bytes" "compress/gzip" "encoding/base64" "encoding/json" "errors" "fmt" "github.com/kovidgoyal/kitty" "io" "io/fs" "maps" "net/url" "os" "os/exec" "os/signal" "os/user" "path" "path/filepath" "regexp" "slices" "strconv" "strings" "syscall" "time" "github.com/kovidgoyal/go-shm" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/themes" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/shell_integration" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/secrets" "github.com/kovidgoyal/kitty/tools/utils/shlex" "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(hostname_for_match, uname string, overrides []string) (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") // Provide data to askpass so it can lookup auth settings in ssh.conf os.Setenv("KITTY_SSH_ASKPASS_HOST", hostname_for_match) os.Setenv("KITTY_SSH_ASKPASS_USER", uname) ov, _ := json.Marshal(overrides) os.Setenv("KITTY_SSH_ASKPASS_OVERRIDES", string(ov)) 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 } // check the secrets syntax here as askpass has no good way to report errors to // the user if err = resolve_secrets(host_opts, true); 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(hostname_for_match, uname, overrides) } 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.PUSH_KEY_FLAGS + loop.HANDLE_TERMIOS_SIGNALS.EscapeCodeToSet()) } if err != nil { return 1, err } restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES + loop.POP_KEY_FLAGS + 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) }, }) } ================================================ FILE: kittens/ssh/main.py ================================================ #!/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() # }}} agr('askpass', 'Askpass automation') # {{{ opt('password', '', long_text=''' Specify a password to use when SSH prompts for a password. The value format is "backend:secret". Currently, only the "text" backend is supported, which stores the secret in plain text in the config file. For example: password text:my_password If the backend prefix is omitted, it is treated as "text:" for backward compatibility. Beware that storing passwords in plain text is insecure. ''') opt('totp_secret', '', long_text=''' Specify a TOTP shared secret to auto-fill one-time codes when SSH asks for them. The value format is "backend:secret". Currently, only the "text" backend is supported. For example: totp_secret text:JBSWY3DPEHPK3PXP If the backend prefix is omitted, it is treated as "text:" for backward compatibility. ''') opt('totp_digits', '6', option_type='int', long_text=''' Number of digits for the generated TOTP codes. Default is 6. ''') opt('totp_period', '30', option_type='int', long_text=''' Time period in seconds for the TOTP code validity. Default is 30. ''') 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()}) ================================================ FILE: kittens/ssh/main_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "encoding/binary" "encoding/json" "fmt" "github.com/kovidgoyal/go-shm" "github.com/kovidgoyal/kitty" "io/fs" "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") } } ================================================ FILE: kittens/ssh/utils.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "bytes" "fmt" "os/exec" "regexp" "strconv" "strings" "sync" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/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() }) ================================================ FILE: kittens/ssh/utils.py ================================================ #!/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 remove_env_var_from_cmdline(key: str, argv: list[str]) -> None: while True: for i, arg in enumerate(tuple(argv)): if arg.startswith(f'--kitten=env={key}='): del argv[i] break elif i > 0 and argv[i-1] == '--kitten' and (arg.startswith(f'env={key}=') or arg.startswith(f'env {key}=')): del argv[i-1:i+1] break else: break def set_single_env_var_in_cmdline(key: str, val: str, argv: list[str]) -> None: remove_env_var_from_cmdline(key, argv) idx = argv.index('ssh') argv.insert(idx+1, f'--kitten=env={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|memoryview]: 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') - 1 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)) ================================================ FILE: kittens/ssh/utils_test.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "fmt" "os" "os/exec" "path/filepath" "testing" "github.com/kovidgoyal/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) } } ================================================ FILE: kittens/themes/__init__.py ================================================ ================================================ FILE: kittens/themes/list.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "fmt" "github.com/kovidgoyal/kitty/tools/themes" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/themes/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "encoding/json" "fmt" "io" "os" "path/filepath" "strings" "time" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/themes" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/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 }, }) } ================================================ FILE: kittens/themes/main.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import sys from kitty.simple_cli_definitions 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') ================================================ FILE: kittens/themes/ui.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "fmt" "github.com/kovidgoyal/kitty" "io" "maps" "regexp" "slices" "strings" "time" "github.com/kovidgoyal/kitty/tools/config" "github.com/kovidgoyal/kitty/tools/themes" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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 underline=true", 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 } // }}} ================================================ FILE: kittens/transfer/__init__.py ================================================ ================================================ FILE: kittens/transfer/algorithm.c ================================================ //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); } // }}} ================================================ FILE: kittens/transfer/ftc.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "encoding/base64" "encoding/json" "fmt" "io/fs" "reflect" "regexp" "strconv" "strings" "sync" "time" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/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}) } } ================================================ FILE: kittens/transfer/ftc_test.go ================================================ // 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.SplitSeq(actual, ";") { ad[x] = true } ed := make(map[string]bool) for x := range strings.SplitSeq(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)) } } ================================================ FILE: kittens/transfer/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "fmt" "io" "os" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/transfer/main.py ================================================ #!/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.simple_cli_definitions 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"') ================================================ FILE: kittens/transfer/receive.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "bytes" "compress/zlib" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "slices" "strconv" "strings" "time" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/kittens/unicode_input" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/kitty/tools/rsync" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/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.progress_drawn = false 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 } var debugprintln = tty.DebugPrintln 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) } ================================================ FILE: kittens/transfer/rsync.pyi ================================================ from typing import Callable, Union from kitty.typing_compat 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: ... ================================================ FILE: kittens/transfer/send.go ================================================ // 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" "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/cli/markup" "github.com/kovidgoyal/kitty/tools/rsync" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/humanize" "github.com/kovidgoyal/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 } ================================================ FILE: kittens/transfer/send_test.go ================================================ // 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.SplitSeq(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) }) } ================================================ FILE: kittens/transfer/utils.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "crypto/rand" "encoding/hex" "fmt" "os" "path/filepath" "strings" "github.com/kovidgoyal/kitty/tools/crypto" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/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) } ================================================ FILE: kittens/transfer/utils.py ================================================ #!/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 | memoryview) -> bytes: return bytes(data) def flush(self) -> bytes: return b'' class ZlibCompressor: def __init__(self) -> None: import zlib self.c = zlib.compressobj() def compress(self, data: bytes | memoryview) -> bytes: return self.c.compress(data) def flush(self) -> bytes: return self.c.flush() ================================================ FILE: kittens/tui/__init__.py ================================================ ================================================ FILE: kittens/tui/dircolors.py ================================================ #!/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])) ================================================ FILE: kittens/tui/handler.py ================================================ #!/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_compat 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', '') if not self.to: raise ValueError('Remote control not enabled, this kitten should be run via a map in kitty.conf, not from the command line') 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 ================================================ FILE: kittens/tui/images.py ================================================ #!/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_compat import CompletedProcess, GRT_f, GRT_o, 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('t') q = quiet = Alias(0) f = format = Alias(32) t = transmission_type = Alias('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(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(0) d = delete_action = Alias('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 | memoryview | str = b'') -> bytes: items = [] for k, val in self._actual_values.items(): items.append(f'{k}={val}') ans: list[bytes|memoryview] = [] 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 ================================================ FILE: kittens/tui/line_edit.py ================================================ #!/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 ================================================ FILE: kittens/tui/loop.py ================================================ #!/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_compat 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: try: self.asyncio_loop = asyncio.get_event_loop() except RuntimeError: self.asyncio_loop = asyncio.new_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) ================================================ FILE: kittens/tui/operations.py ================================================ #!/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_compat 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_compat 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' # }}} ================================================ FILE: kittens/tui/operations_stub.py ================================================ #!/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__) ================================================ FILE: kittens/tui/path_completer.py ================================================ #!/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() ================================================ FILE: kittens/tui/progress.py ================================================ #!/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 ================================================ FILE: kittens/tui/spinners.py ================================================ #!/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_compat 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] ================================================ FILE: kittens/tui/utils.py ================================================ #!/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 '' ================================================ FILE: kittens/unicode_input/__init__.py ================================================ ================================================ FILE: kittens/unicode_input/main.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package unicode_input import ( "bytes" "errors" "fmt" "math" "os" "os/exec" "path/filepath" "slices" "strconv" "strings" "unicode" "unicode/utf8" "github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui" "github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/readline" "github.com/kovidgoyal/kitty/tools/unicode_names" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/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.ParseInt(code_text, 16, 32) if err == nil && code <= utf8.MaxRune && 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 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] if index_word, perr := strconv.ParseInt(strings.TrimLeft(iw, INDEX_CHAR), INDEX_BASE, 0); perr == nil { 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() uval, err := strconv.ParseUint(text, 16, 32) new_val := -1 if err != nil || uval > math.MaxInt { return } val := int(uval) if event.MatchesPressOrRepeat("tab") { new_val = val + 10 } else if event.MatchesPressOrRepeat("up") { new_val = val + 1 } else if event.MatchesPressOrRepeat("down") { new_val = max(32, 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) } ================================================ FILE: kittens/unicode_input/main.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal from kitty.typing_compat 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' ================================================ FILE: kittens/unicode_input/table.go ================================================ // License: GPLv3 Copyright: 2023, Kovid Goyal, package unicode_input import ( "fmt" "slices" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/unicode_names" "github.com/kovidgoyal/kitty/tools/utils" "github.com/kovidgoyal/kitty/tools/utils/style" "github.com/kovidgoyal/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.ParseInt(text, INDEX_BASE, 0) if err != nil || x < 0 { 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() } ================================================ FILE: kitty/__init__.py ================================================ ================================================ FILE: kitty/actions.py ================================================ #!/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', 'fs': 'Font sizes', 'mouse': 'Mouse actions', 'mk': 'Marks', 'lay': 'Layouts', 'misc': 'Miscellaneous', 'debug': 'Debugging', 'session': 'Sessions', } 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) ================================================ FILE: kitty/alpha_blend.glsl ================================================ vec4 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); } ================================================ FILE: kitty/animation.c ================================================ /* * 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; } ================================================ FILE: kitty/animation.h ================================================ /* * 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); ================================================ FILE: kitty/arches.h ================================================ /* * 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 ================================================ FILE: kitty/arena.h ================================================ /* * 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_ ================================================ FILE: kitty/backtrace.h ================================================ /* * 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 ================================================ FILE: kitty/banned.h ================================================ /* * 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) ================================================ FILE: kitty/base64.h ================================================ /* * 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; } ================================================ FILE: kitty/bash.py ================================================ #!/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 ================================================ FILE: kitty/bgimage_fragment.glsl ================================================ #pragma kitty_include_shader uniform sampler2D image; uniform vec4 background; in vec2 texcoord; out vec4 premult_color; void main() { vec4 color = texture(image, texcoord); premult_color = alpha_blend(color, background); } ================================================ FILE: kitty/bgimage_vertex.glsl ================================================ #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 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) { #define window i #define image i + 2 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]; #define x_axis 0 #define y_axis 1 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); } ================================================ FILE: kitty/binary.h ================================================ /* * 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)); } ================================================ FILE: kitty/blit_common.glsl ================================================ out vec2 texcoord; #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); } ================================================ FILE: kitty/blit_fragment.glsl ================================================ #pragma kitty_include_shader #pragma kitty_include_shader #pragma kitty_include_shader uniform sampler2D image; in vec2 texcoord; out vec4 output_color; void main() { vec4 color_premul = texture(image, texcoord); output_color = vec4_premul(linear2srgb(color_premul.rgb / color_premul.a), color_premul.a); } ================================================ FILE: kitty/blit_vertex.glsl ================================================ uniform vec4 src_rect, dest_rect; #pragma kitty_include_shader ================================================ FILE: kitty/border_fragment.glsl ================================================ in vec4 color_premul; out vec4 output_premul; void main() { output_premul = color_premul; } ================================================ FILE: kitty/border_vertex.glsl ================================================ #pragma kitty_include_shader #define DEFAULT_BG 0 #define ACTIVE_BORDER_COLOR 1 #define INACTIVE_BORDER_COLOR 2 #define WINDOW_BACKGROUND_PLACEHOLDER 3 #define BELL_BORDER_COLOR 4 #define TAB_BAR_BG_COLOR 5 #define TAB_BAR_MARGIN_COLOR 6 #define TAB_BAR_EDGE_LEFT_COLOR 7 #define TAB_BAR_EDGE_RIGHT_COLOR 8 uniform uint colors[9]; uniform float background_opacity; uniform float gamma_lut[256]; in vec4 rect; // left, top, right, bottom in uint rect_color; out vec4 color_premul; // 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, int x) { return 1. - step(0.5, abs(float(c) - float(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, WINDOW_BACKGROUND_PLACEHOLDER); // used by window padding areas float is_default_bg = is_integer_value(rc, DEFAULT_BG); color3 = if_one_then(is_window_bg, window_bg, color3); // Actual border quads must be always drawn opaque float is_not_a_border = zero_or_one(abs( (float(rc) - ACTIVE_BORDER_COLOR) * (float(rc) - INACTIVE_BORDER_COLOR) * (float(rc) - BELL_BORDER_COLOR) )); float final_opacity = if_one_then(is_not_a_border, background_opacity, 1.); color_premul = vec4_premul(color3, final_opacity); } ================================================ FILE: kitty/borders.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal from collections.abc import Iterable from enum import IntFlag from functools import partial from typing import NamedTuple from .fast_data_types import BORDERS_PROGRAM, current_focused_os_window_id, get_options, init_borders_program, set_borders_rects from .shaders import program_for from .typing_compat 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 border_type: int = 0 horizontal: bool = False def vertical_edge(rects: list[Border], color: BorderColor, width: int, top: int, bottom: int, left: int, border_type: int) -> None: if width > 0: rects.append(Border(left, top, left + width, bottom, color, border_type, False)) def horizontal_edge(rects: list[Border], color: BorderColor, height: int, left: int, right: int, top: int, border_type: int) -> None: if height > 0: rects.append(Border(left, top, right, top + height, color, border_type, True)) def add_borders(rects: list[Border], color: BorderColor, wg: WindowGroup) -> 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 h = partial(horizontal_edge, rects, color) v = partial(vertical_edge, rects, color) width = wg.effective_border() bt = bottom lr = right left -= width top -= width right += width bottom += width pl = pr = pb = pt = width wid = wg.active_window_id h(pt, left, right, top, -wid) h(pb, left, right, bt, wid) v(pl, top, bottom, left, -wid) v(pr, top, bottom, lr, wid) 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 rects: list[Border] = [] for br in current_layout.blank_rects: rects.append(Border(*br, BorderColor.default_bg)) rects.extend(tab_bar_rects) 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 # Count visible windows num_visible_groups = len(groups) # When draw_window_borders_for_single_window is set and there's only 1 window, # behave like draw_minimal_borders is False (draw full borders around the window) if opts.draw_window_borders_for_single_window and num_visible_groups == 1: draw_minimal_borders = False else: draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1 # For single window with the option enabled, check OS window focus state # When unfocused, the border should appear inactive os_window_focused = True if opts.draw_window_borders_for_single_window and num_visible_groups == 1: os_window_focused = current_focused_os_window_id() == self.os_window_id if draw_borders and not draw_minimal_borders: for i, wg in enumerate(groups): window_bg = color_as_int(wg.default_bg) window_bg = (window_bg << 8) | BorderColor.window_bg # Draw the border rectangles if wg is active_group and draw_active_borders and os_window_focused: color = BorderColor.active else: color = BorderColor.bell if wg.needs_attention else BorderColor.inactive add_borders(rects, color, wg) if draw_minimal_borders: for border_line in current_layout.get_minimal_borders(all_windows): rects.append(Border(*border_line.edges, border_line.color, border_line.window_id, border_line.horizontal)) set_borders_rects(self.os_window_id, self.tab_id, rects) ================================================ FILE: kitty/boss.py ================================================ #!/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 math import floor from time import sleep from typing import ( TYPE_CHECKING, Any, Literal, Optional, Union, ) from weakref import WeakValueDictionary from kitty.types import WindowResizeDrag from .child import cached_process_data, default_env, set_default_env from .cli import create_opts, green, parse_args from .cli_stub import CLIOptions, SaveAsSessionOptions 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 ( BOTTOM_EDGE, CLOSE_BEING_CONFIRMED, GLFW_FKEY_ESCAPE, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, IMPERATIVE_CLOSE_REQUESTED, LEFT_EDGE, NO_CLOSE_REQUESTED, RIGHT_EDGE, TOP_EDGE, ChildMonitor, Color, EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update, background_opacity_of, change_background_opacity, change_drag_thumbnail, 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, get_tab_being_dragged, glfw_get_monitor_workarea, global_font_size, grab_keyboard, is_layer_shell_supported, last_focused_os_window_id, load_png_data, macos_cycle_through_os_windows, 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, set_tab_being_dragged, start_drag_with_data, thread_write, toggle_fullscreen, toggle_maximized, toggle_os_window_visibility, toggle_secure_input, viewport_for_window, 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, close_session_with_confirm, create_sessions, default_save_as_session_opts, get_os_window_sizing_data, goto_session, most_recent_session, save_as_session, ) from .shaders import load_shader_programs from .simple_cli_definitions import grab_keyboard_docs from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager from .types import _T, AsyncResponse, LayerShellConfig, SingleInstanceData, WindowSystemMouseEvent, ac from .typing_compat 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, platform_window_id, resolve_custom_file, safe_print, sanitize_url_for_display_to_user, shlex_split, startup_notification_handler, timed_debug_print, which, ) from .window import CommandOutput, CwdRequest, Window, global_watchers if TYPE_CHECKING: from .fast_data_types import OSWindowSize 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] active_tab_history: tuple[int, ...] 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] = [] self.dump_commands = args.dump_commands 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': if self.dump_commands: 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) elif self.dump_commands: 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), flush=True) # }}} 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.drag_resize_of_window = WindowResizeDrag() 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 getattr(startup_session, 'os_window_title', None) 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 add_os_panel(self, cfg: LayerShellConfig, wclass: str | None = appname, wname: str | None = appname) -> int: if not is_layer_shell_supported(): raise RuntimeError('Creating desktop panels is not supported on this platform') wclass = wclass or appname wname = wname or appname size_data = get_os_window_sizing_data(get_options(), None) os_window_id = create_os_window( initial_window_size_func(size_data, {}), lambda *a: None, appname, wname, wclass, None, layer_shell_config=cfg) tm = TabManager(os_window_id, self.args, wclass, wname, None) 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, 'active_tab_history': tuple(tm.active_tab_history), 'wm_class': tm.wm_class, 'wm_name': tm.wm_name, 'background_opacity': bo, } def serialize_state_as_session(self, session_path: str = '', ser_opts: SaveAsSessionOptions | None = None) -> Iterator[str]: if ser_opts is None: ser_opts = default_save_as_session_opts() matched_windows = frozenset(self.match_windows(ser_opts.match)) if ser_opts.match else None s = {current_focused_os_window_id(): 2, last_focused_os_window_id(): 1} for i, os_window_id in enumerate(sorted(self.os_window_map, key=lambda wid: s.get(wid, 0))): tm = self.os_window_map[os_window_id] yield from tm.serialize_state_as_session(session_path, matched_windows, is_first=i==0, ser_opts=ser_opts) @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, all_windows: Iterable[Window] | None = None) -> Iterator[Window]: all_windows = self.all_windows if all_windows is None else all_windows if match == 'all': yield from 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 wids = {w.id for w in all_windows} window_id_limit = max(wids, default=-1) + 1 active_session = self.active_session prev_active_session = most_recent_session() 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, active_session, prev_active_session)} for wid in search(match, ( 'id', 'title', 'pid', 'cwd', 'cmdline', 'num', 'env', 'var', 'recent', 'state', 'neighbor', 'session', ), wids, get_matches): yield self.window_id_map[wid] def match_tabs(self, match: str, all_tabs: Iterable[Tab] | None = None) -> Iterator[Tab]: all_tabs = self.all_tabs if all_tabs is None else all_tabs if match == 'all': yield from 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 all_tabs} tab_id_limit = max(tim, default=-1) + 1 window_id_limit = max(self.window_id_map, default=-1) + 1 active_session = self.active_session prev_active_session = most_recent_session() 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, active_session, prev_active_session)} found = False for tid in search(match, ( 'id', 'index', 'title', 'window_id', 'window_title', 'pid', 'cwd', 'env', 'var', 'cmdline', 'recent', 'state', 'session', ), set(tim), get_matches): found = True yield tim[tid] if not found: tabs = {w.tabref() for w in self.match_windows(match)} for q in tabs: if q: yield q def focus_os_window(self, os_window_id: int, if_needed_only: bool = True) -> bool: if if_needed_only and current_focused_os_window_id() == os_window_id: return False def doit(token: str = '') -> None: focus_os_window(os_window_id, True, token) if is_wayland(): if not run_with_activation_token(doit): doit() else: doit() return True 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 switch_os_window_if_needed and current_focused_os_window_id() != os_window_id: if activation_token or not is_wayland(): focus_os_window(os_window_id, True, activation_token) else: def doit(token: str = '') -> None: focus_os_window(os_window_id, True, token) if not run_with_activation_token(doit): doit() 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)) startup_session.session_name = '' ans = self.add_os_window(startup_session) if cwd_from is not None and (sow := cwd_from.window) and (tm := self.os_window_map.get(ans)): session_name = sow.created_in_session_name if not session_name and (sow_tab := sow.tabref()): session_name = sow_tab.created_in_session_name if session_name: for tab in tm: tab.created_in_session_name = session_name for w in tab: w.created_in_session_name = session_name return ans @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. The new OS Window is added to the currently active :ref:`session `, if any. ''') 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 isinstance(response, AsyncResponse): send_data_to_peer(peer_id, b'', True) else: 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. Relative paths are resolved with respect to the kitty config directory. ''') def remote_control_script(self, path: str, *args: str) -> None: path = resolve_custom_file(path) 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': if data['args'][0] == 'panel': from kittens.panel.main import handle_single_instance_command handle_single_instance_command(self, data['args'], data['environ'], data.get('notify_on_os_window_death', '')) return None from .cli_stub import CLIOptions startup_id = data['environ'].get('DESKTOP_STARTUP_ID', '') activation_token = data['environ'].get('XDG_ACTIVATION_TOKEN', '') try: args, rest = parse_args(list(data['args'][1:]), result_class=CLIOptions) except BaseException as e: self.show_error(_('Invalid single instance command received'), _('The command: {0} is invalid with error: {1}').format( data['args'], e)) return None 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'], data['session_arg'], data['session_path']) else: args.session = '' if not os.path.isabs(args.directory): args.directory = os.path.join(data['cwd'], args.directory) from .child import process_env clean_env = process_env(data['environ']) focused_os_window = os_window_id = 0 for session in create_sessions(opts, args, respect_cwd=True, env_when_no_session=clean_env): if not session.has_non_background_processes: # background only do not create an 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 wstate = args.start_as if args.start_as and args.start_as != 'normal' else None 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, window_state=wstate) 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 quick_access_terminal_invoked(self) -> None: for os_window_id in self.os_window_map: toggle_os_window_visibility(os_window_id, move_to_active_screen=True) 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: 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 = 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 = '', window_title: str = '', ) -> Window | 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) if window_title: cmd.append(f'--title={window_title}') w = self.run_kitten_with_metadata( 'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal ) return w if isinstance(w, Window) else None def get_save_filepath( 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 = '> ', 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', 'file', '--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) def close_windows_no_confirm(self, windows: Sequence[Window]) -> None: if self.current_visual_select is not None: self.cancel_current_visual_select() for window in windows: 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', 'Cycle through OS windows on macOS') def macos_cycle_through_os_windows(self) -> None: macos_cycle_through_os_windows(False) @ac('misc', 'Cycle through OS windows backwards on macOS') def macos_cycle_through_os_windows_backwards(self) -> None: macos_cycle_through_os_windows(True) @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_window_title_bar_mouse(self, os_window_id: int, window_id: int, button: int, modifiers: int, action: int) -> None: if tm := self.os_window_map.get(os_window_id): tm.handle_window_title_bar_mouse(window_id, button, modifiers, action) def handle_tab_bar_mouse(self, os_window_id: int, x: float, y: float, button: int, modifiers: int, action: int) -> None: if tm := self.os_window_map.get(os_window_id): tm.handle_tab_bar_mouse(x, y, button, modifiers, action) def start_tab_drag(self, os_window_id: int, window_id: int, pixels: bytes, width: int, height: int) -> None: if tm := self.os_window_map.get(os_window_id): tm.start_tab_drag(pixels, width, height) 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 # Erase the last command and its output (needs shell integration to work) map f1 clear_terminal last_command 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) elif action == 'last_command': for w in windows: w.screen.erase_last_command() 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('fs', ''' 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: match increment_operation: case '+': new_size += amt case '-': new_size -= amt case '*': new_size *= amt case '/': new_size /= amt case _: pass # no-op else: new_size = amt new_size = max(MINIMUM_FONT_SIZE, min(new_size, get_options().font_size * 10)) 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 @property def active_session(self) -> str: if t := self.active_tab: if w := t.active_window: return w.created_in_session_name or t.created_in_session_name return t.created_in_session_name return '' @property def all_loaded_session_names(self) -> Iterator[str]: seen = set() for w in self.all_windows: if w.created_in_session_name and w.created_in_session_name not in seen: seen.add(w.created_in_session_name) yield w.created_in_session_name 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 self.mappings.pop_keyboard_mode_if_is('__visual_select__') 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: self.focus_os_window(tab.os_window_id, False) 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' km.keymap[SingleKey(key=GLFW_FKEY_ESCAPE)].append(KeyDefinition(definition='visual_window_select_action_trigger 0')) 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) self.ring_bell_if_allowed(tab.os_window_id) def ring_bell_if_allowed(self, os_window_id: int = 0) -> bool: if get_options().enable_audio_bell: ring_bell(os_window_id or getattr(self.active_tab_manager, 'os_window_id', 0)) return True return False 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) self.ring_bell_if_allowed(tab.os_window_id) 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, metrics: 'None | OSWindowSize' = None) -> None: if not incremental and (width < 0 or height < 0): return metrics = get_os_window_size(os_window_id) if metrics is None else metrics if metrics is None: return if metrics['is_layer_shell']: raise TypeError(f'The OS Window {os_window_id} is a panel and cannot be resized') 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() # Redraw borders when focus changes if draw_window_borders_for_single_window is enabled # and there's only a single window (to show inactive border when OS window loses focus) opts = get_options() if opts.draw_window_borders_for_single_window and (tab := tm.active_tab) is not None and not tab.windows.has_more_than_one_visible_group: tab.relayout_borders() 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_move(self, os_window_id: int, x: int, y: int, from_self: bool, is_leave: bool) -> None: if (tm := self.os_window_map.get(os_window_id)) is None: return if from_self: tab_id, drag_started = get_tab_being_dragged()[:2] if tab_id and drag_started and (tab := self.tab_for_id(tab_id)): central, tab_bar = viewport_for_window(os_window_id)[:2] in_tab_bar = tab_bar.left <= x < tab_bar.right and tab_bar.top <= y < tab_bar.bottom detach = not in_tab_bar or tab.os_window_id != tm.os_window_id or is_leave change_drag_thumbnail(tab.os_window_id, 1 if detach else 0) for q in self.all_tab_managers: is_dest = q is tm and (in_tab_bar or os_window_id != tab.os_window_id) and not is_leave q.on_tab_drop_move(tab_id, is_dest, x, y) def on_drop(self, os_window_id: int, drop: dict[str, bytes] | int, from_self: bool, x: int, y: int) -> None: if isinstance(drop, int): import errno code = errno.errorcode.get(drop, str(drop)) msg = 'Unknown error' with suppress(ValueError): msg = os.strerror(drop) self.show_error(_('Drop failed'), f'[{code}] {msg}') return if (tm := self.os_window_map.get(os_window_id)) is None: return if (tidb := drop.get(f'application/net.kovidgoyal.kitty-tab-{os.getpid()}')) and (tab := self.tab_for_id(int(tidb))): central, tab_bar = viewport_for_window(os_window_id)[:2] in_tab_bar = tab_bar.left <= x < tab_bar.right and tab_bar.top <= y < tab_bar.bottom if in_tab_bar or tab.os_window_id != tm.os_window_id: tm.on_tab_drop(x, y) else: self._move_tab_to(tab) set_tab_being_dragged() for tm in self.all_tab_managers: tm.on_tab_drop_move() tm.layout_tab_bar() # ensure tab bar is fully updated return central, tab_bar = viewport_for_window(os_window_id)[:2] if central.left <= x < central.right and central.top <= y < central.bottom: x -= central.left y -= central.top if tab := tm.active_tab: for window in tab: if window.is_visible_in_layout: g = window.geometry if g.left <= x < g.right and g.top <= y < g.bottom: window.on_drop(drop) break elif tab_bar.left <= x < tab_bar.right and tab_bar.top <= y < tab_bar.bottom: if (tab_id := tm.tab_bar.tab_id_at(x)) and (tab := self.tab_for_id(tab_id)) and (w := tab.active_window): w.on_drop(drop) def on_drag_source_finished( self, was_dropped: bool, was_canceled: bool, accepted_mime_type: str, action: int, data: dict[str, bytes] | None, needs_toplevel_on_wayland: bool ) -> None: if (tab_id := int((data or {}).get(f'application/net.kovidgoyal.kitty-tab-{os.getpid()}', b'0').decode()) ) and get_tab_being_dragged()[0] == tab_id and (tab := self.tab_for_id(tab_id)): if needs_toplevel_on_wayland: for tm in self.all_tab_managers: if tm.tab_being_dropped: tm.on_tab_drop(0, 0, bypass_move=True) return set_tab_being_dragged() for tm in self.all_tab_managers: tm.on_tab_drop_move() if was_dropped: # detach tab into new OS Window self._move_tab_to(tab) @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() self.focus_os_window(os_window_id) elif num > 0: ids = tuple(self.os_window_map.keys()) os_window_id = ids[min(num, len(ids)) - 1] self.focus_os_window(os_window_id) 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] self.focus_os_window(os_window_id) @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 if self.current_visual_select is not None and self.current_visual_select.os_window_id == os_window_id: self.cancel_current_visual_select() 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 current_confirmation_window: Window | None = None if tm.confirm_close_window_id: for tab in tm: for w in tab: if w.id == tm.confirm_close_window_id: current_confirmation_window = w break if current_confirmation_window is not None: break if current_confirmation_window: self.set_active_window(current_confirmation_window, switch_os_window_if_needed=True) 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, x: int, y: int, viewport_width: int, viewport_height: int, is_layer_shell: bool) -> None: tm = self.os_window_map.pop(os_window_id, None) opts = get_options() if not is_layer_shell: if opts.remember_window_position and not is_wayland() and not self.os_window_map: self.cached_values['window-pos'] = x, y self.cached_values['monitor-workarea'] = glfw_get_monitor_workarea() self.cached_values['window-size'] = viewport_width, viewport_height 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 def _call_on_quit_watchers(self, data: dict[str, Any]) -> bool: w = self.active_window if w is None: for window in self.window_id_map.values(): w = window break if w is None: return True data['aborted'] = False for watcher in global_watchers().on_quit: try: watcher(self, w, data) except Exception: import traceback traceback.print_exc() if data.get('aborted'): return False return True @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: if not self._call_on_quit_watchers({'confirmed': True}): return 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: self.focus_os_window(ctm.os_window_id) ctm.set_active_tab(tab) tab.set_active_window(w) return return if not self._call_on_quit_watchers({'confirmed': False}): 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 if confirmed: if not self._call_on_quit_watchers({'confirmed': True}): set_application_quit_request(NO_CLOSE_REQUESTED) return 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) -> Window | 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) return 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 ) return None @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() self.edit_file(confpath) def edit_file(self, path: str) -> None: editor_cmd = get_editor(get_options()) exe = editor_cmd[0] if not os.path.isabs(exe): exe = which(exe) or '' if not exe or not os.access(exe, os.X_OK): self.show_error(_('Cannot find editor'), _( 'Could not edit the file {0} because the editor {1} was not found.').format(editor_cmd[0])) return editor_cmd[0] = exe path = os.path.abspath(os.path.expanduser(path)) self.new_os_window(*editor_cmd, path) 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('misc', ''' Browse and trigger keyboard shortcuts and actions in a searchable overlay. ''') def command_palette(self) -> None: from kittens.command_palette.main import collect_keys_data data = collect_keys_data(get_options()) self.run_kitten_with_metadata('command-palette', input_data=json.dumps(data), 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 if (w := self.window_id_map.get(tab.renaming_in_window)) is not None and w in tab: tab.set_active_window(w) return prefilled = (tab.name or tab.title).strip() tab_id = tab.id def on_rename_done(new_title: str) -> None: if (tab := self.tab_for_id(tab_id)) is not None: tab.renaming_in_window = 0 tab.set_title(new_title) overlay_window = self.get_line( _('Enter the new title for this tab below. An empty title will cause the default title to be used.'), on_rename_done, window=tab.active_window, initial_value=prefilled, window_title=_('Rename tab')) if overlay_window is not None: tab.renaming_in_window = overlay_window.id 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_in_active_tab(self, window_id: int) -> None: tab = self.active_tab if tab: tab.set_active_window(window_id) def drag_resize_start( self, edges: int, x: float, y: float, window_id: int, cell_width: int, cell_height: int, ) -> bool: if (w := self.window_id_map.get(window_id)) and (tab := w.tabref()): data = tab.current_layout.drag_resize_target_windows(w, x, y, edges, tab.windows) if not edges & (LEFT_EDGE | RIGHT_EDGE): data = data._replace(horizontal_id=None) if not edges & (TOP_EDGE | BOTTOM_EDGE): data = data._replace(vertical_id=None) self.drag_resize_of_window = WindowResizeDrag( is_active=True, tab_id=tab.id, data=data, cell_width=cell_width, cell_height=cell_height, initial_x=x, initial_y=y, ) for cw in tab: cw.pause_resize_notifications_to_child() return True return False def drag_resize_update(self, x: float, y: float) -> None: if not (r := self.drag_resize_of_window) or not (tab := self.tab_for_id(r.tab_id)): return if (h := r.data.horizontal_id) is not None: mult = 1 if r.data.width_increases_rightwards else -1 step_x = floor((x - r.initial_x) / r.cell_width) * mult dx = step_x - r.last_step_x if dx != 0: if tab.drag_resize_window(h, float(dx), True): self.drag_resize_of_window = r._replace(last_step_x=step_x) if (v := r.data.vertical_id) is not None: mult = 1 if r.data.height_increases_downwards else -1 step_y = floor((y - r.initial_y) / r.cell_height) * mult dy = step_y - r.last_step_y if dy != 0: if tab.drag_resize_window(v, float(dy), False): self.drag_resize_of_window = r._replace(last_step_y=step_y) def drag_resize_end(self) -> None: if tab := self.tab_for_id(self.drag_resize_of_window.tab_id): for cw in tab: cw.pause_resize_notifications_to_child(pause=False) self.drag_resize_of_window = WindowResizeDrag() 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: w = self.window_for_dispatch or self.active_window if w is not None: if w.send_paste_event(): return text = get_clipboard_string() if text: 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: w = self.window_for_dispatch or self.active_window if w is not None: if w.send_paste_event(is_primary_selection=True): return text = self.current_primary_selection_or_clipboard() if text: 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. The tab is added to the currently active :ref:`session `, if any. ''') 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: w = tab.new_special_window( self.args_to_special_window(args, cwd_from=cwd_from), location=location, allow_remote_control=allow_remote_control) else: w = tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control) if cwd_from is not None and (sw := cwd_from.window): session_name = sw.created_in_session_name if not session_name and (sw_tab := sw.tabref()): session_name = sw_tab.created_in_session_name w.created_in_session_name = session_name return w @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. The new window will belong to the active :ref:`session ` if any.''') 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 args_ and ' ' in args_[0]: # this can happen for example with map f1 launch $EDITOR when $EDITOR is not a single command q = which(args_[0]) if not q or (q is args_[0] and not os.access(q, os.X_OK)): args_[:1] = shlex_split(args_[0]) 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. You can also use drag and drop to re-arrange tabs.') 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. You can also use drag and drop to re-arrange tabs.') 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_before = get_options().background bg_colors_before = {w.id: w.screen.color_profile.default_bg for w in self.all_windows} configured_color_scheme_changed = bg_before.is_dark != opts.background.is_dark # 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.has_applied_theme: theme_colors.refresh() if theme_colors.has_applied_theme: # in case the theme file was deleted assert theme_colors.applied_theme # to make mypy happy 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) elif configured_color_scheme_changed: # the application running in the window could have set the # background color, so it wont change because of a config # reload, but the application might still want to be notified # that the user's color scheme preference has changed. w.report_color_scheme_preference_if_wanted() 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() from .tab_bar import clear_caches clear_caches() 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 monitor_pid(self, pid: int, callback: Callable[[int, Exception | None], None]) -> None: self.background_process_death_notify_map[pid] = callback monitor_pid(pid) 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 = window.tabref() 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 target_tab.attach_windows(src_tab.detach_window(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) -> Tab | None: tab = tab or self.active_tab if tab is None: return None 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() return target_tab 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('session', 'Switch to the specified session, creating it if not already present. See :ref:`goto_session`.') def goto_session(self, *cmdline: str) -> None: goto_session(self, cmdline) @ac('session', 'Save the current kitty state as a session file. See :ref:`save_as_session`.') def save_as_session(self, *cmdline: str) -> None: save_as_session(self, cmdline) @ac('session', ''' Close a session, that is, close all windows that belong to the session. Examples:: # Ask for the session to close map f1 close_session # Close the currently active session map f1 close_session . # Close session by name map f1 close_session "my session" # Close session by path to session file map f1 close_session "/path/to/session/file.kitty-session" ''') def close_session(self, *cmdline: str) -> None: close_session_with_confirm(self, cmdline) @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. You can also use drag and drop to detach tabs. See :ref:`detaching windows ` for details. ''') def detach_tab(self, *args: str) -> None: if not args or args[0] == 'new': self._move_tab_to() return 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'', linear_interpolation: bool | None = None, tint: float | None = None, tint_gaps: float | None = None ) -> None: set_background_image(path, os_windows, configured, layout, png_data, linear_interpolation, tint, tint_gaps) # 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() @ac('debug', '''Simulate a change in OS color scheme preference''') def simulate_color_scheme_preference_change(self, which: str) -> None: which = which.lower().replace('-', '_') match which: case 'light': self.on_system_color_scheme_change('light', False) case 'dark': self.on_system_color_scheme_change('dark', False) case 'no_preference': self.on_system_color_scheme_change('no_preference', False) case 'toggle': match theme_colors.applied_theme: case 'light': self.on_system_color_scheme_change('dark', False) case _: self.on_system_color_scheme_change('light', False) case _: self.show_error(_('Unknown color scheme type'), _('{} is not a valid color scheme type').format(which)) @ac('debug', ''' Start a test drag operation, for use with mouse_map ''') def test_dragging(self) -> None: if wid := current_os_window(): with open(logo_png_file, 'rb') as f: rgba, width, height = load_png_data(f.read()) drag_data = {'text/plain': b'This is a test drag of some basic text with the kitty logo as the drag icon.'} start_drag_with_data(wid, drag_data, ((rgba, width, height),)) 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_display_to_user(self, url: str) -> str: return sanitize_url_for_display_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() @ac('misc', grab_keyboard_docs) def grab_keyboard(self) -> None: grab_keyboard(True) @ac('misc', 'Ungrab the keyboard if it was previously grabbed') def ungrab_keyboard(self) -> None: grab_keyboard(False) def search_scrollback_in_active(self) -> None: if w := self.active_window: w.search_scrollback() ================================================ FILE: kitty/cell_defines.glsl ================================================ #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 BLINK_SHIFT {BLINK_SHIFT} #define MARK_SHIFT {MARK_SHIFT} #define MARK_MASK {MARK_MASK} #define USE_SELECTION_FG #define NUM_COLORS 256 #define COLOR_NOT_SET {COLOR_NOT_SET} #define COLOR_IS_SPECIAL {COLOR_IS_SPECIAL} #define COLOR_IS_RGB {COLOR_IS_RGB} #define COLOR_IS_INDEX {COLOR_IS_INDEX} #if {ONLY_BACKGROUND} == 1 #define ONLY_BACKGROUND #endif #if {ONLY_FOREGROUND} == 1 #define ONLY_FOREGROUND #endif // Linear space luminance values const vec3 Y = vec3(0.2126, 0.7152, 0.0722); ================================================ FILE: kitty/cell_fragment.glsl ================================================ #pragma kitty_include_shader #pragma kitty_include_shader #pragma kitty_include_shader #pragma kitty_include_shader uniform float text_contrast; uniform float text_gamma_adjustment; uniform sampler2DArray sprites; in vec3 background; in vec4 effective_background_premul; #ifndef ONLY_BACKGROUND 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 cell_foreground; in vec4 cursor_color_premult; in vec3 decoration_fg; in float colored_sprite; #endif out vec4 output_color; // 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); } #ifndef ONLY_BACKGROUND #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(cell_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 // ifndef ONLY_BACKGROUND void main() { #ifdef ONLY_FOREGROUND vec4 ans_premul; #else vec4 ans_premul = effective_background_premul; #endif #ifndef ONLY_BACKGROUND // blend in the foreground color 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 ONLY_FOREGROUND ans_premul = text_fg_premul; #else ans_premul = alpha_blend_premul(text_fg_premul, ans_premul); #endif #endif // ifndef ONLY_BACKGROUND output_color = ans_premul; } ================================================ FILE: kitty/cell_vertex.glsl ================================================ #extension GL_ARB_explicit_attrib_location : require #pragma kitty_include_shader #pragma kitty_include_shader // Inputs {{{ layout(std140) uniform CellRenderData { float use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg; uint default_fg, highlight_fg, highlight_bg, main_cursor_fg, main_cursor_bg, url_color, url_style, inverted, extra_cursor_fg, extra_cursor_bg; uint columns, lines, sprites_xnum, sprites_ynum, cursor_shape, cell_width, cell_height; uint cursor_x1, cursor_x2, cursor_y1, cursor_y2; float cursor_opacity, inactive_text_alpha, dim_opacity, blink_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]; uniform uint draw_bg_bitfield; uniform usampler2D sprite_decorations_map; uniform float row_offset; // 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(1u, 0u), // right, top uvec2(1u, 1u), // right, bottom uvec2(0u, 1u), // left, bottom uvec2(0u, 0u) // left, top ); const uint cursor_shape_map[] = uint[5]( // maps cursor shape to foreground sprite index 0u, // NO_CURSOR 0u, // BLOCK (this is rendered as background) 2u, // BEAM 3u, // UNDERLINE 4u // UNFOCUSED ); out vec3 background; out vec4 effective_background_premul; #ifndef ONLY_BACKGROUND out float effective_text_alpha; out vec3 sprite_pos; out vec3 underline_pos; out vec3 cursor_pos; out vec3 strike_pos; flat out uint underline_exclusion_pos; out vec3 cell_foreground; out vec4 cursor_color_premult; out vec3 decoration_fg; out float colored_sprite; #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 = 31u; const uint BIT_MASK = 1u; 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(float a, float b) { return (1.0f - zero_or_one(abs(float(a) - float(b)))); } // Wee need an integer variant to accommodate GPU driver bugs, see // https://github.com/kovidgoyal/kitty/issues/9072 uint one_if_equal_zero_otherwise(int a, int b) { return (1u - uint(zero_or_one(abs(float(a) - float(b))))); } uint one_if_equal_zero_otherwise(uint a, uint b) { return (1u - uint(zero_or_one(abs(float(a) - float(b))))); } 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 = one_if_equal_zero_otherwise(t, 1); uint is_two = 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)); } vec3 resolve_dynamic_color(uint c, vec3 special_val, vec3 defval) { float type = float((c >> 24) & BYTE_MASK); #define q(which, val) one_if_equal_zero_otherwise(type, float(which)) * val return ( q(COLOR_IS_RGB, color_to_vec(c)) + q(COLOR_IS_INDEX, color_to_vec(color_table[c & BYTE_MASK])) + q(COLOR_IS_SPECIAL, special_val) + q(COLOR_NOT_SET, defval) ); #undef q } 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); } float contrast_ratio(vec3 a, vec3 b) { return contrast_ratio(dot(a, Y), dot(b, Y)); } struct ColorPair { vec3 bg, fg; }; float contrast_ratio(ColorPair a) { return contrast_ratio(a.bg, a.fg); } ColorPair if_less_than_pair(float a, float b, ColorPair thenval, ColorPair elseval) { return ColorPair(if_less_than(a, b, thenval.bg, elseval.bg), if_less_than(a, b, thenval.fg, elseval.fg)); } ColorPair if_one_then_pair(float condition, ColorPair thenval, ColorPair elseval) { return ColorPair(if_one_then(condition, thenval.bg, elseval.bg), if_one_then(condition, thenval.fg, elseval.fg)); } ColorPair resolve_extra_cursor_colors_for_special_cursor(vec3 cell_bg, vec3 cell_fg) { ColorPair cell = ColorPair(cell_fg, cell_bg), base = ColorPair(color_to_vec(default_fg), color_to_vec(bg_colors0)); float cr = contrast_ratio(cell), br = contrast_ratio(base); ColorPair higher_contrast_pair = if_less_than_pair(cr, br, base, cell); return if_less_than_pair(cr, 2.5, higher_contrast_pair, cell); } ColorPair resolve_extra_cursor_colors(vec3 cell_bg, vec3 cell_fg, ColorPair main_cursor) { ColorPair ans = ColorPair( resolve_dynamic_color(extra_cursor_bg, main_cursor.bg, main_cursor.bg), resolve_dynamic_color(extra_cursor_fg, cell_bg, main_cursor.fg) ); ColorPair special = resolve_extra_cursor_colors_for_special_cursor(cell_bg, cell_fg); return if_one_then_pair(zero_or_one(abs(float(extra_cursor_bg & BYTE_MASK) - COLOR_IS_SPECIAL)), ans, special); } 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(); // decorations_idx == 0 means no decorations, for example, for a blank line // when drawing fractionally scaled text uint has_decorations = uint(zero_or_one(float(decorations_idx))); uint strike_style = ((text_attrs >> STRIKE_SHIFT) & BIT_MASK); // 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 has_decorations * uvec2(strike_idx, has_underline * (decorations_idx + underline_style)); } uint 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; uint cursor_fg_sprite_idx; ColorPair cursor; } cell_data; CellData set_vertex_position(vec3 cell_fg, vec3 cell_bg) { uint instance_id = uint(gl_InstanceID); float dx = 2.0 / float(columns); float dy = 2.0 / float(lines); /* The current cell being rendered */ uint row = instance_id / columns; uint column = instance_id - row * columns; /* The position of this vertex, at a corner of the cell */ float left = -1.0 + column * dx; float top = 1.0 - (float(row) + row_offset) * dy; uvec2 pos = cell_pos_map[gl_VertexID]; gl_Position = vec4(vec2(left, left + dx)[pos.x], vec2(top, top - dy)[pos.y], 0, 1); // The character sprite being rendered #ifndef ONLY_BACKGROUND 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 // Cursor shape and colors float has_main_cursor = float(is_cursor(column, row)); float multicursor_shape = float((is_selected >> 2) & 3u); float multicursor_uses_main_cursor_shape = float((is_selected >> 4) & BIT_MASK); multicursor_shape = if_one_then(multicursor_uses_main_cursor_shape, cursor_shape, multicursor_shape); float final_cursor_shape = if_one_then(has_main_cursor, cursor_shape, multicursor_shape); float has_cursor = zero_or_one(final_cursor_shape); float is_block_cursor = has_cursor * one_if_equal_zero_otherwise(final_cursor_shape, 1.0); ColorPair main_cursor = ColorPair(color_to_vec(main_cursor_bg), color_to_vec(main_cursor_fg)); ColorPair extra_cursor = resolve_extra_cursor_colors(cell_bg, cell_fg, main_cursor); ColorPair cursor = if_one_then_pair(has_main_cursor, main_cursor, extra_cursor); return CellData(has_cursor, is_block_cursor, pos, cursor_shape_map[int(final_cursor_shape)], cursor); } 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 DO_FG_OVERRIDE == 1 && !defined(ONLY_BACKGROUND) #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 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() { // 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) & BIT_MASK); 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] + (BIT_MASK - has_mark) * bg_as_uint; float cell_has_default_bg = 1.f - step(1.f, abs(float(bg_as_uint - bg_colors0))); // 1 if has default bg else 0 vec3 bg = color_to_vec(bg_as_uint); uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]); fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (1u - has_mark) * fg_as_uint; vec3 foreground = color_to_vec(fg_as_uint); CellData cell_data = set_vertex_position(foreground, bg); // }}} // Foreground {{{ #ifndef ONLY_BACKGROUND // background does not depend on foreground float has_dim = float((text_attrs >> DIM_SHIFT) & BIT_MASK), has_blink = float((text_attrs >> BLINK_SHIFT) & BIT_MASK); effective_text_alpha = inactive_text_alpha * if_one_then(has_dim, dim_opacity, 1.0) * if_one_then( has_blink, blink_opacity, 1.0); float in_url = float((is_selected >> 1) & BIT_MASK); decoration_fg = if_one_then(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint)); // Selection vec3 selection_color = if_one_then(use_cell_bg_for_selection_fg, bg, color_to_vec(highlight_fg)); selection_color = if_one_then(use_cell_fg_for_selection_fg, foreground, selection_color); foreground = if_one_then(float(is_selected & BIT_MASK), selection_color, foreground); decoration_fg = if_one_then(float(is_selected & BIT_MASK), 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(cell_data.cursor.bg * cursor_opacity, cursor_opacity); vec3 final_cursor_text_color = mix(foreground, cell_data.cursor.fg, cursor_opacity); foreground = if_one_then(cell_data.has_block_cursor, final_cursor_text_color, foreground); decoration_fg = if_one_then(cell_data.has_block_cursor, final_cursor_text_color, decoration_fg); cursor_pos = to_sprite_pos(cell_data.pos, cell_data.cursor_fg_sprite_idx * uint(cell_data.has_cursor)); #endif // }}} // Background {{{ float bg_alpha = calc_background_opacity(bg_as_uint); // we use max so that opacity of the block cursor cell background goes from bg_alpha to 1 float effective_cursor_opacity = max(cursor_opacity, bg_alpha); // is_special_cell is either 0 or 1 float is_special_cell = cell_data.has_block_cursor + float(is_selected & BIT_MASK); is_special_cell += float(is_reversed); // reverse video cells should be opaque as well is_special_cell = zero_or_one(is_special_cell); cell_has_default_bg = if_one_then(is_special_cell, 0., cell_has_default_bg); // special cells must always be fully opaque, otherwise leave bg_alpha untouched bg_alpha = if_one_then(is_special_cell, 1.f, bg_alpha); // Selection and cursor bg_alpha = if_one_then(cell_data.has_block_cursor, effective_cursor_opacity, bg_alpha); bg = if_one_then(float(is_selected & BIT_MASK), if_one_then(use_cell_for_selection_bg, color_to_vec(fg_as_uint), color_to_vec(highlight_bg)), bg); vec3 background_rgb = if_one_then(cell_data.has_block_cursor, mix(bg, cell_data.cursor.bg, cursor_opacity), bg); background = background_rgb; // }}} #if !defined(ONLY_BACKGROUND) && defined(OVERRIDE_FG_COLORS) decoration_fg = override_foreground_color(decoration_fg, background_rgb); foreground = override_foreground_color(foreground, background_rgb); #endif #if !defined(ONLY_FOREGROUND) vec4 bgpremul = vec4_premul(background_rgb, bg_alpha); // 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 = 1.f - cell_has_default_bg; uint draw_bg_mask = uint(2.f * cell_has_non_default_bg + cell_has_default_bg); // 1 if has default bg else 2 float draw_bg = step(0.5, float(draw_bg_bitfield & draw_bg_mask)); bgpremul *= draw_bg; effective_background_premul = bgpremul; #endif #ifndef ONLY_BACKGROUND cell_foreground = foreground; #endif } ================================================ FILE: kitty/char-props-data.h ================================================ // Unicode data, built from the Unicode Standard 17.0.0 // Code generated by wcwidth.py, DO NOT EDIT. #pragma once 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, 52, 52, 52, 52, 52, 122, 123, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 124, 52, 125, 126, 111, 111, 111, 111, 111, 111, 111, 111, 111, 127, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 128, 40, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 40, 40, 139, 111, 111, 111, 111, 140, 141, 142, 143, 111, 144, 145, 146, 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, 52, 168, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 169, 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, 170, 52, 52, 171, 172, 172, 172, 172, 172, 172, 172, 172, 172, 52, 52, 173, 172, 172, 172, 172, 174, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 175, 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, 176, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 174, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 178, 179, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 177, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 181, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 181 }; static const uint8_t CharProps_t2[46592] = { 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, 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, 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, 32, 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, 32, 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, 32, 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, 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, 43, 43, 43, 43, 43, 49, 49, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 36, 49, 44, 36, 42, 42, 36, 36, 43, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 5, 5, 43, 43, 43, 43, 43, 43, 42, 42, 36, 36, 43, 43, 43, 43, 36, 36, 36, 43, 49, 49, 49, 43, 43, 49, 49, 49, 49, 49, 49, 49, 43, 43, 43, 36, 36, 36, 36, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 36, 49, 42, 36, 36, 49, 49, 49, 49, 49, 49, 36, 43, 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, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 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, 44, 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, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 42, 36, 42, 36, 36, 36, 36, 36, 36, 36, 38, 44, 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, 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, 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, 43, 43, 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, 36, 45, 36, 36, 36, 36, 36, 45, 36, 45, 42, 42, 42, 42, 36, 45, 56, 43, 43, 43, 43, 43, 43, 43, 43, 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, 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, 42, 36, 36, 36, 36, 42, 42, 36, 36, 45, 44, 36, 36, 43, 43, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 43, 43, 43, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 57, 58, 24, 24, 59, 11, 11, 59, 59, 59, 17, 5, 60, 61, 8, 23, 60, 61, 8, 23, 17, 17, 17, 5, 17, 17, 17, 17, 62, 63, 24, 24, 24, 24, 24, 4, 17, 5, 17, 17, 5, 17, 5, 5, 5, 23, 29, 17, 64, 5, 17, 15, 15, 5, 5, 5, 10, 8, 9, 5, 5, 64, 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, 65, 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, 7, 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, 66, 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, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 55, 55, 55, 55, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 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, 68, 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, 69, 69, 19, 19, 19, 19, 10, 10, 19, 19, 19, 19, 19, 19, 21, 70, 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, 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, 10, 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, 69, 69, 69, 69, 21, 21, 21, 69, 21, 21, 69, 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, 26, 26, 19, 19, 26, 19, 19, 19, 19, 25, 26, 19, 21, 19, 19, 69, 69, 19, 19, 21, 19, 19, 19, 26, 69, 26, 19, 21, 19, 21, 21, 19, 19, 21, 19, 19, 19, 21, 19, 19, 19, 21, 21, 74, 74, 74, 74, 74, 74, 74, 74, 21, 21, 21, 19, 19, 19, 19, 19, 25, 19, 25, 19, 19, 19, 19, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 25, 26, 19, 25, 26, 25, 21, 26, 25, 26, 26, 19, 26, 26, 19, 27, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 21, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 74, 74, 74, 74, 74, 74, 19, 19, 21, 69, 21, 21, 21, 21, 19, 21, 19, 21, 21, 19, 26, 26, 21, 69, 19, 19, 19, 19, 19, 21, 19, 19, 69, 69, 19, 19, 19, 19, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 69, 26, 19, 19, 19, 19, 69, 69, 26, 26, 25, 26, 26, 26, 26, 26, 69, 25, 26, 25, 26, 25, 69, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 26, 19, 19, 19, 19, 26, 25, 69, 26, 26, 26, 26, 26, 25, 25, 69, 69, 25, 69, 26, 25, 25, 69, 69, 26, 26, 69, 26, 26, 19, 19, 21, 19, 19, 69, 19, 19, 21, 21, 69, 69, 69, 69, 19, 21, 19, 19, 21, 19, 21, 19, 21, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 69, 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, 69, 19, 69, 19, 19, 19, 19, 69, 69, 69, 19, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 19, 19, 19, 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, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 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, 69, 69, 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, 69, 19, 19, 19, 19, 69, 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, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 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, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 76, 76, 76, 74, 77, 78, 79, 70, 71, 70, 71, 70, 71, 70, 71, 70, 71, 74, 74, 70, 71, 70, 71, 70, 71, 70, 71, 80, 70, 71, 71, 74, 79, 79, 79, 79, 79, 79, 79, 79, 79, 81, 81, 81, 81, 82, 82, 83, 77, 77, 77, 77, 77, 74, 74, 79, 79, 79, 77, 78, 84, 74, 19, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 81, 81, 85, 85, 77, 77, 78, 80, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 76, 77, 77, 77, 78, 38, 38, 38, 38, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 86, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 74, 74, 87, 87, 87, 87, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 38, 38, 38, 38, 38, 38, 38, 38, 74, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 28, 28, 28, 28, 28, 28, 28, 28, 74, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 69, 74, 69, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 77, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 38, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 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, 13, 16, 13, 16, 13, 16, 13, 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, 34, 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, 43, 43, 43, 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, 36, 42, 42, 36, 36, 36, 36, 42, 42, 36, 36, 42, 42, 56, 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, 43, 43, 43, 43, 43, 36, 34, 43, 43, 43, 43, 43, 43, 43, 43, 43, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 43, 43, 43, 43, 43, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 34, 43, 43, 43, 32, 32, 32, 19, 19, 19, 43, 49, 36, 49, 43, 43, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 42, 36, 36, 42, 42, 5, 5, 32, 34, 34, 42, 44, 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, 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, 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, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 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, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 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, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 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, 19, 19, 19, 19, 19, 19, 19, 19, 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, 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, 76, 76, 76, 76, 76, 76, 76, 70, 71, 76, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 76, 80, 80, 94, 94, 70, 71, 70, 71, 70, 71, 70, 71, 70, 71, 70, 71, 70, 71, 70, 71, 76, 76, 70, 71, 76, 76, 76, 76, 94, 94, 94, 76, 76, 76, 38, 76, 76, 76, 76, 80, 70, 71, 70, 71, 70, 71, 76, 76, 76, 95, 80, 95, 95, 95, 38, 76, 96, 76, 76, 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, 76, 76, 76, 96, 76, 76, 76, 70, 71, 76, 95, 76, 80, 76, 76, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 76, 76, 95, 95, 95, 76, 76, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 70, 76, 71, 85, 94, 85, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 70, 95, 71, 95, 70, 71, 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, 100, 100, 101, 32, 32, 32, 32, 32, 32, 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, 96, 96, 95, 85, 74, 96, 96, 38, 19, 10, 10, 10, 10, 19, 19, 38, 65, 65, 65, 65, 65, 65, 65, 65, 65, 24, 24, 24, 19, 26, 93, 93, 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, 32, 32, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 43, 36, 36, 36, 38, 36, 36, 38, 38, 38, 38, 38, 36, 36, 36, 36, 43, 43, 43, 43, 38, 43, 43, 43, 38, 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, 38, 38, 36, 36, 36, 38, 38, 38, 38, 44, 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, 34, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 5, 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, 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, 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, 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, 36, 36, 36, 42, 36, 36, 36, 36, 36, 36, 44, 36, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 43, 42, 42, 43, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 38, 38, 43, 38, 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, 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, 44, 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, 43, 43, 43, 43, 43, 43, 43, 38, 38, 43, 38, 38, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 38, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 45, 42, 42, 42, 42, 42, 38, 42, 42, 38, 38, 36, 36, 45, 44, 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, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 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, 43, 43, 36, 36, 36, 36, 36, 36, 42, 32, 36, 36, 36, 36, 5, 5, 5, 5, 5, 5, 5, 5, 44, 38, 38, 38, 38, 38, 38, 38, 38, 43, 36, 36, 36, 36, 36, 36, 42, 42, 36, 36, 36, 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, 43, 43, 47, 47, 47, 47, 47, 47, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 36, 44, 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, 36, 42, 36, 36, 36, 42, 36, 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, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 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, 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, 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, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 42, 42, 36, 36, 36, 36, 36, 38, 38, 38, 42, 42, 36, 45, 44, 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, 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, 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, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 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, 77, 77, 76, 77, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 82, 82, 77, 77, 79, 79, 79, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 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, 38, 77, 77, 77, 77, 77, 77, 77, 38, 77, 77, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 38, 38, 78, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 78, 38, 38, 38, 38, 38, 38, 38, 38, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 19, 19, 19, 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, 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, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 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, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 38, 38, 38, 38, 38, 38, 38, 38, 38, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 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, 32, 32, 32, 32, 32, 32, 32, 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, 36, 32, 32, 36, 32, 32, 32, 32, 32, 32, 32, 36, 36, 32, 32, 32, 32, 32, 36, 38, 38, 38, 38, 38, 38, 38, 38, 32, 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, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 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, 19, 19, 19, 19, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 46, 46, 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, 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, 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, 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, 69, 26, 26, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 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, 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, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 74, 69, 69, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 69, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 69, 74, 74, 69, 69, 69, 69, 69, 69, 69, 69, 69, 74, 102, 102, 102, 102, 74, 74, 74, 74, 74, 74, 74, 74, 74, 102, 102, 102, 102, 102, 102, 102, 69, 69, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 74, 74, 74, 74, 74, 74, 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, 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, 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, 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, 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, 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, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 19, 19, 21, 21, 21, 21, 21, 21, 21, 21, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 21, 21, 19, 21, 21, 21, 19, 19, 21, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 21, 69, 69, 69, 69, 69, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 21, 69, 21, 19, 21, 69, 69, 69, 104, 104, 104, 104, 104, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 69, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 21, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 69, 69, 69, 69, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 21, 21, 19, 19, 21, 69, 69, 21, 21, 21, 21, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 21, 21, 21, 21, 19, 19, 69, 19, 19, 19, 19, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 21, 19, 19, 21, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 19, 19, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 21, 19, 19, 21, 19, 21, 19, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 21, 69, 21, 21, 21, 69, 69, 69, 19, 19, 69, 69, 69, 69, 102, 102, 102, 69, 69, 69, 69, 21, 21, 21, 21, 21, 21, 19, 19, 19, 21, 19, 69, 69, 102, 102, 102, 21, 19, 19, 21, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 102, 69, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 10, 10, 10, 10, 10, 10, 10, 10, 10, 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, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 69, 102, 102, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 102, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 102, 102, 102, 102, 102, 102, 102, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 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, 19, 38, 38, 38, 38, 38, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 93, 93, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 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, 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, 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, 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, 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, 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, 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, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 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, 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, 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, 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, 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, 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, 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, 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, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 92, 92, 92, 92, 92, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 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, 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, 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, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 93, 93, 65, 24, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 93, 93 }; static const CharProps CharProps_t3[106] = { {.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_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_Linker, .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_Extend, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 57 {.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}, // 58 {.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}, // 59 {.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}, // 60 {.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}, // 61 {.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}, // 62 {.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}, // 63 {.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}, // 64 {.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}, // 65 {.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}, // 66 {.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}, // 67 {.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}, // 68 {.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}, // 69 {.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}, // 70 {.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}, // 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=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}, // 74 {.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}, // 75 {.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}, // 76 {.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}, // 77 {.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}, // 78 {.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}, // 79 {.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}, // 80 {.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}, // 81 {.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}, // 82 {.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}, // 83 {.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}, // 84 {.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}, // 85 {.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}, // 86 {.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}, // 87 {.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}, // 88 {.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}, // 89 {.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}, // 90 {.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}, // 91 {.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}, // 92 {.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}, // 93 {.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}, // 94 {.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}, // 95 {.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}, // 96 {.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}, // 97 {.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}, // 98 {.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}, // 99 {.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}, // 100 {.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}, // 101 {.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}, // 102 {.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}, // 103 {.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}, // 104 {.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}, // 105 }; 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 }}} ================================================ FILE: kitty/char-props.c ================================================ /* * 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; } ================================================ FILE: kitty/char-props.h ================================================ /* * 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 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 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 } ================================================ FILE: kitty/charsets.c ================================================ /* * 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; } ================================================ FILE: kitty/charsets.h ================================================ /* * 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); ================================================ FILE: kitty/child-monitor.c ================================================ /* * 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 "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, bool is_async_response); 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), false); else if (resp == Py_None) send_response_to_peer(msg->peer_id, NULL, 0, false); else if (resp == Py_True) send_response_to_peer(msg->peer_id, NULL, 0, true); Py_CLEAR(resp); } else send_response_to_peer(msg->peer_id, NULL, 0, false); } 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) { return memcmp(&w->render_data.screen->last_rendered.cursor, &w->render_data.screen->cursor_render_info, sizeof(CursorRenderInfo)) != 0; } 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->is_visible = false; ans->multicursor_count = 0; ans->cursor_opacity = 1; ans->text_blink_opacity = 1; if (!rd->screen->scrolled_by) { ans->multicursor_count = screen_multi_cursor_count(rd->screen); ans->is_visible = screen_is_cursor_visible(rd->screen); } if (!ans->is_visible && ans->multicursor_count == 0 && !rd->screen->sgr_blink_was_used) return cursor_needs_render(w); monotonic_t time_since_start_blink = now - os_window->cursor_blink_zero_time; const bool allow_blinking = OPT(cursor_blink_interval) > 0; const bool blink_has_ceased = OPT(cursor_stop_blinking_after) != 0 && time_since_start_blink > OPT(cursor_stop_blinking_after); const bool cursor_blinking = !cursor->non_blinking && os_window->is_focused; float blink_opacity = 1.f; if (allow_blinking && !blink_has_ceased && (cursor_blinking || rd->screen->sgr_blink_was_used)) { 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; blink_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); blink_opacity = 1 - n % 2; set_maximum_wait((n + 1) * OPT(cursor_blink_interval) - time_since_start_blink); } } ans->text_blink_opacity = blink_opacity; ans->cursor_opacity = cursor_blinking ? blink_opacity: 1.0f; 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; bool was_previously_rendered_with_layers = os_window->needs_layers; os_window->needs_layers = ( !global_state.supports_framebuffer_srgb || effective_os_window_alpha(os_window) < 1.f || os_window->live_resize.in_progress || (os_window->bgimage && os_window->bgimage->texture_id > 0) ); if (TD.screen && os_window->num_tabs && !os_window->has_too_few_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; } // we never render a cursor in the tab bar CursorRenderInfo *cri = &TD.screen->cursor_render_info; zero_at_ptr(cri); cri->x = TD.screen->cursor->x; cri->y = TD.screen->cursor->y; if (send_cell_data_to_gpu(TD.vao_idx, TD.screen, os_window)) needs_render = true; os_window->needs_layers = os_window->needs_layers || screen_needs_rendering_in_layers(os_window, NULL, TD.screen); } if (OPT(mouse_hide.hide_wait) > 0 && !is_mouse_hidden(os_window)) { if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide.hide_wait)) hide_mouse(os_window); else set_maximum_wait(OPT(mouse_hide.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; os_window->needs_layers = os_window->needs_layers || (OPT(cursor_trail) && tab->cursor_trail.needs_render); 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) { os_window->needs_layers = os_window->needs_layers || screen_needs_rendering_in_layers(os_window, w, 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 (os_window->last_active_tab != os_window->active_tab && os_window->last_active_tab < os_window->num_tabs) { tab->cursor_trail = os_window->tabs[os_window->last_active_tab].cursor_trail; tab->cursor_trail.needs_render = true; tab->cursor_trail.updated_at = now; os_window->cursor_blink_zero_time = now; } 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 { if (WD.screen->sgr_blink_was_used) { 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.text_blink_opacity = 1; } WD.screen->cursor_render_info.cursor_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.screen, os_window)) needs_render = true; if (WD.screen->start_visual_bell_at != 0) needs_render = true; // Prepare window title bar screen data for GPU WindowRenderData *trd = &w->window_title_render_data; if (trd->screen && trd->geometry.bottom > trd->geometry.top && trd->geometry.right > trd->geometry.left) { trd->screen->cursor_render_info.is_visible = false; if (send_cell_data_to_gpu(trd->vao_idx, trd->screen, os_window)) needs_render = true; } } } return needs_render || was_previously_rendered_with_layers != os_window->needs_layers; } static void thumbnail_callback(OSWindow *os_window) { #define tc global_state.thumbnail_callback Region region = {.right=os_window->viewport_width, .bottom=os_window->viewport_height}; if (tc.window) { Window *w = window_for_window_id(tc.window); if (!w) return; region.left = w->render_data.geometry.left; region.top = w->render_data.geometry.top; region.right = w->render_data.geometry.right; region.bottom = w->render_data.geometry.bottom; } else { if (!tc.include_tab_bar) { Region central = {0}, tab_bar = {0}; os_window_regions(os_window, ¢ral, &tab_bar); if (tab_bar.bottom > tab_bar.top) region = central; } } unsigned vw = region.right - region.left, vh = region.bottom - region.top; unsigned thumb_w = (unsigned)(vw * tc.scale), thumb_h = (unsigned)(vh * tc.scale); if (thumb_w > tc.max_width) { thumb_w = tc.max_width; double scale = 300. / vw; thumb_h = (unsigned)(vh * scale + 0.5f); } RAII_PyObject(pixels, PyBytes_FromStringAndSize(NULL, 4 * thumb_w * thumb_h)); if (pixels && global_state.boss) { take_screenshot_of_rectangular_region( os_window, region, (unsigned char*)PyBytes_AS_STRING(pixels), &thumb_w, &thumb_h); _PyBytes_Resize(&pixels, 4 * thumb_w *thumb_h); PyObject *r = PyObject_CallMethod( global_state.boss, tc.callback, "KKOII", os_window->id, tc.window, pixels, thumb_w, thumb_h); if (!r) PyErr_Print(); else Py_DECREF(r); } #undef tc } 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) { Tab *tab = os_window->tabs + os_window->active_tab; setup_os_window_for_rendering(os_window, tab, NULL, true); BorderRects *br = &tab->border_rects; draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); br->is_dirty = false; if (TD.screen && os_window->num_tabs && !os_window->has_too_few_tabs) draw_cells(&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, 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); WindowRenderData *trd = &w->window_title_render_data; if (trd->screen && trd->geometry.right > trd->geometry.left && trd->geometry.bottom > trd->geometry.top) draw_cells(trd, os_window, i == tab->active_window, true, false, NULL); } } setup_os_window_for_rendering(os_window, tab, active_window, false); if (global_state.thumbnail_callback.os_window == os_window->id) { thumbnail_callback(os_window); global_state.thumbnail_callback.os_window = 0; } 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 scan_for_animated_images) { if (!w->num_tabs) return false; if (!should_os_window_be_rendered(w) && global_state.thumbnail_callback.os_window != w->id) { update_os_window_title(w); if (w->is_focused) change_menubar_title(w->window_title); return false; } if (!w->keep_rendering_till_swap && 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); if (w->id != global_state.thumbnail_callback.os_window) { // 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); bool needs_render = w->redraw_count > 0 || w->live_resize.in_progress || global_state.thumbnail_callback.os_window == w->id; if (w->viewport_size_dirty) { set_gpu_viewport(w->viewport_width, w->viewport_height); 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\n", 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) && !global_state.thumbnail_callback.os_window) { 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, 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; } if (w->keep_rendering_till_swap) { debug_rendering("Re-rendering window %llu on the %u attempt since swap did not happen\n", w->id, w->keep_rendering_till_swap); set_maximum_wait(OPT(repaint_delay)); w->needs_render = true; w->keep_rendering_till_swap--; } } 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; } int x = 0, y = 0; if (os_window->handle && !global_state.is_wayland) glfwGetWindowPos(os_window->handle, &x, &y); bool is_layer_shell = os_window->is_layer_shell; destroy_os_window(os_window); call_boss(on_os_window_closed, "KiiiiO", os_window->id, x, y, w, h, is_layer_shell ? Py_True : Py_False); 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[CLEAR_LAST_COMMAND]) { call_boss(clear_terminal, "sO", "last_command", 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[MACOS_CYCLE_THROUGH_OS_WINDOWS]) { call_boss(macos_cycle_through_os_windows, NULL); } if (cocoa_pending_actions[MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS]) { call_boss(macos_cycle_through_os_windows_backwards, NULL); } if (cocoa_pending_actions[SEARCH_SCROLLBACK]) { call_boss(search_scrollback_in_active, 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, waiting_for_async_response; 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 && !p->waiting_for_async_response) { 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) read_from_peer(self, p); if (fds[p->fd_array_idx].revents & POLLOUT) write_to_peer(p); if (fds[p->fd_array_idx].revents & POLLHUP) { // try to read and write nonetheless these functions will set the failed flags. if (!p->read.finished) read_from_peer(self, p); if (p->write.used) write_to_peer(p); } if (fds[p->fd_array_idx].revents & POLLNVAL) { p->read.finished = true; p->write.failed = true; p->write.used = 0; } } } } 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 is_async_response) { 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) { peer->waiting_for_async_response = is_async_response; 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; int is_async_response = 0; if (!PyArg_ParseTuple(args, "Ks#|p", &peer_id, &msg, &sz, &is_async_response)) return NULL; send_response_to_peer(peer_id, msg, sz, is_async_response); 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; } // }}} ================================================ FILE: kitty/child.c ================================================ /* * 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 (access(".", X_OK) != 0) { // existing cwd does not exist or dont have permissions for it 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; } ================================================ FILE: kitty/child.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import os import sys import termios 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, Mapping, 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, resolved_shell, which if TYPE_CHECKING: from .window import CwdRequest if is_macos: from kitty.fast_data_types import abspath_of_process as _abspath_of_process 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), strict=True) def abspath_of_exe(pid: int) -> str: return os.path.realpath(_abspath_of_process(pid), strict=True) 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, strict=True) 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, strict=True) 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 def abspath_of_exe(pid: int) -> str: return os.path.realpath(f'/proc/{pid}/exe', strict=True) @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(env: Mapping[str, str] | None = None) -> dict[str, str]: ans = dict(os.environ if env is None else env) 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 ans.pop('KITTY_SI_RUN_COMMAND_AT_STARTUP', None) 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, hold_after_ssh: bool = False, startup_command_via_shell_integration: Sequence[str] | str = (), ): 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, hold_after_ssh=hold_after_ssh) 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.startup_command_via_shell_integration = startup_command_via_shell_integration 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) -> tuple[dict[str, str], bool]: 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) must_run_startup_command_via_kitten = False if self.startup_command_via_shell_integration: if isinstance(self.startup_command_via_shell_integration, str): env['KITTY_SI_RUN_COMMAND_AT_STARTUP'] = self.startup_command_via_shell_integration else: from .shell_integration import join scmd = self.argv or resolved_shell(fast_data_types.get_options()) try: env['KITTY_SI_RUN_COMMAND_AT_STARTUP'] = join(scmd[0], self.startup_command_via_shell_integration) except Exception: must_run_startup_command_via_kitten = True # unknown shell return env, must_run_startup_command_via_kitten 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, must_run_startup_command_via_kitten = self.get_final_env() self.initial_termios_state = termios.tcgetattr(master) 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 or must_run_startup_command_via_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 must_run_startup_command_via_kitten: argv.extend(self.startup_command_via_shell_integration) 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 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 def reset_termios_state(self, when: int = termios.TCSANOW) -> None: if (s := getattr(self, 'initial_termios_state', None)) and self.child_fd is not None: try: termios.tcsetattr(self.child_fd, when, s) except OSError: pass ================================================ FILE: kitty/choose_entry.py ================================================ #!/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_compat 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 ================================================ FILE: kitty/cleanup.c ================================================ /* * 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; } } ================================================ FILE: kitty/cleanup.h ================================================ /* * 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); ================================================ FILE: kitty/cli.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal import os import re import sys from collections.abc import Callable, Iterator, Sequence from re import Match from typing import Any, NoReturn, TypeVar, 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 parse_cli_from_spec, wcswidth from .options.types import Options as KittyOpts from .simple_cli_definitions import ( CompletionType, OptionDefinition, OptionSpecSeq, defval_for_opt, get_option_maps, kitty_options_spec, parse_option_spec, serialize_as_go_string, ) from .types import run_once from .typing_compat import BadLineType is_macos go_type_map = { 'bool-set': 'bool', 'bool-reset': 'bool', 'bool-unset': 'bool', 'int': 'int', 'float': 'float64', '': 'string', 'list': '[]string', 'choices': 'string', 'str': 'string'} class GoOption: def __init__(self, x: OptionDefinition) -> 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_defn = 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_defn.help.strip()) def struct_declaration(self) -> str: return f'{self.go_var_name} {self.go_type}' @property def flags(self) -> list[str]: return sorted(self.obj_defn.aliases) 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(self.flags) 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_defn.completion.type is not CompletionType.none: ans += ''.join(self.obj_defn.completion.as_go_code('Completer', ': ')) + ',' if depth > 0: ans += f'\n\tDepth: {depth},\n' if self.default: ans += f'\n\tDefault: "{serialize_as_go_string(self.default)}",\n' return ans + '})' def as_string_for_commandline(self) -> Iterator[str]: # }}}}}}}}}}}]]]]]]]]]]]]]]]]] flag = self.flags[0] val = f'opts.{self.go_var_name}' if self.go_type == '[]string': yield f'\tfor _, x := range {val} {{ ans = append(ans, `{flag}=` + x) }}' return match self.go_type: case 'bool': yield f'sval = fmt.Sprintf(`%#v`, {val})' godef = '`true`' if self.type != 'bool-set' else '`false`' case 'int': yield f'sval = fmt.Sprintf(`%d`, {val})' godef = f"`{self.default or '0'}`" case 'string': yield f'sval = {val}' godef = f'''"{serialize_as_go_string(self.default or '')}"''' case 'float64': yield f'sval = fmt.Sprintf(`%f`, {val})' godef = f"`{self.default or '0'}`" case _: raise ValueError(f'Unknown type: {self.go_type}') yield f'\tif (sval != {godef}) {{ ans = append(ans, `{flag}=` + sval)}}' @property def sorted_choices(self) -> list[str]: choices = sorted(self.obj_defn.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) 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-') 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()) def help_defval_for_bool(otype: str) -> str: if otype == 'bool-set': return 'no' return 'yes' 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.default if (otype := opt.type).startswith('bool-'): blocks[-1] += italic(f'[={help_defval_for_bool(otype)}]') else: dt = f'''=[{italic(defval or '""')}]''' blocks[-1] += dt if opt.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.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 escape_rst(text: str) -> str: text = text.replace('\\', '\\\\') text = text.replace('*', '\\*') text = text.replace('`', '\\`') text = text.replace('_', '\\_') text = text.replace('|', '\\|') return text 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 (otype := opt.type).startswith('bool-'): val_name = f' [={help_defval_for_bool(otype)}]' else: val_name = ' <{}>'.format(opt.dest.upper()) a(defn + ', '.join(o + val_name for o in sorted(opt.aliases))) if opt.help: defval = opt.default t = help_text.replace('%default', ':code:`' + escape_rst(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:`{escape_rst(str(defval))}`', ' ' * 4)) if opt.choices: a(textwrap.indent('Choices: {}'.format(', '.join(f':code:`{escape_rst(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' bool_map = {'y': True, 'yes': True, 'true': True, 'n': False, 'no': False, 'false': False} def to_bool(alias: str, x: str) -> bool: try: return bool_map[x] except KeyError: raise SystemExit(f'{x} is not a valid value for {alias}. Valid values are y, yes, true, n, no, false only') class Options: do_print = True def __init__(self, seq: OptionSpecSeq, usage: str | None, message: str | None, appname: str | None): self.seq = seq self.usage, self.message, self.appname = usage, message, appname self.names_map, self.alias_map, self.values_map = get_option_maps(seq) self.help_called = self.version_called = False def handle_help(self) -> NoReturn: if self.do_print: print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname) self.help_called = True raise SystemExit(0) def handle_version(self) -> NoReturn: self.version_called = True if self.do_print: print(version()) raise SystemExit(0) PreparsedCLIFlags = tuple[dict[str, tuple[Any, bool]], list[str]] def apply_preparsed_cli_flags( preparsed_from_c: PreparsedCLIFlags, ans: Any, create_oc: Callable[[], Options], track_seen_options: dict[str, Any] | None = None ) -> list[str]: for key, (val, is_seen) in preparsed_from_c[0].items(): if key == 'help' and is_seen and val: create_oc().handle_help() elif key == 'version' and is_seen and val: create_oc().handle_version() if is_seen and track_seen_options is not None: track_seen_options[key] = val setattr(ans, key, val) return preparsed_from_c[1] def parse_cmdline_inner( args: list[str], oc: Options, disabled: OptionSpecSeq, names_map: dict[str, OptionDefinition], values_map: dict[str, OptionDefinition], ans: Any, track_seen_options: dict[str, Any] | None = None ) -> list[str]: preparsed = parse_cli_from_spec(args, names_map, values_map) leftover_args = apply_preparsed_cli_flags(preparsed, ans, lambda: oc, track_seen_options) for opt in disabled: if not isinstance(opt, str): setattr(ans, opt.dest, defval_for_opt(opt)) return leftover_args def parse_cmdline( oc: Options, disabled: OptionSpecSeq, ans: Any, args: list[str] | None = None, track_seen_options: dict[str, Any] | None = None ) -> list[str]: names_map = oc.names_map.copy() values_map = oc.values_map.copy() if 'help' not in names_map: names_map['help'] = OptionDefinition(type='bool-set', aliases=('--help', '-h')) values_map['help'] = False if 'version' not in names_map: names_map['version'] = OptionDefinition(type='bool-set', aliases=('--version', '-v')) values_map['version'] = False try: return parse_cmdline_inner(sys.argv[1:] if args is None else args, oc, disabled, names_map, values_map, ans, track_seen_options) except Exception as e: raise SystemExit(str(e)) spec_cache: dict[str, tuple[Options, OptionSpecSeq]] = {} def cached_parse_cmdline(spec: str, args: list[str], ans: Any) -> list[str]: if (x := spec_cache.get(spec)) is None: seq, disabled = parse_option_spec(spec) oc = Options(seq, '', '', '') x = spec_cache[spec] = oc, disabled oc, disabled = x leftover_args = parse_cmdline_inner(args, oc, disabled, oc.names_map, oc.values_map, ans) return leftover_args 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=kitty_options_spec()) return parse_option_spec(raw)[0] def option_spec_as_rst( ospec: Callable[[], str] = kitty_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] = kitty_options_spec, usage: str | None = None, message: str | None = None, appname: str | None = None, result_class: type[T] | None = None, preparsed_from_c: PreparsedCLIFlags | None = None, track_seen_options: dict[str, Any] | None = None, ) -> tuple[T, list[str]]: if result_class is not None: ans = result_class() else: ans = cast(T, CLIOptions()) def create_oc() -> Options: options = parse_option_spec(ospec()) seq, disabled = options return Options(seq, usage, message, appname) if preparsed_from_c: return ans, apply_preparsed_cli_flags(preparsed_from_c, ans, create_oc) options = parse_option_spec(ospec()) seq, disabled = options oc = Options(seq, usage, message, appname) return ans, parse_cmdline(oc, disabled, ans, args=args, track_seen_options=track_seen_options) 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 ================================================ FILE: kitty/cli_stub.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Sequence class CLIOptions: def __repr__(self) -> str: return repr(vars(self)) LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions BroadcastCLIOptions = ShowKeyCLIOptions = SaveAsSessionOptions = GotoSessionOptions = CLIOptions ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions def generate_stub() -> None: from .cli import as_type_stub from .conf.utils import save_type_stub from .simple_cli_definitions import parse_option_spec 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 panel_kitten_options_spec do(panel_kitten_options_spec(), '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.session import goto_session_options, save_as_session_options do(save_as_session_options(), 'SaveAsSessionOptions') do(goto_session_options(), 'GotoSessionOptions') 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()' ]) ================================================ FILE: kitty/client.py ================================================ #!/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 (h := c.pop('horizontal_align', None)) is not None and h > 0: m += f'h={h}:' if c: raise Exception('Unknown keys in multicell_command: ' + ', '.join(c)) write(f'{OSC}{TEXT_SIZE_CODE};{m.rstrip(":")};{text}\a') def screen_multi_cursor(rest: str) -> None: write(f'{CSI}>{rest.strip()} q') 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', 'screen_multi_cursor', }) 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() ================================================ FILE: kitty/clipboard.py ================================================ #!/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 .typing_compat import WindowType 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 in ('mime', 'name', 'pw'): 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 human_name: str = '' password: str = '' otp_for_response: str = '' def encode_response(self, status: str = 'DATA', mime: str = '', payload: bytes | memoryview = b'') -> bytes: from base64 import standard_b64encode def encode_b64(s: str) -> str: return standard_b64encode(s.encode()).decode() 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)}' if self.otp_for_response: ans += f':pw={encode_b64(self.otp_for_response)}' a = ans.encode('ascii') if payload: a += b';' + 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, human_name: str = '', password: str = '', ) -> None: self.decoder = StreamingBase64Decoder() self.human_name = human_name self.password = password 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 self.permission_pending = True self.commit_pending = 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 if self.permission_pending: self.commit_pending = True return self.committed = True self.commit_pending = False 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 | memoryview, 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 | memoryview) -> 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 GrantedPermission: one_time: bool = False write_ban: bool = False read_ban: bool = False def __init__(self, read: bool = False, write: bool = False, one_time: bool = False): self.read, self.write = read, write self.one_time = one_time 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] = {} self.granted_passwords: dict[str, GrantedPermission] = {} 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', '')), human_name=m.get('name', ''), password=m.get('pw', ''), ) 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', '')), human_name=m.get('name', ''), password=m.get('pw', ''), ) 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: self.commit_write_request(wr) def commit_write_request(self, wr: WriteRequest, needs_flush: bool = True) -> None: if needs_flush: wr.flush_base64_data() wr.commit() if wr.committed: self.in_flight_write_request = None w = get_boss().window_id_map.get(self.window_id) 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: wr.permission_pending = not allowed if wr.protocol_type is ProtocolType.osc_52: self.fulfill_legacy_write_request(wr, allowed) return 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 None: self.in_flight_write_request = None return if not cp.enabled: self.in_flight_write_request = None w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='ENOSYS')) return if not allowed: if wr.password and wr.human_name: if self.password_is_allowed_already(wr.password, for_write=True): wr.permission_pending = False else: wid = w.id def callback(granted: bool) -> None: if wr is not self.in_flight_write_request: return if granted: wr.permission_pending = False if wr.commit_pending: self.commit_write_request(wr, needs_flush=False) else: w = get_boss().window_id_map.get(wid) if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EPERM')) self.in_flight_write_request = None self.request_permission(w, wr.human_name, wr.password, callback, for_write=True) else: self.in_flight_write_request = None w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EPERM')) def request_permission(self, window: WindowType, human_name: str, password: str, callback: Callable[[bool], None], for_write: bool = False) -> None: if (gp := self.granted_passwords.get(password)) and (gp.write_ban if for_write else gp.read_ban): callback(False) return def cb(q: str) -> None: p = self.granted_passwords.get(password) if p is None: p = self.granted_passwords[password] = GrantedPermission() callback(q in ('a', 'w')) match q: case 'w': if for_write: p.write = True else: p.read = True case 'b': if for_write: p.write = False p.write_ban = True else: p.read = False p.read_ban = True if for_write: msg = _('The program {0} running in this window wants to write to the system clipboard.') else: msg = _('The program {0} running in this window wants to read from the system clipboard.') msg += '\n\n' + ('If you choose "Always" similar requests from this program will be automatically allowed for the rest of this session.') msg += '\n\n' + ('If you choose "Ban" similar requests from this program will be automatically dis-allowed for the rest of this session.') from kittens.tui.operations import styled get_boss().choose(msg.format(styled(human_name, fg='yellow')), cb, 'a;green:Allow', 'w;yellow:Always', 'd;red:Deny', 'b;red:Ban', default='d', window=window, title=_('A program wants to access the clipboard')) def password_is_allowed_already(self, password: str, for_write: bool = False) -> bool: q = self.granted_passwords.get(password) if q is not None: if q.one_time: self.granted_passwords.pop(password, None) return q.write if for_write else q.read return False 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 send_paste_event(self, is_primary_selection: bool) -> None: from kitty.short_uuid import uuid4 pw = uuid4() self.granted_passwords[pw] = GrantedPermission(read=True, one_time=True) rr = ReadRequest(is_primary_selection=is_primary_selection, mime_types=(TARGETS_MIME,), protocol_type=ProtocolType.osc_5522) rr = rr._replace(otp_for_response=pw) self.fulfill_read_request(rr) 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 if rr.password and rr.human_name: if self.password_is_allowed_already(rr.password): self.handle_clipboard_confirmation(True) return if (p := self.granted_passwords.get(rr.password)) and p.read_ban: self.handle_clipboard_confirmation(False) return self.request_permission(w, rr.human_name, rr.password, self.handle_clipboard_confirmation) else: if rr.human_name: msg = _( 'The program {} running in this window wants to read from the system clipboard.' ' Allow it to do so, once?').format(rr.human_name) else: msg = _( 'A program running in this window wants to read from the system clipboard.' ' Allow it to do so, once?') get_boss().confirm(msg, 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() ================================================ FILE: kitty/cocoa_window.h ================================================ /* * 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, CLEAR_LAST_COMMAND, RELOAD_CONFIG, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY, MACOS_CYCLE_THROUGH_OS_WINDOWS, MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS, SEARCH_SCROLLBACK, 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_application_lifecycle_event(bool); 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); void cocoa_set_dock_badge(const char *label); void cocoa_clear_dock_badge_if_set(void); ================================================ FILE: kitty/cocoa_window.m ================================================ /* * 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 bool application_has_finished_launching = false; 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(clear_last_command, CLEAR_LAST_COMMAND) PENDING(reload_config, RELOAD_CONFIG) PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY) PENDING(macos_cycle_through_os_windows, MACOS_CYCLE_THROUGH_OS_WINDOWS) PENDING(macos_cycle_through_os_windows_backwards, MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS) PENDING(search_scrollback, SEARCH_SCROLLBACK) 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_last_command:) || 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, clear_last_command; GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen, open_kitty_website; GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit, search_scrollback; GlobalShortcut macos_cycle_through_os_windows, macos_cycle_through_os_windows_backwards; } 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(clear_last_command); 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); else Q(search_scrollback); else Q(macos_cycle_through_os_windows); else Q(macos_cycle_through_os_windows_backwards); #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; } - (void)quickAccessTerminal:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString **)error { // we ignore event during application launch as it will cause the window to be shown and hidden static bool is_first_event = true; if (!is_first_event || monotonic() >= s_double_to_monotonic_t(2.0)) { call_boss(quick_access_terminal_invoked, NULL); } is_first_event = false; } @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; } } static 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); MENU_ITEM(editMenu, @"Clear Last Command", clear_last_command); MENU_ITEM(editMenu, @"Find", search_scrollback); [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]]; MENU_ITEM(windowMenu, @"Cycle Through OS Windows", macos_cycle_through_os_windows); MENU_ITEM(windowMenu, @"Cycle Through OS Windows backwards", macos_cycle_through_os_windows_backwards); [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_application_lifecycle_event(bool application_launch_finished) { if (application_launch_finished) { // applicationDidFinishLaunching application_has_finished_launching = true; } else cocoa_create_global_menu(); // applicationWillFinishLaunching } 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; NSString *menuTitle = [NSString stringWithFormat:@" :: %@", title]; if (title_menu != NULL) { [[title_menu submenu] setTitle:menuTitle]; } else { NSMenu *bar = [NSApp mainMenu]; title_menu = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu *m = [[NSMenu alloc] initWithTitle:menuTitle]; [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) { if (![dock_pbar isIndeterminate]) { [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; } // }}} // Dock badge {{{ static bool dock_badge_is_set = false; void cocoa_set_dock_badge(const char *label) { @autoreleasepool { NSDockTile *dockTile = [NSApp dockTile]; [dockTile setBadgeLabel:label ? @(label) : nil]; [dockTile display]; dock_badge_is_set = (label != NULL); } } void cocoa_clear_dock_badge_if_set(void) { if (dock_badge_is_set) cocoa_set_dock_badge(NULL); } // }}} 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); [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note UNUSED) { cocoa_set_dock_badge(NULL); }]; return true; } ================================================ FILE: kitty/color-names.h ================================================ /* ANSI-C code produced by gperf version 3.3 */ /* Command-line: gperf -m 2000 --struct-type --includes --readonly-tables --lookup-function-name in_color_name_set --global-table --null-strings --hash-function-name color_name_hash --word-array-name color_names --pic --compare-strncmp /dev/stdin */ /* Computed positions: -k'1,3,5-9,12-15,$' */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) /* The character set is not based on ISO-646. */ #error "gperf generated tables don't work with this execution character set. Please report a bug to ." #endif #line 1 "/dev/stdin" struct Keyword { int name, value; }; #include #define TOTAL_KEYWORDS 753 #define MIN_WORD_LENGTH 3 #define MAX_WORD_LENGTH 22 #define MIN_HASH_VALUE 172 #define MAX_HASH_VALUE 3478 /* maximum key range = 3307, duplicates = 0 */ #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static unsigned int color_name_hash (register const char *str, register size_t len) { static const unsigned short asso_values[] = { 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 384, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 689, 61, 60, 57, 56, 917, 884, 827, 824, 815, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 72, 68, 615, 56, 56, 92, 56, 375, 575, 56, 631, 86, 289, 101, 75, 202, 134, 57, 56, 191, 137, 987, 777, 3479, 239, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479, 3479 }; register unsigned int hval = len; switch (hval) { default: hval += asso_values[(unsigned char)str[14]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 14: hval += asso_values[(unsigned char)str[13]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 13: hval += asso_values[(unsigned char)str[12]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 12: hval += asso_values[(unsigned char)str[11]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 11: case 10: case 9: hval += asso_values[(unsigned char)str[8]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 8: hval += asso_values[(unsigned char)str[7]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 7: hval += asso_values[(unsigned char)str[6]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 6: hval += asso_values[(unsigned char)str[5]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 5: hval += asso_values[(unsigned char)str[4]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 4: case 3: hval += asso_values[(unsigned char)str[2]]; #if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9))) [[fallthrough]]; #elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10) __attribute__ ((__fallthrough__)); #endif /*FALLTHROUGH*/ case 2: case 1: hval += asso_values[(unsigned char)str[0]]; break; } return hval + asso_values[(unsigned char)str[len - 1]]; } struct stringpool_t { char stringpool_str172[sizeof("red")]; char stringpool_str173[sizeof("red4")]; char stringpool_str174[sizeof("red3")]; char stringpool_str177[sizeof("red2")]; char stringpool_str178[sizeof("red1")]; char stringpool_str202[sizeof("gold")]; char stringpool_str229[sizeof("grey4")]; char stringpool_str231[sizeof("grey3")]; char stringpool_str237[sizeof("grey2")]; char stringpool_str239[sizeof("grey1")]; char stringpool_str245[sizeof("gray4")]; char stringpool_str247[sizeof("gray3")]; char stringpool_str248[sizeof("snow4")]; char stringpool_str250[sizeof("snow3")]; char stringpool_str253[sizeof("gray2")]; char stringpool_str255[sizeof("gray1")]; char stringpool_str256[sizeof("snow2")]; char stringpool_str258[sizeof("snow1")]; char stringpool_str259[sizeof("gold4")]; char stringpool_str261[sizeof("gold3")]; char stringpool_str265[sizeof("blue")]; char stringpool_str267[sizeof("gold2")]; char stringpool_str269[sizeof("gold1")]; char stringpool_str286[sizeof("grey44")]; char stringpool_str287[sizeof("grey34")]; char stringpool_str288[sizeof("grey43")]; char stringpool_str289[sizeof("grey33")]; char stringpool_str290[sizeof("grey24")]; char stringpool_str291[sizeof("grey14")]; char stringpool_str292[sizeof("grey23")]; char stringpool_str293[sizeof("grey13")]; char stringpool_str294[sizeof("grey42")]; char stringpool_str295[sizeof("grey32")]; char stringpool_str296[sizeof("grey41")]; char stringpool_str297[sizeof("grey31")]; char stringpool_str298[sizeof("grey22")]; char stringpool_str299[sizeof("grey12")]; char stringpool_str300[sizeof("grey21")]; char stringpool_str301[sizeof("grey11")]; char stringpool_str302[sizeof("gray44")]; char stringpool_str303[sizeof("gray34")]; char stringpool_str304[sizeof("gray43")]; char stringpool_str305[sizeof("gray33")]; char stringpool_str306[sizeof("gray24")]; char stringpool_str307[sizeof("gray14")]; char stringpool_str308[sizeof("gray23")]; char stringpool_str309[sizeof("gray13")]; char stringpool_str310[sizeof("gray42")]; char stringpool_str311[sizeof("gray32")]; char stringpool_str312[sizeof("gray41")]; char stringpool_str313[sizeof("gray31")]; char stringpool_str314[sizeof("gray22")]; char stringpool_str315[sizeof("gray12")]; char stringpool_str316[sizeof("gray21")]; char stringpool_str317[sizeof("gray11")]; char stringpool_str319[sizeof("green")]; char stringpool_str321[sizeof("orange")]; char stringpool_str322[sizeof("blue4")]; char stringpool_str324[sizeof("blue3")]; char stringpool_str326[sizeof("azure")]; char stringpool_str330[sizeof("blue2")]; char stringpool_str331[sizeof("green4")]; char stringpool_str332[sizeof("blue1")]; char stringpool_str333[sizeof("green3")]; char stringpool_str339[sizeof("green2")]; char stringpool_str341[sizeof("green1")]; char stringpool_str345[sizeof("darkred")]; char stringpool_str350[sizeof("brown")]; char stringpool_str352[sizeof("tan4")]; char stringpool_str353[sizeof("tan3")]; char stringpool_str355[sizeof("grey")]; char stringpool_str356[sizeof("tan2")]; char stringpool_str357[sizeof("tan1")]; char stringpool_str362[sizeof("brown4")]; char stringpool_str363[sizeof("sienna")]; char stringpool_str364[sizeof("brown3")]; char stringpool_str370[sizeof("brown2")]; char stringpool_str371[sizeof("gray")]; char stringpool_str372[sizeof("brown1")]; char stringpool_str378[sizeof("orange4")]; char stringpool_str379[sizeof("bisque")]; char stringpool_str380[sizeof("orange3")]; char stringpool_str383[sizeof("azure4")]; char stringpool_str385[sizeof("azure3")]; char stringpool_str386[sizeof("orange2")]; char stringpool_str388[sizeof("orange1")]; char stringpool_str391[sizeof("azure2")]; char stringpool_str393[sizeof("azure1")]; char stringpool_str394[sizeof("linen")]; char stringpool_str396[sizeof("tan")]; char stringpool_str400[sizeof("peru")]; char stringpool_str404[sizeof("sienna4")]; char stringpool_str406[sizeof("sienna3")]; char stringpool_str412[sizeof("sienna2")]; char stringpool_str414[sizeof("sienna1")]; char stringpool_str420[sizeof("pink4")]; char stringpool_str422[sizeof("pink3")]; char stringpool_str425[sizeof("salmon")]; char stringpool_str428[sizeof("pink2")]; char stringpool_str430[sizeof("pink1")]; char stringpool_str436[sizeof("bisque4")]; char stringpool_str437[sizeof("salmon4")]; char stringpool_str438[sizeof("bisque3")]; char stringpool_str439[sizeof("salmon3")]; char stringpool_str444[sizeof("bisque2")]; char stringpool_str445[sizeof("salmon2")]; char stringpool_str446[sizeof("bisque1")]; char stringpool_str447[sizeof("salmon1")]; char stringpool_str456[sizeof("plum4")]; char stringpool_str458[sizeof("plum3")]; char stringpool_str463[sizeof("purple")]; char stringpool_str464[sizeof("plum2")]; char stringpool_str466[sizeof("plum1")]; char stringpool_str493[sizeof("orangered")]; char stringpool_str494[sizeof("orangered4")]; char stringpool_str495[sizeof("orangered3")]; char stringpool_str498[sizeof("orangered2")]; char stringpool_str499[sizeof("orangered1")]; char stringpool_str507[sizeof("seagreen")]; char stringpool_str519[sizeof("seagreen4")]; char stringpool_str520[sizeof("purple4")]; char stringpool_str521[sizeof("seagreen3")]; char stringpool_str522[sizeof("purple3")]; char stringpool_str524[sizeof("darkblue")]; char stringpool_str527[sizeof("seagreen2")]; char stringpool_str528[sizeof("purple2")]; char stringpool_str529[sizeof("seagreen1")]; char stringpool_str530[sizeof("purple1")]; char stringpool_str531[sizeof("debianred")]; char stringpool_str540[sizeof("darkorange")]; char stringpool_str541[sizeof("darkorange4")]; char stringpool_str542[sizeof("darkorange3")]; char stringpool_str545[sizeof("darkorange2")]; char stringpool_str546[sizeof("darkorange1")]; char stringpool_str549[sizeof("darkgreen")]; char stringpool_str551[sizeof("springgreen")]; char stringpool_str552[sizeof("goldenrod")]; char stringpool_str553[sizeof("goldenrod4")]; char stringpool_str554[sizeof("goldenrod3")]; char stringpool_str557[sizeof("goldenrod2")]; char stringpool_str558[sizeof("goldenrod1")]; char stringpool_str563[sizeof("springgreen4")]; char stringpool_str564[sizeof("sea green")]; char stringpool_str565[sizeof("springgreen3")]; char stringpool_str566[sizeof("saddlebrown")]; char stringpool_str571[sizeof("springgreen2")]; char stringpool_str573[sizeof("springgreen1")]; char stringpool_str582[sizeof("dodgerblue")]; char stringpool_str583[sizeof("dodgerblue4")]; char stringpool_str584[sizeof("dodgerblue3")]; char stringpool_str587[sizeof("dodgerblue2")]; char stringpool_str588[sizeof("dodgerblue1")]; char stringpool_str596[sizeof("slateblue")]; char stringpool_str597[sizeof("slateblue4")]; char stringpool_str598[sizeof("slateblue3")]; char stringpool_str601[sizeof("slateblue2")]; char stringpool_str602[sizeof("slateblue1")]; char stringpool_str610[sizeof("steelblue")]; char stringpool_str611[sizeof("steelblue4")]; char stringpool_str612[sizeof("steelblue3")]; char stringpool_str615[sizeof("steelblue2")]; char stringpool_str616[sizeof("steelblue1")]; char stringpool_str624[sizeof("darkseagreen")]; char stringpool_str629[sizeof("maroon")]; char stringpool_str632[sizeof("plum")]; char stringpool_str636[sizeof("darkseagreen4")]; char stringpool_str637[sizeof("skyblue")]; char stringpool_str638[sizeof("darkseagreen3")]; char stringpool_str641[sizeof("maroon4")]; char stringpool_str642[sizeof("darkgoldenrod")]; char stringpool_str643[sizeof("maroon3")]; char stringpool_str644[sizeof("darkseagreen2")]; char stringpool_str646[sizeof("darkseagreen1")]; char stringpool_str649[sizeof("maroon2")]; char stringpool_str651[sizeof("maroon1")]; char stringpool_str669[sizeof("lightgreen")]; char stringpool_str674[sizeof("slategray4")]; char stringpool_str675[sizeof("slategray3")]; char stringpool_str677[sizeof("forestgreen")]; char stringpool_str678[sizeof("slategray2")]; char stringpool_str679[sizeof("slategray1")]; char stringpool_str680[sizeof("palegreen4")]; char stringpool_str681[sizeof("palegreen3")]; char stringpool_str684[sizeof("palegreen2")]; char stringpool_str685[sizeof("palegreen1")]; char stringpool_str694[sizeof("skyblue4")]; char stringpool_str696[sizeof("skyblue3")]; char stringpool_str699[sizeof("darkgoldenrod4")]; char stringpool_str701[sizeof("darkgoldenrod3")]; char stringpool_str702[sizeof("skyblue2")]; char stringpool_str704[sizeof("skyblue1")]; char stringpool_str706[sizeof("sky blue")]; char stringpool_str707[sizeof("darkgoldenrod2")]; char stringpool_str709[sizeof("darkgoldenrod1")]; char stringpool_str724[sizeof("palegreen")]; char stringpool_str730[sizeof("dark red")]; char stringpool_str745[sizeof("lightblue")]; char stringpool_str746[sizeof("lightblue4")]; char stringpool_str747[sizeof("lightblue3")]; char stringpool_str750[sizeof("lightblue2")]; char stringpool_str751[sizeof("lightblue1")]; char stringpool_str760[sizeof("beige")]; char stringpool_str768[sizeof("darkgrey")]; char stringpool_str770[sizeof("darkmagenta")]; char stringpool_str784[sizeof("darkgray")]; char stringpool_str788[sizeof("magenta")]; char stringpool_str792[sizeof("cyan")]; char stringpool_str794[sizeof("royalblue")]; char stringpool_str795[sizeof("royalblue4")]; char stringpool_str796[sizeof("royalblue3")]; char stringpool_str799[sizeof("royalblue2")]; char stringpool_str800[sizeof("royalblue1")]; char stringpool_str802[sizeof("darksalmon")]; char stringpool_str804[sizeof("cyan4")]; char stringpool_str806[sizeof("cyan3")]; char stringpool_str811[sizeof("limegreen")]; char stringpool_str812[sizeof("cyan2")]; char stringpool_str814[sizeof("cyan1")]; char stringpool_str817[sizeof("palegoldenrod")]; char stringpool_str822[sizeof("orange red")]; char stringpool_str825[sizeof("seashell")]; char stringpool_str827[sizeof("tomato")]; char stringpool_str829[sizeof("magenta4")]; char stringpool_str830[sizeof("dodger blue")]; char stringpool_str831[sizeof("magenta3")]; char stringpool_str833[sizeof("dark green")]; char stringpool_str836[sizeof("darkslateblue")]; char stringpool_str837[sizeof("magenta2")]; char stringpool_str839[sizeof("magenta1")]; char stringpool_str840[sizeof("slategrey")]; char stringpool_str844[sizeof("lightseagreen")]; char stringpool_str849[sizeof("coral")]; char stringpool_str852[sizeof("seashell4")]; char stringpool_str854[sizeof("seashell3")]; char stringpool_str856[sizeof("slategray")]; char stringpool_str860[sizeof("seashell2")]; char stringpool_str862[sizeof("seashell1")]; char stringpool_str864[sizeof("lightgoldenrod")]; char stringpool_str865[sizeof("tomato4")]; char stringpool_str867[sizeof("tomato3")]; char stringpool_str869[sizeof("dark orange")]; char stringpool_str873[sizeof("tomato2")]; char stringpool_str875[sizeof("tomato1")]; char stringpool_str876[sizeof("coral4")]; char stringpool_str878[sizeof("coral3")]; char stringpool_str884[sizeof("coral2")]; char stringpool_str886[sizeof("coral1")]; char stringpool_str893[sizeof("mistyrose")]; char stringpool_str894[sizeof("mistyrose4")]; char stringpool_str895[sizeof("mistyrose3")]; char stringpool_str898[sizeof("mistyrose2")]; char stringpool_str899[sizeof("mistyrose1")]; char stringpool_str909[sizeof("dark blue")]; char stringpool_str912[sizeof("snow")]; char stringpool_str921[sizeof("lightgoldenrod4")]; char stringpool_str923[sizeof("lightgoldenrod3")]; char stringpool_str924[sizeof("lightyellow4")]; char stringpool_str925[sizeof("slate blue")]; char stringpool_str926[sizeof("lightyellow3")]; char stringpool_str929[sizeof("lightgoldenrod2")]; char stringpool_str931[sizeof("lightgoldenrod1")]; char stringpool_str932[sizeof("lightyellow2")]; char stringpool_str934[sizeof("lightyellow1")]; char stringpool_str937[sizeof("oldlace")]; char stringpool_str938[sizeof("pink")]; char stringpool_str939[sizeof("steel blue")]; char stringpool_str943[sizeof("dimgrey")]; char stringpool_str948[sizeof("lightsalmon")]; char stringpool_str950[sizeof("darkturquoise")]; char stringpool_str959[sizeof("dimgray")]; char stringpool_str960[sizeof("lightsalmon4")]; char stringpool_str962[sizeof("lightsalmon3")]; char stringpool_str968[sizeof("lightsalmon2")]; char stringpool_str970[sizeof("lightsalmon1")]; char stringpool_str977[sizeof("saddle brown")]; char stringpool_str981[sizeof("spring green")]; char stringpool_str986[sizeof("slate grey")]; char stringpool_str989[sizeof("lightgrey")]; char stringpool_str998[sizeof("light green")]; char stringpool_str1000[sizeof("dim grey")]; char stringpool_str1002[sizeof("slate gray")]; char stringpool_str1005[sizeof("lightgray")]; char stringpool_str1007[sizeof("ivory4")]; char stringpool_str1008[sizeof("pale green")]; char stringpool_str1009[sizeof("ivory3")]; char stringpool_str1011[sizeof("darkslategray4")]; char stringpool_str1013[sizeof("darkslategray3")]; char stringpool_str1015[sizeof("ivory2")]; char stringpool_str1016[sizeof("dim gray")]; char stringpool_str1017[sizeof("ivory1")]; char stringpool_str1019[sizeof("darkslategray2")]; char stringpool_str1021[sizeof("darkslategray1")]; char stringpool_str1024[sizeof("old lace")]; char stringpool_str1025[sizeof("olivedrab4")]; char stringpool_str1026[sizeof("olivedrab3")]; char stringpool_str1028[sizeof("dark goldenrod")]; char stringpool_str1029[sizeof("olivedrab2")]; char stringpool_str1030[sizeof("olivedrab1")]; char stringpool_str1036[sizeof("olivedrab")]; char stringpool_str1038[sizeof("indianred")]; char stringpool_str1039[sizeof("indianred4")]; char stringpool_str1040[sizeof("indianred3")]; char stringpool_str1041[sizeof("lightsteelblue")]; char stringpool_str1043[sizeof("indianred2")]; char stringpool_str1044[sizeof("indianred1")]; char stringpool_str1045[sizeof("grey94")]; char stringpool_str1046[sizeof("gainsboro")]; char stringpool_str1047[sizeof("grey93")]; char stringpool_str1053[sizeof("grey92")]; char stringpool_str1054[sizeof("grey84")]; char stringpool_str1055[sizeof("grey91")]; char stringpool_str1056[sizeof("grey83")]; char stringpool_str1057[sizeof("grey74")]; char stringpool_str1059[sizeof("grey73")]; char stringpool_str1061[sizeof("gray94")]; char stringpool_str1062[sizeof("grey82")]; char stringpool_str1063[sizeof("gray93")]; char stringpool_str1064[sizeof("grey81")]; char stringpool_str1065[sizeof("grey72")]; char stringpool_str1067[sizeof("grey71")]; char stringpool_str1069[sizeof("gray92")]; char stringpool_str1070[sizeof("gray84")]; char stringpool_str1071[sizeof("gray91")]; char stringpool_str1072[sizeof("gray83")]; char stringpool_str1073[sizeof("gray74")]; char stringpool_str1074[sizeof("light blue")]; char stringpool_str1075[sizeof("gray73")]; char stringpool_str1078[sizeof("gray82")]; char stringpool_str1080[sizeof("gray81")]; char stringpool_str1081[sizeof("gray72")]; char stringpool_str1083[sizeof("gray71")]; char stringpool_str1087[sizeof("lightslateblue")]; char stringpool_str1092[sizeof("sandy brown")]; char stringpool_str1095[sizeof("lime green")]; char stringpool_str1098[sizeof("lightsteelblue4")]; char stringpool_str1100[sizeof("lightsteelblue3")]; char stringpool_str1106[sizeof("lightsteelblue2")]; char stringpool_str1107[sizeof("forest green")]; char stringpool_str1108[sizeof("lightsteelblue1")]; char stringpool_str1112[sizeof("dark salmon")]; char stringpool_str1114[sizeof("grey64")]; char stringpool_str1115[sizeof("aliceblue")]; char stringpool_str1116[sizeof("grey63")]; char stringpool_str1121[sizeof("darkslategrey")]; char stringpool_str1122[sizeof("grey62")]; char stringpool_str1123[sizeof("royal blue")]; char stringpool_str1124[sizeof("grey61")]; char stringpool_str1125[sizeof("paleturquoise")]; char stringpool_str1126[sizeof("dark magenta")]; char stringpool_str1128[sizeof("mediumblue")]; char stringpool_str1130[sizeof("gray64")]; char stringpool_str1132[sizeof("gray63")]; char stringpool_str1133[sizeof("ivory")]; char stringpool_str1135[sizeof("light grey")]; char stringpool_str1137[sizeof("darkslategray")]; char stringpool_str1138[sizeof("gray62")]; char stringpool_str1140[sizeof("gray61")]; char stringpool_str1142[sizeof("wheat4")]; char stringpool_str1144[sizeof("wheat3")]; char stringpool_str1145[sizeof("light salmon")]; char stringpool_str1147[sizeof("grey54")]; char stringpool_str1149[sizeof("grey53")]; char stringpool_str1150[sizeof("wheat2")]; char stringpool_str1151[sizeof("light gray")]; char stringpool_str1152[sizeof("wheat1")]; char stringpool_str1153[sizeof("dark grey")]; char stringpool_str1155[sizeof("grey52")]; char stringpool_str1157[sizeof("grey51")]; char stringpool_str1162[sizeof("thistle")]; char stringpool_str1163[sizeof("gray54")]; char stringpool_str1165[sizeof("gray53")]; char stringpool_str1169[sizeof("dark gray")]; char stringpool_str1171[sizeof("gray52")]; char stringpool_str1173[sizeof("gray51")]; char stringpool_str1182[sizeof("paleturquoise4")]; char stringpool_str1184[sizeof("paleturquoise3")]; char stringpool_str1190[sizeof("paleturquoise2")]; char stringpool_str1192[sizeof("paleturquoise1")]; char stringpool_str1203[sizeof("pale goldenrod")]; char stringpool_str1212[sizeof("turquoise")]; char stringpool_str1213[sizeof("turquoise4")]; char stringpool_str1214[sizeof("turquoise3")]; char stringpool_str1217[sizeof("turquoise2")]; char stringpool_str1218[sizeof("turquoise1")]; char stringpool_str1219[sizeof("thistle4")]; char stringpool_str1220[sizeof("wheat")]; char stringpool_str1221[sizeof("thistle3")]; char stringpool_str1222[sizeof("misty rose")]; char stringpool_str1227[sizeof("thistle2")]; char stringpool_str1229[sizeof("thistle1")]; char stringpool_str1235[sizeof("chocolate")]; char stringpool_str1236[sizeof("chocolate4")]; char stringpool_str1237[sizeof("chocolate3")]; char stringpool_str1238[sizeof("peachpuff4")]; char stringpool_str1239[sizeof("peachpuff3")]; char stringpool_str1240[sizeof("chocolate2")]; char stringpool_str1241[sizeof("chocolate1")]; char stringpool_str1242[sizeof("peachpuff2")]; char stringpool_str1243[sizeof("peachpuff1")]; char stringpool_str1248[sizeof("lightcoral")]; char stringpool_str1249[sizeof("darkcyan")]; char stringpool_str1250[sizeof("chartreuse")]; char stringpool_str1251[sizeof("chartreuse4")]; char stringpool_str1252[sizeof("chartreuse3")]; char stringpool_str1255[sizeof("chartreuse2")]; char stringpool_str1256[sizeof("chartreuse1")]; char stringpool_str1257[sizeof("rosybrown4")]; char stringpool_str1258[sizeof("rosybrown3")]; char stringpool_str1259[sizeof("deepskyblue")]; char stringpool_str1261[sizeof("rosybrown2")]; char stringpool_str1262[sizeof("rosybrown1")]; char stringpool_str1273[sizeof("peachpuff")]; char stringpool_str1274[sizeof("cadetblue")]; char stringpool_str1275[sizeof("cadetblue4")]; char stringpool_str1276[sizeof("cadetblue3")]; char stringpool_str1279[sizeof("cadetblue2")]; char stringpool_str1280[sizeof("cadetblue1")]; char stringpool_str1283[sizeof("mediumseagreen")]; char stringpool_str1287[sizeof("light sea green")]; char stringpool_str1291[sizeof("mediumpurple")]; char stringpool_str1294[sizeof("light goldenrod")]; char stringpool_str1296[sizeof("yellow4")]; char stringpool_str1298[sizeof("yellow3")]; char stringpool_str1299[sizeof("lawngreen")]; char stringpool_str1301[sizeof("rosybrown")]; char stringpool_str1304[sizeof("yellow2")]; char stringpool_str1306[sizeof("yellow1")]; char stringpool_str1316[sizeof("deepskyblue4")]; char stringpool_str1318[sizeof("deepskyblue3")]; char stringpool_str1320[sizeof("dark slate blue")]; char stringpool_str1324[sizeof("deepskyblue2")]; char stringpool_str1326[sizeof("deepskyblue1")]; char stringpool_str1331[sizeof("navy")]; char stringpool_str1343[sizeof("lightslategrey")]; char stringpool_str1348[sizeof("mediumpurple4")]; char stringpool_str1350[sizeof("mediumpurple3")]; char stringpool_str1353[sizeof("olive drab")]; char stringpool_str1356[sizeof("mediumpurple2")]; char stringpool_str1358[sizeof("mediumpurple1")]; char stringpool_str1359[sizeof("lightslategray")]; char stringpool_str1367[sizeof("indian red")]; char stringpool_str1369[sizeof("aquamarine")]; char stringpool_str1370[sizeof("aquamarine4")]; char stringpool_str1371[sizeof("aquamarine3")]; char stringpool_str1374[sizeof("aquamarine2")]; char stringpool_str1375[sizeof("aquamarine1")]; char stringpool_str1376[sizeof("medium blue")]; char stringpool_str1383[sizeof("orchid")]; char stringpool_str1393[sizeof("dark sea green")]; char stringpool_str1396[sizeof("khaki4")]; char stringpool_str1398[sizeof("khaki3")]; char stringpool_str1403[sizeof("mediumslateblue")]; char stringpool_str1404[sizeof("khaki2")]; char stringpool_str1406[sizeof("khaki1")]; char stringpool_str1407[sizeof("black")]; char stringpool_str1408[sizeof("lavender")]; char stringpool_str1412[sizeof("burlywood")]; char stringpool_str1413[sizeof("burlywood4")]; char stringpool_str1414[sizeof("burlywood3")]; char stringpool_str1417[sizeof("burlywood2")]; char stringpool_str1418[sizeof("burlywood1")]; char stringpool_str1426[sizeof("lightcyan4")]; char stringpool_str1427[sizeof("lightcyan3")]; char stringpool_str1429[sizeof("mediumspringgreen")]; char stringpool_str1430[sizeof("lightcyan2")]; char stringpool_str1431[sizeof("lightcyan1")]; char stringpool_str1440[sizeof("orchid4")]; char stringpool_str1442[sizeof("orchid3")]; char stringpool_str1444[sizeof("alice blue")]; char stringpool_str1448[sizeof("orchid2")]; char stringpool_str1449[sizeof("powderblue")]; char stringpool_str1450[sizeof("orchid1")]; char stringpool_str1451[sizeof("lightskyblue")]; char stringpool_str1458[sizeof("yellowgreen")]; char stringpool_str1468[sizeof("greenyellow")]; char stringpool_str1469[sizeof("white")]; char stringpool_str1470[sizeof("lightcyan")]; char stringpool_str1484[sizeof("sandybrown")]; char stringpool_str1495[sizeof("grey0")]; char stringpool_str1499[sizeof("navyblue")]; char stringpool_str1506[sizeof("violet")]; char stringpool_str1508[sizeof("lightskyblue4")]; char stringpool_str1510[sizeof("lightskyblue3")]; char stringpool_str1511[sizeof("gray0")]; char stringpool_str1516[sizeof("lightskyblue2")]; char stringpool_str1518[sizeof("lightskyblue1")]; char stringpool_str1543[sizeof("violetred")]; char stringpool_str1544[sizeof("violetred4")]; char stringpool_str1545[sizeof("violetred3")]; char stringpool_str1548[sizeof("violetred2")]; char stringpool_str1549[sizeof("violetred1")]; char stringpool_str1552[sizeof("grey40")]; char stringpool_str1553[sizeof("grey30")]; char stringpool_str1556[sizeof("grey20")]; char stringpool_str1557[sizeof("grey10")]; char stringpool_str1561[sizeof("light coral")]; char stringpool_str1564[sizeof("dark slate grey")]; char stringpool_str1566[sizeof("peach puff")]; char stringpool_str1568[sizeof("gray40")]; char stringpool_str1569[sizeof("gray30")]; char stringpool_str1572[sizeof("gray20")]; char stringpool_str1573[sizeof("gray10")]; char stringpool_str1580[sizeof("dark slate gray")]; char stringpool_str1583[sizeof("lawn green")]; char stringpool_str1585[sizeof("rosy brown")]; char stringpool_str1588[sizeof("lightyellow")]; char stringpool_str1603[sizeof("cadet blue")]; char stringpool_str1609[sizeof("medium sea green")]; char stringpool_str1616[sizeof("blanchedalmond")]; char stringpool_str1634[sizeof("dark cyan")]; char stringpool_str1642[sizeof("mediumorchid")]; char stringpool_str1678[sizeof("light slate blue")]; char stringpool_str1686[sizeof("dark orchid")]; char stringpool_str1697[sizeof("powder blue")]; char stringpool_str1699[sizeof("mediumorchid4")]; char stringpool_str1701[sizeof("mediumorchid3")]; char stringpool_str1705[sizeof("medium purple")]; char stringpool_str1707[sizeof("mediumorchid2")]; char stringpool_str1709[sizeof("mediumorchid1")]; char stringpool_str1725[sizeof("honeydew4")]; char stringpool_str1727[sizeof("honeydew3")]; char stringpool_str1733[sizeof("honeydew2")]; char stringpool_str1734[sizeof("midnightblue")]; char stringpool_str1735[sizeof("honeydew1")]; char stringpool_str1739[sizeof("light slate grey")]; char stringpool_str1742[sizeof("deeppink4")]; char stringpool_str1744[sizeof("deeppink3")]; char stringpool_str1747[sizeof("grey9")]; char stringpool_str1750[sizeof("deeppink2")]; char stringpool_str1752[sizeof("deeppink1")]; char stringpool_str1754[sizeof("light cyan")]; char stringpool_str1755[sizeof("light slate gray")]; char stringpool_str1763[sizeof("gray9")]; char stringpool_str1765[sizeof("grey8")]; char stringpool_str1767[sizeof("light steel blue")]; char stringpool_str1771[sizeof("grey7")]; char stringpool_str1773[sizeof("dark turquoise")]; char stringpool_str1777[sizeof("mintcream")]; char stringpool_str1781[sizeof("gray8")]; char stringpool_str1787[sizeof("gray7")]; char stringpool_str1804[sizeof("grey49")]; char stringpool_str1805[sizeof("grey39")]; char stringpool_str1808[sizeof("grey29")]; char stringpool_str1809[sizeof("grey19")]; char stringpool_str1817[sizeof("moccasin")]; char stringpool_str1820[sizeof("gray49")]; char stringpool_str1821[sizeof("gray39")]; char stringpool_str1822[sizeof("grey48")]; char stringpool_str1823[sizeof("grey38")]; char stringpool_str1824[sizeof("gray29")]; char stringpool_str1825[sizeof("gray19")]; char stringpool_str1826[sizeof("grey28")]; char stringpool_str1827[sizeof("grey18")]; char stringpool_str1828[sizeof("grey47")]; char stringpool_str1829[sizeof("grey37")]; char stringpool_str1830[sizeof("lightgoldenrodyellow")]; char stringpool_str1832[sizeof("grey27")]; char stringpool_str1833[sizeof("grey17")]; char stringpool_str1838[sizeof("gray48")]; char stringpool_str1839[sizeof("gray38")]; char stringpool_str1842[sizeof("gray28")]; char stringpool_str1843[sizeof("gray18")]; char stringpool_str1844[sizeof("gray47")]; char stringpool_str1845[sizeof("gray37")]; char stringpool_str1848[sizeof("gray27")]; char stringpool_str1849[sizeof("gray17")]; char stringpool_str1858[sizeof("khaki")]; char stringpool_str1866[sizeof("antiquewhite")]; char stringpool_str1872[sizeof("violet red")]; char stringpool_str1873[sizeof("mint cream")]; char stringpool_str1876[sizeof("darkorchid")]; char stringpool_str1877[sizeof("darkorchid4")]; char stringpool_str1878[sizeof("darkorchid3")]; char stringpool_str1881[sizeof("darkorchid2")]; char stringpool_str1882[sizeof("darkorchid1")]; char stringpool_str1884[sizeof("navy blue")]; char stringpool_str1885[sizeof("grey6")]; char stringpool_str1888[sizeof("yellow green")]; char stringpool_str1901[sizeof("gray6")]; char stringpool_str1908[sizeof("lightpink4")]; char stringpool_str1909[sizeof("lightpink3")]; char stringpool_str1912[sizeof("lightpink2")]; char stringpool_str1913[sizeof("lightpink1")]; char stringpool_str1923[sizeof("antiquewhite4")]; char stringpool_str1925[sizeof("antiquewhite3")]; char stringpool_str1931[sizeof("antiquewhite2")]; char stringpool_str1933[sizeof("antiquewhite1")]; char stringpool_str1942[sizeof("grey46")]; char stringpool_str1943[sizeof("grey36")]; char stringpool_str1946[sizeof("grey26")]; char stringpool_str1947[sizeof("grey16")]; char stringpool_str1948[sizeof("pale turquoise")]; char stringpool_str1951[sizeof("grey5")]; char stringpool_str1958[sizeof("gray46")]; char stringpool_str1959[sizeof("gray36")]; char stringpool_str1960[sizeof("yellow")]; char stringpool_str1962[sizeof("gray26")]; char stringpool_str1963[sizeof("gray16")]; char stringpool_str1964[sizeof("medium slate blue")]; char stringpool_str1967[sizeof("gray5")]; char stringpool_str1968[sizeof("lavenderblush4")]; char stringpool_str1970[sizeof("lavenderblush3")]; char stringpool_str1976[sizeof("lavenderblush2")]; char stringpool_str1978[sizeof("lavenderblush1")]; char stringpool_str1985[sizeof("floral white")]; char stringpool_str1987[sizeof("medium orchid")]; char stringpool_str1989[sizeof("mediumturquoise")]; char stringpool_str1991[sizeof("mediumaquamarine")]; char stringpool_str1992[sizeof("light sky blue")]; char stringpool_str1993[sizeof("hotpink4")]; char stringpool_str1995[sizeof("hotpink3")]; char stringpool_str2001[sizeof("hotpink2")]; char stringpool_str2003[sizeof("hotpink1")]; char stringpool_str2008[sizeof("grey45")]; char stringpool_str2009[sizeof("grey35")]; char stringpool_str2012[sizeof("grey25")]; char stringpool_str2013[sizeof("grey15")]; char stringpool_str2022[sizeof("light goldenrod yellow")]; char stringpool_str2024[sizeof("gray45")]; char stringpool_str2025[sizeof("gray35")]; char stringpool_str2028[sizeof("gray25")]; char stringpool_str2029[sizeof("gray15")]; char stringpool_str2067[sizeof("antique white")]; char stringpool_str2068[sizeof("deep sky blue")]; char stringpool_str2093[sizeof("darkviolet")]; char stringpool_str2107[sizeof("cornflowerblue")]; char stringpool_str2119[sizeof("floralwhite")]; char stringpool_str2130[sizeof("medium spring green")]; char stringpool_str2141[sizeof("cornsilk4")]; char stringpool_str2143[sizeof("cornsilk3")]; char stringpool_str2149[sizeof("cornsilk2")]; char stringpool_str2151[sizeof("cornsilk1")]; char stringpool_str2161[sizeof("firebrick4")]; char stringpool_str2162[sizeof("firebrick3")]; char stringpool_str2165[sizeof("firebrick2")]; char stringpool_str2166[sizeof("firebrick1")]; char stringpool_str2176[sizeof("cornflower blue")]; char stringpool_str2185[sizeof("blueviolet")]; char stringpool_str2188[sizeof("midnight blue")]; char stringpool_str2218[sizeof("blanched almond")]; char stringpool_str2220[sizeof("darkolivegreen")]; char stringpool_str2230[sizeof("lavenderblush")]; char stringpool_str2232[sizeof("darkolivegreen4")]; char stringpool_str2234[sizeof("darkolivegreen3")]; char stringpool_str2236[sizeof("light pink")]; char stringpool_str2240[sizeof("darkolivegreen2")]; char stringpool_str2242[sizeof("darkolivegreen1")]; char stringpool_str2247[sizeof("grey100")]; char stringpool_str2248[sizeof("palevioletred")]; char stringpool_str2260[sizeof("deeppink")]; char stringpool_str2263[sizeof("gray100")]; char stringpool_str2279[sizeof("white smoke")]; char stringpool_str2305[sizeof("palevioletred4")]; char stringpool_str2306[sizeof("ghostwhite")]; char stringpool_str2307[sizeof("palevioletred3")]; char stringpool_str2311[sizeof("grey90")]; char stringpool_str2313[sizeof("palevioletred2")]; char stringpool_str2315[sizeof("palevioletred1")]; char stringpool_str2320[sizeof("grey80")]; char stringpool_str2323[sizeof("grey70")]; char stringpool_str2327[sizeof("gray90")]; char stringpool_str2336[sizeof("gray80")]; char stringpool_str2339[sizeof("gray70")]; char stringpool_str2347[sizeof("lemonchiffon")]; char stringpool_str2359[sizeof("lemonchiffon4")]; char stringpool_str2361[sizeof("lemonchiffon3")]; char stringpool_str2367[sizeof("lemonchiffon2")]; char stringpool_str2369[sizeof("lemonchiffon1")]; char stringpool_str2380[sizeof("grey60")]; char stringpool_str2389[sizeof("honeydew")]; char stringpool_str2396[sizeof("gray60")]; char stringpool_str2398[sizeof("medium turquoise")]; char stringpool_str2413[sizeof("grey50")]; char stringpool_str2422[sizeof("dark violet")]; char stringpool_str2427[sizeof("medium aquamarine")]; char stringpool_str2429[sizeof("gray50")]; char stringpool_str2464[sizeof("papaya whip")]; char stringpool_str2482[sizeof("lightpink")]; char stringpool_str2500[sizeof("ghost white")]; char stringpool_str2511[sizeof("hotpink")]; char stringpool_str2514[sizeof("blue violet")]; char stringpool_str2525[sizeof("whitesmoke")]; char stringpool_str2544[sizeof("green yellow")]; char stringpool_str2562[sizeof("dark olive green")]; char stringpool_str2563[sizeof("grey99")]; char stringpool_str2572[sizeof("grey89")]; char stringpool_str2575[sizeof("grey79")]; char stringpool_str2579[sizeof("gray99")]; char stringpool_str2581[sizeof("grey98")]; char stringpool_str2587[sizeof("grey97")]; char stringpool_str2588[sizeof("gray89")]; char stringpool_str2590[sizeof("grey88")]; char stringpool_str2591[sizeof("gray79")]; char stringpool_str2593[sizeof("grey78")]; char stringpool_str2596[sizeof("grey87")]; char stringpool_str2597[sizeof("gray98")]; char stringpool_str2599[sizeof("grey77")]; char stringpool_str2603[sizeof("gray97")]; char stringpool_str2606[sizeof("gray88")]; char stringpool_str2609[sizeof("gray78")]; char stringpool_str2612[sizeof("gray87")]; char stringpool_str2615[sizeof("gray77")]; char stringpool_str2632[sizeof("grey69")]; char stringpool_str2645[sizeof("deep pink")]; char stringpool_str2648[sizeof("gray69")]; char stringpool_str2650[sizeof("grey68")]; char stringpool_str2654[sizeof("papayawhip")]; char stringpool_str2656[sizeof("grey67")]; char stringpool_str2659[sizeof("cornsilk")]; char stringpool_str2664[sizeof("light yellow")]; char stringpool_str2665[sizeof("grey59")]; char stringpool_str2666[sizeof("gray68")]; char stringpool_str2672[sizeof("gray67")]; char stringpool_str2681[sizeof("gray59")]; char stringpool_str2683[sizeof("grey58")]; char stringpool_str2684[sizeof("lavender blush")]; char stringpool_str2689[sizeof("grey57")]; char stringpool_str2699[sizeof("gray58")]; char stringpool_str2701[sizeof("grey96")]; char stringpool_str2705[sizeof("gray57")]; char stringpool_str2710[sizeof("grey86")]; char stringpool_str2713[sizeof("grey76")]; char stringpool_str2714[sizeof("hot pink")]; char stringpool_str2715[sizeof("lemon chiffon")]; char stringpool_str2717[sizeof("gray96")]; char stringpool_str2726[sizeof("gray86")]; char stringpool_str2729[sizeof("gray76")]; char stringpool_str2735[sizeof("firebrick")]; char stringpool_str2767[sizeof("grey95")]; char stringpool_str2770[sizeof("grey66")]; char stringpool_str2776[sizeof("grey85")]; char stringpool_str2779[sizeof("grey75")]; char stringpool_str2783[sizeof("gray95")]; char stringpool_str2786[sizeof("gray66")]; char stringpool_str2791[sizeof("dark khaki")]; char stringpool_str2792[sizeof("gray85")]; char stringpool_str2795[sizeof("gray75")]; char stringpool_str2803[sizeof("grey56")]; char stringpool_str2819[sizeof("gray56")]; char stringpool_str2836[sizeof("grey65")]; char stringpool_str2839[sizeof("mediumvioletred")]; char stringpool_str2852[sizeof("gray65")]; char stringpool_str2869[sizeof("grey55")]; char stringpool_str2879[sizeof("navajo white")]; char stringpool_str2885[sizeof("gray55")]; char stringpool_str2981[sizeof("darkkhaki")]; char stringpool_str3013[sizeof("navajowhite")]; char stringpool_str3019[sizeof("pale violet red")]; char stringpool_str3070[sizeof("navajowhite4")]; char stringpool_str3072[sizeof("navajowhite3")]; char stringpool_str3078[sizeof("navajowhite2")]; char stringpool_str3080[sizeof("navajowhite1")]; char stringpool_str3478[sizeof("medium violet red")]; }; static const struct stringpool_t stringpool_contents = { "red", "red4", "red3", "red2", "red1", "gold", "grey4", "grey3", "grey2", "grey1", "gray4", "gray3", "snow4", "snow3", "gray2", "gray1", "snow2", "snow1", "gold4", "gold3", "blue", "gold2", "gold1", "grey44", "grey34", "grey43", "grey33", "grey24", "grey14", "grey23", "grey13", "grey42", "grey32", "grey41", "grey31", "grey22", "grey12", "grey21", "grey11", "gray44", "gray34", "gray43", "gray33", "gray24", "gray14", "gray23", "gray13", "gray42", "gray32", "gray41", "gray31", "gray22", "gray12", "gray21", "gray11", "green", "orange", "blue4", "blue3", "azure", "blue2", "green4", "blue1", "green3", "green2", "green1", "darkred", "brown", "tan4", "tan3", "grey", "tan2", "tan1", "brown4", "sienna", "brown3", "brown2", "gray", "brown1", "orange4", "bisque", "orange3", "azure4", "azure3", "orange2", "orange1", "azure2", "azure1", "linen", "tan", "peru", "sienna4", "sienna3", "sienna2", "sienna1", "pink4", "pink3", "salmon", "pink2", "pink1", "bisque4", "salmon4", "bisque3", "salmon3", "bisque2", "salmon2", "bisque1", "salmon1", "plum4", "plum3", "purple", "plum2", "plum1", "orangered", "orangered4", "orangered3", "orangered2", "orangered1", "seagreen", "seagreen4", "purple4", "seagreen3", "purple3", "darkblue", "seagreen2", "purple2", "seagreen1", "purple1", "debianred", "darkorange", "darkorange4", "darkorange3", "darkorange2", "darkorange1", "darkgreen", "springgreen", "goldenrod", "goldenrod4", "goldenrod3", "goldenrod2", "goldenrod1", "springgreen4", "sea green", "springgreen3", "saddlebrown", "springgreen2", "springgreen1", "dodgerblue", "dodgerblue4", "dodgerblue3", "dodgerblue2", "dodgerblue1", "slateblue", "slateblue4", "slateblue3", "slateblue2", "slateblue1", "steelblue", "steelblue4", "steelblue3", "steelblue2", "steelblue1", "darkseagreen", "maroon", "plum", "darkseagreen4", "skyblue", "darkseagreen3", "maroon4", "darkgoldenrod", "maroon3", "darkseagreen2", "darkseagreen1", "maroon2", "maroon1", "lightgreen", "slategray4", "slategray3", "forestgreen", "slategray2", "slategray1", "palegreen4", "palegreen3", "palegreen2", "palegreen1", "skyblue4", "skyblue3", "darkgoldenrod4", "darkgoldenrod3", "skyblue2", "skyblue1", "sky blue", "darkgoldenrod2", "darkgoldenrod1", "palegreen", "dark red", "lightblue", "lightblue4", "lightblue3", "lightblue2", "lightblue1", "beige", "darkgrey", "darkmagenta", "darkgray", "magenta", "cyan", "royalblue", "royalblue4", "royalblue3", "royalblue2", "royalblue1", "darksalmon", "cyan4", "cyan3", "limegreen", "cyan2", "cyan1", "palegoldenrod", "orange red", "seashell", "tomato", "magenta4", "dodger blue", "magenta3", "dark green", "darkslateblue", "magenta2", "magenta1", "slategrey", "lightseagreen", "coral", "seashell4", "seashell3", "slategray", "seashell2", "seashell1", "lightgoldenrod", "tomato4", "tomato3", "dark orange", "tomato2", "tomato1", "coral4", "coral3", "coral2", "coral1", "mistyrose", "mistyrose4", "mistyrose3", "mistyrose2", "mistyrose1", "dark blue", "snow", "lightgoldenrod4", "lightgoldenrod3", "lightyellow4", "slate blue", "lightyellow3", "lightgoldenrod2", "lightgoldenrod1", "lightyellow2", "lightyellow1", "oldlace", "pink", "steel blue", "dimgrey", "lightsalmon", "darkturquoise", "dimgray", "lightsalmon4", "lightsalmon3", "lightsalmon2", "lightsalmon1", "saddle brown", "spring green", "slate grey", "lightgrey", "light green", "dim grey", "slate gray", "lightgray", "ivory4", "pale green", "ivory3", "darkslategray4", "darkslategray3", "ivory2", "dim gray", "ivory1", "darkslategray2", "darkslategray1", "old lace", "olivedrab4", "olivedrab3", "dark goldenrod", "olivedrab2", "olivedrab1", "olivedrab", "indianred", "indianred4", "indianred3", "lightsteelblue", "indianred2", "indianred1", "grey94", "gainsboro", "grey93", "grey92", "grey84", "grey91", "grey83", "grey74", "grey73", "gray94", "grey82", "gray93", "grey81", "grey72", "grey71", "gray92", "gray84", "gray91", "gray83", "gray74", "light blue", "gray73", "gray82", "gray81", "gray72", "gray71", "lightslateblue", "sandy brown", "lime green", "lightsteelblue4", "lightsteelblue3", "lightsteelblue2", "forest green", "lightsteelblue1", "dark salmon", "grey64", "aliceblue", "grey63", "darkslategrey", "grey62", "royal blue", "grey61", "paleturquoise", "dark magenta", "mediumblue", "gray64", "gray63", "ivory", "light grey", "darkslategray", "gray62", "gray61", "wheat4", "wheat3", "light salmon", "grey54", "grey53", "wheat2", "light gray", "wheat1", "dark grey", "grey52", "grey51", "thistle", "gray54", "gray53", "dark gray", "gray52", "gray51", "paleturquoise4", "paleturquoise3", "paleturquoise2", "paleturquoise1", "pale goldenrod", "turquoise", "turquoise4", "turquoise3", "turquoise2", "turquoise1", "thistle4", "wheat", "thistle3", "misty rose", "thistle2", "thistle1", "chocolate", "chocolate4", "chocolate3", "peachpuff4", "peachpuff3", "chocolate2", "chocolate1", "peachpuff2", "peachpuff1", "lightcoral", "darkcyan", "chartreuse", "chartreuse4", "chartreuse3", "chartreuse2", "chartreuse1", "rosybrown4", "rosybrown3", "deepskyblue", "rosybrown2", "rosybrown1", "peachpuff", "cadetblue", "cadetblue4", "cadetblue3", "cadetblue2", "cadetblue1", "mediumseagreen", "light sea green", "mediumpurple", "light goldenrod", "yellow4", "yellow3", "lawngreen", "rosybrown", "yellow2", "yellow1", "deepskyblue4", "deepskyblue3", "dark slate blue", "deepskyblue2", "deepskyblue1", "navy", "lightslategrey", "mediumpurple4", "mediumpurple3", "olive drab", "mediumpurple2", "mediumpurple1", "lightslategray", "indian red", "aquamarine", "aquamarine4", "aquamarine3", "aquamarine2", "aquamarine1", "medium blue", "orchid", "dark sea green", "khaki4", "khaki3", "mediumslateblue", "khaki2", "khaki1", "black", "lavender", "burlywood", "burlywood4", "burlywood3", "burlywood2", "burlywood1", "lightcyan4", "lightcyan3", "mediumspringgreen", "lightcyan2", "lightcyan1", "orchid4", "orchid3", "alice blue", "orchid2", "powderblue", "orchid1", "lightskyblue", "yellowgreen", "greenyellow", "white", "lightcyan", "sandybrown", "grey0", "navyblue", "violet", "lightskyblue4", "lightskyblue3", "gray0", "lightskyblue2", "lightskyblue1", "violetred", "violetred4", "violetred3", "violetred2", "violetred1", "grey40", "grey30", "grey20", "grey10", "light coral", "dark slate grey", "peach puff", "gray40", "gray30", "gray20", "gray10", "dark slate gray", "lawn green", "rosy brown", "lightyellow", "cadet blue", "medium sea green", "blanchedalmond", "dark cyan", "mediumorchid", "light slate blue", "dark orchid", "powder blue", "mediumorchid4", "mediumorchid3", "medium purple", "mediumorchid2", "mediumorchid1", "honeydew4", "honeydew3", "honeydew2", "midnightblue", "honeydew1", "light slate grey", "deeppink4", "deeppink3", "grey9", "deeppink2", "deeppink1", "light cyan", "light slate gray", "gray9", "grey8", "light steel blue", "grey7", "dark turquoise", "mintcream", "gray8", "gray7", "grey49", "grey39", "grey29", "grey19", "moccasin", "gray49", "gray39", "grey48", "grey38", "gray29", "gray19", "grey28", "grey18", "grey47", "grey37", "lightgoldenrodyellow", "grey27", "grey17", "gray48", "gray38", "gray28", "gray18", "gray47", "gray37", "gray27", "gray17", "khaki", "antiquewhite", "violet red", "mint cream", "darkorchid", "darkorchid4", "darkorchid3", "darkorchid2", "darkorchid1", "navy blue", "grey6", "yellow green", "gray6", "lightpink4", "lightpink3", "lightpink2", "lightpink1", "antiquewhite4", "antiquewhite3", "antiquewhite2", "antiquewhite1", "grey46", "grey36", "grey26", "grey16", "pale turquoise", "grey5", "gray46", "gray36", "yellow", "gray26", "gray16", "medium slate blue", "gray5", "lavenderblush4", "lavenderblush3", "lavenderblush2", "lavenderblush1", "floral white", "medium orchid", "mediumturquoise", "mediumaquamarine", "light sky blue", "hotpink4", "hotpink3", "hotpink2", "hotpink1", "grey45", "grey35", "grey25", "grey15", "light goldenrod yellow", "gray45", "gray35", "gray25", "gray15", "antique white", "deep sky blue", "darkviolet", "cornflowerblue", "floralwhite", "medium spring green", "cornsilk4", "cornsilk3", "cornsilk2", "cornsilk1", "firebrick4", "firebrick3", "firebrick2", "firebrick1", "cornflower blue", "blueviolet", "midnight blue", "blanched almond", "darkolivegreen", "lavenderblush", "darkolivegreen4", "darkolivegreen3", "light pink", "darkolivegreen2", "darkolivegreen1", "grey100", "palevioletred", "deeppink", "gray100", "white smoke", "palevioletred4", "ghostwhite", "palevioletred3", "grey90", "palevioletred2", "palevioletred1", "grey80", "grey70", "gray90", "gray80", "gray70", "lemonchiffon", "lemonchiffon4", "lemonchiffon3", "lemonchiffon2", "lemonchiffon1", "grey60", "honeydew", "gray60", "medium turquoise", "grey50", "dark violet", "medium aquamarine", "gray50", "papaya whip", "lightpink", "ghost white", "hotpink", "blue violet", "whitesmoke", "green yellow", "dark olive green", "grey99", "grey89", "grey79", "gray99", "grey98", "grey97", "gray89", "grey88", "gray79", "grey78", "grey87", "gray98", "grey77", "gray97", "gray88", "gray78", "gray87", "gray77", "grey69", "deep pink", "gray69", "grey68", "papayawhip", "grey67", "cornsilk", "light yellow", "grey59", "gray68", "gray67", "gray59", "grey58", "lavender blush", "grey57", "gray58", "grey96", "gray57", "grey86", "grey76", "hot pink", "lemon chiffon", "gray96", "gray86", "gray76", "firebrick", "grey95", "grey66", "grey85", "grey75", "gray95", "gray66", "dark khaki", "gray85", "gray75", "grey56", "gray56", "grey65", "mediumvioletred", "gray65", "grey55", "navajo white", "gray55", "darkkhaki", "navajowhite", "pale violet red", "navajowhite4", "navajowhite3", "navajowhite2", "navajowhite1", "medium violet red" }; #define stringpool ((const char *) &stringpool_contents) #if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || (defined __clang__ && __clang_major__ >= 3) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif static const struct Keyword color_names[] = { {-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}, {-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}, {-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}, {-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}, {-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}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 635 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str172, 16711680}, #line 639 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str173, 9109504}, #line 638 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str174, 13434880}, {-1}, {-1}, #line 637 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str177, 15597568}, #line 636 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str178, 16711680}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 177 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str202, 16766720}, {-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}, #line 332 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str229, 657930}, {-1}, #line 321 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str231, 526344}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 310 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str237, 328965}, {-1}, #line 298 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str239, 197379}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 223 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str245, 657930}, {-1}, #line 212 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str247, 526344}, #line 701 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str248, 9144713}, {-1}, #line 700 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str250, 13486537}, {-1}, {-1}, #line 201 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str253, 328965}, {-1}, #line 189 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str255, 197379}, #line 699 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str256, 15657449}, {-1}, #line 698 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str258, 16775930}, #line 181 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str259, 9139456}, {-1}, #line 180 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str261, 13479168}, {-1}, {-1}, {-1}, #line 30 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str265, 255}, {-1}, #line 179 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str267, 15649024}, {-1}, #line 178 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str269, 16766720}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 337 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str286, 7368816}, #line 326 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str287, 5723991}, #line 336 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str288, 7237230}, #line 325 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str289, 5526612}, #line 315 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str290, 4013373}, #line 304 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str291, 2368548}, #line 314 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str292, 3881787}, #line 303 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str293, 2171169}, #line 335 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str294, 7039851}, #line 324 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str295, 5395026}, #line 334 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str296, 6908265}, #line 323 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str297, 5197647}, #line 313 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str298, 3684408}, #line 302 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str299, 2039583}, #line 312 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str300, 3552822}, #line 301 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str301, 1842204}, #line 228 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str302, 7368816}, #line 217 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str303, 5723991}, #line 227 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str304, 7237230}, #line 216 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str305, 5526612}, #line 206 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str306, 4013373}, #line 195 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str307, 2368548}, #line 205 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str308, 3881787}, #line 194 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str309, 2171169}, #line 226 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str310, 7039851}, #line 215 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str311, 5395026}, #line 225 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str312, 6908265}, #line 214 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str313, 5197647}, #line 204 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str314, 3684408}, #line 193 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str315, 2039583}, #line 203 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str316, 3552822}, #line 192 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str317, 1842204}, {-1}, #line 289 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str319, 65280}, {-1}, #line 573 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str321, 16753920}, #line 35 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str322, 139}, {-1}, #line 34 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str324, 205}, {-1}, #line 16 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str326, 15794175}, {-1}, {-1}, {-1}, #line 33 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str330, 238}, #line 294 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str331, 35584}, #line 32 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str332, 255}, #line 293 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str333, 52480}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 292 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str339, 60928}, {-1}, #line 291 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str341, 65280}, {-1}, {-1}, {-1}, #line 126 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str345, 9109504}, {-1}, {-1}, {-1}, {-1}, #line 37 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str350, 10824234}, {-1}, #line 718 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str352, 9132587}, #line 717 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str353, 13468991}, {-1}, #line 296 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str355, 12500670}, #line 716 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str356, 15637065}, #line 715 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str357, 16753999}, {-1}, {-1}, {-1}, {-1}, #line 41 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str362, 9118499}, #line 672 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str363, 10506797}, #line 40 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str364, 13447987}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 39 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str370, 15612731}, #line 187 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str371, 12500670}, #line 38 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str372, 16728128}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 578 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str378, 9132544}, #line 22 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str379, 16770244}, #line 577 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str380, 13468928}, {-1}, {-1}, #line 20 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str383, 8620939}, {-1}, #line 19 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str385, 12701133}, #line 576 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str386, 15636992}, {-1}, #line 575 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str388, 16753920}, {-1}, {-1}, #line 18 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str391, 14741230}, {-1}, #line 17 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str393, 15794175}, #line 508 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str394, 16445670}, {-1}, #line 714 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str396, 13808780}, {-1}, {-1}, {-1}, #line 617 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str400, 13468991}, {-1}, {-1}, {-1}, #line 676 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str404, 9127718}, {-1}, #line 675 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str406, 13461561}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 674 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str412, 15628610}, {-1}, #line 673 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str414, 16745031}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 622 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str420, 9134956}, {-1}, #line 621 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str422, 13472158}, {-1}, {-1}, #line 654 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str425, 16416882}, {-1}, {-1}, #line 620 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str428, 15641016}, {-1}, #line 619 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str430, 16758213}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 26 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str436, 9141611}, #line 658 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str437, 9129017}, #line 25 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str438, 13481886}, #line 657 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str439, 13463636}, {-1}, {-1}, {-1}, {-1}, #line 24 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str444, 15652279}, #line 656 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str445, 15630946}, #line 23 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str446, 16770244}, #line 655 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str447, 16747625}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 627 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str456, 9135755}, {-1}, #line 626 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str458, 13473485}, {-1}, {-1}, {-1}, {-1}, #line 630 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str463, 10494192}, #line 625 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str464, 15642350}, {-1}, #line 624 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str466, 16759807}, {-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}, #line 579 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str493, 16729344}, #line 583 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str494, 9118976}, #line 582 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str495, 13448960}, {-1}, {-1}, #line 581 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str498, 15613952}, #line 580 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str499, 16729344}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 662 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str507, 3050327}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 666 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str519, 3050327}, #line 634 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str520, 5577355}, #line 665 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str521, 4443520}, #line 633 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str522, 8201933}, {-1}, #line 99 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str524, 139}, {-1}, {-1}, #line 664 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str527, 5172884}, #line 632 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str528, 9514222}, #line 663 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str529, 5570463}, #line 631 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str530, 10170623}, #line 142 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str531, 14092113}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 116 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str540, 16747520}, #line 120 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str541, 9127168}, #line 119 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str542, 13460992}, {-1}, {-1}, #line 118 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str545, 15627776}, #line 117 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str546, 16744192}, {-1}, {-1}, #line 107 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str549, 25600}, {-1}, #line 703 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str551, 65407}, #line 182 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str552, 14329120}, #line 186 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str553, 9136404}, #line 185 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str554, 13474589}, {-1}, {-1}, #line 184 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str557, 15643682}, #line 183 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str558, 16761125}, {-1}, {-1}, {-1}, {-1}, #line 707 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str563, 35653}, #line 661 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str564, 3050327}, #line 706 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str565, 52582}, #line 653 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str566, 9127187}, {-1}, {-1}, {-1}, {-1}, #line 705 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str571, 61046}, {-1}, #line 704 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str573, 65407}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 160 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str582, 2003199}, #line 164 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str583, 1068683}, #line 163 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str584, 1602765}, {-1}, {-1}, #line 162 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str587, 1869550}, #line 161 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str588, 2003199}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 686 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str596, 6970061}, #line 690 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str597, 4668555}, #line 689 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str598, 6904269}, {-1}, {-1}, #line 688 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str601, 8021998}, #line 687 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str602, 8613887}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 709 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str610, 4620980}, #line 713 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str611, 3564683}, #line 712 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str612, 5215437}, {-1}, {-1}, #line 711 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str615, 6073582}, #line 710 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str616, 6535423}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 128 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str624, 9419919}, {-1}, {-1}, {-1}, {-1}, #line 514 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str629, 11546720}, {-1}, {-1}, #line 623 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str632, 14524637}, {-1}, {-1}, {-1}, #line 132 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str636, 6916969}, #line 678 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str637, 8900331}, #line 131 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str638, 10210715}, {-1}, {-1}, #line 518 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str641, 9116770}, #line 101 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str642, 12092939}, #line 517 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str643, 13445520}, #line 130 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str644, 11857588}, {-1}, #line 129 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str646, 12713921}, {-1}, {-1}, #line 516 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str649, 15610023}, {-1}, #line 515 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str651, 16725171}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 475 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str669, 9498256}, {-1}, {-1}, {-1}, {-1}, #line 695 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str674, 7109515}, #line 694 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str675, 10467021}, {-1}, #line 173 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str677, 2263842}, #line 693 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str678, 12178414}, #line 692 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str679, 13034239}, #line 598 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str680, 5540692}, #line 597 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str681, 8179068}, {-1}, {-1}, #line 596 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str684, 9498256}, #line 595 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str685, 10157978}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 682 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str694, 4878475}, {-1}, #line 681 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str696, 7120589}, {-1}, {-1}, #line 105 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str699, 9135368}, {-1}, #line 104 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str701, 13473036}, #line 680 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str702, 8306926}, {-1}, #line 679 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str704, 8900351}, {-1}, #line 677 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str706, 8900331}, #line 103 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str707, 15641870}, {-1}, #line 102 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str709, 16759055}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 594 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str724, 10025880}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 91 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str730, 9109504}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 457 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str745, 11393254}, #line 461 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str746, 6849419}, #line 460 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str747, 10141901}, {-1}, {-1}, #line 459 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str750, 11722734}, #line 458 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str751, 12578815}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 21 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str760, 16119260}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 108 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str768, 11119017}, {-1}, #line 110 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str770, 9109643}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 106 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str784, 11119017}, {-1}, {-1}, {-1}, #line 509 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str788, 16711935}, {-1}, {-1}, {-1}, #line 75 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str792, 65535}, {-1}, #line 647 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str794, 4286945}, #line 651 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str795, 2572427}, #line 650 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str796, 3825613}, {-1}, {-1}, #line 649 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str799, 4419310}, #line 648 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str800, 4749055}, {-1}, #line 127 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str802, 15308410}, {-1}, #line 79 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str804, 35723}, {-1}, #line 78 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str806, 52685}, {-1}, {-1}, {-1}, {-1}, #line 507 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str811, 3329330}, #line 77 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str812, 61166}, {-1}, #line 76 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str814, 65535}, {-1}, {-1}, #line 593 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str817, 15657130}, {-1}, {-1}, {-1}, {-1}, #line 574 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str822, 16729344}, {-1}, {-1}, #line 667 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str825, 16774638}, {-1}, #line 724 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str827, 16737095}, {-1}, #line 513 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str829, 9109643}, #line 159 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str830, 2003199}, #line 512 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str831, 13435085}, {-1}, #line 84 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str833, 25600}, {-1}, {-1}, #line 133 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str836, 4734347}, #line 511 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str837, 15597806}, {-1}, #line 510 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str839, 16711935}, #line 696 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str840, 7372944}, {-1}, {-1}, {-1}, #line 487 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str844, 2142890}, {-1}, {-1}, {-1}, {-1}, #line 63 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str849, 16744272}, {-1}, {-1}, #line 671 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str852, 9143938}, {-1}, #line 670 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str854, 13485503}, {-1}, #line 691 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str856, 7372944}, {-1}, {-1}, {-1}, #line 669 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str860, 15656414}, {-1}, #line 668 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str862, 16774638}, {-1}, #line 468 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str864, 15654274}, #line 728 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str865, 9123366}, {-1}, #line 727 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str867, 13455161}, {-1}, #line 89 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str869, 16747520}, {-1}, {-1}, {-1}, #line 726 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str873, 15621186}, {-1}, #line 725 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str875, 16737095}, #line 67 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str876, 9125423}, {-1}, #line 66 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str878, 13458245}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 65 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str884, 15624784}, {-1}, #line 64 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str886, 16740950}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 550 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str893, 16770273}, #line 554 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str894, 9141627}, #line 553 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str895, 13481909}, {-1}, {-1}, #line 552 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str898, 15652306}, #line 551 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str899, 16770273}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 80 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str909, 139}, {-1}, {-1}, #line 697 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str912, 16775930}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 472 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str921, 9142604}, {-1}, #line 471 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str923, 13483632}, #line 505 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str924, 9145210}, #line 683 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str925, 6970061}, #line 504 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str926, 13487540}, {-1}, {-1}, #line 470 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str929, 15654018}, {-1}, #line 469 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str931, 16772235}, #line 503 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str932, 15658705}, {-1}, #line 502 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str934, 16777184}, {-1}, {-1}, #line 566 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str937, 16643558}, #line 618 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str938, 16761035}, #line 708 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str939, 4620980}, {-1}, {-1}, {-1}, #line 158 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str943, 6908265}, {-1}, {-1}, {-1}, {-1}, #line 482 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str948, 16752762}, {-1}, #line 140 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str950, 52945}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 157 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str959, 6908265}, #line 486 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str960, 9131842}, {-1}, #line 485 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str962, 13468002}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 484 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str968, 15635826}, {-1}, #line 483 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str970, 16752762}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 652 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str977, 9127187}, {-1}, {-1}, {-1}, #line 702 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str981, 65407}, {-1}, {-1}, {-1}, {-1}, #line 685 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str986, 7372944}, {-1}, {-1}, #line 476 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str989, 13882323}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 446 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str998, 9498256}, {-1}, #line 156 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1000, 6908265}, {-1}, #line 684 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1002, 7372944}, {-1}, {-1}, #line 474 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1005, 13882323}, {-1}, #line 419 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1007, 9145219}, #line 590 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1008, 10025880}, #line 418 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1009, 13487553}, {-1}, #line 138 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1011, 5409675}, {-1}, #line 137 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1013, 7982541}, {-1}, #line 417 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1015, 15658720}, #line 155 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1016, 6908265}, #line 416 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1017, 16777200}, {-1}, #line 136 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1019, 9301742}, {-1}, #line 135 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1021, 9961471}, {-1}, {-1}, #line 565 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1024, 16643558}, #line 572 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1025, 6916898}, #line 571 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1026, 10145074}, {-1}, #line 82 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1028, 12092939}, #line 570 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1029, 11791930}, #line 569 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1030, 12648254}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 568 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1036, 7048739}, {-1}, #line 410 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1038, 13458524}, #line 414 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1039, 9124410}, #line 413 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1040, 13456725}, #line 496 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1041, 11584734}, {-1}, #line 412 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1043, 15623011}, #line 411 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1044, 16738922}, #line 392 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1045, 15790320}, #line 174 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1046, 14474460}, #line 391 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1047, 15592941}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 390 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1053, 15461355}, #line 381 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1054, 14079702}, #line 389 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1055, 15263976}, #line 380 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1056, 13948116}, #line 370 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1057, 12434877}, {-1}, #line 369 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1059, 12237498}, {-1}, #line 283 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1061, 15790320}, #line 379 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1062, 13750737}, #line 282 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1063, 15592941}, #line 378 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1064, 13619151}, #line 368 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1065, 12105912}, {-1}, #line 367 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1067, 11908533}, {-1}, #line 281 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1069, 15461355}, #line 272 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1070, 14079702}, #line 280 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1071, 15263976}, #line 271 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1072, 13948116}, #line 261 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1073, 12434877}, #line 440 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1074, 11393254}, #line 260 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1075, 12237498}, {-1}, {-1}, #line 270 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1078, 13750737}, {-1}, #line 269 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1080, 13619151}, #line 259 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1081, 12105912}, {-1}, #line 258 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1083, 11908533}, {-1}, {-1}, {-1}, #line 493 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1087, 8679679}, {-1}, {-1}, {-1}, {-1}, #line 659 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1092, 16032864}, {-1}, {-1}, #line 506 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1095, 3329330}, {-1}, {-1}, #line 500 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1098, 7240587}, {-1}, #line 499 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1100, 10663373}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 498 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1106, 12374766}, #line 172 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1107, 2263842}, #line 497 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1108, 13296127}, {-1}, {-1}, {-1}, #line 92 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1112, 15308410}, {-1}, #line 359 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1114, 10724259}, #line 4 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1115, 15792383}, #line 358 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1116, 10592673}, {-1}, {-1}, {-1}, {-1}, #line 139 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1121, 3100495}, #line 357 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1122, 10395294}, #line 646 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1123, 4286945}, #line 356 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1124, 10263708}, #line 599 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1125, 11529966}, #line 87 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1126, 9109643}, {-1}, #line 529 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1128, 205}, {-1}, #line 250 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1130, 10724259}, {-1}, #line 249 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1132, 10592673}, #line 415 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1133, 16777200}, {-1}, #line 447 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1135, 13882323}, {-1}, #line 134 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1137, 3100495}, #line 248 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1138, 10395294}, {-1}, #line 247 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1140, 10263708}, {-1}, #line 745 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1142, 9141862}, {-1}, #line 744 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1144, 13482646}, #line 449 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1145, 16752762}, {-1}, #line 348 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1147, 9079434}, {-1}, #line 347 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1149, 8882055}, #line 743 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1150, 15653038}, #line 445 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1151, 13882323}, #line 742 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1152, 16771002}, #line 85 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1153, 11119017}, {-1}, #line 346 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1155, 8750469}, {-1}, #line 345 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1157, 8553090}, {-1}, {-1}, {-1}, {-1}, #line 719 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1162, 14204888}, #line 239 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1163, 9079434}, {-1}, #line 238 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1165, 8882055}, {-1}, {-1}, {-1}, #line 83 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1169, 11119017}, {-1}, #line 237 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1171, 8750469}, {-1}, #line 236 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1173, 8553090}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 603 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1182, 6720395}, {-1}, #line 602 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1184, 9883085}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 601 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1190, 11464430}, {-1}, #line 600 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1192, 12320767}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 589 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1203, 15657130}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 729 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1212, 4251856}, #line 733 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1213, 34443}, #line 732 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1214, 50637}, {-1}, {-1}, #line 731 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1217, 58862}, #line 730 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1218, 62975}, #line 723 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1219, 9141131}, #line 741 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1220, 16113331}, #line 722 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1221, 13481421}, #line 549 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1222, 16770273}, {-1}, {-1}, {-1}, {-1}, #line 721 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1227, 15651566}, {-1}, #line 720 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1229, 16769535}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 58 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1235, 13789470}, #line 62 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1236, 9127187}, #line 61 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1237, 13461021}, #line 616 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1238, 9140069}, #line 615 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1239, 13479829}, #line 60 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1240, 15627809}, #line 59 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1241, 16744228}, #line 614 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1242, 15649709}, #line 613 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1243, 16767673}, {-1}, {-1}, {-1}, {-1}, #line 462 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1248, 15761536}, #line 100 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1249, 35723}, #line 53 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1250, 8388352}, #line 57 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1251, 4557568}, #line 56 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1252, 6737152}, {-1}, {-1}, #line 55 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1255, 7794176}, #line 54 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1256, 8388352}, #line 645 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1257, 9136489}, #line 644 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1258, 13474715}, #line 150 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1259, 49151}, {-1}, #line 643 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1261, 15643828}, #line 642 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1262, 16761281}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 612 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1273, 16767673}, #line 48 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1274, 6266528}, #line 52 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1275, 5473931}, #line 51 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1276, 8046029}, {-1}, {-1}, #line 50 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1279, 9364974}, #line 49 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1280, 10024447}, {-1}, {-1}, #line 540 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1283, 3978097}, {-1}, {-1}, {-1}, #line 450 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1287, 2142890}, {-1}, {-1}, {-1}, #line 535 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1291, 9662683}, {-1}, {-1}, #line 443 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1294, 15654274}, {-1}, #line 754 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1296, 9145088}, {-1}, #line 753 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1298, 13487360}, #line 433 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1299, 8190976}, {-1}, #line 641 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1301, 12357519}, {-1}, {-1}, #line 752 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1304, 15658496}, {-1}, #line 751 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1306, 16776960}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 154 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1316, 26763}, {-1}, #line 153 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1318, 39629}, {-1}, #line 94 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1320, 4734347}, {-1}, {-1}, {-1}, #line 152 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1324, 45806}, {-1}, #line 151 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1326, 49151}, {-1}, {-1}, {-1}, {-1}, #line 562 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1331, 128}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 495 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1343, 7833753}, {-1}, {-1}, {-1}, {-1}, #line 539 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1348, 6113163}, {-1}, #line 538 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1350, 9005261}, {-1}, {-1}, #line 567 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1353, 7048739}, {-1}, {-1}, #line 537 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1356, 10451438}, {-1}, #line 536 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1358, 11240191}, #line 494 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1359, 7833753}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 409 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1367, 13458524}, {-1}, #line 11 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1369, 8388564}, #line 15 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1370, 4557684}, #line 14 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1371, 6737322}, {-1}, {-1}, #line 13 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1374, 7794374}, #line 12 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1375, 8388564}, #line 520 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1376, 205}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 584 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1383, 14315734}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 93 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1393, 9419919}, {-1}, {-1}, #line 424 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1396, 9143886}, {-1}, #line 423 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1398, 13485683}, {-1}, {-1}, {-1}, {-1}, #line 541 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1403, 8087790}, #line 422 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1404, 15656581}, {-1}, #line 421 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1406, 16774799}, #line 27 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1407, 0}, #line 425 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1408, 15132410}, {-1}, {-1}, {-1}, #line 42 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1412, 14596231}, #line 46 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1413, 9139029}, #line 45 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1414, 13478525}, {-1}, {-1}, #line 44 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1417, 15648145}, #line 43 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1418, 16765851}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 467 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1426, 8031115}, #line 466 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1427, 11849165}, {-1}, #line 542 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1429, 64154}, #line 465 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1430, 13758190}, #line 464 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1431, 14745599}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 588 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1440, 9127817}, {-1}, #line 587 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1442, 13461961}, {-1}, #line 3 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1444, 15792383}, {-1}, {-1}, {-1}, #line 586 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1448, 15629033}, #line 629 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1449, 11591910}, #line 585 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1450, 16745466}, #line 488 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1451, 8900346}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 755 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1458, 10145074}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 295 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1468, 11403055}, #line 746 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1469, 16777215}, #line 463 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1470, 14745599}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 660 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1484, 16032864}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 297 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1495, 0}, {-1}, {-1}, {-1}, #line 564 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1499, 128}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 734 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1506, 15631086}, {-1}, #line 492 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1508, 6323083}, {-1}, #line 491 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1510, 9287373}, #line 188 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1511, 0}, {-1}, {-1}, {-1}, {-1}, #line 490 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1516, 10802158}, {-1}, #line 489 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1518, 11592447}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 736 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1543, 13639824}, #line 740 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1544, 9118290}, #line 739 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1545, 13447800}, {-1}, {-1}, #line 738 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1548, 15612556}, #line 737 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1549, 16727702}, {-1}, {-1}, #line 333 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1552, 6710886}, #line 322 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1553, 5066061}, {-1}, {-1}, #line 311 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1556, 3355443}, #line 299 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1557, 1710618}, {-1}, {-1}, {-1}, #line 441 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1561, 15761536}, {-1}, {-1}, #line 96 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1564, 3100495}, {-1}, #line 611 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1566, 16767673}, {-1}, #line 224 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1568, 6710886}, #line 213 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1569, 5066061}, {-1}, {-1}, #line 202 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1572, 3355443}, #line 190 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1573, 1710618}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 95 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1580, 3100495}, {-1}, {-1}, #line 432 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1583, 8190976}, {-1}, #line 640 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1585, 12357519}, {-1}, {-1}, #line 501 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1588, 16777184}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 47 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1603, 6266528}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 523 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1609, 3978097}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 29 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1616, 16772045}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 81 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1634, 35723}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 530 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1642, 12211667}, {-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}, {-1}, {-1}, #line 452 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1678, 8679679}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 90 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1686, 10040012}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 628 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1697, 11591910}, {-1}, #line 534 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1699, 8009611}, {-1}, #line 533 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1701, 11817677}, {-1}, {-1}, {-1}, #line 522 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1705, 9662683}, {-1}, #line 532 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1707, 13721582}, {-1}, #line 531 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1709, 14706431}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 402 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1725, 8620931}, {-1}, #line 401 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1727, 12701121}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 400 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1733, 14741216}, #line 546 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1734, 1644912}, #line 399 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1735, 15794160}, {-1}, {-1}, {-1}, #line 454 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1739, 7833753}, {-1}, {-1}, #line 149 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1742, 9112144}, {-1}, #line 148 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1744, 13439094}, {-1}, {-1}, #line 387 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1747, 1513239}, {-1}, {-1}, #line 147 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1750, 15602313}, {-1}, #line 146 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1752, 16716947}, {-1}, #line 442 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1754, 14745599}, #line 453 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1755, 7833753}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 278 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1763, 1513239}, {-1}, #line 376 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1765, 1315860}, {-1}, #line 455 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1767, 11584734}, {-1}, {-1}, {-1}, #line 365 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1771, 1184274}, {-1}, #line 97 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1773, 52945}, {-1}, {-1}, {-1}, #line 548 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1777, 16121850}, {-1}, {-1}, {-1}, #line 267 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1781, 1315860}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 256 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1787, 1184274}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 342 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1804, 8224125}, #line 331 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1805, 6513507}, {-1}, {-1}, #line 320 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1808, 4868682}, #line 309 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1809, 3158064}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 555 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1817, 16770229}, {-1}, {-1}, #line 233 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1820, 8224125}, #line 222 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1821, 6513507}, #line 341 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1822, 8026746}, #line 330 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1823, 6381921}, #line 211 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1824, 4868682}, #line 200 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1825, 3158064}, #line 319 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1826, 4671303}, #line 308 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1827, 3026478}, #line 340 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1828, 7895160}, #line 329 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1829, 6184542}, #line 473 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1830, 16448210}, {-1}, #line 318 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1832, 4539717}, #line 307 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1833, 2829099}, {-1}, {-1}, {-1}, {-1}, #line 232 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1838, 8026746}, #line 221 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1839, 6381921}, {-1}, {-1}, #line 210 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1842, 4671303}, #line 199 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1843, 3026478}, #line 231 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1844, 7895160}, #line 220 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1845, 6184542}, {-1}, {-1}, #line 209 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1848, 4539717}, #line 198 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1849, 2829099}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 420 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1858, 15787660}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 6 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1866, 16444375}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 735 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1872, 13639824}, #line 547 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1873, 16121850}, {-1}, {-1}, #line 121 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1876, 10040012}, #line 125 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1877, 6824587}, #line 124 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1878, 10105549}, {-1}, {-1}, #line 123 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1881, 11680494}, #line 122 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1882, 12533503}, {-1}, #line 563 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1884, 128}, #line 354 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1885, 986895}, {-1}, {-1}, #line 750 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1888, 10145074}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 245 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1901, 986895}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 481 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1908, 9133925}, #line 480 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1909, 13470869}, {-1}, {-1}, #line 479 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1912, 15639213}, #line 478 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1913, 16756409}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 10 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1923, 9143160}, {-1}, #line 9 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1925, 13484208}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 8 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1931, 15654860}, {-1}, #line 7 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1933, 16773083}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 339 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1942, 7697781}, #line 328 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1943, 6052956}, {-1}, {-1}, #line 317 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1946, 4342338}, #line 306 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1947, 2697513}, #line 591 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1948, 11529966}, {-1}, {-1}, #line 343 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1951, 855309}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 230 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1958, 7697781}, #line 219 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1959, 6052956}, #line 749 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1960, 16776960}, {-1}, #line 208 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1962, 4342338}, #line 197 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1963, 2697513}, #line 524 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1964, 8087790}, {-1}, {-1}, #line 234 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1967, 855309}, #line 431 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1968, 9143174}, {-1}, #line 430 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1970, 13484485}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 429 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1976, 15655141}, {-1}, #line 428 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1978, 16773365}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 170 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1985, 16775920}, {-1}, #line 521 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1987, 12211667}, {-1}, #line 543 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1989, 4772300}, {-1}, #line 528 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1991, 6737322}, #line 451 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1992, 8900346}, #line 408 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1993, 9124450}, {-1}, #line 407 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str1995, 13459600}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 406 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2001, 15624871}, {-1}, #line 405 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2003, 16740020}, {-1}, {-1}, {-1}, {-1}, #line 338 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2008, 7566195}, #line 327 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2009, 5855577}, {-1}, {-1}, #line 316 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2012, 4210752}, #line 305 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2013, 2500134}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 444 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2022, 16448210}, {-1}, #line 229 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2024, 7566195}, #line 218 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2025, 5855577}, {-1}, {-1}, #line 207 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2028, 4210752}, #line 196 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2029, 2500134}, {-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}, {-1}, {-1}, {-1}, {-1}, #line 5 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2067, 16444375}, #line 144 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2068, 49151}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 141 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2093, 9699539}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 69 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2107, 6591981}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 171 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2119, 16775920}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 525 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2130, 64154}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 74 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2141, 9144440}, {-1}, #line 73 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2143, 13486257}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 72 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2149, 15657165}, {-1}, #line 71 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2151, 16775388}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 169 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2161, 9116186}, #line 168 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2162, 13444646}, {-1}, {-1}, #line 167 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2165, 15608876}, #line 166 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2166, 16724016}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 68 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2176, 6591981}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 36 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2185, 9055202}, {-1}, {-1}, #line 545 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2188, 1644912}, {-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}, #line 28 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2218, 16772045}, {-1}, #line 111 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2220, 5597999}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 427 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2230, 16773365}, {-1}, #line 115 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2232, 7244605}, {-1}, #line 114 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2234, 10669402}, {-1}, #line 448 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2236, 16758465}, {-1}, {-1}, {-1}, #line 113 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2240, 12381800}, {-1}, #line 112 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2242, 13303664}, {-1}, {-1}, {-1}, {-1}, #line 300 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2247, 16777215}, #line 604 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2248, 14381203}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 145 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2260, 16716947}, {-1}, {-1}, #line 191 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2263, 16777215}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 747 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2279, 16119285}, {-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}, #line 608 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2305, 9127773}, #line 176 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2306, 16316671}, #line 607 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2307, 13461641}, {-1}, {-1}, {-1}, #line 388 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2311, 15066597}, {-1}, #line 606 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2313, 15628703}, {-1}, #line 605 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2315, 16745131}, {-1}, {-1}, {-1}, {-1}, #line 377 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2320, 13421772}, {-1}, {-1}, #line 366 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2323, 11776947}, {-1}, {-1}, {-1}, #line 279 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2327, 15066597}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 268 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2336, 13421772}, {-1}, {-1}, #line 257 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2339, 11776947}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 435 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2347, 16775885}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 439 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2359, 9144688}, {-1}, #line 438 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2361, 13486501}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 437 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2367, 15657407}, {-1}, #line 436 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2369, 16775885}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 355 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2380, 10066329}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 398 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2389, 15794160}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 246 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2396, 10066329}, {-1}, #line 526 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2398, 4772300}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 344 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2413, 8355711}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 98 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2422, 9699539}, {-1}, {-1}, {-1}, {-1}, #line 519 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2427, 6737322}, {-1}, #line 235 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2429, 8355711}, {-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}, {-1}, #line 609 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2464, 16773077}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 477 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2482, 16758465}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 175 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2500, 16316671}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 404 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2511, 16738740}, {-1}, {-1}, #line 31 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2514, 9055202}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 748 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2525, 16119285}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 290 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2544, 11403055}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 88 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2562, 5597999}, #line 397 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2563, 16579836}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 386 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2572, 14935011}, {-1}, {-1}, #line 375 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2575, 13224393}, {-1}, {-1}, {-1}, #line 288 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2579, 16579836}, {-1}, #line 396 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2581, 16448250}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 395 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2587, 16250871}, #line 277 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2588, 14935011}, {-1}, #line 385 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2590, 14737632}, #line 266 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2591, 13224393}, {-1}, #line 374 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2593, 13092807}, {-1}, {-1}, #line 384 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2596, 14606046}, #line 287 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2597, 16448250}, {-1}, #line 373 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2599, 12895428}, {-1}, {-1}, {-1}, #line 286 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2603, 16250871}, {-1}, {-1}, #line 276 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2606, 14737632}, {-1}, {-1}, #line 265 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2609, 13092807}, {-1}, {-1}, #line 275 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2612, 14606046}, {-1}, {-1}, #line 264 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2615, 12895428}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 364 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2632, 11579568}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 143 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2645, 16716947}, {-1}, {-1}, #line 255 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2648, 11579568}, {-1}, #line 363 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2650, 11382189}, {-1}, {-1}, {-1}, #line 610 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2654, 16773077}, {-1}, #line 362 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2656, 11250603}, {-1}, {-1}, #line 70 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2659, 16775388}, {-1}, {-1}, {-1}, {-1}, #line 456 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2664, 16777184}, #line 353 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2665, 9868950}, #line 254 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2666, 11382189}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 253 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2672, 11250603}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 244 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2681, 9868950}, {-1}, #line 352 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2683, 9737364}, #line 426 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2684, 16773365}, {-1}, {-1}, {-1}, {-1}, #line 351 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2689, 9539985}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 243 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2699, 9737364}, {-1}, #line 394 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2701, 16119285}, {-1}, {-1}, {-1}, #line 242 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2705, 9539985}, {-1}, {-1}, {-1}, {-1}, #line 383 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2710, 14408667}, {-1}, {-1}, #line 372 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2713, 12763842}, #line 403 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2714, 16738740}, #line 434 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2715, 16775885}, {-1}, #line 285 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2717, 16119285}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 274 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2726, 14408667}, {-1}, {-1}, #line 263 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2729, 12763842}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 165 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2735, 11674146}, {-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}, #line 393 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2767, 15921906}, {-1}, {-1}, #line 361 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2770, 11053224}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 382 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2776, 14277081}, {-1}, {-1}, #line 371 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2779, 12566463}, {-1}, {-1}, {-1}, #line 284 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2783, 15921906}, {-1}, {-1}, #line 252 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2786, 11053224}, {-1}, {-1}, {-1}, {-1}, #line 86 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2791, 12433259}, #line 273 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2792, 14277081}, {-1}, {-1}, #line 262 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2795, 12566463}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 350 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2803, 9408399}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 241 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2819, 9408399}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 360 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2836, 10921638}, {-1}, {-1}, #line 544 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2839, 13047173}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 251 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2852, 10921638}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 349 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2869, 9211020}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 556 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2879, 16768685}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 240 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2885, 9211020}, {-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}, {-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}, {-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}, #line 109 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2981, 12433259}, {-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}, #line 557 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3013, 16768685}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 592 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3019, 14381203}, {-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}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 561 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3070, 9140574}, {-1}, #line 560 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3072, 13480843}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 559 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3078, 15650721}, {-1}, #line 558 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3080, 16768685}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-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}, {-1}, #line 527 "/dev/stdin" {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3478, 13047173} }; #if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || (defined __clang__ && __clang_major__ >= 3) #pragma GCC diagnostic pop #endif const struct Keyword * in_color_name_set (register const char *str, register size_t len) { if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register unsigned int key = color_name_hash (str, len); if (key <= MAX_HASH_VALUE) { register int o = color_names[key].name; if (o >= 0) { register const char *s = o + stringpool; if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0') return &color_names[key]; } } } return (struct Keyword *) 0; } #line 756 "/dev/stdin" ================================================ FILE: kitty/colors.c ================================================ /* * colors.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include #include "colors.h" #include "color-names.h" #ifdef __APPLE__ // Needed for strod_l #include #endif 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; } void colorprofile_reset(ColorProfile *self) { memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256)); self->dirty = true; self->color_stack_idx = 0; zero_at_ptr(&self->overridden); for (unsigned i = 0; i < arraysz(self->overriden_transparent_colors); i++) { zero_at_ptr(self->overriden_transparent_colors + i); } for (unsigned i = 0; i < self->color_stack_sz; i++) { zero_at_ptr(self->color_stack + i); } } 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_vectorcall(PyObject *type UNUSED, PyObject *const *args, size_t nargsf, PyObject *kwnames) { const Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); if (nargs > 4) { PyErr_SetString(PyExc_TypeError, "Color() takes at most 4 arguments"); return NULL; } unsigned char rgba[4] = {0, 0, 0, 0}; for (Py_ssize_t i = 0; i < nargs; i++) { unsigned long val = PyLong_AsUnsignedLongMask(args[i]); if (val == (unsigned long)-1 && PyErr_Occurred()) return NULL; rgba[i] = (unsigned char)val; } if (kwnames) { const unsigned num = PyTuple_GET_SIZE(kwnames); for (unsigned i = 0; i < num; i++) { const char *name = PyUnicode_AsUTF8(PyTuple_GET_ITEM(kwnames, i)); if (!name) return NULL; int idx; #define C(ch, i, expected) case ch: idx = i; if (strcmp(name, expected) != 0) { \ PyErr_Format(PyExc_TypeError, "Color() got an unexpected keyword argument '%s'", name); return NULL; }; break; switch(name[0]) { C('r', 0, "red"); C('g', 1, "green"); C('b', 2, "blue"); C('a', 3, "alpha"); default: PyErr_Format(PyExc_TypeError, "Color() got an unexpected keyword argument '%s'", name); return NULL; } #undef C if (idx < nargs) { PyErr_Format(PyExc_TypeError, "Color() got multiple values for argument '%s'", name); return NULL; } unsigned long val = PyLong_AsUnsignedLongMask(args[nargs + i]); if (val == (unsigned long)-1 && PyErr_Occurred()) return NULL; rgba[idx] = (unsigned char)val; } } return (PyObject*)alloc_color(rgba[0], rgba[1], rgba[2], rgba[3]); } 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 int hexchar_to_int(char c) { switch (c) { START_ALLOW_CASE_RANGE case '0' ... '9': return c - '0'; case 'a' ... 'f': return c - 'a' + 10; case 'A' ... 'F': return c - 'A' + 10; END_ALLOW_CASE_RANGE } return -1; } static bool parse_base16_uchar(const char *hex, unsigned char *out) { const int hi = hexchar_to_int(hex[0]); const int lo = hexchar_to_int(hex[1]); if (hi < 0 || lo < 0) return false; *out = (unsigned char)((hi << 4) | lo); return true; } static bool parse_double(const char *src, double *out) { char *endptr; errno = 0; *out = strtod_l(src, &endptr, get_c_locale()); return endptr != src && *endptr == 0 && errno == 0; } static bool parse_single_color(const char *c, size_t len, unsigned char *out) { char buf[2]; if (len == 1) { buf[0] = c[0]; buf[1] = c[0]; c = buf; } return parse_base16_uchar(c, out); } static PyObject* parse_sharp(const char *spec, size_t len) { unsigned char r, g, b; switch(len) { case 3: if (!parse_single_color(spec, 1, &r) || !parse_single_color(spec + 1, 1, &g) || !parse_single_color(spec + 2, 1, &b)) Py_RETURN_NONE; break; case 6: case 9: case 12: if (!parse_single_color(spec, 2, &r) || !parse_single_color(spec + len/3, 2, &g) || !parse_single_color(spec + 2 * len / 3, 2, &b)) Py_RETURN_NONE; break; default: Py_RETURN_NONE; } return (PyObject*)alloc_color(r, g, b, 0); } static PyObject* parse_rgb(const char *spec, size_t len) { char buf[32]; if (len >= sizeof(buf)) Py_RETURN_NONE; memcpy(buf, spec, len); buf[len] = 0; unsigned char r, g, b; char *tok; #define p(buf, out) if (!(tok = strtok(buf, "/")) || !parse_single_color(tok, strlen(tok), &out)) Py_RETURN_NONE; p(buf, r); p(NULL, g); p(NULL, b); #undef p return (PyObject*)alloc_color(r, g, b, 0); } static unsigned char as8bit(double f) { return (unsigned char)(round(f * 255.)); } static bool parse_single_intensity(const char *s, unsigned char *out) { double f; if (!parse_double(s, &f)) return false; *out = as8bit(f); return true; } static PyObject* parse_rgbi(const char *spec, size_t len) { char buf[256]; if (len >= sizeof(buf)) Py_RETURN_NONE; memcpy(buf, spec, len); buf[len] = 0; unsigned char r, g, b; char *tok; #define p(buf, out) if (!(tok = strtok(buf, "/")) || !parse_single_intensity(tok, &out)) Py_RETURN_NONE; p(buf, r); p(NULL, g); p(NULL, b); #undef p return (PyObject*)alloc_color(r, g, b, 0); } static bool parse_double_intensity(char *s, double *out, double percentage_divider) { size_t l = strlen(s); if (l == 0) return false; double divisor = 1; if (s[l-1] == '%') { s[l-1] = 0; divisor = percentage_divider; } if (!parse_double(s, out)) return false; *out /= divisor; return true; } static double clamp(const double f) { return MAX(0, MIN(f, 1)); } static double linear_to_srgb(double c) { return c <= 0.0031308 ? c * 12.92 : (1.055 * pow(c, (1 / 2.4)) - 0.055); } static double degrees_to_radians(double degrees) { return degrees * (M_PI / 180); } static double radians_to_degrees(double radians) { return 180 * radians / M_PI; } static void oklch_to_srgb(double l, double c, double h, double *r, double *g, double *b) { // Convert OKLCH to OKLab const double h_rad = degrees_to_radians(h); const double a = c * cos(h_rad); const double lb = c * sin(h_rad); // Convert OKLab to Linear sRGB // Using the OKLab to Linear sRGB transformation const double l_ = l + 0.3963377774 * a + 0.2158037573 * lb; const double m_ = l - 0.1055613458 * a - 0.0638541728 * lb; const double s_ = l - 0.0894841775 * a - 1.2914855480 * lb; const double l_lin = l_ * l_ * l_; const double m_lin = m_ * m_ * m_; const double s_lin = s_ * s_ * s_; const double r_lin = +4.0767416621 * l_lin - 3.3077115913 * m_lin + 0.2309699292 * s_lin; const double g_lin = -1.2684380046 * l_lin + 2.6097574011 * m_lin - 0.3413193965 * s_lin; const double b_lin = -0.0041960863 * l_lin - 0.7034186147 * m_lin + 1.7076147010 * s_lin; *r = linear_to_srgb(clamp(r_lin)); *g = linear_to_srgb(clamp(g_lin)); *b = linear_to_srgb(clamp(b_lin)); } static double srgb_to_linear(double c) { return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); } static void srgb_to_oklab(double r, double g, double b, double *l, double *a, double *lb) { // Convert sRGB to linear sRGB const double r_lin = srgb_to_linear(r); const double g_lin = srgb_to_linear(g); const double b_lin = srgb_to_linear(b); // Convert Linear sRGB to OKLab (inverse of oklch_to_srgb) const double l_lin = 0.4122214708 * r_lin + 0.5363325363 * g_lin + 0.0514459929 * b_lin; const double m_lin = 0.2119034982 * r_lin + 0.6806995451 * g_lin + 0.1073969566 * b_lin; const double s_lin = 0.0883024619 * r_lin + 0.2817188376 * g_lin + 0.6299787005 * b_lin; const double l_ = l_lin != 0 ? copysign(pow(fabs(l_lin), 1./3.), l_lin) : 0; const double m_ = m_lin != 0 ? copysign(pow(fabs(m_lin), 1./3.), m_lin) : 0; const double s_ = s_lin != 0 ? copysign(pow(fabs(s_lin), 1./3.), s_lin) : 0; // OKLab coordinates *l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; *a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; *lb = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; } static double distance(double x_l, double x_a, double x_b, double y_l, double y_a, double y_b) { return sqrt((x_l - y_l)*(x_l - y_l) + (x_a - y_a)*(x_a - y_a) + (x_b - y_b)*(x_b - y_b)); } static void oklch_to_srgb_gamut_map(double l, double c, double h, double *r, double *g, double *b) { // Edge cases: pure black or white don't need gamut mapping if (!isfinite(l) || !isfinite(c) || !isfinite(h) || l <= 0) { *r = 0; *g = 0; *b = 0; return; } if (l >= 1) { *r = 1; *g = 1; *b = 1; return; } // Constants from CSS Color Module Level 4 static const double JND = 0.02; // Just Noticeable Difference threshold (2% in deltaEOK) static const double MIN_CONVERGENCE = 0.0001; // Binary search precision (0.01% chroma) static const double EPSILON = 0.00001; // Small value for doubleing point comparisons // If chroma is very small, color is essentially achromatic if (c < EPSILON) { *r = linear_to_srgb(l); *g = *r; *b = *r; return; } // Try the original color first oklch_to_srgb(l, c, h, r, g, b); #define in_gamut(r,g,b) (0. <= r && r <= 1. && 0. <= g && g <= 1. && 0. <= b && b <= 1.) if (in_gamut(*r,*g,*b)) return; // Binary search for maximum in-gamut chroma double low_chroma = 0, high_chroma = c, r_test, g_test, b_test, r_clipped, g_clipped, b_clipped; // Convert original color to OKLab for deltaE calculations while ((high_chroma - low_chroma) > MIN_CONVERGENCE) { double mid_chroma = (high_chroma + low_chroma) * 0.5; // Try this chroma value oklch_to_srgb(l, mid_chroma, h, &r_test, &g_test, &b_test); // Check if in gamut (before clipping) if (in_gamut(r_test, g_test, b_test)) { // In gamut - try higher chroma low_chroma = mid_chroma; } else { // Out of gamut - clip and check deltaE r_clipped = clamp(r_test); g_clipped = clamp(g_test); b_clipped = clamp(b_test); // Convert both to OKLab for comparison double l_test, a_test, lb_test, l_clipped, a_clipped, lb_clipped; srgb_to_oklab(r_test, g_test, b_test, &l_test, &a_test, &lb_test); srgb_to_oklab(r_clipped, g_clipped, b_clipped, &l_clipped, &a_clipped, &lb_clipped); // Calculate perceptual difference double de = distance(l_test, a_test, lb_test, l_clipped, a_clipped, lb_clipped); if (de < JND) { // Difference is imperceptible - accept this chroma low_chroma = mid_chroma; } else { // Difference is noticeable - reduce chroma more high_chroma = mid_chroma; } } } // Use the final chroma value and clip to ensure in-gamut oklch_to_srgb(l, low_chroma, h, r, g, b); *r = clamp(*r); *g = clamp(*g); *b = clamp(*b); #undef in_gamut } static double f_inv(double t) { static const double delta = 6. / 29.; return t > delta ? t*t*t : 3 * delta * delta * (t - 4. / 29.); } static void lab_to_oklch(double l, double a, double b, double *okl, double *c, double *h) { const double y = (l + 16.) / 116.; const double x = a / 500. + y; const double z = y - b / 200.; const double x_val = 0.95047 * f_inv(x); const double y_val = f_inv(y); const double z_val = 1.08883 * f_inv(z); // XYZ to Linear sRGB (don't clip here to preserve out-of-gamut info) const double r_lin = +3.2404542 * x_val - 1.5371385 * y_val - 0.4985314 * z_val; const double g_lin = -0.9692660 * x_val + 1.8760108 * y_val + 0.0415560 * z_val; const double b_lin = +0.0556434 * x_val - 0.2040259 * y_val + 1.0572252 * z_val; // Convert linear sRGB to sRGB gamma const double r_srgb = r_lin >= 0 ? linear_to_srgb(r_lin) : 0; const double g_srgb = g_lin >= 0 ? linear_to_srgb(g_lin) : 0; const double b_srgb = b_lin >= 0 ? linear_to_srgb(b_lin) : 0; // Convert to OKLab double a_ok, b_ok; srgb_to_oklab(r_srgb, g_srgb, b_srgb, okl, &a_ok, &b_ok); // Convert OKLab to OKLCH *c = sqrt(a_ok * a_ok + b_ok * b_ok); *h = fmod(radians_to_degrees(atan2(b_ok, a_ok)), 360.f); } static PyObject* parse_oklch(const char *spec, size_t len) { if (len < 10 || spec[--len] != ')') Py_RETURN_NONE; if (spec[0] != 'k' || spec[1] != 'l' || spec[2] != 'c' || spec[3] != 'h' || spec[4] != '(') Py_RETURN_NONE; spec += 5; len -= 5; char buf[256]; if (len >= sizeof(buf)) Py_RETURN_NONE; memcpy(buf, spec, len); buf[len] = 0; double l, c, h; char *tok; #define p(buf, out) if (!(tok = strtok(buf, " ,")) || !parse_double_intensity(tok, &out, 100)) Py_RETURN_NONE; p(buf, l); p(NULL, c); p(NULL, h); #undef p // Clamp to reasonable ranges l = clamp(l); c = MAX(0.f, c); // Chroma is unbounded but we don't clamp high end h = fmod(h, 360); // Wrap hue to 0-360 double r, g, b; oklch_to_srgb_gamut_map(l, c, h, &r, &g, &b); return (PyObject*)alloc_color(as8bit(r), as8bit(g), as8bit(b), 0); } static PyObject* parse_lab(const char *spec, size_t len) { if (len < 8 || spec[--len] != ')') Py_RETURN_NONE; if (spec[0] != 'a' || spec[1] != 'b' || spec[2] != '(') Py_RETURN_NONE; spec += 3; len -= 3; char buf[256]; if (len >= sizeof(buf)) Py_RETURN_NONE; memcpy(buf, spec, len); buf[len] = 0; double l, a, b; char *tok; #define p(buf, out) if (!(tok = strtok(buf, " ,")) || !parse_double_intensity(tok, &out, 1)) Py_RETURN_NONE; p(buf, l); p(NULL, a); p(NULL, b); #undef p // Clamp to reasonable ranges double okl, c, h, r, g, bb; lab_to_oklch(MAX(0., MIN(l, 100.)), a, b, &okl, &c, &h); oklch_to_srgb_gamut_map(okl, c, h, &r, &g, &bb); return (PyObject*)alloc_color(as8bit(r), as8bit(g), as8bit(bb), 0); } static const char* trim_view(const char *str, Py_ssize_t *len) { if (str == NULL || *len == 0) return str; const char *start = str; const char *end = str + *len - 1; while (start <= end && isspace((unsigned char)*start)) start++; while (end > start && isspace((unsigned char)*end)) end--; if (start > end) { *len = 0; } else { *len = (size_t)(end - start + 1); } return start; } static PyObject* parse_color(PyTypeObject *type UNUSED, PyObject *pspec) { if (!PyUnicode_Check(pspec)) { PyErr_SetString(PyExc_TypeError, "spec must be a string"); return NULL; } RAII_PyObject(lower_cased, PyObject_CallMethod(pspec, "lower", NULL)); if (!lower_cased) return NULL; Py_ssize_t len; const char *spec = PyUnicode_AsUTF8AndSize(lower_cased, &len); spec = trim_view(spec, &len); if (len < 2) Py_RETURN_NONE; // Remove trailing comments switch (spec[0]) { case '#': { const char *s = strchr(spec, ' '); if (s) len = s - spec; } break; default: { const char *s = strchr(spec, '#'); if (s) { len = s - spec; spec = trim_view(spec, &len); } } break; } const struct Keyword *k = in_color_name_set(spec, len); if (k) return (PyObject*)alloc_color((k->value >> 16) & 0xff, (k->value >> 8) & 0xff, k->value & 0xff, 0); if (len < 4) Py_RETURN_NONE; switch (spec[0]) { case '#': return parse_sharp(spec + 1, len - 1); case 'r': if (spec[1] != 'g' || spec[2] != 'b' || len < 6) Py_RETURN_NONE; switch(spec[3]) { case ':': return parse_rgb(spec + 4, len - 4); case 'i': if (spec[4] == 'i' && spec[5] == ':') return parse_rgbi(spec + 5, len - 5); } Py_RETURN_NONE; case 'o': return parse_oklch(spec + 1, len - 1); case 'l': return parse_lab(spec + 1, len - 1); } Py_RETURN_NONE; } static PyMethodDef color_methods[] = { METHODB(contrast, METH_O), METHODB(parse_color, METH_O | METH_CLASS), {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 | Py_TPFLAGS_HAVE_VECTORCALL, .tp_doc = "Color", .tp_new = new_color, .tp_vectorcall = color_vectorcall, .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 PyObject* all_color_names(PyObject *self UNUSED, PyObject *args UNUSED) { RAII_PyObject(ans, PyTuple_New(TOTAL_KEYWORDS)); if (!ans) return NULL; const struct Keyword *k; Py_ssize_t n = 0; for (unsigned i = 0; i <= MAX_HASH_VALUE; i++) { if ((k = &color_names[i])->name > -1) { const char *name = color_names[i].name + stringpool; PyObject *t = Py_BuildValue("sN", name, alloc_color((k->value >> 16) & 0xff, (k->value >> 8) & 0xff, k->value & 0xff, 0)); if (!t) return NULL; PyTuple_SET_ITEM(ans, n, t); n++; } } return Py_NewRef(ans); } static PyMethodDef module_methods[] = { METHODB(default_color_table, METH_NOARGS), METHODB(patch_color_profiles, METH_VARARGS), METHODB(all_color_names, METH_NOARGS), {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; } // }}} ================================================ FILE: kitty/colors.h ================================================ /* * 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); } ================================================ FILE: kitty/colors.py ================================================ #!/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, TypedDict 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, special_colors from .rgb import color_from_int from .typing_compat import WindowType ColorsSpec = dict[str, Optional[int]] TransparentBackgroundColors = tuple[tuple[Color, float], ...] ColorSchemes = Literal['light', 'dark', 'no_preference'] Colors = tuple[ColorsSpec, TransparentBackgroundColors] class BackgroundImageOptions(TypedDict, total=False): background_image: str | None background_image_layout: str | None background_image_linear: bool | None background_tint: float | None background_tint_gaps: float | None 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 default_background_image_options: BackgroundImageOptions| 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) for name in special_colors: ans[name] = getattr(defaults, name) self.default_colors = ans self.default_background_image_options: BackgroundImageOptions = { k: getattr(defaults, k) for k in BackgroundImageOptions.__optional_keys__} # type: ignore return self.default_colors def parse_colors(self, f: Iterable[str], background_image_options: BackgroundImageOptions | None = None) -> 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. dc_spec = self.get_default_colors() if background_image_options is not None and self.default_background_image_options: background_image_options.update(self.default_background_image_options) spec, tbc = parse_colors((f,), background_image_options) 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: d: BackgroundImageOptions = {} self.dark_spec, self.dark_tbc = self.parse_colors(f, d) self.dark_background_image_options = d 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: d = {} self.light_spec, self.light_tbc = self.parse_colors(f, d) self.light_background_image_options = d 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: d = {} self.no_preference_spec, self.no_preference_tbc = self.parse_colors(f, d) self.no_preference_background_image_options = d self.no_preference_mtime = mtime found = True return found @property def has_applied_theme(self) -> bool: match self.applied_theme: case '': return False case 'dark': return self.has_dark_theme case 'light': return self.has_light_theme case 'no_preference': return self.has_no_preference_theme @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 bgo: BackgroundImageOptions | None = None if which == 'dark' and self.has_dark_theme: cols = self.dark_spec, self.dark_tbc bgo = self.dark_background_image_options elif which == 'light' and self.has_light_theme: cols = self.light_spec, self.light_tbc bgo = self.light_background_image_options elif which == 'no_preference' and self.has_no_preference_theme: cols = self.no_preference_spec, self.no_preference_tbc bgo = self.no_preference_background_image_options if cols is not None: patch_options_with_color_spec(opts, *cols, background_image_options=bgo) 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, background_image_options=self.dark_background_image_options) 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, background_image_options=self.light_background_image_options) 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, background_image_options=self.no_preference_background_image_options) 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]], background_image_options: BackgroundImageOptions | None = None) -> Colors: colors: dict[str, Color | None | int] = {} nullable_color_map: dict[str, int | None] = {} special_color_map: dict[str, int] = {} 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', ()) if background_image_options is not None: for key in BackgroundImageOptions.__optional_keys__: if key in conf: background_image_options.__setitem__(key, conf[key]) 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 for k in special_colors: sq = colors.pop(k, None) if isinstance(sq, int): special_color_map[k] = sq ans: dict[str, int | None] = {k: int(v) for k, v in colors.items() if isinstance(v, Color)} ans.update(nullable_color_map) ans.update(special_color_map) return ans, transparent_background_colors def patch_options_with_color_spec( opts: Options, spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors, background_image_options: BackgroundImageOptions | None = None ) -> 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: if k in special_colors: setattr(opts, k, v) else: setattr(opts, k, color_from_int(v)) opts.transparent_background_colors = transparent_background_colors if background_image_options is not None: for k, bv in background_image_options.items(): if hasattr(opts, k): setattr(opts, k, bv) def patch_colors( spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors, configured: bool = False, windows: Sequence[WindowType] | None = None, notify_on_bg_change: bool = True, background_image_options: BackgroundImageOptions | None = None ) -> 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, background_image_options) os_window_ids = set() 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() os_window_ids.add(tm.os_window_id) patch_global_colors(spec, configured) # changes macos_titlebar_color for oswid in os_window_ids: set_os_window_chrome(oswid) default_bg_changed = 'background' in spec notify_bg = notify_on_bg_change and default_bg_changed boss = get_boss() if background_image_options is not None: boss.set_background_image( background_image_options.get('background_image'), tuple(os_window_ids), configured, layout=background_image_options.get('background_image_layout'), linear_interpolation=background_image_options.get('background_image_linear'), tint=background_image_options.get('background_tint'), tint_gaps=background_image_options.get('background_tint_gaps')) 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() ================================================ FILE: kitty/conf/__init__.py ================================================ ================================================ FILE: kitty/conf/generate.py ================================================ #!/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.simple_cli_definitions import serialize_as_go_string 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 or is_multiple: if ctype in ('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)' if p == 'unit_float': return 'float64', 'config.UnitFloat(val)' 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 "github.com/kovidgoyal/kitty/tools/config"', 'import "github.com/kovidgoyal/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) defval = [] for x in option.items: if x.add_to_default: defval.append(option.parser_func(x.defval_as_str)) defaults[name] = defval 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.fast_data_types import Color def basic_defval(d: Any) -> str: 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, list): dval = '[]string{' for k in d: dval += f'"{serialize_as_go_string(k)}",' 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) return dval for name, pname in go_parsers.items(): d = defaults[name] if d: a(f'{name}: {basic_defval(d)},') 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('}''}') has_parsers = bool(go_parsers or keyboard_shortcuts) a('func (c *Config) Parse(key, val string) (err error) {') if has_parsers: if go_parsers: 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}') else: a('return fmt.Errorf("Unknown configuration key: %#v", key)') a('}') 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') ================================================ FILE: kitty/conf/types.py ================================================ #!/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, Sequence, 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: Sequence['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: Sequence['Option'] = () ) -> list[str]: ans: list[str] = [] a = ans.append if not self.documented: return ans mopts = [self] + list(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) ================================================ FILE: kitty/conf/utils.py ================================================ #!/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_compat 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 text = (text[:-1] + "\\'") if text.endswith("'") else text 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) ================================================ FILE: kitty/config.py ================================================ #!/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_compat 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 nm.timeout = defn.options.timeout if defn.options.timeout is not None else opts.map_timeout 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 = [] 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, } ================================================ FILE: kitty/constants.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal 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, 46, 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') kitty_run_data: dict[str, Any] = getattr(sys, 'kitty_run_data', {}) launched_by_launch_services = kitty_run_data.get('launched_by_launch_services', False) is_quick_access_terminal_app = kitty_run_data.get('is_quick_access_terminal_app', False) unserialize_launch_flag = 'kitty-unserialize-data=' if getattr(sys, 'frozen', False): extensions_dir: str = 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 = 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: cdir = kitty_run_data.get('config_dir', '') if cdir: return str(cdir) import atexit import tempfile ans = tempfile.mkdtemp(prefix='kitty-conf-') def cleanup() -> None: import shutil with suppress(Exception): shutil.rmtree(ans) atexit.register(cleanup) 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 import ctypes with suppress(Exception): setattr(detect_if_wayland_ok, 'keep_module_loaded', ctypes.CDLL(wayland)) return True return False 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 = 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 ================================================ FILE: kitty/control-codes.h ================================================ /* * 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 ================================================ FILE: kitty/core_text.m ================================================ /* * 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 #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 system_ui_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 (system_ui_font) CFRelease(system_ui_font); system_ui_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 (system_ui_font) { if (for_height == in_height) return true; CFRelease(system_ui_font); } system_ui_font = CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, 0.f, NULL); if (!system_ui_font) return false; CGFloat line_height = MAX(1, floor(CTFontGetAscent(system_ui_font) + CTFontGetDescent(system_ui_font) + MAX(0, CTFontGetLeading(system_ui_font)) + 0.5)); CGFloat pts_per_px = CTFontGetSize(system_ui_font) / line_height; CGFloat desired_size = in_height * pts_per_px; if (desired_size != CTFontGetSize(system_ui_font)) { CTFontRef sized = CTFontCreateCopyWithAttributes(system_ui_font, desired_size, NULL, NULL); CFRelease(system_ui_font); system_ui_font = sized; if (!system_ui_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); NSColor *color = [NSColor colorWithCalibratedRed:((fg >> 16) & 0xff) / 255.f green:((fg >> 8) & 0xff) / 255.f blue:(fg & 0xff) / 255.f alpha:1.0]; NSAttributedString *str = [[NSAttributedString alloc] initWithString:@(text) attributes:@{(NSString *)kCTFontAttributeName: (__bridge id)system_ui_font, NSForegroundColorAttributeName: color}]; 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(system_ui_font, &chars, buffers.glyphs, 1); CTFontGetAdvancesForGlyphs(system_ui_font, kCTFontOrientationDefault, buffers.glyphs, local_advances, 1); CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(system_ui_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(system_ui_font), CTFontGetAscent(system_ui_font), height, bounding_box.size.height); */ CGContextSetTextPosition(render_ctx, -bounding_box.origin.x, -bounding_box.origin.y); CTFontDrawGlyphs(system_ui_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; } // }}} ================================================ FILE: kitty/cross-platform-random.h ================================================ /* * 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 ================================================ FILE: kitty/crypto.c ================================================ /* * 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; } ================================================ FILE: kitty/cursor.c ================================================ /* * 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 memcmp(&a->sgr, &b->sgr, sizeof(a->sgr)) == 0 && EQ(x) && EQ(y) && EQ(shape) && EQ(non_blinking); } #undef EQ 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, text_blink=%R)", self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"), BOOL(!self->non_blinking), self->sgr.fg, self->sgr.bg, BOOL(self->sgr.bold), BOOL(self->sgr.italic), BOOL(self->sgr.reverse), BOOL(self->sgr.strikethrough), BOOL(self->sgr.dim), self->sgr.decoration, self->sgr.decoration_fg, BOOL(self->sgr.blink) ); } void cursor_reset_display_attrs(Cursor *self) { zero_at_ptr(&self->sgr); } 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->sgr.bold = true; break; case 2: self->sgr.dim = true; break; case 3: self->sgr.italic = true; break; case 4: if (is_group && i < count) { self->sgr.decoration = MIN(5, params[i]); i++; } else self->sgr.decoration = 1; break; case 5: self->sgr.blink = true; break; case 7: self->sgr.reverse = true; break; case 9: self->sgr.strikethrough = true; break; case 21: self->sgr.decoration = 2; break; case 221: self->sgr.bold = false; break; case 222: self->sgr.dim = false; break; case 22: self->sgr.bold = false; self->sgr.dim = false; break; case 23: self->sgr.italic = false; break; case 24: self->sgr.decoration = 0; break; case 25: self->sgr.blink = false; break; case 27: self->sgr.reverse = false; break; case 29: self->sgr.strikethrough = false; break; case 30 ... 37: self->sgr.fg = ((attr - 30) << 8) | 1; break; case 38: SET_COLOR(sgr.fg); case 39: self->sgr.fg = 0; break; case 40 ... 47: self->sgr.bg = ((attr - 40) << 8) | 1; break; case 48: SET_COLOR(sgr.bg); case 49: self->sgr.bg = 0; break; case 90 ... 97: self->sgr.fg = ((attr - 90 + 8) << 8) | 1; break; case 100 ... 107: self->sgr.bg = ((attr - 100 + 8) << 8) | 1; break; case DECORATION_FG_CODE: SET_COLOR(sgr.decoration_fg); case DECORATION_FG_CODE + 1: self->sgr.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 5: S(blink, true); 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 25: S(blink, false); 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->sgr.fg & COL_MASK, .bg = self->sgr.bg & COL_MASK, .decoration_fg = self->sgr.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); #undef CCY memcpy(&dest->sgr, &src->sgr, sizeof(dest->sgr)); } static PyObject* copy(Cursor *self, PyObject*); #define copy_doc "Create a clone of this cursor" // Boilerplate {{{ #define SGR_BOOL_GETSET(x) \ static PyObject* x##_get(Cursor *self, void UNUSED *closure) { PyObject *ans = self->sgr.x ? Py_True : Py_False; Py_INCREF(ans); return ans; } \ static int x##_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->sgr.x = PyObject_IsTrue(value) ? true : false; return 0; } SGR_BOOL_GETSET(bold) SGR_BOOL_GETSET(italic) SGR_BOOL_GETSET(reverse) SGR_BOOL_GETSET(strikethrough) SGR_BOOL_GETSET(dim) #undef SGR_BOOL_GETSET 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 PyObject* text_blink_get(Cursor *self, void UNUSED *closure) { return Py_NewRef(self->sgr.blink ? Py_True : Py_False); } static int text_blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->sgr.blink = PyObject_IsTrue(value) ? false : true; return 0; } #define SGR_UINT_GETSET(x) \ static PyObject* x##_get(Cursor *self, void UNUSED *closure) { return PyLong_FromUnsignedLong((unsigned long)self->sgr.x); } \ static int x##_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } if (!PyLong_Check(value)) { PyErr_SetString(PyExc_TypeError, "value must be an int"); return -1; } self->sgr.x = PyLong_AsUnsignedLong(value); return 0; } SGR_UINT_GETSET(decoration) SGR_UINT_GETSET(fg) SGR_UINT_GETSET(bg) SGR_UINT_GETSET(decoration_fg) 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"}, {NULL} /* Sentinel */ }; static PyGetSetDef getseters[] = { GETSET(bold) GETSET(italic) GETSET(reverse) GETSET(strikethrough) GETSET(dim) GETSET(blink) GETSET(text_blink) GETSET(decoration) GETSET(fg) GETSET(bg) GETSET(decoration_fg) {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) ================================================ FILE: kitty/cursor_trail.c ================================================ #include #include "state.h" #define WD w->render_data #define EDGE(axis, index) ct->cursor_edge_##axis[index] inline static float norm(float x, float y) { return sqrtf(x * x + y * y); } typedef struct ndc_coords { float xstart, ystart, dx, dy; } ndc_coords; static void update_cursor_trail_target(CursorTrail *ct, Window *w, ndc_coords g) { 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 = g.xstart + WD.screen->cursor_render_info.x * g.dx; bottom = g.ystart - (WD.screen->cursor_render_info.y + 1) * g.dy; default: break; } switch (WD.screen->cursor_render_info.shape) { case CURSOR_BLOCK: case CURSOR_HOLLOW: right = left + g.dx; top = bottom + g.dy; break; case CURSOR_BEAM: right = left + g.dx / WD.screen->cell_size.width * OPT(cursor_beam_thickness); top = bottom + g.dy; break; case CURSOR_UNDERLINE: right = left + g.dx; top = bottom + g.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, ndc_coords g, 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)) / g.dx); int dy = (int)round((ct->corner_y[0] - EDGE(y, 0)) / g.dy); if (abs(dx) + abs(dy) <= OPT(cursor_trail_start_threshold)) { return true; } } return false; } static void update_cursor_trail_corners(CursorTrail *ct, ndc_coords g, 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, g, 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, ndc_coords g) { 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 = g.dx / WD.screen->cell_size.width * 0.5f; const float dy_threshold = g.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) { ndc_coords g = { .xstart = gl_pos_x(w->render_data.geometry.left, os_window->viewport_width), .ystart = gl_pos_y(w->render_data.geometry.top, os_window->viewport_height), .dx = gl_size (w->render_data.screen->cell_size.width, os_window->viewport_width), .dy = gl_size (w->render_data.screen->cell_size.height, os_window->viewport_height), }; 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, g); } update_cursor_trail_corners(ct, g, now, os_window); update_cursor_trail_opacity(ct, w, now); bool needs_render_prev = ct->needs_render; update_cursor_trail_needs_render(ct, w, g); 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 ================================================ FILE: kitty/data-types.c ================================================ /* * 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 "launcher/utils.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* 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 locale_t c_locale = 0; locale_t get_c_locale(void) { return c_locale; } 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 supply 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 PyObject* expanduser(PyObject *self UNUSED, PyObject *path) { if (!PyUnicode_Check(path)) { PyErr_SetString(PyExc_TypeError, "path must a string"); return NULL; } char buf[PATH_MAX + 1]; expand_tilde(PyUnicode_AsUTF8(path), buf, arraysz(buf)); return PyUnicode_FromString(buf); } static PyObject* abspath(PyObject *self UNUSED, PyObject *path) { if (!PyUnicode_Check(path)) { PyErr_SetString(PyExc_TypeError, "path must a string"); return NULL; } char buf[PATH_MAX + 1]; lexical_absolute_path(PyUnicode_AsUTF8(path), buf, arraysz(buf)); return PyUnicode_FromString(buf); } static PyObject* read_file(PyObject *self UNUSED, PyObject *path) { if (!PyUnicode_Check(path)) { PyErr_SetString(PyExc_TypeError, "path must a string"); return NULL; } size_t sz; char *result = read_full_file(PyUnicode_AsUTF8(path), &sz); if (!result) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } PyObject *ans = PyBytes_FromStringAndSize(result, sz); free(result); return ans; } static PyObject* py_makedirs(PyObject *self UNUSED, PyObject *args) { int mode = 0755; const char *p; if (!PyArg_ParseTuple(args, "s|i", &p, &mode)) return NULL; if (!makedirs(p, mode)) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; } static PyObject* py_get_config_dir(PyObject *self UNUSED, PyObject *args UNUSED) { char buf[PATH_MAX]; if (get_config_dir(buf, PATH_MAX)) return PyUnicode_FromString(buf); return PyUnicode_FromString(""); } #include "launcher/cli-parser.h" static PyMethodDef module_methods[] = { METHODB(replace_c0_codes_except_nl_space_tab, METH_O), METHODB(read_file, METH_O), {"parse_cli_from_spec", parse_cli_from_python_spec, METH_VARARGS, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"expanduser", (PyCFunction)expanduser, METH_O, ""}, {"abspath", (PyCFunction)abspath, METH_O, ""}, {"expand_ansi_c_escapes", (PyCFunction)expand_ansi_c_escapes, METH_O, ""}, {"get_docs_ref_map", (PyCFunction)get_docs_ref_map, METH_NOARGS, ""}, {"get_config_dir", (PyCFunction)py_get_config_dir, METH_NOARGS, ""}, {"wcswidth", (PyCFunction)wcswidth_std, METH_O, ""}, {"open_tty", open_tty, METH_VARARGS, ""}, {"makedirs", py_makedirs, 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, ""}, {"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) { freelocale(c_locale); 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); s(BLINK, blink); #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); PyModule_AddIntMacro(m, COLOR_NOT_SET); PyModule_AddIntMacro(m, COLOR_IS_SPECIAL); PyModule_AddIntMacro(m, COLOR_IS_INDEX); PyModule_AddIntMacro(m, COLOR_IS_RGB); #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; c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); if (!c_locale) { PyErr_NoMemory(); return NULL; } return m; } ================================================ FILE: kitty/data-types.h ================================================ /* * 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 #include "glfw-wrapper.h" #include "banned.h" // Required minimum OpenGL version #define OPENGL_REQUIRED_VERSION_MAJOR 3 #ifdef __APPLE__ #include #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_DEL 0x0 ... 0x8: case 0xb ... 0x1f #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 { SCROLLBAR_NEVER, SCROLLBAR_ON_SCROLLED, SCROLLBAR_ON_HOVERED, SCROLLBAR_ON_SCROLL_AND_HOVER, SCROLLBAR_ALWAYS } ScrollbarVisibilityPolicy; 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 { // right = left + width, bottom = top + height 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 monotonic_t position_changed_by_client_at; unsigned int x, y; bool non_blinking; CursorShape shape; struct { bool bold, italic, reverse, strikethrough, dim, blink; uint8_t decoration; color_type fg, bg, decoration_fg; } sgr; } Cursor; typedef struct { bool is_focused, render_even_when_unfocused, is_visible; CursorShape shape; unsigned x, y, multicursor_count; float cursor_opacity, text_blink_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 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); void colorprofile_reset(ColorProfile *self); 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 leave_event(int modifiers); void mouse_event(const int, int, int); void focus_in_event(void); void scroll_event(const GLFWScrollEvent *ev); void on_key_input(const GLFWkeyevent *ev); void request_window_attention(id_type, bool); void free_drag_source(void); locale_t get_c_locale(void); #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__; \ } \ } ================================================ FILE: kitty/debug_config.py ================================================ #!/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, gpu_driver_version_string, num_users, wayland_compositor_data from .options.types import Options as KittyOpts from .options.types import defaults, secret_options from .options.utils import KeyboardMode, KeyDefinition from .rgb import color_as_sharp, color_from_int from .types import MouseEvent, Shortcut, mod_to_names from .utils import shlex_split 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: if f in secret_options: print(title(f'{f}:'), 'REDACTED FOR SECURITY') continue val = getattr(opts, f) if isinstance(val, dict): print(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]) elif f == 'exe_search_path': for k in val: print(' ', 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_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) def build_shortcut_map(km: KeyboardMode) -> ShortcutMap: result: ShortcutMap = {} for defns in km.keymap.values(): by_seq: dict[tuple[SingleKey, ...], list[KeyDefinition]] = {} for d in defns: by_seq.setdefault(d.full_key_sequence_to_trigger, []).append(d) for seq, seq_defns in by_seq.items(): result[Shortcut(seq)] = as_str(seq_defns) return result for kmn, initial_ in default_opts.keyboard_modes.items(): initial = build_shortcut_map(initial_) final_ = opts.keyboard_modes.get(kmn, KeyboardMode(kmn)) final = build_shortcut_map(final_) 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 = build_shortcut_map(initial_) final_ = opts.keyboard_modes[kmn] final = build_shortcut_map(final_) 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') self.vars = {} with suppress(Exception), open('/etc/os-release') as osf: for line in osf: k, _, v = line.strip().partition('=') self.vars[k] = ' '.join(shlex_split(v)) if not self.vars: with suppress(Exception), open('/usr/lib/os-release') as osf: for line in osf: k, _, v = line.strip().partition('=') self.vars[k] = ' '.join(shlex_split(v)) 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]: state = 'normal' varname = '' for ch in issue_file.read(): match state: case 'normal': if ch == '\\': state = 'escape' else: yield ch case 'escape': match ch: case 'S': state = 'sub_start' case '\\': yield '\\' state = 'normal' case _: yield self.translate_issue_char(ch) state = 'normal' case 'sub_start': if ch == '{': # } state = 'sub' varname = '' else: yield ch state = 'normal' case 'sub': if ch == '}': if val := self.vars.get(varname): yield val state = 'normal' else: varname += ch 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 issue_data() -> str: with suppress(Exception): idata = IssueData() with open('/etc/issue', encoding='utf-8', errors='replace') as f: return ''.join(idata.parse_issue_file(f)).strip() return '' def debug_config(opts: KittyOpts | None = None, global_shortcuts: dict[str, SingleKey] | None = None) -> str: if opts is None: from kitty.cli import create_default_opts opts = create_default_opts() 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 with suppress(Exception): p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip()) if (idata := issue_data()): p(idata) with suppress(Exception), 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:'), gpu_driver_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() ================================================ FILE: kitty/decorations.c ================================================ /* * 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; memset(ans + offset, 0xff, width); } } 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; memset(ans + offset, 0xff, fcm.cell_width); } 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 FloatPoint { double x, y; } FloatPoint; 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 double thickness_as_float(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 self->supersample_factor * self->scale * pts * dpi / 72.0; } static uint thickness(Canvas *self, uint level, bool horizontal) { return (uint)ceil(thickness_as_float(self, level, horizontal)); } 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 Range hline_limits(Canvas *self, uint y, uint level) { uint sz = thickness(self, level, false); Range r = {.start=minus(y, sz / 2)}; r.end = min(r.start + sz, self->height); return r; } 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 Range r = hline_limits(self, y, level); for (uint y = r.start; y < r.end; y++) { uint8_t *py = self->mask + y * self->width; memset(py + x1, 255, minus(min(x2, self->width), x1)); } } static Range vline_limits(Canvas *self, uint x, uint level) { uint sz = thickness(self, level, true); Range r = {.start = minus(x, sz / 2)}; r.end = min(r.start + sz, self->width); return r; } 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 Range r = vline_limits(self, x, level); uint xsz = minus(r.end, r.start); for (uint y = y1; y < min(y2, self->height); y++) { uint8_t *py = self->mask + y * self->width; memset(py + r.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 double unit_double(double x) { return x < 0.0 ? 0.0 : (x > 1.0 ? 1.0 : x); } static double smoothstep(double edge0, double edge1, double x) { if (edge0 == edge1) return x < edge0 ? 0.0 : 1.0; double t = unit_double((x - edge0) / (edge1 - edge0)); return t * t * (3.0 - 2.0 * t); } 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) { \ const CubicBezier *cb = v; \ const double u = 1. - t; \ const double u_3 = u * u * u; \ const double t_3 = t * t * t; \ return u_3 * cb->start.which + 3 * t * u * (u * cb->c1.which + t * cb->c2.which) + t_3 * cb->end.which; \ } #define bezier_prime_eq(which) { \ const CubicBezier *cb = v; \ const double u = 1. - t; \ const double u_2 = u * u; \ const double t_2 = t * t; \ return 3 * u_2 * (cb->c1.which - cb->start.which) + 6 * t * u * (cb->c2.which - cb->c1.which) + 3 * t_2 * (cb->end.which - cb->c2.which); \ } static double bezier_x(const void *v, double t) { bezier_eq(x); } static double bezier_y(const void *v, double t) { bezier_eq(y); } static double bezier_prime_x(const void *v, double t) { bezier_prime_eq(x); } static double bezier_prime_y(const void *v, double t) { bezier_prime_eq(y); } #undef bezier_eq #undef bezier_prime_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(const 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, const 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)); } typedef double(*curve_func)(const void *, double t); #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; } typedef struct ClipRect { uint left, top, x_end, y_end; } ClipRect; static void draw_parametrized_curve_with_derivative_and_antialiasing( Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime, double x_offset, double y_offset, const ClipRect *clip_to ) { line_width = fmax(1., line_width); double half_thickness = line_width / 2.0; uint i=0, larger_dim = MAX(self->height, self->width); double t = 0; double step = 1.0 / larger_dim; uint cap = 2 * larger_dim; const double min_step = step / 1000., max_step = step; RAII_ALLOC(FloatPoint, samples, malloc(sizeof(FloatPoint) * cap)); if (!samples) fatal("Out of memory"); ClipRect cr = clip_to ? *clip_to : (ClipRect){.x_end=self->width, .y_end=self->height}; while (true) { samples[i] = (FloatPoint){xfunc(curve_data, t) + x_offset, yfunc(curve_data, t) + y_offset}; if (t >= 1.0) break; // Dynamically adjust step size based on curve's derivative double dx = x_prime(curve_data, t), dy = y_prime(curve_data, t); double d = sqrt(dx * dx + dy * dy); step = 1.0 / fmax(1e-6, d); step = fmax(min_step, fmin(step, max_step)); t = fmin(t + step, 1.0); i++; if (i >= cap) { cap *= 2; samples = realloc(samples, sizeof(samples[0]) * cap); if (!samples) fatal("Out of memory"); } } const uint num_samples = i; for (uint py = cr.top; py < cr.y_end; py++) { uint ypos = self->width * py; for (uint px = cr.left; px < cr.x_end; px++) { // Center of the current pixel double pixel_center_x = (double)px + 0.5; double pixel_center_y = (double)py + 0.5; double min_dist_sq = -1.0; // Find the closest point on the curve to the pixel center by sampling the curve. for (uint i = 0; i < num_samples; ++i) { double dx = samples[i].x - pixel_center_x; double dy = samples[i].y - pixel_center_y; double dist_sq = dx * dx + dy * dy; if (min_dist_sq < 0 || dist_sq < min_dist_sq) min_dist_sq = dist_sq; } double distance = sqrt(min_dist_sq); // Calculate alpha based on the distance from the curve. // This creates the anti-aliased edge. The distance from the center // of the pixel to the edge of the stroke is used. // We assume a pixel has a width of 1.0 for this calculation. double alpha_unclamped = half_thickness - distance + 0.5; uint offset = ypos + px; uint8_t old_alpha = self->mask[offset]; double alpha = MAX(0.0, MIN(alpha_unclamped, 1.0)); self->mask[offset] = (uint8_t)(alpha * 255 + (1 - alpha) * old_alpha); // alpha blend } } } 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), minus(self->height, gap)); uint half_gap = gap / 2; CubicBezier cb = {.end={.y=minus(self->height, 1 + half_gap)}, .c1={.x=c1x}, .c2={.x=c1x, .y=minus(self->height, 1 + half_gap)}}; double line_width = thickness_as_float(self, level, true); #define d draw_parametrized_curve_with_derivative_and_antialiasing(\ self, &cb, line_width, bezier_x, bezier_y, bezier_prime_x, bezier_prime_y, 0, half_gap, NULL) if (left) { d; } else { mirror_horizontally(d); } #undef d } 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 { double x, y, radius; double start, end, amt; } Circle; static Circle circle(double x, double y, double radius, double start_at, double end_at) { double conv = M_PI / 180.; Circle ans = {.x=x, .y=y, .radius=radius, .start=start_at*conv, .end=end_at*conv}; ans.amt = ans.end - ans.start; return ans; } static double circle_x(const void *v, double t) { const Circle *c=v; return c->x + c->radius * cos(c->start + c->amt * t); } static double circle_y(const void *v, double t) { const Circle *c=v; return c->y + c->radius * sin(c->start + c->amt * t); } static double circle_prime_x(const void *v, double t) { const Circle *c=v; return -c->radius * sin(c->start + c->amt * t); } static double circle_prime_y(const void *v, double t) { const Circle *c=v; return c->radius * cos(c->start + c->amt * t); } static void spinner(Canvas *self, uint level, double start_degrees, double end_degrees) { double x = self->width / 2.0, y = self->height / 2.0; double line_width = thickness_as_float(self, level, true); double half_real_line_width = fmax(0.5, line_width / 2.0); double radius = fmax(0, fmin(x, y) - half_real_line_width); Circle c = circle(x, y, radius, start_degrees, end_degrees); uint leftover = minus(self->height, 2*(uint)(ceil(radius) + half_real_line_width) + 1) / 2; ClipRect cr = {.top=leftover, .y_end=self->height - leftover, .x_end=self->width}; draw_parametrized_curve_with_derivative_and_antialiasing( self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, &cr); } static void fill_circle_of_radius(Canvas *self, double origin_x, double origin_y, double radius, uint8_t alpha) { 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 - origin_x, yh = (double)y - origin_y; if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = alpha; } } } static void fill_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; fill_circle_of_radius(self, w, h, radius, fill); } static void draw_fish_eye(Canvas *self, uint level UNUSED) { double x = self->width / 2., y = self->height / 2.; double radius = fmin(x, y); uint leftover = minus(self->height, 2*(uint)ceil(radius) + 1) / 2; double central_radius = (2./3.) * radius; fill_circle_of_radius(self, x, y, central_radius, 255); double line_width = fmax(1. * self->supersample_factor, (radius - central_radius) / 2.5); radius = fmax(0, fmin(x, y) - line_width / 2.); Circle c = circle(x, y, radius, 0, 360); ClipRect cr = {.top=leftover, .y_end=self->height - leftover, .x_end=self->width}; draw_parametrized_curve_with_derivative_and_antialiasing( self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, &cr); } 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) { xd = -1; Range vlinelimit = vline_limits(self, self->width / 2 + (xd * hgap), level); x2 = vlinelimit.end; } 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); } } static void rounded_corner(Canvas *self, uint level, Corner which) { // Render a rounded box corner. const Range hori_line_range = hline_limits(self, half_height(self), level); const Range vert_line_range = vline_limits(self, half_width(self), level); const uint hori_line_height = hori_line_range.end - hori_line_range.start; const uint vert_line_width = vert_line_range.end - vert_line_range.start; const double adjusted_Hx = (double)vert_line_range.start + (double)vert_line_width / 2.0; const double adjusted_Hy = (double)hori_line_range.start + (double)hori_line_height / 2.0; const double stroke = (double)max(hori_line_height, vert_line_width); const double corner_radius = fmin(adjusted_Hx, adjusted_Hy); const double bx = adjusted_Hx - corner_radius; const double by = adjusted_Hy - corner_radius; // Anti-aliasing on corner const double aa_corner = (double)self->supersample_factor * 0.5; const double half_stroke = 0.5 * stroke; const double x_shift = (which & RIGHT_EDGE) ? adjusted_Hx : -adjusted_Hx; const double y_shift = (which & TOP_EDGE) ? -adjusted_Hy : adjusted_Hy; for (uint y = 0; y < self->height; y++) { const double sample_y = (double)y + y_shift + 0.5; const double pos_y = sample_y - adjusted_Hy; const uint row_off = y * self->width; for (uint x = 0; x < self->width; x++) { const double sample_x = (double)x + x_shift + 0.5; const double pos_x = sample_x - adjusted_Hx; const double qx = fabs(pos_x) - bx; const double qy = fabs(pos_y) - by; const double dx = qx > 0.0 ? qx : 0.0; const double dy = qy > 0.0 ? qy : 0.0; const double dist = hypot(dx, dy) + fmin(fmax(qx, qy), 0.0) - corner_radius; const double aa = (qx > 1e-7 && qy > 1e-7) ? aa_corner : 0.0; const double outer = half_stroke - dist; const double inner = -half_stroke - dist; const double alpha = smoothstep(-aa, aa, outer) - smoothstep(-aa, aa, inner); if (alpha <= 0.0) continue; const uint8_t value = (uint8_t)lrint(unit_double(alpha) * 255.0); uint8_t *p = &self->mask[row_off + x]; if (value > *p) *p = value; } } } 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); fill_circle(self, scale, 0, false); if (!solid) fill_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) { const uint v_thickness = thickness(self, vlevel, true); uint v_half_tickness; if (which & LEFT_EDGE && v_thickness % 2 != 0) { v_half_tickness = v_thickness / 2 + 1; } else { v_half_tickness = v_thickness / 2; } half_hline(self, hlevel, which & RIGHT_EDGE, v_half_tickness); 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) { Point dline_position = half_dhline(self, level, which & LEFT_EDGE, TOP_EDGE | BOTTOM_EDGE); if (which & BOTTOM_EDGE) { Range bottom_limit = hline_limits(self, dline_position.y, level); draw_vline(self, 0, bottom_limit.end, half_width(self), level); } else { Range top_limit = hline_limits(self, dline_position.x, level); draw_vline(self, top_limit.start, self->height, half_width(self), level); } } static void dhcorner(Canvas *self, uint level, Corner which) { Point dline_position = half_dvline(self, level, which & TOP_EDGE, LEFT_EDGE | RIGHT_EDGE); if (which & RIGHT_EDGE) { Range right_limit = vline_limits(self, dline_position.y, level); draw_hline(self, 0, right_limit.end, half_height(self), level); } else { Range left_limit = vline_limits(self, dline_position.x, level); draw_hline(self, left_limit.start, self->width, half_height(self), level); } } 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 xpos = self->width / 2; int xdelta = (which & LEFT_EDGE) ? vgap : -vgap; Range top_hline_limit = hline_limits(self, ypos + ydelta, level); Range bottom_hline_limit = hline_limits(self, ypos - ydelta, level); if (which & TOP_EDGE) { draw_vline(self, top_hline_limit.start, self->height, xpos - xdelta, level); draw_vline(self, bottom_hline_limit.start, self->height, xpos + xdelta, level); } else { draw_vline(self, 0, bottom_hline_limit.end, xpos + xdelta, level); draw_vline(self, 0, top_hline_limit.end, 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); C(L'', rounded_separator, 1, true); C(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); C(L'', spinner, 1, 235, 305); C(L'', spinner, 1, 270, 390); C(L'', spinner, 1, 315, 470); C(L'', spinner, 1, 360, 540); C(L'', spinner, 1, 80, 220); C(L'', spinner, 1, 170, 270); C(L'○', spinner, 0, 0, 360); C(L'◜', spinner, 1, 180, 270); C(L'◝', spinner, 1, 270, 360); C(L'◞', spinner, 1, 360, 450); C(L'◟', spinner, 1, 450, 540); C(L'◠', spinner, 1, 180, 360); C(L'◡', spinner, 1, 0, 180); S(L'●', fill_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); C(L'', rounded_corner, 1, TOP_LEFT); C(L'', rounded_corner, 1, TOP_RIGHT); C(L'', rounded_corner, 1, BOTTOM_LEFT); C(L'', rounded_corner, 1, BOTTOM_RIGHT); CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT)); CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT)); CC(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT)); CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT)); CC(L'', rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT)); CC(L'', rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT)); CC(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT)); CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT)); CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); CC(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 C(L'╭', rounded_corner, 1, TOP_LEFT); C(L'╮', rounded_corner, 1, TOP_RIGHT); C(L'╰', rounded_corner, 1, BOTTOM_LEFT); C(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 } ================================================ FILE: kitty/decorations.h ================================================ /* * 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 ================================================ FILE: kitty/desktop.c ================================================ /* * 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; } ================================================ FILE: kitty/disk-cache.c ================================================ /* * 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, uses_encryption; 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; off_t end_of_data_offset; bool needs_encryption; } 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; self->needs_encryption = true; } 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, bool *opened_securely) { int fd = -1; *opened_securely = false; #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 *opened_securely = true; #else fd = open_cache_file_without_tmpfile(cache_path); #endif return fd; } // Write loop {{{ 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 = self->end_of_data_offset; if (size_on_disk <= 0) goto cleanup; size_t num_entries = vt_size(&self->map); if (!num_entries) goto cleanup; bool opened_securely; new_cache_file = open_cache_file(self->cache_dir, &opened_securely); 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); } self->end_of_data_offset = lseek(self->cache_file_fd, 0, SEEK_CUR); self->needs_encryption = !opened_securely; } 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) { return self->total_size && self->end_of_data_offset > 0 && (size_t)self->end_of_data_offset > 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; s->uses_encryption = false; if (self->needs_encryption && secure_random_bytes(s->encryption_key, sizeof(s->encryption_key))) { xor_data64(s->encryption_key, self->currently_writing.val.data, s->data_sz); s->uses_encryption = true; } 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 = self->end_of_data_offset; 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; self->end_of_data_offset = MAX(self->end_of_data_offset, offset); } 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 int clear_disk_cache_with_lock_held(DiskCache *self) { vt_cleanup(&self->map); cleanup_holes(&self->holes); self->total_size = 0; self->end_of_data_offset = 0; if (self->cache_file_fd > -1) { if (ftruncate(self->cache_file_fd, 0) == -1) return errno; } return 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) clear_disk_cache_with_lock_held(self); // failure to truncate is not fatal 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) { bool opened_securely; self->cache_file_fd = open_cache_file(self->cache_dir, &opened_securely); if (self->cache_file_fd < 0) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, self->cache_dir); return false; } self->needs_encryption = !opened_securely; } 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(); 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; } static int clear_disk_cache(PyObject *self_) { // This is currently only used in testing DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return 0; int saved_errno = 0; disk_cache_wait_for_write(self_, 0); mutex(lock); saved_errno = clear_disk_cache_with_lock_held(self); mutex(unlock); wakeup_write_loop(self); return saved_errno; } 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); if (s->uses_encryption) xor_data64(s->encryption_key, data, s->data_sz); } else { read_from_cache_entry(self, s, data); if (s->uses_encryption) 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; } // The Python interface used only for testing {{{ #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; } 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* end_of_data_offset(PyObject *self_, PyObject *args UNUSED) { // Only used for testing DiskCache *self = (DiskCache*)self_; unsigned long long ans = 0; mutex(lock); if (self->cache_file_fd > -1) ans = MAX(0, self->end_of_data_offset); mutex(unlock); return PyLong_FromUnsignedLongLong(ans); } static PyObject* clear(PyObject *self, PyObject *args UNUSED) { int saved_errno = clear_disk_cache(self); if (saved_errno) return PyErr_SetFromErrno(PyExc_OSError); 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), {"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}, {"end_of_data_offset", end_of_data_offset, 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); } // }}} ================================================ FILE: kitty/disk-cache.h ================================================ /* * 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); 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); if (PyErr_Occurred()) { PyErr_Clear(); return false; } return *data != NULL; } ================================================ FILE: kitty/entry_points.py ================================================ #!/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 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 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['shebang'] = shebang 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) and (ext_dir := getattr(sys, 'kitty_run_data', {}).get('extensions_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:]) ================================================ FILE: kitty/fast-file-copy.c ================================================ /* * 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; } ================================================ FILE: kitty/fast-file-copy.h ================================================ /* * 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); ================================================ FILE: kitty/fast_data_types.pyi ================================================ import termios from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Sequence, Tuple, TypedDict, Union, overload from kitty.borders import Border 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.simple_cli_definitions import OptionDefinition from kitty.types import LayerShellConfig, SignalInfo from kitty.typing_compat import EdgeLiteral, NotRequired, ReadableBuffer, WriteableBuffer # Constants {{{ SCALE_BITS: int WIDTH_BITS: int SUBSCALE_BITS: int COLOR_IS_SPECIAL: int COLOR_NOT_SET: int COLOR_IS_RGB: int COLOR_IS_INDEX: int GLFW_DRAG_OPERATION_MOVE: int GLFW_DRAG_OPERATION_COPY: int GLFW_DRAG_OPERATION_GENERIC: 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_CENTER_SIZED: 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_UPTO_SURROUNDING_WHITESPACE: 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_PROGRAM: int CELL_FG_PROGRAM: int CELL_BG_PROGRAM: int BLIT_PROGRAM: int SCREENSHOT_PROGRAM: int ROUNDED_RECT_PROGRAM: int DECORATION: int BLINK: int DIM: int GRAPHICS_ALPHA_MASK_PROGRAM: int GRAPHICS_PROGRAM: int GRAPHICS_PREMULT_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 WINDOW_HIDDEN: int TEXT_SIZE_CODE: int TOP_EDGE: int BOTTOM_EDGE: int LEFT_EDGE: int RIGHT_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 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 abspath_of_process(pid: int) -> str: ... 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 set_borders_rects(os_window_id: int, tab_id: int, rects: list[Border]) -> None: ... def init_borders_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[[], 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 glfw_get_monitor_workarea() -> tuple[tuple[int, int, 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'', linear: bool | None = None, tint: float | None = None, tint_gaps: float | None = None, ) -> 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: @classmethod def parse_color(cls, spec: str) -> Color | None: ... @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 macos_cycle_through_os_windows(backwards: bool) -> 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: ... def reorder_tabs(os_window_id: int, *tab_ids: int) -> None: ... 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(os_window_id: int = 0) -> None: ... def request_attention(os_window_id: int) -> None: ... 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, should_be_shown: bool) -> None: pass def is_tab_bar_visible(os_window_id: int) -> bool: ... 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 text_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 paste_events: 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 erase_last_command(self, include_prompt: bool = True) -> bool: ... 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 fractional_scroll(self, amt: float) -> 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, scroll_offset: int = 0) -> 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 delete_characters(self, num: int) -> None: ... def erase_characters(self, num: int) -> None: ... 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_title_bar_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 set_window_render_data( os_window_id: int, tab_id: int, window_id: int, screen: Screen, left: int, top: int, right: int, bottom: int, spaces_left: int, spaces_top: int, spaces_right: int, spaces_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 | memoryview) -> 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 get_window_logo_settings_if_not_default(os_window_id: int, tab_id: int, window_id: int) -> None | tuple[ str, float, tuple[float, float, float, float]]: ... 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 is_layer_shell: bool 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 glfw_get_monitor_names() -> tuple[tuple[str, str], ...]: ... 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], is_async_response: bool = False) -> 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]: ... def __next__(self) -> str: ... def __iter__(self) -> Iterator[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 toggle_os_window_visibility(os_window_id: int, visible: bool | Literal[-1] = -1, move_to_active_screen: bool = False) -> bool: ... def parse_cli_from_spec( args: list[str], names_map: dict[str, OptionDefinition], defval_map: dict[str, Any] ) -> tuple[dict[str, tuple[Any, bool]], list[str]]: ... def layer_shell_config_for_os_window(os_window_id: int) -> dict[str, Any] | None: ... def set_layer_shell_config(os_window_id: int, cfg: LayerShellConfig) -> bool: ... 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 is_layer_shell_supported() -> bool: ... 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 gpu_driver_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: ... def all_color_names() -> tuple[tuple[str, Color], ...]: ... def grab_keyboard(grab: bool | None) -> bool: ... 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: ... def start_drag_with_data( os_window_id: int, data_map: dict[str, bytes], thumbnails: Sequence[tuple[bytes, int, int]], operations: int = GLFW_DRAG_OPERATION_MOVE ) -> None: ... def change_drag_thumbnail(os_window_id: int, idx: int = -1) -> None: ... def draw_single_line_of_text(os_window_id: int, text: str, fg: int, bg: int, width: int, padding_y: int = 2) -> bytes: ... def set_tab_being_dragged(tab_id: int = 0, drag_started: bool = False, x: float = 0, y: float = 0) -> None: ... def get_tab_being_dragged() -> tuple[int, bool, float, float]: ... def request_callback_with_thumbnail( callback: str, os_window_id: int, window_id: int = 0, include_tab_bar: bool = False, scale: float = 0.25, max_width: int = 480 ) -> None: ... def png_from_32bit_rgba_data(data: bytes, width: int, height: int, flip_vertically: bool = False) -> bytes: ... ================================================ FILE: kitty/file_transmission.py ================================================ #!/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_compat 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 | memoryview, 'Field[Any]']: ans: dict[bytes | memoryview, '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 | memoryview = 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 == bytes | memoryview: 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 == bytes | memoryview: 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 | memoryview, is_last: bool = False) -> bytes: return bytes(data) class ZlibDecompressor: def __init__(self) -> None: import zlib self.d = zlib.decompressobj(wbits=0) def __call__(self, data: bytes | memoryview, 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 | memoryview, 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]: data: bytes | memoryview = b'' 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 ================================================ FILE: kitty/fixed_size_deque.h ================================================ /* * fixed_size_deque.h * Copyright (C) 2026 Kovid Goyal * * Distributed under terms of the GPL3 license. * * A fixed size deque that does not allocate. To use define DEQUE_NAME, DEQUE_CAPACITY and * DEQUE_DATA_TYPE and include header. Use deque_push_back() to append. To * iterate in append order use deque_at(i) for 0 <= i < deque_size(). */ #include #include #ifndef DEQUE_NAME #define DEQUE_NAME CircularDeque #endif #ifndef DEQUE_CAPACITY #define DEQUE_CAPACITY 50 #endif #ifndef DEQUE_DATA_TYPE #define DEQUE_DATA_TYPE int #endif typedef struct { DEQUE_DATA_TYPE items[DEQUE_CAPACITY]; unsigned head; // Index of first element unsigned tail; // Index one past last element unsigned count; // Number of elements } DEQUE_NAME; // Check if empty static inline bool deque_is_empty(const DEQUE_NAME* dq) { return dq->count == 0; } // Check if full static inline bool deque_is_full(const DEQUE_NAME* dq) { return dq->count == DEQUE_CAPACITY; } // Get current size static inline unsigned deque_size(const DEQUE_NAME* dq) { return dq->count; } // Push to back auto-evicts from front if full. // Returns true if an item was evicted, which will be copied to *evicted_item is not NULL. static inline bool deque_push_back(DEQUE_NAME* dq, DEQUE_DATA_TYPE item, DEQUE_DATA_TYPE *evicted_item) { bool evicted = false; if (deque_is_full(dq)) { // Evict front item if (evicted_item) *evicted_item = dq->items[dq->head]; evicted = true; dq->head = (dq->head + 1) % DEQUE_CAPACITY; dq->count--; } dq->items[dq->tail] = item; dq->tail = (dq->tail + 1) % DEQUE_CAPACITY; dq->count++; return evicted; } // Push to front, auto-evicts from back if full. // Returns true if an item was evicted, which will be copied to *evicted_item is not NULL. static inline bool deque_push_front(DEQUE_NAME* dq, DEQUE_DATA_TYPE item, DEQUE_DATA_TYPE *evicted_item) { bool evicted = false; if (deque_is_full(dq)) { // Evict oldest (back) item dq->tail = (dq->tail - 1 + DEQUE_CAPACITY) % DEQUE_CAPACITY; if (evicted_item) *evicted_item = dq->items[dq->tail]; evicted = true; dq->count--; } dq->head = (dq->head - 1 + DEQUE_CAPACITY) % DEQUE_CAPACITY; dq->items[dq->head] = item; dq->count++; return evicted; } // Pop from front static inline bool deque_pop_front(DEQUE_NAME* dq, DEQUE_DATA_TYPE *ans) { if (deque_is_empty(dq)) return false; if (ans) *ans = dq->items[dq->head]; dq->head = (dq->head + 1) % DEQUE_CAPACITY; dq->count--; return true; } // Pop from back static inline bool deque_pop_back(DEQUE_NAME* dq, DEQUE_DATA_TYPE *ans) { if (deque_is_empty(dq)) return false; dq->tail = (dq->tail - 1 + DEQUE_CAPACITY) % DEQUE_CAPACITY; if (ans) *ans = dq->items[dq->tail]; dq->count--; return true; } // Peek at front without removing static inline const DEQUE_DATA_TYPE* deque_peek_front(const DEQUE_NAME* dq) { if (deque_is_empty(dq)) return NULL; return &dq->items[dq->head]; } // Peek at back without removing static inline const DEQUE_DATA_TYPE* deque_peek_back(const DEQUE_NAME* dq) { if (deque_is_empty(dq)) return NULL; int idx = (dq->tail - 1 + DEQUE_CAPACITY) % DEQUE_CAPACITY; return &dq->items[idx]; } // Access by index (0 = oldest, count-1 = newest) static inline const DEQUE_DATA_TYPE* deque_at(const DEQUE_NAME* dq, unsigned index) { if (index >= dq->count) return NULL; return &dq->items[(dq->head + index) % DEQUE_CAPACITY]; } // Clear all items (doesn't free items) static inline void deque_clear(DEQUE_NAME* dq) { dq->head = 0; dq->tail = 0; dq->count = 0; } #undef DEQUE_CAPACITY #undef DEQUE_DATA_TYPE #undef DEQUE_NAME ================================================ FILE: kitty/font-names.c ================================================ /* * 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) { uint16_t elided_fallback_name_id = 0; 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; elided_fallback_name_id = next; if (major_version == 1 && minor_version < 1) elided_fallback_name_id = 0; 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; 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; 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 ================================================ FILE: kitty/fontconfig.c ================================================ /* * 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 built-in // 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; } ================================================ FILE: kitty/fonts/__init__.py ================================================ from 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_compat 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() ================================================ FILE: kitty/fonts/common.py ================================================ #!/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() ================================================ FILE: kitty/fonts/core_text.py ================================================ #!/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_compat 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', {}) ================================================ FILE: kitty/fonts/features.py ================================================ #!/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) # }}} ================================================ FILE: kitty/fonts/fontconfig.py ================================================ #!/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_compat 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 ================================================ FILE: kitty/fonts/list.py ================================================ #!/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') ================================================ FILE: kitty/fonts/render.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal 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_compat 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: import ctypes 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) ================================================ FILE: kitty/fonts.c ================================================ /* * 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" #include "print-graphics.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_glyph(const pixel *b, unsigned width, unsigned height) { print_abgr32(b, width, height); } 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_glyph(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_glyph(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: // Optimisation to avoid rendering spaces, except in the case of // scaled multicells as the decorations there have to rendered // scaled as well. if (lc->count == 1 && (lc->chars[0] == ' ' || lc->chars[0] == 0x2002 /* en-space */) && (!cpu_cell->is_multicell || cpu_cell->scale == 1)) 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) & 0xffffff00; 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("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 max_render_width, unsigned canvas_height, unsigned num_cells, unsigned num_glyphs, bool was_colored) { int delta = 0; #ifdef __APPLE__ if (num_cells == 2 && was_colored) center_glyph = true; #else (void)was_colored; #endif if (rf.subscale_n && rf.subscale_d && rf.align.horizontal && rf.subscale_n < rf.subscale_d) { delta = max_render_width - ri.rendered_width; if (rf.align.horizontal == 2) delta /= 2; } else if (center_glyph && num_glyphs && num_cells > 1 && ri.rendered_width < max_render_width) { unsigned half = (max_render_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 num_cells, unsigned 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; const Font *font = fg->fonts + rf.font_idx; bool all_rendered = true; unsigned num_scaled_cells = MAX(1u, (unsigned)ceil(num_cells / scale)); const unsigned canvas_width = num_cells * unscaled_metrics.cell_width; const bool rendering_in_smaller_area = rf.subscale_n < rf.subscale_d; if (rendering_in_smaller_area) { // scw might be 1 px less than scaled_metrics.cell_width because of rounding, but it is the correct value // to use to determine the num of scaled cells unsigned scw = (unsigned)(unscaled_metrics.cell_width * scale); num_scaled_cells = num_cells * unscaled_metrics.cell_width / scw; } unsigned scaled_canvas_width = num_scaled_cells * scaled_metrics.cell_width; #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. const 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); if (rendering_in_smaller_area) ensure_canvas_can_fit(fg, 2 * num_cells + 1, (unsigned)ceil(scale)); // scratch space pixel *scratch = fg->canvas.buf + canvas_width * unscaled_metrics.cell_height; text_in_cell(cpu_cells, tc, global_glyph_render_scratch.lc); bool is_only_filled_boxes = false; 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; } bool was_colored = !is_only_filled_boxes && has_emoji_presentation(cpu_cells, global_glyph_render_scratch.lc); GlyphRenderInfo ri = {0}; pixel *canvas = rendering_in_smaller_area && canvas_width != scaled_canvas_width ? scratch : fg->canvas.buf; if (is_only_filled_boxes) { // special case rendering of █ for tests render_filled_sprite(canvas, num_glyphs, scaled_metrics, num_scaled_cells); ri.canvas_width = canvas_width; ri.rendered_width = num_glyphs * scaled_metrics.cell_width; // dump_sprite(canvas, 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, canvas, scaled_metrics.cell_width, scaled_metrics.cell_height, num_scaled_cells, scaled_metrics.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, &ri); ri.rendered_width = MIN(ri.rendered_width, ri.canvas_width); } // printf("num_cells: %u num_scaled_cells: %u num_glyphs: %u scale: %f unscaled: %ux%u scaled: %ux%u rendered_width: %d\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, ri.rendered_width); if (canvas == scratch) { if (canvas_width != scaled_canvas_width) { unsigned stride = MIN(canvas_width, scaled_canvas_width); for (unsigned y = 0; y < scaled_metrics.cell_height; y++) { pixel *dest_row = fg->canvas.buf + y * canvas_width; memcpy(dest_row, canvas + y * scaled_canvas_width, sizeof(pixel) * stride); if (scaled_canvas_width < canvas_width) memset( dest_row + scaled_canvas_width, 0, sizeof(pixel) * (canvas_width - scaled_canvas_width)); } ri.canvas_width = canvas_width; scaled_canvas_width = canvas_width; } else memcpy(fg->canvas.buf, canvas, sizeof(pixel) * canvas_width * scaled_metrics.cell_height); canvas = fg->canvas.buf; } apply_horizontal_alignment( canvas, rf, center_glyph, ri, canvas_width, scaled_metrics.cell_height, num_scaled_cells, num_glyphs, was_colored); if (PyErr_Occurred()) PyErr_Print(); // display_glyph(canvas, canvas_width, scaled_metrics.cell_height); printf("\n"); fg->fcm = unscaled_metrics; // needed for current_send_sprite_to_gpu() if (num_cells == num_scaled_cells && scale == 1.f && !rendering_in_smaller_area) { 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 ? canvas : 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); if (rendering_in_smaller_area) src.right = unscaled_metrics.cell_width; // 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_canvas_width, 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) { if (cpu_cell->x + 1u < mcd_x_limit(cpu_cell)) i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1u; 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; } ================================================ FILE: kitty/fonts.h ================================================ /* * 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); } } ================================================ FILE: kitty/freetype.c ================================================ /* * 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 s ? FT_Get_Char_Index(((Face*)s)->face, cp) : 0; } 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_text_extents_t extents; cairo_glyph_extents(self->cairo.cr, &g, 1, &extents); 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; ans->right_edge = (int)ceil(extents.x_advance); 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; 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; } } } float 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; } // }}} ================================================ FILE: kitty/freetype_render_ui_text.c ================================================ /* * 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[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 = calloc(1, sizeof(Face)); if (!ans) fatal("Out of memory"); bool ok = load_font(&q, ans); if (PyErr_Occurred()) PyErr_Print(); free(q.path); if (!ok) { free(ans); return NULL; } main_face.fallbacks[main_face.count] = ans; 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; } size_t avail_height = *result_height; if (avail_height < 4) { PyErr_Format(PyExc_ValueError, "Invalid available height: %zu", avail_height); return NULL; } Face *face = &main_face; RAII_TempFontData(temp); temp.face = face; temp.orig_sz = face->pixel_size; set_pixel_size(ctx, face, avail_height, false); 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); float ratio = ((float)height) / avail_height; 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); 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; } ================================================ FILE: kitty/freetype_render_ui_text.h ================================================ /* * 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); ================================================ FILE: kitty/gl-wrapper.c ================================================ #define GLAD_GL_IMPLEMENTATION #include "gl-wrapper.h" ================================================ FILE: kitty/gl-wrapper.h ================================================ /** * Loader generated by glad 2.0.8 on Mon Aug 11 01:10:28 2025 * * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 * * Generator: C/C++ * Specification: gl * Extensions: 8 * * 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_framebuffer_sRGB,GL_ARB_instanced_arrays,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_EXT_framebuffer_sRGB,GL_KHR_debug' c --debug --header-only * * Online: * http://glad.sh/#api=gl%3Acore%3D3.1&extensions=GL_ARB_copy_image%2CGL_ARB_framebuffer_sRGB%2CGL_ARB_instanced_arrays%2CGL_ARB_multisample%2CGL_ARB_robustness%2CGL_ARB_texture_storage%2CGL_EXT_framebuffer_sRGB%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.8" 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_SRGB_CAPABLE_EXT 0x8DBA #define GL_FRAMEBUFFER_SRGB_EXT 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_framebuffer_sRGB 1 GLAD_API_CALL int GLAD_GL_ARB_framebuffer_sRGB; #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_EXT_framebuffer_sRGB 1 GLAD_API_CALL int GLAD_GL_EXT_framebuffer_sRGB; #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_framebuffer_sRGB = 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_EXT_framebuffer_sRGB = 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"); } static void glad_gl_free_extensions(char **exts_i) { if (exts_i != NULL) { unsigned int index; for(index = 0; exts_i[index]; index++) { free((void *) (exts_i[index])); } free((void *)exts_i); exts_i = NULL; } } static int glad_gl_get_extensions( const char **out_exts, char ***out_exts_i) { #if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) if (glad_glGetStringi != NULL && glad_glGetIntegerv != NULL) { unsigned int index = 0; unsigned int num_exts_i = 0; char **exts_i = NULL; glad_glGetIntegerv(GL_NUM_EXTENSIONS, (int*) &num_exts_i); exts_i = (char **) malloc((num_exts_i + 1) * (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) { exts_i[index] = NULL; glad_gl_free_extensions(exts_i); return 0; } memcpy(local_str, gl_str_tmp, len * sizeof(char)); exts_i[index] = local_str; } exts_i[index] = NULL; *out_exts_i = exts_i; return 1; } #else GLAD_UNUSED(out_exts_i); #endif if (glad_glGetString == NULL) { return 0; } *out_exts = (const char *)glad_glGetString(GL_EXTENSIONS); return 1; } static int glad_gl_has_extension(const char *exts, char **exts_i, const char *ext) { if(exts_i) { unsigned int index; for(index = 0; exts_i[index]; index++) { const char *e = exts_i[index]; if(strcmp(e, ext) == 0) { return 1; } } } else { 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; } } 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(void) { const char *exts = NULL; char **exts_i = NULL; if (!glad_gl_get_extensions(&exts, &exts_i)) return 0; GLAD_GL_ARB_copy_image = glad_gl_has_extension(exts, exts_i, "GL_ARB_copy_image"); GLAD_GL_ARB_framebuffer_sRGB = glad_gl_has_extension(exts, exts_i, "GL_ARB_framebuffer_sRGB"); GLAD_GL_ARB_instanced_arrays = glad_gl_has_extension(exts, exts_i, "GL_ARB_instanced_arrays"); GLAD_GL_ARB_multisample = glad_gl_has_extension(exts, exts_i, "GL_ARB_multisample"); GLAD_GL_ARB_robustness = glad_gl_has_extension(exts, exts_i, "GL_ARB_robustness"); GLAD_GL_ARB_texture_storage = glad_gl_has_extension(exts, exts_i, "GL_ARB_texture_storage"); GLAD_GL_EXT_framebuffer_sRGB = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_sRGB"); GLAD_GL_KHR_debug = glad_gl_has_extension(exts, exts_i, "GL_KHR_debug"); glad_gl_free_extensions(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; 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()) 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 */ ================================================ FILE: kitty/gl.c ================================================ /* * 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" #include "png-reader.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 #ifdef __APPLE__ // See nsgl_context.m srgb is always supported on macOS but its OpenGL // drivers dont report the extensions, so hardcode to true. global_state.supports_framebuffer_srgb = true; #else global_state.supports_framebuffer_srgb = (GLAD_GL_ARB_framebuffer_sRGB + GLAD_GL_EXT_framebuffer_sRGB) != 0; #endif 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); } } } const char* check_framebuffer_status(void) { GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); switch (status) { case GL_FRAMEBUFFER_COMPLETE: return NULL; case GL_FRAMEBUFFER_UNDEFINED: return("GL_FRAMEBUFFER_UNDEFINED"); case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: return("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: return("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); case GL_FRAMEBUFFER_UNSUPPORTED: return("GL_FRAMEBUFFER_UNSUPPORTED"); case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); default: return("Unknown error"); } } 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; } static GLuint output_framebuffer = 0; void bind_framebuffer_for_output(unsigned fbid) { glBindFramebuffer(GL_FRAMEBUFFER, fbid ? fbid : output_framebuffer); } void set_framebuffer_to_use_for_output(unsigned fbid) { output_framebuffer = fbid; } static void set_blending(bool allowed) { if (allowed) { glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } // blending of pre-multiplied colors else { glDisable(GL_BLEND); glBlendFunc(GL_ONE, GL_ZERO); } // no blending } void draw_quad(bool blend, unsigned instance_count) { set_blending(blend); if (instance_count) glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, instance_count); else glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } static struct { GLsizei items[16][4]; size_t used; } saved_viewports; void set_gpu_viewport(unsigned w, unsigned h) { glViewport(0, 0, w, h); } Viewport get_gpu_viewport(void) { GLsizei v[4]; glGetIntegerv(GL_VIEWPORT, v); return (Viewport){.left=v[0], .top=v[1], .width=v[2], .height=v[3]}; } void save_viewport_using_bottom_left_origin(GLsizei newx, GLsizei newy, GLsizei width, GLsizei height) { if (saved_viewports.used >= arraysz(saved_viewports.items)) fatal("Too many nested saved viewports"); GLsizei *saved_viewport = saved_viewports.items[saved_viewports.used++]; glGetIntegerv(GL_VIEWPORT, saved_viewport); glViewport(newx, newy, width, height); } void save_viewport_using_top_left_origin(GLsizei newx, GLsizei newy, GLsizei width, GLsizei height, GLsizei full_framebuffer_height) { // Converts the viewport defined by the specified arguments which are // assumed to be in the usual co-ord system with origin at top left to the // OpenGL viewport co-ord system with origin at bottom left. // Use restore_viewport() to restore the viewport to what it was before. if (saved_viewports.used >= arraysz(saved_viewports.items)) fatal("Too many nested saved viewports"); GLsizei *saved_viewport = saved_viewports.items[saved_viewports.used++]; glGetIntegerv(GL_VIEWPORT, saved_viewport); newy = full_framebuffer_height - (newy + height); glViewport(newx, newy, width, height); } void restore_viewport(void) { if (!saved_viewports.used) fatal("Trying to restore a viewport when none is saved"); GLsizei *saved_viewport = saved_viewports.items[--saved_viewports.used]; glViewport(saved_viewport[0], saved_viewport[1], saved_viewport[2], saved_viewport[3]); } void enable_scissor_using_top_left_origin(Viewport vp, unsigned full_framebuffer_height) { glEnable(GL_SCISSOR_TEST); GLsizei newy = full_framebuffer_height - (vp.top + vp.height); glScissor(vp.left, newy, vp.width, vp.height); } void disable_scissor(void) { glDisable(GL_SCISSOR_TEST); } static float linear_to_srgb(float c) { return (c <= 0.0031308f) ? 12.92f * c : 1.055f * powf(c, 1.0f / 2.4f) - 0.055f; } void save_texture_as_png(uint32_t texture_id, const char *filename) { GLint prev_tex = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_tex); glBindTexture(GL_TEXTURE_2D, texture_id); int width = 0, height = 0; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); size_t sz = sizeof(uint32_t) * width * height; uint32_t* data = malloc(sz); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // assume data is linear and pre-multiplied for (int i = 0; i < width * height; i++) { uint32_t px = data[i]; uint8_t r = (px >> 0) & 0xFF; uint8_t g = (px >> 8) & 0xFF; uint8_t b = (px >> 16) & 0xFF; uint8_t a = (px >> 24) & 0xFF; float alpha = a / 255.0f; float rf = 0, gf = 0, bf = 0; if (alpha > 0.0f) { rf = (r / 255.0f) / alpha; gf = (g / 255.0f) / alpha; bf = (b / 255.0f) / alpha; } rf = linear_to_srgb(rf); gf = linear_to_srgb(gf); bf = linear_to_srgb(bf); r = (uint8_t)(rf*255); g = (uint8_t)(gf * 255); b = (uint8_t)(bf * 255); data[i] = (r << 0) | (g << 8) | (b << 16) | (a << 24); } const char *png = png_from_32bit_rgba((char*)data, width, height, &sz, true); if (!sz) fatal("Failed to save PNG to %s with error: %s", filename, png); free(data); FILE* file = fopen(filename, "wb"); fwrite(png, 1, sz, file); fclose(file); glBindTexture(GL_TEXTURE_2D, prev_tex); } // }}} // 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 for %s", name); } 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* map_buffer_range(ssize_t idx, GLbitfield access, int offset, unsigned size) { return glMapBufferRange(buffers[idx].usage, offset, size, access); } 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_for_write_only(ssize_t vao_idx, size_t bufnum, int offset, unsigned size) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; bind_buffer(buf_idx); return map_buffer_range(buf_idx, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT, offset, size); } void* alloc_and_map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, bool frequently_updated) { ssize_t buf_idx = alloc_vao_buffer(vao_idx, size, bufnum, frequently_updated ? GL_STREAM_DRAW : GL_STATIC_DRAW); return map_buffer(buf_idx, GL_WRITE_ONLY); } 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); } // }}} ================================================ FILE: kitty/gl.h ================================================ /* * 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; typedef struct Viewport { unsigned left, top, width, height; } Viewport; void gl_init(void); const char* gl_version_string(void); void set_gpu_viewport(unsigned w, unsigned h); Viewport get_gpu_viewport(void); void draw_quad(bool blend, unsigned instance_count); void save_texture_as_png(uint32_t texture_id, const char *filename); 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, bool frequently_updated); 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* map_vao_buffer_for_write_only(ssize_t vao_idx, size_t bufnum, int offset, unsigned size); 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); void save_viewport_using_top_left_origin(GLsizei x, GLsizei y, GLsizei width, GLsizei height, GLsizei full_framebuffer_height); void save_viewport_using_bottom_left_origin(GLsizei x, GLsizei y, GLsizei width, GLsizei height); const char* check_framebuffer_status(void); void restore_viewport(void); void bind_framebuffer_for_output(unsigned fbid); void set_framebuffer_to_use_for_output(unsigned fbid); void enable_scissor_using_top_left_origin(Viewport, unsigned); void disable_scissor(void); ================================================ FILE: kitty/glfw-wrapper.c ================================================ // 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 **) (&glfwIsLayerShellSupported_impl) = dlsym(handle, "glfwIsLayerShellSupported"); if (glfwIsLayerShellSupported_impl == NULL) fail("Failed to load glfw function glfwIsLayerShellSupported 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 **) (&glfwGetMonitorDescription_impl) = dlsym(handle, "glfwGetMonitorDescription"); if (glfwGetMonitorDescription_impl == NULL) fail("Failed to load glfw function glfwGetMonitorDescription 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 **) (&glfwGetLayerShellConfig_impl) = dlsym(handle, "glfwGetLayerShellConfig"); if (glfwGetLayerShellConfig_impl == NULL) fail("Failed to load glfw function glfwGetLayerShellConfig with error: %s", dlerror()); *(void **) (&glfwSetLayerShellConfig_impl) = dlsym(handle, "glfwSetLayerShellConfig"); if (glfwSetLayerShellConfig_impl == NULL) fail("Failed to load glfw function glfwSetLayerShellConfig 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 **) (&glfwGrabKeyboard_impl) = dlsym(handle, "glfwGrabKeyboard"); if (glfwGrabKeyboard_impl == NULL) fail("Failed to load glfw function glfwGrabKeyboard 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 **) (&glfwSetLiveResizeCallback_impl) = dlsym(handle, "glfwSetLiveResizeCallback"); if (glfwSetLiveResizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetLiveResizeCallback with error: %s", dlerror()); *(void **) (&glfwSetDropEventCallback_impl) = dlsym(handle, "glfwSetDropEventCallback"); if (glfwSetDropEventCallback_impl == NULL) fail("Failed to load glfw function glfwSetDropEventCallback with error: %s", dlerror()); *(void **) (&glfwRequestDropData_impl) = dlsym(handle, "glfwRequestDropData"); if (glfwRequestDropData_impl == NULL) fail("Failed to load glfw function glfwRequestDropData with error: %s", dlerror()); *(void **) (&glfwEndDrop_impl) = dlsym(handle, "glfwEndDrop"); if (glfwEndDrop_impl == NULL) fail("Failed to load glfw function glfwEndDrop with error: %s", dlerror()); *(void **) (&glfwSetDragSourceCallback_impl) = dlsym(handle, "glfwSetDragSourceCallback"); if (glfwSetDragSourceCallback_impl == NULL) fail("Failed to load glfw function glfwSetDragSourceCallback with error: %s", dlerror()); *(void **) (&glfwStartDrag_impl) = dlsym(handle, "glfwStartDrag"); if (glfwStartDrag_impl == NULL) fail("Failed to load glfw function glfwStartDrag 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 **) (&glfwCocoaRecreateGLDrawable_impl) = dlsym(handle, "glfwCocoaRecreateGLDrawable"); if (glfwCocoaRecreateGLDrawable_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 **) (&glfwCocoaCycleThroughOSWindows_impl) = dlsym(handle, "glfwCocoaCycleThroughOSWindows"); if (glfwCocoaCycleThroughOSWindows_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaSetWindowChrome_impl) = dlsym(handle, "glfwCocoaSetWindowChrome"); if (glfwCocoaSetWindowChrome_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaRegisterMIMETypes_impl) = dlsym(handle, "glfwCocoaRegisterMIMETypes"); if (glfwCocoaRegisterMIMETypes_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 **) (&glfwWaylandSetTitlebarHidden_impl) = dlsym(handle, "glfwWaylandSetTitlebarHidden"); if (glfwWaylandSetTitlebarHidden_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 **) (&glfwWaylandBeep_impl) = dlsym(handle, "glfwWaylandBeep"); if (glfwWaylandBeep_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID"); if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwConfigureMomentumScroller_impl) = dlsym(handle, "glfwConfigureMomentumScroller"); if (glfwConfigureMomentumScroller_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 return NULL; } void unload_glfw(void) { if (handle) { dlclose(handle); handle = NULL; } } ================================================ FILE: kitty/glfw-wrapper.h ================================================ // // 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; typedef enum GLFWMomentumType { GLFW_NO_MOMENTUM_DATA = 0, GLFW_MOMENTUM_PHASE_BEGAN = 1, GLFW_MOMENTUM_PHASE_STATIONARY = 2, GLFW_MOMENTUM_PHASE_ACTIVE = 3, GLFW_MOMENTUM_PHASE_ENDED = 4, GLFW_MOMENTUM_PHASE_CANCELED = 5, GLFW_MOMENTUM_PHASE_MAY_BEGIN = 6, } GLFWMomentumType; typedef enum GLFWOffsetType { GLFW_SCROLL_OFFSET_LINES = 0, GLFW_SCROLL_OFFEST_V120 = 1, GLFW_SCROLL_OFFEST_HIGHRES = 2, } GLFWOffsetType; typedef struct GLFWScrollEvent { double x_offset, y_offset; // offsets are scaled by the window scale for HIGHRES struct { double x, y; } unscaled; // unscaled offsets, aka logical pixels GLFWMomentumType momentum_type; GLFWOffsetType offset_type; int keyboard_modifiers; } GLFWScrollEvent; /*! @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_WAYLAND_WINDOW_TAG 0x00025003 /*! @} */ #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; /*! @brief Opaque drop data object. * * Opaque drop data object representing data from a drag and drop operation. * This object is passed to the drop callback and can be used to query * available MIME types and read the dropped data in chunks. * * @see @ref path_drop * @see @ref glfwGetDropMimeTypes * @see @ref glfwReadDropData * * @since Added in version 4.0. * * @ingroup input */ 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, GLFW_EDGE_CENTER_SIZED } GLFWEdge; typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy; typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; struct { GLFWEdge edge; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; } previous; bool was_toggled_to_fullscreen; char output_name[128]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; int requested_exclusive_zone, hide_on_focus_loss; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing); struct { float xscale, yscale; } expected; struct { float background_opacity; int background_blur, color_space; } related; } 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; typedef enum { GLFW_DROP_ENTER, GLFW_DROP_MOVE, GLFW_DROP_LEAVE, GLFW_DROP_DROP, GLFW_DROP_STATUS_UPDATE, GLFW_DROP_DATA_AVAILABLE } GLFWDropEventType; /*! @brief Drag operation types. * * These constants specify the type of drag operation (copy, move, or generic). * * @ingroup input */ typedef enum { GLFW_DRAG_OPERATION_NONE = 0, // no operation, drop was not accepted /*! Move the dragged data to the destination. */ GLFW_DRAG_OPERATION_MOVE = 1, /*! Copy the dragged data to the destination. */ GLFW_DRAG_OPERATION_COPY = 2, /*! Generic drag operation (platform decides semantics). */ GLFW_DRAG_OPERATION_GENERIC = 4 } GLFWDragOperationType; typedef struct GLFWDropEvent { GLFWDropEventType type; const char **mimes; size_t num_mimes; // Positions are only valid for GLFW_DROP_ENTER and GLFW_DROP_MOVE. // They are in window co-ordinates same as for mouse events double xpos, ypos; bool from_self; // Only valid upto GLFW_DROP_DROP ssize_t (*read_data)(GLFWwindow *w, struct GLFWDropEvent* ev, char *buffer, size_t sz); // Only valid for GLFW_DROP_DATA_AVAILABLE void (*finish_drop)(GLFWwindow *w, GLFWDragOperationType op); // Only valid for GLFW_DROP_DROP and GLFW_DROP_DATA_AVAILABLE } GLFWDropEvent; typedef void (* GLFWdropeventfun)(GLFWwindow*, GLFWDropEvent *event); /*! @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*,const GLFWScrollEvent*); /*! @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*); typedef enum { GLFW_DRAG_DATA_REQUEST, // request data for specified mime type GLFW_DRAG_CANCELLED, GLFW_DRAG_FINSHED, GLFW_DRAG_ACCEPTED, // mimetype was accepted or NULL if drag was accepted but no mime type specified GLFW_DRAG_ACTION_CHANGED, // action was changed 0 or GLFWDragOperationType GLFW_DRAG_DROPPED, // drop was performed but no data transferred yet } GLFWDragEventType; typedef struct GLFWDragSourceItem { const char *mime_type; // Can be on null to provide data when the drag is started should be used only when the data is relatively small const char *optional_data; size_t data_size; } GLFWDragSourceItem; typedef struct GLFWDragEvent { GLFWDragEventType type; // When the drag event callback is called with a mimetype and no data, the // application should set the data ans data_sz and err_num fields. // Once glfw is done reading the data the drag event callback will be // called with the data pointer unchanged. The application is now free // to delete the data, as needed. const char *mime_type; const char *data; size_t data_sz; int err_num; // POSIX error code indicating failure fetching data GLFWDragOperationType action; // can be 0 indicating no action } GLFWDragEvent; typedef void (* GLFWdragsourcefun)(GLFWwindow* window, GLFWDragEvent *ev); /*! @brief The function pointer type for drag event callbacks. * * This is the function pointer type for drag event callbacks. A drag event * callback function has the following signature: * @code * int function_name(GLFWwindow* window, int event, double xpos, double ypos, const char** mime_types, int* mime_count) * @endcode * * @param[in] window The window that received the drag event. * @param[in] event The drag event type: @ref GLFW_DRAG_ENTER, @ref GLFW_DRAG_MOVE, * or @ref GLFW_DRAG_LEAVE. * @param[in] xpos The x-coordinate of the drag position in window coordinates. * @param[in] ypos The y-coordinate of the drag position in window coordinates. * @param[in,out] mime_types A writable array of MIME type strings available from the drag source. * For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events this is non-NULL and contains all * available MIME types. The callback is responsible for sorting this list by priority and * keeping only the MIME types it wants to accept. The first MIME type in the sorted list * will be used for the drop operation. The strings are only valid for the duration of the * callback; if you need to store them, make copies. For @ref GLFW_DRAG_LEAVE events this * is `NULL`. * @param[in,out] mime_count Pointer to the number of MIME types in the array. The callback * should update this to reflect the new count after sorting and filtering. For * @ref GLFW_DRAG_LEAVE events this is `NULL`. * @return For @ref GLFW_DRAG_ENTER and @ref GLFW_DRAG_MOVE events, return non-zero * to accept the drag or zero to reject it. This allows the application to * dynamically accept or reject the drag based on the current position. * Return value is ignored for @ref GLFW_DRAG_LEAVE events. * * @sa @ref drag_events * @sa @ref glfwSetDragCallback * @sa @ref glfwUpdateDragState * * @since Added in version 4.0. * * @ingroup input */ typedef int (* GLFWdragfun)(GLFWwindow*, GLFWDragEventType event, double xpos, double ypos, const char** mime_types, int* mime_count); 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. */ const 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)(bool); 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 bool (*glfwIsLayerShellSupported_func)(void); GFW_EXTERN glfwIsLayerShellSupported_func glfwIsLayerShellSupported_impl; #define glfwIsLayerShellSupported glfwIsLayerShellSupported_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 const char* (*glfwGetMonitorDescription_func)(GLFWmonitor*); GFW_EXTERN glfwGetMonitorDescription_func glfwGetMonitorDescription_impl; #define glfwGetMonitorDescription glfwGetMonitorDescription_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*, const GLFWLayerShellConfig*); 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 const GLFWLayerShellConfig* (*glfwGetLayerShellConfig_func)(GLFWwindow*); GFW_EXTERN glfwGetLayerShellConfig_func glfwGetLayerShellConfig_impl; #define glfwGetLayerShellConfig glfwGetLayerShellConfig_impl typedef bool (*glfwSetLayerShellConfig_func)(GLFWwindow*, const GLFWLayerShellConfig*); GFW_EXTERN glfwSetLayerShellConfig_func glfwSetLayerShellConfig_impl; #define glfwSetLayerShellConfig glfwSetLayerShellConfig_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*, bool); 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 bool (*glfwGrabKeyboard_func)(int); GFW_EXTERN glfwGrabKeyboard_func glfwGrabKeyboard_impl; #define glfwGrabKeyboard glfwGrabKeyboard_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 GLFWliveresizefun (*glfwSetLiveResizeCallback_func)(GLFWwindow*, GLFWliveresizefun); GFW_EXTERN glfwSetLiveResizeCallback_func glfwSetLiveResizeCallback_impl; #define glfwSetLiveResizeCallback glfwSetLiveResizeCallback_impl typedef GLFWdropeventfun (*glfwSetDropEventCallback_func)(GLFWwindow*, GLFWdropeventfun); GFW_EXTERN glfwSetDropEventCallback_func glfwSetDropEventCallback_impl; #define glfwSetDropEventCallback glfwSetDropEventCallback_impl typedef int (*glfwRequestDropData_func)(GLFWwindow*, const char*); GFW_EXTERN glfwRequestDropData_func glfwRequestDropData_impl; #define glfwRequestDropData glfwRequestDropData_impl typedef void (*glfwEndDrop_func)(GLFWwindow*, GLFWDragOperationType); GFW_EXTERN glfwEndDrop_func glfwEndDrop_impl; #define glfwEndDrop glfwEndDrop_impl typedef GLFWdragsourcefun (*glfwSetDragSourceCallback_func)(GLFWwindow*, GLFWdragsourcefun); GFW_EXTERN glfwSetDragSourceCallback_func glfwSetDragSourceCallback_impl; #define glfwSetDragSourceCallback glfwSetDragSourceCallback_impl typedef int (*glfwStartDrag_func)(GLFWwindow*, const GLFWDragSourceItem*, size_t, const GLFWimage*, int, bool); GFW_EXTERN glfwStartDrag_func glfwStartDrag_impl; #define glfwStartDrag glfwStartDrag_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 bool (*glfwCocoaRecreateGLDrawable_func)(GLFWwindow*); GFW_EXTERN glfwCocoaRecreateGLDrawable_func glfwCocoaRecreateGLDrawable_impl; #define glfwCocoaRecreateGLDrawable glfwCocoaRecreateGLDrawable_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 (*glfwCocoaCycleThroughOSWindows_func)(bool); GFW_EXTERN glfwCocoaCycleThroughOSWindows_func glfwCocoaCycleThroughOSWindows_impl; #define glfwCocoaCycleThroughOSWindows glfwCocoaCycleThroughOSWindows_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 void (*glfwCocoaRegisterMIMETypes_func)(GLFWwindow*, const char**, size_t); GFW_EXTERN glfwCocoaRegisterMIMETypes_func glfwCocoaRegisterMIMETypes_impl; #define glfwCocoaRegisterMIMETypes glfwCocoaRegisterMIMETypes_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 (*glfwWaylandSetTitlebarHidden_func)(GLFWwindow*, bool); GFW_EXTERN glfwWaylandSetTitlebarHidden_func glfwWaylandSetTitlebarHidden_impl; #define glfwWaylandSetTitlebarHidden glfwWaylandSetTitlebarHidden_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 bool (*glfwWaylandBeep_func)(GLFWwindow*); GFW_EXTERN glfwWaylandBeep_func glfwWaylandBeep_impl; #define glfwWaylandBeep glfwWaylandBeep_impl typedef pid_t (*glfwWaylandCompositorPID_func)(void); GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl; #define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl typedef void (*glfwConfigureMomentumScroller_func)(double, double, double, unsigned); GFW_EXTERN glfwConfigureMomentumScroller_func glfwConfigureMomentumScroller_impl; #define glfwConfigureMomentumScroller glfwConfigureMomentumScroller_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 const char* load_glfw(const char* path); ================================================ FILE: kitty/glfw.c ================================================ /* * 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" #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 bool is_continuation_byte(unsigned char byte) { return (byte & 0xC0) == 0x80; // Continuation bytes have the form 10xxxxxx } static int utf8_sequence_length(unsigned char byte) { if ((byte & 0x80) == 0) return 1; // 0xxxxxxx: Single-byte ASCII if ((byte & 0xE0) == 0xC0) return 2; // 110xxxxx: Two-byte sequence if ((byte & 0xF0) == 0xE0) return 3; // 1110xxxx: Three-byte sequence if ((byte & 0xF8) == 0xF0) return 4; // 11110xxx: Four-byte sequence return -1; // Invalid first byte } // Function to remove invalid UTF-8 bytes from the end of a string static void remove_invalid_utf8_from_end(char *str, size_t len) { if (!len) return; // Start from the end of the string and move backward size_t i = len - 1; while (i > 0) { if (is_continuation_byte((unsigned char)str[i])) { // Continue backward to find the start of the potential UTF-8 sequence size_t start = i; while (start > 0 && is_continuation_byte((unsigned char)str[start])) start--; // Check if the sequence is valid int seq_len = utf8_sequence_length((unsigned char)str[start]); if (seq_len > 0 && start + seq_len == len) return; // Valid sequence found, stop trimming // Invalid sequence, trim it str[start] = '\0'; len = start; i = start - 1; } else { // Not a continuation byte, check if it's a valid start byte int seq_len = utf8_sequence_length((unsigned char)str[i]); if (seq_len > 0 && i + seq_len == len) return; // Valid sequence found, stop trimming // Invalid byte, trim it str[i] = '\0'; len = i; i--; } } // Handle the case where the entire string is invalid if (utf8_sequence_length((unsigned char)str[0]) < 0) str[0] = '\0'; } 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; remove_invalid_utf8_from_end(buf, dest - buf); } 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); static bool set_layer_shell_config_for(OSWindow *w, GLFWLayerShellConfig *lsc) { if (lsc) { lsc->related.background_opacity = effective_os_window_alpha(w); lsc->related.background_blur = OPT(background_blur); lsc->related.color_space = OPT(macos_colorspace); w->hide_on_focus_loss = lsc->hide_on_focus_loss; } return glfwSetLayerShellConfig(w->handle, lsc); } 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); } if (dpi_changed && window->is_layer_shell && window->handle) set_layer_shell_config_for(window, NULL); } // 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 cursor_active_callback(monotonic_t now) { if (!global_state.callback_os_window) return; if (OPT(mouse_hide.unhide_wait) == 0) { show_mouse_cursor(global_state.callback_os_window->handle); } else if (OPT(mouse_hide.unhide_wait) > 0) { if (global_state.callback_os_window->mouse_activate_deadline == -1) { global_state.callback_os_window->mouse_activate_deadline = OPT(mouse_hide.unhide_wait) + now; global_state.callback_os_window->mouse_show_threshold = (int) (monotonic_t_to_s_double(OPT(mouse_hide.unhide_wait)) * OPT(mouse_hide.unhide_threshold)); } else if (now < global_state.callback_os_window->mouse_activate_deadline) { if (global_state.callback_os_window->mouse_show_threshold > 0) { global_state.callback_os_window->mouse_show_threshold--; } } else { if ( now < global_state.callback_os_window->mouse_activate_deadline + s_double_to_monotonic_t(0.5) && global_state.callback_os_window->mouse_show_threshold == 0 ) { show_mouse_cursor(global_state.callback_os_window->handle); } global_state.callback_os_window->mouse_activate_deadline = -1; } } } 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; // On macOS Tahoe, the default framebuffer can become undefined during // screen change events. Try to recover by recreating the drawable. // See https://github.com/kovidgoyal/kitty/issues/9463 if (!current_framebuffer_is_ok()) { debug_rendering("Cocoa OpenGL framebuffer broken, re-creating\n"); if (!glfwCocoaRecreateGLDrawable(window->handle) || !current_framebuffer_is_ok()) { debug_rendering("Cocoa OpenGL framebuffer re-creation failed\n"); request_tick_callback(); return; } } bool rendered = false; if (window->fonts_data->sprite_map) { window->needs_render = true; window->render_state = RENDER_FRAME_READY; rendered = render_os_window(window, monotonic(), true); } if (!rendered) { debug_rendering("Cocoa out of sequence render did not happen\n"); 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); set_gpu_viewport(width, height); 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; #ifdef __APPLE__ cocoa_clear_dock_badge_if_set(); #endif #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; double x, y; glfwGetCursorPos(w, &x, &y); 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 (entered) { debug_input("Mouse cursor entered window: %llu at %fx%f\n", global_state.callback_os_window->id, x, y); cursor_active_callback(now); if (is_window_ready_for_callbacks()) enter_event(mods_at_last_key_or_button_event); } else { debug_input("Mouse cursor left window: %llu\n", global_state.callback_os_window->id); if (is_window_ready_for_callbacks()) leave_event(mods_at_last_key_or_button_event); } request_tick_callback(); 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; #ifdef __APPLE__ cocoa_clear_dock_badge_if_set(); #endif monotonic_t now = monotonic(); cursor_active_callback(now); mods_at_last_key_or_button_event = mods; 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 on_mouse_position_update(double x, double y) { monotonic_t now = monotonic(); cursor_active_callback(now); 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(); } static void cursor_pos_callback(GLFWwindow *w, double x, double y) { if (!set_callback_window(w)) return; on_mouse_position_update(x, y); global_state.callback_os_window = NULL; } static void scroll_callback(GLFWwindow *w, const GLFWScrollEvent *ev) { if (!set_callback_window(w)) return; monotonic_t now = monotonic(); if (OPT(mouse_hide.scroll_unhide)) cursor_active_callback(now); global_state.callback_os_window->last_mouse_activity_at = now; if (is_window_ready_for_callbacks()) scroll_event(ev); request_tick_callback(); global_state.callback_os_window = NULL; } static id_type focus_counter = 0; static void set_os_window_visibility(OSWindow *w, int set_visible, bool move_to_active_screen) { if (set_visible) { glfwShowWindow(w->handle, move_to_active_screen); w->needs_render = true; w->render_state = RENDER_FRAME_NOT_REQUESTED; w->keep_rendering_till_swap = 256; // try this many times request_tick_callback(); } else glfwHideWindow(w->handle); } static void update_os_window_visibility_based_on_focus(id_type timer_id UNUSED, void*d) { OSWindow * osw = os_window_for_id((uintptr_t)d); if (osw && osw->hide_on_focus_loss && !osw->is_focused) set_os_window_visibility(osw, 0, false); } static void window_focus_callback(GLFWwindow *w, int focused) { if (!set_callback_window(w)) return; #define osw global_state.callback_os_window debug_input("\x1b[35mon_focus_change\x1b[m: window id: 0x%llu focused: %d\n", osw->id, focused); bool focus_changed = osw->is_focused != focused; osw->is_focused = focused ? true : false; monotonic_t now = monotonic(); id_type wid = osw->id; if (focused) { cursor_active_callback(now); focus_in_event(); osw->last_focused_counter = ++focus_counter; global_state.check_for_active_animated_images = true; } osw->last_mouse_activity_at = now; osw->cursor_blink_zero_time = now; if (is_window_ready_for_callbacks()) { WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False); if (!osw || osw->id != wid) osw = os_window_for_id(wid); if (osw) { GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_FOCUS, .focused = focused }; glfwUpdateIMEState(osw->handle, &ev); if (focused) { Tab *tab = osw->tabs + osw->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(); if (osw && osw->handle && !focused && focus_changed && osw->hide_on_focus_loss && glfwGetWindowAttrib(osw->handle, GLFW_VISIBLE)) { add_main_loop_timer(0, false, update_os_window_visibility_based_on_focus, (void*)(uintptr_t)osw->id, NULL); } osw = NULL; #undef osw } #define TAB_DRAG_MIME_NUMBER 400 static int is_droppable_mime(const char *mime) { static char tab_mime[64] = {0}; if (!tab_mime[0]) snprintf(tab_mime, sizeof(tab_mime), "application/net.kovidgoyal.kitty-tab-%d", getpid()); if (strcmp(mime, tab_mime) == 0) return TAB_DRAG_MIME_NUMBER; 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; } static size_t remove_duplicate_mimes(const char **mimes, size_t count) { // Use simple O(n²) scan since lists are typically small size_t new_count = 0; for (size_t i = 0; i < count; i++) { bool is_duplicate = false; for (size_t j = 0; j < new_count; j++) { if (strcmp(mimes[i], mimes[j]) == 0) { is_duplicate = true; break; } } if (!is_duplicate) { if (new_count != i) SWAP(mimes[i], mimes[new_count]); new_count++; } } return new_count; } static void update_allowed_mimes_for_drop(GLFWDropEvent *ev) { if (ev->mimes && ev->num_mimes) { // Sort MIME types by priority (descending) and keep only accepted ones // Use simple bubble sort since lists are typically small size_t new_count = 0; // Use stack-allocated array for priorities (count is typically small) int priorities[32]; int* prio_arr = (ev->num_mimes <= (int)arraysz(priorities)) ? priorities : (int*)malloc(ev->num_mimes * sizeof(int)); if (!prio_arr) return; // First pass: filter droppable MIME types and cache priorities for (size_t i = 0; i < ev->num_mimes; i++) { int prio = is_droppable_mime(ev->mimes[i]); if (prio > 0) { // Move this mime to the new_count position if (new_count != i) { SWAP(ev->mimes[i], ev->mimes[new_count]); } prio_arr[new_count] = prio; new_count++; } } // Second pass: sort by cached priorities (descending) for (size_t i = 0; i + 1 < new_count; i++) { for (size_t j = i + 1; j < new_count; j++) { if (prio_arr[j] > prio_arr[i]) { SWAP(ev->mimes[i], ev->mimes[j]); SWAP(prio_arr[i], prio_arr[j]); } } } if (prio_arr != priorities) free(prio_arr); ev->num_mimes = new_count; } } static void read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) { RAII_PyObject(chunk, PyBytes_FromStringAndSize(NULL, 8192)); #define finish(ok) ev->finish_drop(window, ok ? GLFW_DRAG_OPERATION_COPY : GLFW_DRAG_OPERATION_GENERIC); Py_CLEAR(global_state.drop_dest.data); if (PyErr_Occurred()) PyErr_Print() if (!chunk) { finish(false); return; } ssize_t ret = ev->read_data(window, ev, PyBytes_AS_STRING(chunk), PyBytes_GET_SIZE(chunk)); if (ret == 0) { global_state.drop_dest.num_left--; if (!global_state.drop_dest.num_left) { WINDOW_CALLBACK(on_drop, "OOii", global_state.drop_dest.data, Py_False, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); finish(true); } } else if (ret > 0) { _PyBytes_Resize(&chunk, ret); PyObject *data = chunk; RAII_PyObject(existing, PyDict_GetItemString(global_state.drop_dest.data, ev->mimes[0])); if (existing) { existing = Py_NewRef(existing); // because PyBytes_Concat steals a reference PyBytes_Concat(&existing, chunk); data = existing; } if (!data || PyDict_SetItemString(global_state.drop_dest.data, ev->mimes[0], data) != 0) { finish(false); } } else { int posix_errno = -ret; WINDOW_CALLBACK(on_drop, "iOii", posix_errno, Py_False, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); finish(false); } #undef finish } static void on_drop(GLFWwindow *window, GLFWDropEvent *ev) { if (!set_callback_window(window)) return; OSWindow *os_window = global_state.callback_os_window; switch (ev->type) { case GLFW_DROP_ENTER: case GLFW_DROP_MOVE: os_window->last_drag_event.x = (int)(ev->xpos * os_window->viewport_x_ratio); os_window->last_drag_event.y = (int)(ev->ypos * os_window->viewport_y_ratio); on_mouse_position_update(ev->xpos, ev->ypos); call_boss(on_drop_move, "KiiOO", os_window->id, os_window->last_drag_event.x, os_window->last_drag_event.y, ev->from_self ? Py_True : Py_False, Py_False); /* fallthrough */ case GLFW_DROP_STATUS_UPDATE: update_allowed_mimes_for_drop(ev); break; case GLFW_DROP_LEAVE: call_boss(on_drop_move, "KiiOO", os_window->id, os_window->last_drag_event.x, os_window->last_drag_event.y, ev->from_self ? Py_True : Py_False, Py_True); break; case GLFW_DROP_DROP: Py_CLEAR(global_state.drop_dest.data); if (ev->from_self) { if (global_state.drag_source.drag_data) { global_state.drag_source.was_dropped = true; WINDOW_CALLBACK(on_drop, "OOii", global_state.drag_source.drag_data, Py_True, global_state.callback_os_window->last_drag_event.x, global_state.callback_os_window->last_drag_event.y); } else log_error("Got a drop from self but drag_source.drag_data is NULL"); ev->finish_drop(window, GLFW_DRAG_OPERATION_COPY); break; } update_allowed_mimes_for_drop(ev); ev->num_mimes = remove_duplicate_mimes(ev->mimes, ev->num_mimes); global_state.drop_dest.num_left = ev->num_mimes; if (!global_state.drop_dest.num_left || !(global_state.drop_dest.data = PyDict_New())) { ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); } break; case GLFW_DROP_DATA_AVAILABLE: if (!global_state.drop_dest.data) ev->finish_drop(window, GLFW_DRAG_OPERATION_GENERIC); else read_drop_data(window, ev); break; } } 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(); } } } #define ds (global_state.drag_source) void free_drag_source(void) { if (ds.accepted_mime_type) free(ds.accepted_mime_type); Py_CLEAR(ds.drag_data); Py_CLEAR(ds.thumbnails); zero_at_ptr(&ds); } static void drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) { #define finish \ call_boss(on_drag_source_finished, "OOsiOO", \ ds.was_dropped ? Py_True : Py_False, ds.was_canceled ? Py_True: Py_False, \ ds.accepted_mime_type ? ds.accepted_mime_type : "", \ ds.action, ds.drag_data ? ds.drag_data : Py_None, ds.needs_toplevel_on_wayland ? Py_True : Py_False); \ free_drag_source(); switch (ev->type) { case GLFW_DRAG_DATA_REQUEST: // we currently pre-provide all data so this should never happen if (ev->data_sz) { // previously returned data is consumed, free it } else { ev->err_num = ENOENT; } break; case GLFW_DRAG_ACCEPTED: free(ds.accepted_mime_type); ds.accepted_mime_type = ev->mime_type ? strdup(ev->mime_type) : NULL; break; case GLFW_DRAG_ACTION_CHANGED: ds.action = ev->action; break; case GLFW_DRAG_DROPPED: ds.was_dropped = true; if (ev->action == GLFW_DRAG_OPERATION_NONE) { finish } break; case GLFW_DRAG_CANCELLED: ds.was_canceled = true; /* fallthrough */ case GLFW_DRAG_FINSHED: finish break; } #undef finish } #undef ds 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(double font_sz_pts UNUSED, double ydpi 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 bold_render_ctx = NULL, normal_render_ctx = NULL; static FreeTypeRenderCtx freetype_render_ctx(bool bold) { FreeTypeRenderCtx *which = bold ? &bold_render_ctx : &normal_render_ctx; if (!*which) { *which = create_freetype_render_context(NULL, bold, false); if (!*which) { if (PyErr_Occurred()) PyErr_Print(); return NULL; } } return *which; } 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; FreeTypeRenderCtx ctx; if (!(ctx = freetype_render_ctx(true))) 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(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(double font_sz_pts, double ydpi, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { FreeTypeRenderCtx ctx; if (!(ctx = freetype_render_ctx(false))) return false; static char buf[2048]; strip_csi_(text, buf, arraysz(buf)); unsigned px_sz = (unsigned)(font_sz_pts * ydpi / 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(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) { FreeTypeRenderCtx ctx; if (!(ctx = freetype_render_ctx(true))) return false; uint8_t *ans = render_single_ascii_char_as_mask(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 (os_window->is_layer_shell && global_state.is_wayland) Py_RETURN_NONE; 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) return false; if (!w->is_layer_shell) { #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) return do_toggle_fullscreen(w, 1, false); #endif return do_toggle_fullscreen(w, 0, true); } const GLFWLayerShellConfig *prev = glfwGetLayerShellConfig(w->handle); if (!prev) return false; GLFWLayerShellConfig lsc; memcpy(&lsc, prev, sizeof(lsc)); if (prev->type == GLFW_LAYER_SHELL_OVERLAY || prev->type == GLFW_LAYER_SHELL_TOP) { if (prev->was_toggled_to_fullscreen) { lsc.edge = prev->previous.edge; lsc.requested_bottom_margin = prev->previous.requested_bottom_margin; lsc.requested_top_margin = prev->previous.requested_top_margin; lsc.requested_left_margin = prev->previous.requested_left_margin; lsc.requested_right_margin = prev->previous.requested_right_margin; lsc.was_toggled_to_fullscreen = false; glfwSetLayerShellConfig(w->handle, &lsc); return true; } lsc.edge = GLFW_EDGE_CENTER; lsc.previous.edge = prev->edge; lsc.previous.requested_right_margin = prev->requested_right_margin; lsc.previous.requested_left_margin = prev->requested_left_margin; lsc.previous.requested_top_margin = prev->requested_top_margin; lsc.previous.requested_bottom_margin = prev->requested_bottom_margin; lsc.requested_bottom_margin = 0; lsc.requested_top_margin = 0; lsc.requested_left_margin = 0; lsc.requested_right_margin = 0; lsc.was_toggled_to_fullscreen = true; glfwSetLayerShellConfig(w->handle, &lsc); return true; } return false; } bool is_os_window_fullscreen(OSWindow *w) { unsigned int flags = 0; if (!w || !w->handle) return false; if (w->is_layer_shell) { const GLFWLayerShellConfig *c = glfwGetLayerShellConfig(w->handle); return c && c->was_toggled_to_fullscreen; } #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) flags = 1; #endif return glfwIsFullscreen(w->handle, flags); } static bool toggle_maximized_for_os_window(OSWindow *w) { bool maximized = false; if (w && w->handle && !w->is_layer_shell) { 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: if (!w->is_layer_shell) glfwMaximizeWindow(w->handle); break; case WINDOW_MINIMIZED: if (!w->is_layer_shell) 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 if (!w->is_layer_shell) glfwRestoreWindow(w->handle); break; case WINDOW_HIDDEN: glfwHideWindow(w->handle); break; } } #ifdef __APPLE__ 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 (!set_callback_window(w)) return false; if (!OPT(macos_traditional_fullscreen)) { // In non traditional fullscreen macOS forces the window to opaque global_state.callback_os_window->background_opacity.os_forces_opaque = !is_os_window_fullscreen( global_state.callback_os_window); return false; } // macOS Split View uses Cocoa fullscreen internally, so the window // can end up with NSWindowStyleMaskFullScreen set even when // macos_traditional_fullscreen is enabled. Redirecting to traditional // fullscreen would crash in setStyleMask: (see #9572). if (glfwIsFullscreen(w, 1)) { global_state.callback_os_window->background_opacity.os_forces_opaque = false; 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, float background_opacity) { zero_at_ptr(s); const bool should_blur = background_opacity < 1.f && OPT(background_blur) > 0; #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 (global_state.is_wayland && glfwWaylandSetTitlebarHidden) { bool titlebar_only = (new_state.hide_window_decorations & 2) != 0; glfwWaylandSetTitlebarHidden(w, titlebar_only); } 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 || w->is_layer_shell) 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, effective_os_window_alpha(w)); 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: case GLFW_EDGE_CENTER_SIZED: 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, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing) { OSWindow *os_window = os_window_for_glfw_window(window); double xdpi, ydpi; dpi_from_scale(xscale, yscale, &xdpi, &ydpi); FONTS_DATA_HANDLE fonts_data = load_fonts_data(os_window ? os_window->fonts_data->font_sz_in_pts : OPT(font_size), xdpi, ydpi); *cell_width = fonts_data->fcm.cell_width; *cell_height = fonts_data->fcm.cell_height; double x_factor = xdpi / 72., y_factor = ydpi / 72.; *left_edge_spacing = edge_spacing(GLFW_EDGE_LEFT) * x_factor; *top_edge_spacing = edge_spacing(GLFW_EDGE_TOP) * y_factor; *right_edge_spacing = edge_spacing(GLFW_EDGE_RIGHT) * x_factor; *bottom_edge_spacing = edge_spacing(GLFW_EDGE_BOTTOM) * y_factor; } static PyObject* layer_shell_config_to_python(const GLFWLayerShellConfig *c) { RAII_PyObject(ans, PyDict_New()); if (!ans) return ans; #define fl(x) PyLong_FromLong((long)x) #define fu(x) PyLong_FromUnsignedLong((unsigned long)x) #define b(x) Py_NewRef(x ? Py_True : Py_False) #define A(attr, convert) RAII_PyObject(attr, convert(c->attr)); if (!attr) return NULL; if (PyDict_SetItemString(ans, #attr, attr) != 0) return NULL; A(type, fl); A(output_name, PyUnicode_FromString); A(edge, fl); A(focus_policy, fl); A(x_size_in_cells, fu); A(y_size_in_cells, fu); A(x_size_in_pixels, fu); A(y_size_in_pixels, fu); A(requested_top_margin, fl); A(requested_left_margin, fl); A(requested_bottom_margin, fl); A(requested_right_margin, fl); A(requested_exclusive_zone, fl); A(hide_on_focus_loss, b) A(override_exclusive_zone, b); #undef A #undef fl #undef fu #undef b return Py_NewRef(ans); } static bool layer_shell_config_from_python(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_AsUnsignedLong); A(y_size_in_cells, PyLong_Check, PyLong_AsUnsignedLong); A(x_size_in_pixels, PyLong_Check, PyLong_AsUnsignedLong); A(y_size_in_pixels, PyLong_Check, PyLong_AsUnsignedLong); 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); A(hide_on_focus_loss, 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 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); } } 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; GLFWLayerShellConfig *lsc = NULL, lsc_stack = {0}; 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 (layer_shell_config && layer_shell_config != Py_None ) { if (!glfwIsLayerShellSupported()) { PyErr_SetString(PyExc_RuntimeError, "The window manager/compositor does not support the primitives needed to make panels."); return NULL; } lsc = &lsc_stack; } else { 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_HIDDEN) window_state = WINDOW_NORMAL; } if (PyErr_Occurred()) return NULL; if (lsc && window_state != WINDOW_HIDDEN) window_state = WINDOW_NORMAL; static bool is_first_window = true; if (is_first_window) { #ifndef __APPLE__ glfwConfigureMomentumScroller(OPT(momentum_scroll), -1, -1, 0); #endif 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 // GL_FRAMEBUFFER_SRGB works anyway without this on Wayland. if (!global_state.is_wayland) glfwWindowHint(GLFW_SRGB_CAPABLE, true); #ifdef __APPLE__ cocoa_set_activation_policy(OPT(macos_hide_from_tasks) || lsc != NULL); glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true); glfwSetApplicationShouldHandleReopen(on_application_reopen); glfwSetApplicationWillFinishLaunching(cocoa_application_lifecycle_event); #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. On Wayland no temp window can be // used, so start with window visible unless hidden window requested. GLFWwindow *common_context = global_state.num_os_windows ? global_state.os_windows[0].handle : NULL; GLFWwindow *temp_window = NULL; glfwWindowHint(GLFW_VISIBLE, window_state != WINDOW_HIDDEN && global_state.is_wayland); 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 { #define glfw_failure { \ PyErr_Format(PyExc_OSError, "Failed to create GLFWwindow. This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL %d.%d drivers.", OPENGL_REQUIRED_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MINOR); \ return NULL; } temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context, NULL); if (temp_window == NULL) glfw_failure; 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 (lsc) { if (!layer_shell_config_from_python(layer_shell_config, lsc)) return NULL; lsc->expected.xscale = xscale; lsc->expected.yscale = yscale; } GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window ? temp_window : common_context, lsc); if (temp_window) { glfwDestroyWindow(temp_window); temp_window = NULL; } if (glfw_window == NULL) glfw_failure; #undef glfw_failure // Set titlebar-only mode before the window becomes visible if (global_state.is_wayland && (OPT(hide_window_decorations) & 2) && glfwWaylandSetTitlebarHidden) { glfwWaylandSetTitlebarHidden(glfw_window, true); } glfwMakeContextCurrent(glfw_window); if (is_first_window) gl_init(); 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), true); 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); if (!global_state.is_apple && !global_state.is_wayland && window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window, false); if (global_state.is_wayland || global_state.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 || lsc) { // 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_CallNoArgs(load_programs); if (ret == NULL) return NULL; Py_DECREF(ret); get_platform_dependent_config_values(glfw_window); if (!global_state.supports_framebuffer_srgb) { log_error("The OpenGL drivers dont support GL_FRAMEBUFFER_SRGB this will cause a small rendering performance penalty"); } is_first_window = false; } OSWindow *w = add_os_window(); w->handle = glfw_window; w->disallow_title_changes = disallow_override_title; if (lsc != NULL) { w->is_layer_shell = true; w->hide_on_focus_loss = lsc->hide_on_focus_loss; } update_os_window_references(); if (!w->is_layer_shell || (global_state.is_apple && w->is_layer_shell && lsc->focus_policy == GLFW_FOCUS_EXCLUSIVE)) { 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 && (!lsc || !global_state.is_wayland)) 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); glfwSetDragSourceCallback(glfw_window, drag_source_callback); glfwSetDropEventCallback(glfw_window, on_drop); monotonic_t now = monotonic(); w->is_focused = true; w->cursor_blink_zero_time = now; w->last_mouse_activity_at = now; w->mouse_activate_deadline = -1; w->mouse_show_threshold = 0; w->background_opacity.supports_transparency = is_semi_transparent; if (want_semi_transparent && !w->background_opacity.supports_transparency) { 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), effective_os_window_alpha(w)); if (w->is_layer_shell) { if (global_state.is_apple) set_layer_shell_config_for(w, lsc); } else apply_window_chrome_state( w->handle, w->last_window_chrome, width, height, global_state.is_apple ? OPT(hide_window_decorations) != 0 : false); // 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 if (window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window, false); #endif w->redraw_count = 1; debug("OS Window created\n"); return PyLong_FromUnsignedLongLong(w->id); } void on_os_window_font_size_change(OSWindow *os_window, double new_sz) { 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); os_window_update_size_increments(os_window); if (os_window->is_layer_shell) set_layer_shell_config_for(os_window, NULL); } #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 && !glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED) && (!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'), '\0' }; \ 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* 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* macos_cycle_through_os_windows(PyObject *self UNUSED, PyObject *backwards) { #ifdef __APPLE__ glfwCocoaCycleThroughOSWindows(PyObject_IsTrue(backwards)); #else (void)backwards; #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(OSWindow *w) { 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__ (void)w; 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 { if (!global_state.is_wayland || !glfwWaylandBeep(w ? w->handle : NULL)) play_canberra_sound( "bell", "kitty bell", false, "event", OPT(bell_theme)); } #endif } static PyObject* ring_bell(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id = 0; if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL; OSWindow *w = os_window_for_id(os_window_id); ring_audio_bell(w); Py_RETURN_NONE; } static PyObject* request_attention(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id; if (!PyArg_ParseTuple(args, "K", &os_window_id)) return NULL; OSWindow *w = os_window_for_id(os_window_id); if (w && w->handle) glfwRequestWindowAttention(w->handle); 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 || w->is_layer_shell) 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(w); if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle); #ifdef __APPLE__ if (OPT(macos_dock_badge_on_bell)) cocoa_set_dock_badge("!"); #endif 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); w->mouse_activate_deadline = -1; } 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); os_window->keep_rendering_till_swap = 0; } } 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* get_monitor_workarea(PYNOARG) { int count = 0; GLFWmonitor **monitors = glfwGetMonitors(&count); if (count <= 0 || !monitors) return PyTuple_New(0); RAII_PyObject(result, PyTuple_New(count)); if (!result) return NULL; for (int i = 0; i < count; i++) { int xpos, ypos, width, height; glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); PyObject *monitor_workarea = Py_BuildValue("iiii", xpos, ypos, width, height); if (!monitor_workarea) return NULL; PyTuple_SET_ITEM(result, i, monitor_workarea); } return Py_NewRef(result); } static PyObject* get_monitor_names(PYNOARG) { int count = 0; GLFWmonitor **monitors = glfwGetMonitors(&count); if (count <= 0 || !monitors) return PyTuple_New(0); RAII_PyObject(result, PyTuple_New(count)); if (!result) return NULL; for (int i = 0; i < count; i++) { const char *name = glfwGetMonitorName(monitors[i]); const char *description = glfwGetMonitorDescription(monitors[i]); PyObject *x = Py_BuildValue("ss", name, description); if (!x) return NULL; PyTuple_SET_ITEM(result, i, x); } return Py_NewRef(result); } 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) { 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* is_layer_shell_supported(PyObject *self UNUSED, PyObject *args UNUSED) { return Py_NewRef(glfwIsLayerShellSupported() ? Py_True : Py_False); } static PyObject* toggle_os_window_visibility(PyObject *self UNUSED, PyObject *args, PyObject *kw) { unsigned long long wid; int set_visible = -1, move_to_active_screen = 0; static const char* kwlist[] = {"os_window_id", "visible", "move_to_active_screen", NULL}; if (!PyArg_ParseTupleAndKeywords( args, kw, "K|pp", (char**)kwlist, &wid, &set_visible, &move_to_active_screen)) return NULL; OSWindow *w = os_window_for_id(wid); if (!w || !w->handle) Py_RETURN_FALSE; bool is_visible = glfwGetWindowAttrib(w->handle, GLFW_VISIBLE) != 0; if (set_visible == -1) set_visible = !is_visible; else if (set_visible == is_visible) Py_RETURN_FALSE; set_os_window_visibility(w, set_visible, move_to_active_screen); Py_RETURN_TRUE; } static PyObject* layer_shell_config_for_os_window(PyObject *self UNUSED, PyObject *wid) { if (!PyLong_Check(wid)) { PyErr_SetString(PyExc_TypeError, "os_window_id must be a int"); return NULL; } id_type id = PyLong_AsUnsignedLongLong(wid); OSWindow *w = os_window_for_id(id); if (!w || !w->handle) Py_RETURN_NONE; const GLFWLayerShellConfig *c = glfwGetLayerShellConfig(w->handle); if (!c) Py_RETURN_NONE; return layer_shell_config_to_python(c); } static PyObject* set_layer_shell_config(PyObject *self UNUSED, PyObject *args) { unsigned long long wid; PyObject *pylsc; if (!PyArg_ParseTuple(args, "KO", &wid, &pylsc)) return NULL; OSWindow *window = os_window_for_id(wid); if (!window || !window->handle || !window->is_layer_shell) Py_RETURN_FALSE; GLFWLayerShellConfig lsc = {0}; if (!layer_shell_config_from_python(pylsc, &lsc)) return NULL; return Py_NewRef(set_layer_shell_config_for(window, &lsc) ? Py_True : Py_False); } static PyObject* grab_keyboard(PyObject *self UNUSED, PyObject *action) { return Py_NewRef(glfwGrabKeyboard(action == Py_None ? 2 : PyObject_IsTrue(action)) ? Py_True : Py_False); } static PyObject* draw_single_line_of_text(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id; const char *text; unsigned int fg, bg; int width, padding_y = 2; if (!PyArg_ParseTuple(args, "KsIIi|i", &os_window_id, &text, &fg, &bg, &width, &padding_y)) return NULL; OSWindow *w = os_window_for_id(os_window_id); if (!w || !w->fonts_data) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist or has no fonts data"); return NULL; } double font_sz_pts = w->fonts_data->font_sz_in_pts; double ydpi = w->fonts_data->logical_dpi_y; size_t height = (size_t)w->fonts_data->fcm.cell_height + padding_y; size_t buf_sz = (size_t)width * height * 4; RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, buf_sz)); if (!ans) return NULL; if (!draw_window_title(font_sz_pts, ydpi, text, fg, bg, (uint8_t*)PyBytes_AS_STRING(ans), width, height)) { if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "Failed to render text"); return NULL; } return Py_NewRef(ans); } static bool get_thumbnail(PyObject *thumbnails, GLFWimage *thumbnail, int idx) { RAII_PyObject(t, PySequence_GetItem(thumbnails, idx)); if (!PyTuple_Check(t) || PyTuple_GET_SIZE(t) != 3) { PyErr_SetString(PyExc_TypeError, "thumbnail must be a 3-tuple"); return false; } thumbnail->pixels = (uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(t, 0)); thumbnail->width = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(t, 1)); thumbnail->height = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(t, 2)); return true; } static PyObject* change_drag_thumbnail(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id; int idx = -1; if (!PyArg_ParseTuple(args, "K|i", &os_window_id, &idx)) return NULL; if (global_state.drag_source.thumbnail_idx == idx) Py_RETURN_NONE; OSWindow *w = os_window_for_id(os_window_id); if (!w || !w->handle) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist"); return NULL; } GLFWimage thumbnail = {0}; if (idx >=0 && global_state.drag_source.thumbnails && idx < PySequence_Size(global_state.drag_source.thumbnails)) { if (!get_thumbnail(global_state.drag_source.thumbnails, &thumbnail, idx)) return NULL; global_state.drag_source.thumbnail_idx = idx; } else global_state.drag_source.thumbnail_idx = -1; errno = glfwStartDrag(w->handle, NULL, 0, thumbnail.pixels ? &thumbnail : NULL, -2, false); if (errno != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; } static PyObject* start_drag_with_data(PyObject *self UNUSED, PyObject *args, PyObject *kw) { static const char* kwlist[] = {"os_window_id", "data_map", "thumbnails", "operations", NULL}; unsigned long long os_window_id; PyObject *data_map; int operations = GLFW_DRAG_OPERATION_MOVE; PyObject *thumbnails = NULL; if (!PyArg_ParseTupleAndKeywords(args, kw, "KO!|Oi", (char**)kwlist, &os_window_id, &PyDict_Type, &data_map, &thumbnails, &operations)) return NULL; OSWindow *w = os_window_for_id(os_window_id); if (!w || !w->handle) { PyErr_SetString(PyExc_KeyError, "OS Window with specified id does not exist"); return NULL; } GLFWimage thumbnail = {0}; if (!PySequence_Check(thumbnails)) { PyErr_SetString(PyExc_TypeError, "thumbnails must be a sequence"); return NULL; } if (thumbnails && PySequence_Size(thumbnails) && !get_thumbnail(thumbnails, &thumbnail, 0)) return NULL; RAII_ALLOC(GLFWDragSourceItem, items, calloc(PyDict_Size(data_map), sizeof(GLFWDragSourceItem))); if (!items) { PyErr_NoMemory(); return NULL; } PyObject *key, *value; Py_ssize_t pos = 0; size_t num = 0; bool needs_toplevel_on_wayland = false; while (PyDict_Next(data_map, &pos, &key, &value)) { if (!PyUnicode_Check(key)) { PyErr_SetString(PyExc_TypeError, "data_map must have string keys"); return NULL; } if (!PyBytes_Check(value)) { PyErr_SetString(PyExc_TypeError, "data_map must have bytes values"); return NULL; } GLFWDragSourceItem *item = items + num++; item->mime_type = PyUnicode_AsUTF8(key); item->optional_data = PyBytes_AS_STRING(value); item->data_size = PyBytes_GET_SIZE(value); if (global_state.is_wayland && is_droppable_mime(item->mime_type) == TAB_DRAG_MIME_NUMBER) needs_toplevel_on_wayland = true; } free_drag_source(); global_state.drag_source.is_active = true; global_state.drag_source.needs_toplevel_on_wayland = needs_toplevel_on_wayland; global_state.drag_source.drag_data = Py_NewRef(data_map); if (thumbnails) global_state.drag_source.thumbnails = Py_NewRef(thumbnails); global_state.drag_source.thumbnail_idx = thumbnail.pixels ? 0 : -1; errno = glfwStartDrag(w->handle, items, num, thumbnail.pixels ? &thumbnail : NULL, operations, needs_toplevel_on_wayland); if (errno != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; } // Boilerplate {{{ static PyMethodDef module_methods[] = { METHODB(set_custom_cursor, METH_VARARGS), METHODB(is_css_pointer_name_valid, METH_O), {"toggle_os_window_visibility", (PyCFunction)(void (*) (void))(toggle_os_window_visibility), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(layer_shell_config_for_os_window, METH_O), METHODB(set_layer_shell_config, METH_VARARGS), METHODB(grab_keyboard, METH_O), METHODB(pointer_name_to_css_name, METH_O), {"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL}, {"start_drag_with_data", (PyCFunction)(void (*) (void))(start_drag_with_data), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(change_drag_thumbnail, METH_VARARGS), METHODB(draw_single_line_of_text, METH_VARARGS), 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(macos_cycle_through_os_windows, METH_O), METHODB(get_content_scale_for_window, METH_NOARGS), METHODB(ring_bell, METH_VARARGS), METHODB(request_attention, METH_VARARGS), 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(is_layer_shell_supported, METH_NOARGS), METHODB(x11_window_id, METH_O), 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, ""}, {"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_get_monitor_workarea", (PyCFunction)get_monitor_workarea, METH_NOARGS, ""}, {"glfw_get_monitor_names", (PyCFunction)get_monitor_names, 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((void*)logo.pixels); logo.pixels = NULL; Py_CLEAR(edge_spacing_func); #ifndef __APPLE__ Py_CLEAR(dbus_notification_callback); release_freetype_render_context(bold_render_ctx); release_freetype_render_context(normal_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_DRAG_OPERATION_MOVE); ADDC(GLFW_DRAG_OPERATION_COPY); ADDC(GLFW_DRAG_OPERATION_GENERIC); 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_EDGE_CENTER_SIZED); 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; } ================================================ FILE: kitty/glyph-cache.c ================================================ /* * 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; } } ================================================ FILE: kitty/glyph-cache.h ================================================ /* * 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); ================================================ FILE: kitty/graphics.c ================================================ /* * 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 MAX_IMAGE_DIMENSION 10000u #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 void set_layers_dirty(GraphicsManager *self) { self->layers_dirty = true; } static image_map_itr remove_image_itr(GraphicsManager *self, image_map_itr i) { free_image(self, i.data->val); set_layers_dirty(self); 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; dest->last_scroll_offset_lines = 0.0f; 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, MAX_IMAGE_DIMENSION); 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, MAX_IMAGE_DIMENSION); 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; \ } 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, width or height greater than %u", MAX_IMAGE_DIMENSION); 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; img->current_frame_index = 0; img->animation_duration = 0; img->animation_state = ANIMATION_STOPPED; img->max_loops = 0; img->current_loop = 0; *is_dirty = true; set_layers_dirty(self); } 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(); set_layers_dirty(self); 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; set_layers_dirty(self); 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 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 scroll_offset_lines, 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->last_scroll_offset_lines != scroll_offset_lines) set_layers_dirty(self); self->last_scrolled_by = scrolled_by; self->last_scroll_offset_lines = scroll_offset_lines; 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 * ((float)scrolled_by + scroll_offset_lines); // 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, width or height greater than %u", MAX_IMAGE_DIMENSION); 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->compose_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); set_layers_dirty(self); 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)) { set_layers_dirty(self); 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); set_layers_dirty(self); } 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; set_layers_dirty(self); 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; set_layers_dirty(self); 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, scroll_offset_lines = 0.0f; CellPixelSize cell; PA("IffffIIII|f", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height, &scroll_offset_lines); grman_update_layers(self, scrolled_by, scroll_offset_lines, 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) { set_layers_dirty(self); } void grman_set_window_id(GraphicsManager *self, id_type id) { self->window_id = id; } bool grman_has_images(GraphicsManager *self) { return self->num_of_below_refs + self->num_of_negative_refs + self->num_of_positive_refs > 0; } 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; } // }}} ================================================ FILE: kitty/graphics.h ================================================ /* * 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; }; 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, id; 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; float last_scroll_offset_lines; 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 gl_pos_x(const 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 tex_pos_x(const int px_from_left_margin, const unsigned texture_width) { return px_from_left_margin / (float)texture_width; } static inline float gl_pos_y(const 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; } static inline float tex_pos_y(const int px_from_top_margin, const unsigned texture_height) { const int px_from_bottom_margin = texture_height - px_from_top_margin; return px_from_bottom_margin / (float)texture_height; } 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 scroll_offset_lines, 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 grman_pause_rendering(GraphicsManager *self, GraphicsManager *dest); void grman_mark_layers_dirty(GraphicsManager *self); void grman_set_window_id(GraphicsManager *self, id_type id); bool grman_has_images(GraphicsManager *self); GraphicsRenderData grman_render_data(GraphicsManager *self); ================================================ FILE: kitty/graphics_fragment.glsl ================================================ #pragma kitty_include_shader #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 extra_alpha; #endif in vec2 texcoord; out vec4 output_color; void main() { vec4 color = texture(image, texcoord); #ifdef ALPHA_MASK color = vec4(amask_fg, color.r); color = vec4_premul(color); color = alpha_blend_premul(color, amask_bg_premult); #else color.a *= extra_alpha; #if TEXTURE_IS_NOT_PREMULTIPLIED color = vec4_premul(color); #endif #endif output_color = color; } ================================================ FILE: kitty/graphics_vertex.glsl ================================================ uniform vec4 src_rect, dest_rect; #pragma kitty_include_shader ================================================ FILE: kitty/guess_mime_type.py ================================================ #!/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', 'nix': 'text/nix', } 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 ================================================ FILE: kitty/history.c ================================================ /* * 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; } void historybuf_delete_newest_lines(HistoryBuf *self, index_type count) { if (!count) return; count = MIN(self->count, count); self->count -= count - 1; // now nuke multi cell chars that overlap onto the last line index_type idx = (self->start_of_data + self->count - 1) % self->ynum; init_line(self, idx, self->line); CPUCell *cells = self->line->cpu_cells; self->count--; for (index_type x = 0; x < self->line->xnum; x++) { CPUCell *c = cells + x; if (c->is_multicell && c->y) { for (index_type i = 0, pos = self->count; i < c->y && pos; i++, pos--) { index_type idx = (self->start_of_data + pos - 1) % self->ynum; init_line(self, idx, self->line); CPUCell *m = self->line->cpu_cells + x; cell_set_char(m, 0); m->is_multicell = false; clear_sprite_position(self->line->gpu_cells[x]); } } } } 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; } ================================================ FILE: kitty/history.h ================================================ /* * 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); void historybuf_delete_newest_lines(HistoryBuf *self, index_type count); ================================================ FILE: kitty/hsluv.glsl ================================================ /* 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)); } ================================================ FILE: kitty/hyperlink.c ================================================ /* * 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; } ================================================ FILE: kitty/hyperlink.h ================================================ /* * 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); ================================================ FILE: kitty/iqsort.h ================================================ /* $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; \ } \ } \ } \ } \ \ } ================================================ FILE: kitty/key_encoding.c ================================================ /* * 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); } ================================================ FILE: kitty/key_encoding.py ================================================ #!/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() ================================================ FILE: kitty/key_names.py ================================================ #!/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 ================================================ FILE: kitty/keys.c ================================================ /* * 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->render_data.geometry.left, top = w->render_data.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 || screen->pixel_scroll_offset_y != 0) && 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.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("buffering 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; } ================================================ FILE: kitty/keys.h ================================================ /* * 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); ================================================ FILE: kitty/keys.py ================================================ #!/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, grab_keyboard, is_modifier_key, ring_bell, set_ignore_os_keyboard_processing, ) from .options.types import Options from .options.utils import KeyboardMode, KeyDefinition, KeyMap from .typing_compat 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.mode_timeout_timer_id: int | None = None 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._cancel_mode_timeout() 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._cancel_mode_timeout() 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._start_mode_timeout(mode) 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 _start_mode_timeout(self, mode: KeyboardMode) -> None: if mode.timeout > 0: from .fast_data_types import add_timer self._cancel_mode_timeout() self.mode_timeout_timer_id = mode.timeout_timer_id = add_timer( self._on_mode_timeout, mode.timeout, False) def _cancel_mode_timeout(self) -> None: if self.mode_timeout_timer_id is not None: from .fast_data_types import remove_timer remove_timer(self.mode_timeout_timer_id) self.mode_timeout_timer_id = None def _on_mode_timeout(self, timer_id: int | None) -> None: self.mode_timeout_timer_id = None if self.keyboard_mode_stack: self.pop_keyboard_mode() def _get_effective_timeout(self, key_def: KeyDefinition) -> float: if key_def.options.timeout is not None: return key_def.options.timeout return self.get_options().map_timeout 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 and self.global_shortcuts_map and (global_key_action := get_shortcut(self.global_shortcuts_map, ev)) is not None: if grab_keyboard(None): # the shortcuts in the global menubar will have been bypassed so trigger them here key_action = global_key_action else: return True if key_action is None: if is_modifier_key(ev.key): return False 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] sm.timeout = self._get_effective_timeout(final_actions[0]) 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._start_mode_timeout(mode) 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) elif not is_root_mode and mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode: self._start_mode_timeout(mode) 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) # }}} ================================================ FILE: kitty/kittens.c ================================================ /* * 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; } ================================================ FILE: kitty/kitty-verstable.h ================================================ /* * 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 ================================================ FILE: kitty/launch.py ================================================ #!/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, Callable, Literal, 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 LayerShellConfig, 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] # Options definition {{{ 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,os-panel,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`. :code:`os-panel` Similar to :code:`os-window`, except that it creates the new OS Window as a desktop panel. Only works on platforms that support this, such as Wayand compositors that support the layer shell protocol. Use the :option:`kitten @ launch --os-panel` option to configure the panel. #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. When opening in the same working directory as the current window causes the new window to connect to a remote host, you can use the :option:`--hold-after-ssh` flag to prevent the new window from closing when the connection is terminated. --add-to-session Add the newly created window/tab to the specified session. Use :code:`.` to add to the session of the :option:`source window `, if any. See :ref:`sessions` for what a session is and how to use one. By default, the window is added to the session of the :option:`source window ` when :option:`launch --cwd` is set to use the the working directory from that window, otherwise the newly created window does not belong to any session. To force adding to no session, use the value :code:`!`. Adding a newly created window to a session is purely temporary, it does not change the actual session file, for that you have to resave the session. Note that using this flag in a launch command within a session file has no effect as the window is always added to the session belonging to that file. --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 this option is ignored unless the matched window is in the target tab (default active 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 allotted 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`. --os-panel type=list Options to control the creation of desktop panels. Takes the same settings as the :doc:`panel kitten
`, except for :option:`--override ` and :option:`--config `. Can be specified multiple times. For example, to create a desktop panel at the bottom of the screen two lines high:: launch --type os-panel --os-panel lines=2 --os-panel edge=bottom sh -c "echo; echo; echo hello; sleep 5s" --hold-after-ssh type=bool-set When using :option:`--cwd`:code:`=current` or similar from a window that is running the ssh kitten, the new window will run a local shell after disconnecting from the remote host, when this option is specified. """ # }}} 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 layer_shell_config_from_panel_opts(panel_opts: Iterable[str]) -> LayerShellConfig: from kittens.panel.main import layer_shell_config, parse_panel_args args = [('' if x.startswith('--') else '--') + x for x in panel_opts] try: opts, _ = parse_panel_args(args) except SystemExit as e: raise ValueError(str(e)) return layer_shell_config(opts) def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Tab | None, next_to: Window | None, add_to_session: str) -> Tab: def create_tab(tm: TabManager | None = None) -> Tab: if tm is None: if opts.type == 'os-panel': oswid = boss.add_os_panel(layer_shell_config_from_panel_opts(opts.os_panel), opts.os_window_class, opts.os_window_name) else: 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) tab.created_in_session_name = add_to_session 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 in ('os-window', 'os-panel'): 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) w = m.get('on_tab_bar_dirty') if callable(w): ans.on_tab_bar_dirty.append(w) w = m.get('on_quit') if callable(w): ans.on_quit.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 hold_after_ssh: bool 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, child_death_callback: Callable[[int, Exception | None], None] | None = None, startup_command_via_shell_integration: Sequence[str] | str = (), ) -> 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, 'hold_after_ssh': False } 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.hold_after_ssh: if opts.cwd not in ('current', 'last_reported', 'oldest'): raise ValueError("--hold-after-ssh can only be supplied if --cwd=current or similar is also supplied") kw['hold_after_ssh'] = True 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'], notify_on_death=child_death_callback, ) 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') if child_death_callback is not None: child_death_callback(0, None) else: add_to_session = opts.add_to_session or '' match add_to_session: case '.': add_to_session = source_window.created_in_session_name if source_window else '' case '!': add_to_session = '' case '': if kw['cwd_from'] is not None and source_window: add_to_session = source_window.created_in_session_name 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, add_to_session) 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, startup_command_via_shell_integration=startup_command_via_shell_integration, **kw) new_window.created_in_session_name = add_to_session if child_death_callback is not None: boss.monitor_pid(new_window.child.pid or 0, child_death_callback) if new_window.creation_spec: if opts.watcher: new_window.creation_spec = new_window.creation_spec._replace(watchers=tuple(opts.watcher)) if opts.spacing: new_window.creation_spec = new_window.creation_spec._replace(spacing=tuple(opts.spacing)) if opts.color: new_window.creation_spec = new_window.creation_spec._replace(colors=tuple(opts.color)) 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: vars = tuple(parse_var(opts.var)) if new_window.creation_spec: new_window.creation_spec = new_window.creation_spec._replace(user_vars=vars) for key, val in vars: 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, child_death_callback: Callable[[int, Exception | None], None] | None = None, startup_command_via_shell_integration: Sequence[str] | str = (), ) -> 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, child_death_callback, startup_command_via_shell_integration) 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', 'hold_after_ssh' )) 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: abort_signaled: Literal['closed', 'replaced', 'disconnected', ''] = '' def __init__(self, msg: str, child_is_remote: bool) -> None: self.child_is_remote = child_is_remote 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 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: if not self.abort_signaled == 'disconnected': 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: if not self.is_local_file and self.child_is_remote and not window.child_is_remote: self.abort_signaled = 'disconnected' get_boss().show_error( 'edit-in-kitty', f'Failed to sync file due to the SSH connection dropping. Your local changes can still be found at {self.file_localpath}' ) return 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') @run_once def excluded_env_vars() -> frozenset[str]: return frozenset({ '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', }) def is_excluded_env_var(x: str) -> bool: # conda state env vars for multi-level virtual environments CONDA_PREFIX_* return x in excluded_env_vars() or x.startswith('CONDA_PREFIX_') 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 not is_excluded_env_var(k)} 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, window.child_is_remote) 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) ================================================ FILE: kitty/launcher/cli-parser.h ================================================ /* * cli-parser.h * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include #include #include #include #include "../iqsort.h" #ifndef RAII_PyObject static inline void cleanup_decref2(PyObject **p) { Py_CLEAR(*p); } #define RAII_PyObject(name, initializer) __attribute__((cleanup(cleanup_decref2))) PyObject *name = initializer #undef MAX #define MAX(x, y) __extension__ ({ \ const __typeof__ (x) __a__ = (x); const __typeof__ (y) __b__ = (y); \ __a__ > __b__ ? __a__ : __b__;}) #endif typedef enum CLIValueType { CLI_VALUE_STRING, CLI_VALUE_BOOL, CLI_VALUE_INT, CLI_VALUE_FLOAT, CLI_VALUE_LIST, CLI_VALUE_CHOICE } CLIValueType; typedef struct CLIValue { CLIValueType type; long long intval; double floatval; bool boolval; const char* strval; struct { const char* * items; size_t count, capacity; } listval; } CLIValue; #define NAME cli_hash #define KEY_TY const char* #define VAL_TY CLIValue #include "../kitty-verstable.h" #define value_map_for_loop(x) vt_create_for_loop(cli_hash_itr, itr, x) #define NAME alias_hash #define KEY_TY const char* #define VAL_TY const char* #include "../kitty-verstable.h" #define alias_map_for_loop(x) vt_create_for_loop(alias_hash_itr, itr, x) typedef struct FlagSpec { CLIValue defval; const char *dest; } FlagSpec; #define NAME flag_hash #define KEY_TY const char* #define VAL_TY FlagSpec #include "../kitty-verstable.h" #define flag_map_for_loop(x) vt_create_for_loop(flag_hash_itr, itr, x) typedef struct CLISpec { cli_hash value_map; alias_hash alias_map; flag_hash flag_map, disabled_map; char **argv; int argc; // leftover args char **original_argv; int original_argc; // original args const char* errmsg; struct { struct { char *buf; size_t capacity, used; } *items; size_t count, capacity; } blocks; } CLISpec; static void out_of_memory(int line) { fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, line); exit(1); } #define OOM out_of_memory(__LINE__) static void* alloc_for_cli(CLISpec *spec, size_t sz) { sz++; if (!spec->blocks.capacity) { spec->blocks.capacity = 8; spec->blocks.items = calloc(spec->blocks.capacity, sizeof(spec->blocks.items[0])); if (!spec->blocks.items) return NULL; spec->blocks.count = 1; } #define block spec->blocks.items[spec->blocks.count-1] if (block.used + sz >= block.capacity) { if (block.capacity) { // need new block spec->blocks.count++; if (spec->blocks.count >= spec->blocks.capacity) { spec->blocks.capacity *= 2; spec->blocks.items = realloc(spec->blocks.items, spec->blocks.capacity * sizeof(spec->blocks.items[0])); if (!spec->blocks.items) return NULL; } } block.capacity = MAX(sz, 8192u); block.buf = malloc(block.capacity); if (!block.buf) return NULL; block.used = 0; } char *ans = block.buf + block.used; ans[sz-1] = 0; block.used += sz; // keep returned memory regions aligned to size of pointer size_t extra = sz % sizeof(void*); if (extra) block.used += sizeof(void*) - extra; return ans; #undef block } #define set_err(fmt, ...) { \ int sz = snprintf(NULL, 0, fmt, __VA_ARGS__); \ char *buf = alloc_for_cli(spec, sz + 4); \ if (!buf) OOM; \ snprintf(buf, sz + 4, fmt, __VA_ARGS__); spec->errmsg = buf; \ } static ssize_t levenshtein_distance(size_t *cache, const char *a, const char *b) { if (a == b) return 0; const size_t length = strlen(a); const size_t bLength = strlen(b); if (length == 0) return bLength; if (bLength == 0) return length; size_t index = 0, bIndex = 0, distance = 0, bDistance = 0, result = 0; char code = 0; // initialize the vector. while (index < length) { cache[index] = index + 1; index++; } while (bIndex < bLength) { code = b[bIndex]; result = distance = bIndex++; index = SIZE_MAX; while (++index < length) { bDistance = code == a[index] ? distance : distance + 1; distance = cache[index]; cache[index] = result = distance > result ? bDistance > result ? result + 1 : bDistance : bDistance > distance ? distance + 1 : bDistance; } } return result; } static bool add_to_listval(CLISpec *spec, CLIValue *v, const char *val) { if (v->listval.count + 1 >= v->listval.capacity) { size_t cap = MAX(64u, v->listval.capacity * 2u); char **new = alloc_for_cli(spec, cap * sizeof(v->listval.items[0])); if (!new) return false; v->listval.capacity = cap; if (v->listval.count) memcpy(new, v->listval.items, sizeof(new[0]) * v->listval.count); v->listval.items = (void*)new; } v->listval.items[v->listval.count++] = val; return true; } static bool use_ansi_escape_codes(void) { static bool checked = false, ans; if (!checked) { ans = isatty(STDERR_FILENO); checked = true; } return ans; } static const char* formatted_text(CLISpec *spec, const char *start_code, const char *text, const char *end_code) { if (!use_ansi_escape_codes()) return text; static const char *fmt = "\x1b[%sm%s\x1b[%sm"; int sz = snprintf(NULL, 0, fmt, start_code, text, end_code); char *ans = alloc_for_cli(spec, sz+1); snprintf(ans, sz+1, fmt, start_code, text, end_code); return ans; } #define red_text(text) formatted_text(spec, "91", text, "39") #define yellow_text(text) formatted_text(spec, "93", text, "39") #define green_text(text) formatted_text(spec, "32", text, "39") #define italic_text(text) formatted_text(spec, "3", text, "23") typedef struct similiar_alias { const char *alias; ssize_t distance; } similiar_alias; static const char* dest_for_alias(CLISpec *spec, const char *alias) { alias_hash_itr itr = vt_get(&spec->alias_map, alias); if (vt_is_end(itr)) { const char *match_key = NULL, *match_val = NULL; size_t total = 0; alias_hash matches; vt_init(&matches); alias_map_for_loop(&spec->alias_map) { if (strstr(itr.data->key, alias) == itr.data->key) { total += strlen(itr.data->key) + 8; if (!match_key) { match_key = itr.data->key; match_val = itr.data->val; } if (vt_is_end(vt_insert(&matches, itr.data->val, itr.data->key))) OOM; } } if (match_key) { if (vt_size(&matches) == 1) { vt_cleanup(&matches); return match_val; } total += 256 + total; char *buf = alloc_for_cli(spec, total); if (!buf) OOM; int n = snprintf(buf, total, "The flag %s is ambiguous. Possible matches:", yellow_text(alias)); alias_map_for_loop(&matches) { if ((ssize_t)total > n) n += snprintf(buf + n, total - n, " %s,", itr.data->val); } vt_cleanup(&matches); buf[n-1] = '.'; spec->errmsg = buf; return NULL; } size_t *cache = alloc_for_cli(spec, sizeof(size_t) * strlen(alias)); size_t num_aliases = vt_size(&spec->alias_map); similiar_alias *candidates = alloc_for_cli(spec, sizeof(similiar_alias) * num_aliases); size_t num_candidates = 0; alias_map_for_loop(&spec->alias_map) { const char *q = itr.data->key; ssize_t d = levenshtein_distance(cache, alias, q); if (d < 0) break; if (d < 3) candidates[num_candidates++] = (similiar_alias){.alias=q, .distance=d}; } if (num_candidates) { #define lt(a, b) (a->distance < b->distance) QSORT(similiar_alias, candidates, num_candidates, lt); set_err("Unknown flag: %s. Did you mean: %s?", red_text(alias), green_text(candidates[0].alias)); return NULL; #undef lt } set_err("Unknown flag: %s use --help.", red_text(alias)); return NULL; } return itr.data->val; } static bool is_alias_bool(CLISpec* spec, const char *alias, const char **dest_out) { *dest_out = dest_for_alias(spec, alias); if (!*dest_out) return false; flag_hash_itr itr = vt_get(&spec->flag_map, *dest_out); return itr.data->val.defval.type == CLI_VALUE_BOOL; } static void add_list_value(CLISpec *spec, const char *dest, const char *val) { cli_hash_itr itr = vt_get_or_insert(&spec->value_map, dest, (CLIValue){.type=CLI_VALUE_LIST}); if (vt_is_end(itr)) OOM; CLIValue v = itr.data->val; if (!add_to_listval(spec, &v, val)) OOM; if (vt_is_end(vt_insert(&spec->value_map, dest, v))) OOM; } static bool process_cli_arg(CLISpec* spec, const char *alias, const char *payload, const char *dest) { if (!dest) dest = dest_for_alias(spec, alias); if (!dest) return false; flag_hash_itr itr = vt_get(&spec->flag_map, dest); const FlagSpec *flag = &itr.data->val; CLIValue val = {.type=flag->defval.type}; #define streq(q) (strcmp(payload, #q) == 0) switch(val.type) { case CLI_VALUE_STRING: val.strval = payload; break; case CLI_VALUE_BOOL: if (payload) { if (streq(y) || streq(yes) || streq(true)) val.boolval = true; else if (streq(n) || streq(no) || streq(false)) val.boolval = false; else { set_err("%s is an invalid value for %s. Valid values are: %s, %s, %s, %s, %s and %s.", red_text(payload[0] ? payload : ""), green_text(alias), italic_text("y"), italic_text("yes"), italic_text("true"), italic_text("n"), italic_text("no"), italic_text("false")); return false; } } else val.boolval = !flag->defval.boolval; break; case CLI_VALUE_CHOICE: val.strval = NULL; for (size_t c = 0; c < flag->defval.listval.count; c++) { if (strcmp(payload, flag->defval.listval.items[c]) == 0) { val.strval = payload; break; } } if (!val.strval) { size_t bufsz = 0; for (size_t c = 0; c < flag->defval.listval.count; c++) bufsz += strlen(flag->defval.listval.items[c]) + 8; bufsz += 256 + strlen(alias) + strlen(payload) + bufsz; char *buf = alloc_for_cli(spec, bufsz); int n = snprintf(buf, bufsz, "%s is an invalid value for %s. Valid values are:", red_text(payload[0] ? payload : ""), green_text(alias)); for (size_t c = 0; c < flag->defval.listval.count; c++) if ((ssize_t)bufsz > n) n += snprintf(buf + n, bufsz - n, " %s,", italic_text(flag->defval.listval.items[c])); buf[n-1] = '.'; spec->errmsg = buf; return false; } break; case CLI_VALUE_INT: errno = 0; val.intval = strtoll(payload, NULL, 10); if (errno) { set_err("%s is an invalid value for %s, it must be an integer number.", red_text(payload), green_text(alias)); return false; } break; case CLI_VALUE_FLOAT: errno = 0; val.floatval = strtod(payload, NULL); if (errno) { set_err("%s is an invalid value for %s, it must be a number.", red_text(payload), green_text(alias)); return false; } break; case CLI_VALUE_LIST: add_list_value(spec, flag->dest, payload); return true; } if (vt_is_end(vt_insert(&spec->value_map, flag->dest, val))) OOM; return true; #undef streq } static void alloc_cli_spec(CLISpec *spec) { vt_init(&spec->value_map); vt_init(&spec->alias_map); vt_init(&spec->flag_map); vt_init(&spec->disabled_map); } static void dealloc_cli_spec(void *v) { CLISpec *spec = v; for (size_t i = 0; i < spec->blocks.count; i++) free(spec->blocks.items[i].buf); free(spec->blocks.items); vt_cleanup(&spec->value_map); vt_cleanup(&spec->alias_map); vt_cleanup(&spec->flag_map); vt_cleanup(&spec->disabled_map); } #define RAII_CLISpec(name) __attribute__((cleanup(dealloc_cli_spec))) CLISpec name = {0}; alloc_cli_spec(&name) static bool parse_cli_loop(CLISpec *spec, bool save_original_argv, int argc, char **argv) { enum { NORMAL, EXPECTING_ARG } state = NORMAL; spec->argc = 0; spec->argv = NULL; spec->errmsg = NULL; spec->original_argc = argc; spec->original_argv = NULL; if (save_original_argv) { char **copy = alloc_for_cli(spec, sizeof(char*) * (argc + 1)); if (!copy) OOM; copy[argc] = NULL; for (int i = 0; i < argc; i++) { size_t len = strlen(argv[i]); copy[i] = alloc_for_cli(spec, len); if (!copy[i]) OOM; memcpy(copy[i], argv[i], len); } spec->original_argv = argv; argv = copy; } char flag[3] = {'-', 0, 0}; const char *current_option = NULL; const char *dest_for_current_arg = NULL; for (int i = 1; i < argc; i++) { char *arg = argv[i]; switch(state) { case NORMAL: { if (arg[0] == '-') { const bool is_long_opt = arg[1] == '-'; if (is_long_opt && arg[2] == 0) { spec->argc = argc - i - 1; if (spec->argc > 0) spec->argv = argv + i + 1; return true; } char *has_equal = strchr(arg, '='); const char *payload = NULL; if (has_equal) { *has_equal = 0; payload = has_equal + 1; } if (is_long_opt) { if (is_alias_bool(spec, arg, &dest_for_current_arg)) { if (!process_cli_arg(spec, arg, payload, dest_for_current_arg)) return false; } else { if (spec->errmsg) return false; if (has_equal) { if (!process_cli_arg(spec, arg, payload, dest_for_current_arg)) return false; } else { state = EXPECTING_ARG; current_option = arg; } } } else { for (const char *letter = arg + 1; *letter; letter++) { flag[1] = *letter; if (letter[1]) { if (!process_cli_arg(spec, flag, NULL, NULL)) return false; } else { if (is_alias_bool(spec, flag, &dest_for_current_arg) || payload) { if (!process_cli_arg(spec, flag, payload, dest_for_current_arg)) return false; } else { if (spec->errmsg) return false; state = EXPECTING_ARG; current_option = flag; } if (spec->errmsg) return false; } } } } else { spec->argc = argc - i; if (spec->argc > 0) spec->argv = argv + i; return true; } } break; case EXPECTING_ARG: { if (current_option && !process_cli_arg(spec, current_option, arg, NULL)) return false; current_option = NULL; state = NORMAL; } break; } } if (state == EXPECTING_ARG) set_err("The %s flag must be followed by an argument.", yellow_text(current_option ? current_option : "")); return spec->errmsg != NULL; } #ifdef FOR_LAUNCHER static void output_argv(const char *name, int argc, char **argv) { printf("%s:", name); for (int i = 0; i < argc; i++) printf("\x1e%s", argv[i]); printf("\n"); } static void output_values_for_testing(CLISpec *spec) { value_map_for_loop(&spec->value_map) { CLIValue v = itr.data->val; switch (v.type) { case CLI_VALUE_STRING: case CLI_VALUE_CHOICE: printf("%s: %s", itr.data->key, v.strval ? v.strval : ""); break; case CLI_VALUE_BOOL: printf("%s: %d", itr.data->key, v.boolval); break; case CLI_VALUE_INT: printf("%s: %lld", itr.data->key, v.intval); break; case CLI_VALUE_FLOAT: printf("%s: %f", itr.data->key, v.floatval); break; case CLI_VALUE_LIST: output_argv(itr.data->key, v.listval.count, (char**)v.listval.items); break; } printf("\n"); } } static void output_for_testing(CLISpec *spec) { output_argv("original_argv", spec->original_argc, spec->original_argv); output_argv("argv", spec->argc, spec->argv); output_values_for_testing(spec); } static CLIValue get_cli_val(CLISpec *spec, const char *name) { cli_hash_itr itr = vt_get(&spec->value_map, name); if (vt_is_end(itr)) { flag_hash_itr itr = vt_get(&spec->flag_map, name); if (vt_is_end(itr)) return (CLIValue){0}; return itr.data->val.defval; } return itr.data->val; } static bool get_bool_cli_val(CLISpec *spec, const char *name) { return get_cli_val(spec, name).boolval; } static const char* get_string_cli_val(CLISpec *spec, const char *name) { return get_cli_val(spec, name).strval; } #endif static bool clival_as_python(const CLIValue *v, PyObject *is_seen, const char *dest, PyObject *ans) { #define S(fv) { \ RAII_PyObject(temp, Py_BuildValue("NO", fv, is_seen)); if (!temp) return false; \ if (PyDict_SetItemString(ans, dest, temp) != 0) return false; \ } switch (v->type) { case CLI_VALUE_BOOL: S(PyBool_FromLong((long)v->boolval)); break; case CLI_VALUE_STRING: if (v->strval) { S(PyUnicode_FromString(v->strval)); } else { S(Py_NewRef(Py_None)); } break; case CLI_VALUE_CHOICE: S(PyUnicode_FromString(v->strval)); break; case CLI_VALUE_INT: S(PyLong_FromLongLong(v->intval)); break; case CLI_VALUE_FLOAT: S(PyFloat_FromDouble(v->floatval)); break; case CLI_VALUE_LIST: { RAII_PyObject(l, PyList_New(v->listval.count)); if (!l) return false; for (size_t i = 0; i < v->listval.count; i++) { PyObject *x = PyUnicode_FromString(v->listval.items[i]); if (!x) return false; PyList_SET_ITEM(l, i, x); } S(Py_NewRef(l)); } break; } #undef S return true; } static PyObject* cli_parse_result_as_python(CLISpec *spec) { if (PyErr_Occurred()) return NULL; if (spec->errmsg) { PyErr_SetString(PyExc_ValueError, spec->errmsg); return NULL; } RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; flag_map_for_loop(&spec->flag_map) { const FlagSpec *flag = &itr.data->val; cli_hash_itr i = vt_get(&spec->value_map, flag->dest); PyObject *is_seen = vt_is_end(i) ? Py_False : Py_True; const CLIValue *v = is_seen == Py_True ? &i.data->val : &flag->defval; if (!clival_as_python(v, is_seen, flag->dest, ans)) return NULL; } flag_map_for_loop(&spec->disabled_map) { const FlagSpec *flag = &itr.data->val; if (!clival_as_python(&flag->defval, Py_False, flag->dest, ans)) return NULL; } RAII_PyObject(leftover_args, PyList_New(spec->argc)); if (!leftover_args) return NULL; for (int i = 0; i < spec->argc; i++) { PyObject *t = PyUnicode_FromString(spec->argv[i]); if (!t) return NULL; PyList_SET_ITEM(leftover_args, i, t); } return Py_BuildValue("OO", ans, leftover_args); } #ifndef FOR_LAUNCHER static PyObject* parse_cli_from_python_spec(PyObject *self, PyObject *args) { (void)self; PyObject *pyargs, *names_map, *defval_map; if (!PyArg_ParseTuple(args, "O!O!O!", &PyList_Type, &pyargs, &PyDict_Type, &names_map, &PyDict_Type, &defval_map)) return NULL; int argc = PyList_GET_SIZE(pyargs); RAII_CLISpec(spec); char **argv = alloc_for_cli(&spec, sizeof(char*) * (argc + 2)); if (!argv) return PyErr_NoMemory(); argv[0] = "parse_cli_from_python_spec"; for (int i = 0; i < argc; i++) { Py_ssize_t sz; const char *src = PyUnicode_AsUTF8AndSize(PyList_GET_ITEM(pyargs, i), &sz); argv[i + 1] = alloc_for_cli(&spec, sz); if (!argv[i + 1]) return PyErr_NoMemory(); memcpy(argv[i + 1], src, sz); } argv[++argc] = 0; PyObject *key = NULL, *optdef = NULL; Py_ssize_t pos = 0; while (PyDict_Next(names_map, &pos, &key, &optdef)) { FlagSpec flag = {.dest=PyUnicode_AsUTF8(key)}; RAII_PyObject(pytype, PyObject_GetAttrString(optdef, "type")); if (!pytype) return NULL; const char *type = PyUnicode_AsUTF8(pytype); PyObject *defval = PyDict_GetItemWithError(defval_map, key); if (!defval && PyErr_Occurred()) return NULL; RAII_PyObject(pyaliases, PyObject_GetAttrString(optdef, "aliases")); if (!pyaliases) return NULL; for (int a = 0; a < PyTuple_GET_SIZE(pyaliases); a++) { const char *alias = PyUnicode_AsUTF8(PyTuple_GET_ITEM(pyaliases, a)); if (vt_is_end(vt_insert(&spec.alias_map, alias, flag.dest))) return PyErr_NoMemory(); } if (strstr(type, "bool-") == type) { flag.defval.type = CLI_VALUE_BOOL; flag.defval.boolval = PyObject_IsTrue(defval); } else if (strcmp(type, "int") == 0) { flag.defval.type = CLI_VALUE_INT; flag.defval.intval = PyLong_AsLongLong(defval); } else if (strcmp(type, "float") == 0) { flag.defval.type = CLI_VALUE_FLOAT; flag.defval.floatval = PyFloat_AsDouble(defval); } else if (strcmp(type, "list") == 0) { flag.defval.type = CLI_VALUE_LIST; if (PyObject_IsTrue(defval)) { for (ssize_t l = 0; l < PyList_GET_SIZE(defval); l++) add_to_listval(&spec, &flag.defval, PyUnicode_AsUTF8(PyList_GET_ITEM(defval, l))); } } else if (strcmp(type, "choices") == 0) { flag.defval.type = CLI_VALUE_CHOICE; flag.defval.strval = PyUnicode_AsUTF8(defval); RAII_PyObject(pyc, PyObject_GetAttrString(optdef, "choices")); if (!pyc) return NULL; flag.defval.listval.items = alloc_for_cli(&spec, PyTuple_GET_SIZE(pyc) * sizeof(char*)); if (!flag.defval.listval.items) return PyErr_NoMemory(); flag.defval.listval.count = PyTuple_GET_SIZE(pyc); flag.defval.listval.capacity = PyTuple_GET_SIZE(pyc); for (size_t n = 0; n < flag.defval.listval.count; n++) { flag.defval.listval.items[n] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(pyc, n)); if (!flag.defval.listval.items[n]) return NULL; } } else { flag.defval.type = CLI_VALUE_STRING; flag.defval.strval = PyUnicode_Check(defval) ? PyUnicode_AsUTF8(defval) : NULL; } if (vt_is_end(vt_insert(&spec.flag_map, flag.dest, flag))) return PyErr_NoMemory(); } if (PyErr_Occurred()) return NULL; parse_cli_loop(&spec, false, argc, argv); PyObject *ans = cli_parse_result_as_python(&spec); return ans; } #endif ================================================ FILE: kitty/launcher/cmdline.c ================================================ /* * cmdline.c * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define _POSIX_C_SOURCE 200809L #include "shlex.h" #include "utils.h" #include "launcher.h" #ifdef __APPLE__ #include #endif void free_argv_array(argv_array *a) { if (a && a->needs_free) { free(a->buf); free(a->argv); *a = (argv_array){0}; } } static bool add_to_argv(argv_array *a, const char* arg, size_t sz) { if (a->count + 2 > a->capacity) { size_t cap = a->capacity * 2; if (!cap) cap = 256; void *m = realloc(a->argv, cap * sizeof(a->argv[0])); if (!m) return false; a->argv = m; a->argv[a->count] = 0; a->capacity = cap; a->needs_free = true; } memcpy(a->buf + a->pos, arg, sz); a->argv[a->count++] = a->buf + a->pos; a->argv[a->count] = 0; a->pos += sz; a->buf[a->pos++] = 0; return true; } bool get_argv_from(const char *filename, const char *argv0, argv_array *final_ans) { (void)get_config_dir; if (!filename || !filename[0]) return true; size_t src_sz; char* src = read_full_file(filename, &src_sz); if (!src) { if (errno == ENOENT || errno == ENOTDIR) return true; #ifdef __APPLE__ int saved = errno; os_log_error(OS_LOG_DEFAULT, "Failed to read from %{public}s with error: %{darwin.errno}d", filename, errno); errno = saved; #endif fprintf(stderr, "Failed to read from %s ", filename); perror("with error"); return true; } ShlexState s = {0}; argv_array ans = {0}; bool ok = false; ans.buf = malloc(src_sz + strlen(argv0) + 64); if (!ans.buf) goto oom; ans.needs_free = true; if (!add_to_argv(&ans, argv0, strlen(argv0))) goto oom; if (!alloc_shlex_state(&s, src, src_sz, false)) goto oom; bool keep_going = true; while (keep_going) { ssize_t q = next_word(&s); switch(q) { case -1: fprintf(stderr, "Failed to parse %s with error: %s\n", filename, s.err); goto end; case -2: keep_going = false; break; default: if (ans.count == 1 && strcmp(s.buf, "kitty") == 0) continue; if (!add_to_argv(&ans, s.buf, q)) goto oom; break; } } ok = true; oom: if (!ok) { errno = ENOMEM; fprintf(stderr, "Failed to read from %s ", filename); perror("with error"); } end: free(src); dealloc_shlex_state(&s); if (ok) *final_ans = ans; else free_argv_array(final_ans); return ok; } ================================================ FILE: kitty/launcher/launcher.h ================================================ /* * launcher.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include typedef struct CLIOptions { const char *session, *instance_group; bool wait_for_single_instance_window_close; int open_url_count; char **open_urls; } CLIOptions; typedef struct argv_array { char **argv, *buf; size_t capacity, count, pos; bool needs_free; } argv_array; void single_instance_main(int argc, char *argv[], const CLIOptions *opts); bool get_argv_from(const char *filename, const char* argv0, argv_array *ans); void free_argv_array(argv_array *a); ================================================ FILE: kitty/launcher/main.c ================================================ /* * 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 #include #else #include #endif #include "launcher.h" #include "utils.h" #define FOR_LAUNCHER #include "cli-parser-data_generated.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 static bool being_tested = false; #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; safe_snprintf(buf, buf_sz, "%s", ans); return true; } #endif typedef struct { const char *exe, *exe_dir, *lc_ctype, *lib_dir, *config_dir; CLISpec *cli_spec; bool launched_by_launch_services, is_quick_access_terminal; } 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); } PyObject *lbls = run_data->launched_by_launch_services ? Py_True : Py_False; Py_INCREF(lbls); S(launched_by_launch_services, lbls); lbls = run_data->is_quick_access_terminal ? Py_True : Py_False; Py_INCREF(lbls); S(is_quick_access_terminal_app, lbls); char buf[PATH_MAX + 1]; if (run_data->config_dir == NULL) { if (get_config_dir(buf, sizeof(buf))) run_data->config_dir = buf; } if (run_data->config_dir) { PyObject *cdir = PyUnicode_DecodeFSDefaultAndSize(run_data->config_dir, strlen(run_data->config_dir)); if (!cdir) { PyErr_Print(); return false; } S(config_dir, cdir); } PyObject *cli_flags = cli_parse_result_as_python(run_data->cli_spec); if (!cli_flags) { if (PyErr_Occurred()) PyErr_Print(); return false; } S(cli_flags, cli_flags); #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 void canonicalize_path_wide(const char *srcpath, wchar_t *dest, size_t sz) { char buf[sz + 1]; lexical_absolute_path(srcpath, buf, sz); buf[sz] = 0; mbstowcs(dest, buf, sz - 1); dest[sz-1] = 0; } 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 safe_snprintf(extensions_dir_full, PATH_MAX, "%s/%s/kitty-extensions", run_data->exe_dir, python_relpath); wchar_t extensions_dir[PATH_MAX]; canonicalize_path_wide(extensions_dir_full, extensions_dir, PATH_MAX); safe_snprintf(python_home_full, PATH_MAX, "%s/%s/python%s", run_data->exe_dir, python_relpath, PYVER); wchar_t python_home[PATH_MAX]; canonicalize_path_wide(python_home_full, python_home, PATH_MAX); bypy_initialize_interpreter( L"kitty", python_home, L"kitty_main", extensions_dir, run_data->cli_spec->original_argc, run_data->cli_spec->original_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->cli_spec->original_argc, run_data->cli_spec->original_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]; safe_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}; safe_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]; safe_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}; safe_snprintf(exe, PATH_MAX, "%s/kitten", exe_dir); argv[0] = "kitten"; if (being_tested) { printf("kitten_exe: %s\n", exe); output_argv("argv", argc, argv); exit(0); } errno = 0; execv(exe, argv); fprintf(stderr, "Failed to execute kitten (%s) with error: %s\n", exe, strerror(errno)); exit(1); } static bool parse_and_check_kitty_cli(CLISpec *cli_spec, int argc, char **argv) { parse_cli_for_kitty(cli_spec, argc, argv); if (cli_spec->errmsg) { fprintf(stderr, "%s\n", cli_spec->errmsg); #ifdef __APPLE__ os_log_error(OS_LOG_DEFAULT, "%{public}s", cli_spec->errmsg); #endif return false; } return true; } static bool parse_and_check_panel_kitten_cli(CLISpec *cli_spec, int argc, char **argv) { parse_cli_for_panel_kitten(cli_spec, argc, argv); if (cli_spec->errmsg) { fprintf(stderr, "%s\n", cli_spec->errmsg); #ifdef __APPLE__ os_log_error(OS_LOG_DEFAULT, "%{public}s", cli_spec->errmsg); #endif return false; } return true; } static int offset_for_plus_subcommand(int argc, char **argv, const char *name) { int offset = 0; #define arg_eq(num, what) (strcmp(argv[num], what) == 0) if (argc > 1 && argv[1][0] == '+' && strcmp(argv[1] + 1, name) == 0) { offset = 1; } else if (argc > 2 && arg_eq(1, "+") && arg_eq(2, name)) { offset = 2; } #undef arg_eq return offset; } static void handle_fast_commandline(CLISpec *cli_spec, const char *instance_group_prefix) { CLIOptions opts = {0}; RAII_CLISpec(subcommand_cli_spec); #define swap_cli_spec \ subcommand_cli_spec.original_argc = cli_spec->original_argc; \ subcommand_cli_spec.original_argv = cli_spec->original_argv; \ cli_spec = &subcommand_cli_spec; if (instance_group_prefix == NULL) { // Look for +open int offset = offset_for_plus_subcommand(cli_spec->original_argc, cli_spec->original_argv, "open"); if (offset) { if (!parse_and_check_kitty_cli(&subcommand_cli_spec, cli_spec->original_argc - offset, cli_spec->original_argv + offset)) exit(1); swap_cli_spec; opts.open_url_count = cli_spec->argc; opts.open_urls = cli_spec->argv; } } else { parse_and_check_panel_kitten_cli( &subcommand_cli_spec, cli_spec->original_argc, cli_spec->original_argv); swap_cli_spec; } if (get_bool_cli_val(cli_spec, "help")) return; if (get_bool_cli_val(cli_spec, "version")) { 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); } opts.session = get_string_cli_val(cli_spec, "session"); #ifdef __APPLE__ char pid_str[32]; snprintf(pid_str, sizeof(pid_str), "%d", getpid()); const char *ekfd = getenv("KITTY_EXEC_FOR_DETACH"); bool is_exec_for_detach = ekfd && strcmp(getenv("KITTY_EXEC_FOR_DETACH"), pid_str) == 0; if (is_exec_for_detach) unsetenv("KITTY_EXEC_FOR_DETACH"); #else bool is_exec_for_detach = false; #endif if (get_bool_cli_val(cli_spec, "detach") && !is_exec_for_detach) { const char *detached_log = get_string_cli_val(cli_spec, "detached_log"); if (being_tested) { printf("detach: true\n"); printf("detached_log: %s\n", detached_log ? detached_log : ""); printf("session: %s\n", opts.session ? opts.session : ""); exit(0); } else { int fds[2] = {0}; if (pipe(fds) == -1) { perror("failed to create a pipe"); exit(1); } #define reopen_or_fail(path, mode, which) { errno = 0; if (freopen(path, mode, which) == NULL) { int s = errno; fprintf(stderr, "Failed to redirect %s to %s with error: ", #which, path); errno = s; perror(NULL); exit(1); } setlinebuf(which); } if (!(opts.session && ((opts.session[0] == '-' && opts.session[1] == 0) || strcmp(opts.session, "/dev/stdin") == 0))) reopen_or_fail("/dev/null", "rb", stdin); if (!detached_log || !detached_log[0]) detached_log = "/dev/null"; reopen_or_fail(detached_log, "ab", stdout); reopen_or_fail(detached_log, "ab", stderr); #undef reopen_or_fail if (fork() != 0) { // wait until child has done setsid() before exiting so that it doesnt get a SIGHUP, // see: https://github.com/kovidgoyal/kitty/issues/8680 char buf[4]; errno = 0; while (close(fds[1]) != 0 && errno == EINTR); errno = 0; while(read(fds[0], buf, sizeof(buf)) == -1 && errno == EINTR); exit(0); } errno = 0; while (close(fds[0]) != 0 && errno == EINTR); setsid(); errno = 0; while (close(fds[1]) != 0 && errno == EINTR); #ifdef __APPLE__ // fork() without exec() is unsafe on macOS. It used to work since // in this case we havent yet loaded any major Cocoa libraries but // in Tahoe 26.2 Apple broke that as well. So now just exec() on --detach snprintf(pid_str, sizeof(pid_str), "%d", getpid()); setenv("KITTY_EXEC_FOR_DETACH", pid_str, 1); char exe_path[PATH_MAX] = {0}; read_exe_path(exe_path, PATH_MAX); execv(exe_path, cli_spec->original_argv); fprintf(stderr, "Failed to execv() for --detach with exe_path: %s\n", exe_path); exit(1); #endif } } unsetenv("KITTY_SI_DATA"); if (get_bool_cli_val(cli_spec, "single_instance")) { char igbuf[256]; opts.wait_for_single_instance_window_close = get_bool_cli_val(cli_spec, "wait_for_single_instance_window_close"); opts.instance_group = get_string_cli_val(cli_spec, "instance_group"); if (instance_group_prefix && instance_group_prefix[0]) { opts.instance_group = get_string_cli_val(cli_spec, "instance_group"); if (opts.instance_group && opts.instance_group[0]) { safe_snprintf(igbuf, sizeof(igbuf), "%s-%s", instance_group_prefix, opts.instance_group ? opts.instance_group : ""); opts.instance_group = igbuf; } else { opts.instance_group = instance_group_prefix; } } if (being_tested) { output_argv("argv", cli_spec->original_argc, cli_spec->original_argv); output_argv("open_urls", opts.open_url_count, opts.open_urls); output_values_for_testing(cli_spec); printf("single_instance: 1\n"); printf("instance_group: %s\n", opts.instance_group ? opts.instance_group : ""); printf("session: %s\n", opts.session ? opts.session : ""); exit(0); } else { single_instance_main(cli_spec->original_argc, cli_spec->original_argv, &opts); } } } static bool delegate_to_kitten_if_possible(int argc, char **argv, char* exe_dir) { if (argc > 1 && argv[1][0] == '@') exec_kitten(argc, argv, exe_dir); int offset = offset_for_plus_subcommand(argc, argv, "kitten"); if (offset && argc > offset+1) { const char *kitten = argv[offset + 1]; if (is_wrapped_kitten(kitten)) exec_kitten(argc - offset, argv + offset, exe_dir); if (strcmp(kitten, "panel") == 0) { offset++; CLISpec t = {.original_argv = argv + offset, .original_argc=argc - offset}; handle_fast_commandline(&t, "panel"); return true; } } return false; } static bool endswith(const char *str, const char *suffix) { size_t strLen = strlen(str); size_t suffixLen = strlen(suffix); if (suffixLen > strLen) return false; return strcmp(str + strLen - suffixLen, suffix) == 0; } static void output_test_data(RunData *rd) { printf("launched_by_launch_services: %d\n", rd->launched_by_launch_services); printf("is_quick_access_terminal: %d\n", rd->is_quick_access_terminal); char buf[PATH_MAX + 1]; if (rd->config_dir == NULL) { if (get_config_dir(buf, sizeof(buf))) rd->config_dir = buf; } printf("config_dir: %s\n", rd->config_dir ? rd->config_dir : ""); output_for_testing(rd->cli_spec); } int main(int argc_, char *argv_[], char* envp[]) { if (argc_ < 1 || !argv_) { fprintf(stderr, "Invalid argc/argv\n"); return 1; } if (argc_ > 1 && strcmp(argv_[1], "+testing-launcher-code") == 0) { being_tested = true; memmove(argv_ + 1, argv_ + 2, (--argc_ - 1) * sizeof(argv_[0])); } if (!ensure_working_stdio()) return 1; char exe[PATH_MAX+1] = {0}; if (!read_exe_path(exe, sizeof(exe))) return 1; char exe_dir_buf[PATH_MAX+1] = {0}; strncpy(exe_dir_buf, exe, sizeof(exe_dir_buf)); char *exe_dir = dirname(exe_dir_buf); RAII_ALLOC(const char, lc_ctype, NULL); bool launched_by_launch_services = false; const char *config_dir = NULL; bool is_quick_access_terminal = false; argv_array argva = {.argv = argv_, .count = argc_}; #ifdef __APPLE__ lc_ctype = getenv("LC_CTYPE"); if (lc_ctype) lc_ctype = strdup(lc_ctype); char abuf[PATH_MAX+1]; is_quick_access_terminal = endswith(exe, "/kitty-quick-access"); if (getenv("KITTY_LAUNCHED_BY_LAUNCH_SERVICES")) { launched_by_launch_services = true; unsetenv("KITTY_LAUNCHED_BY_LAUNCH_SERVICES"); if (!get_config_dir(abuf, sizeof(abuf))) abuf[0] = 0; config_dir = abuf; if (launched_by_launch_services && config_dir[0]) { char cbuf[PATH_MAX]; safe_snprintf(cbuf, sizeof(cbuf), "%s/macos-launch-services-cmdline", config_dir); if (!get_argv_from(cbuf, argva.argv[0], &argva)) exit(1); } } #else (void)endswith; #endif (void)read_full_file; RAII_CLISpec(cli_spec); bool handle_fast_commandline_called = delegate_to_kitten_if_possible(argva.count, argva.argv, exe_dir); bool ok = parse_and_check_kitty_cli(&cli_spec, argva.count, argva.argv); if (!ok) return 1; if (!handle_fast_commandline_called) handle_fast_commandline(&cli_spec, NULL); int ret=0; char lib[PATH_MAX+1] = {0}; if (KITTY_LIB_PATH[0] == '/') { safe_snprintf(lib, PATH_MAX, "%s", KITTY_LIB_PATH); } else { safe_snprintf(lib, PATH_MAX, "%s/%s", exe_dir, KITTY_LIB_PATH); } RunData run_data = { .exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .cli_spec = &cli_spec, .lc_ctype = lc_ctype, .launched_by_launch_services=launched_by_launch_services, .config_dir = config_dir, .is_quick_access_terminal=is_quick_access_terminal, }; if (being_tested) output_test_data(&run_data); else ret = run_embedded(&run_data); free_argv_array(&argva); single_instance_main(-1, NULL, NULL); if (!being_tested) Py_FinalizeEx(); return ret; } ================================================ FILE: kitty/launcher/shlex.h ================================================ /* * shlex.h * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include typedef enum { NORMAL, WORD, STRING_WITHOUT_ESCAPES, STRING_WITH_ESCAPES, ANSI_C_QUOTED } ShlexEnum; typedef struct { const char *src; bool support_ansi_c_quoting, allow_empty; char *buf; size_t src_sz, src_pos, word_start, buf_pos; ShlexEnum state; const char *err; } ShlexState; static bool alloc_shlex_state(ShlexState *s, const char *src, size_t src_sz, bool support_ansi_c_quoting) { *s = (ShlexState){ // for NULL termination and some safety we add 16 bytes .src=src, .src_sz=src_sz, .support_ansi_c_quoting=support_ansi_c_quoting, .buf=malloc(16 + src_sz) }; return s->buf != NULL; } static void dealloc_shlex_state(ShlexState *s) { free(s->buf); s->buf = NULL; *s = (ShlexState){0}; } #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(ShlexState *self) { self->word_start = self->src_pos - 1; self->buf_pos = 0; } static void write_ch(ShlexState *self, char ch) { self->buf[self->buf_pos++] = ch; } static unsigned encode_utf8(unsigned long 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; } static void write_unich(ShlexState *self, unsigned long ch) { self->buf_pos += encode_utf8(ch, self->buf + self->buf_pos); } static size_t get_word(ShlexState *self) { size_t ans = self->buf_pos; self->buf_pos = 0; self->buf[ans] = 0; self->allow_empty = false; return ans; } static char read_ch(ShlexState *self) { return self->src[self->src_pos++]; } static bool write_escape_ch(ShlexState *self) { if (self->src_pos < self->src_sz) { char nch = read_ch(self); write_ch(self, nch); return true; } return false; } static bool write_control_ch(ShlexState *self) { if (self->src_pos >= self->src_sz) { self->err = "Trailing \\c escape at end of input data"; return false; } char ch = read_ch(self); write_ch(self, ch & 0x1f); return true; } static void read_valid_digits(ShlexState *self, int max, char *output, bool(*is_valid)(char ch)) { for (int i = 0; i < max && self->src_pos < self->src_sz; i++, output++) { char ch = read_ch(self); if (!is_valid(ch)) { self->src_pos--; break; } *output = ch; } } static bool is_octal_digit(char ch) { return '0' <= ch && ch <= '7'; } static bool is_hex_digit(char ch) { return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'); } static void write_octal_ch(ShlexState *self, char ch) { char chars[4] = {ch, 0, 0, 0}; read_valid_digits(self, 2, chars + 1, is_octal_digit); write_unich(self, strtol(chars, NULL, 8)); } static bool write_unicode_ch(ShlexState *self, int max) { char chars[16] = {0}; read_valid_digits(self, max, chars, is_hex_digit); if (!chars[0]) { self->err = "Trailing unicode escape at end of input data"; return false; } write_unich(self, strtol(chars, NULL, 16)); return true; } static bool write_ansi_escape_ch(ShlexState *self) { if (self->src_pos >= self->src_sz) { self->err = "Trailing backslash at end of input data"; return false; } char 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); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': write_octal_ch(self, ch); return true; default: write_ch(self, ch); return true; } } static void set_state(ShlexState *self, ShlexEnum s) { self->state = s; } static ssize_t next_word(ShlexState *self) { #define write_escaped_or_fail() if (!write_escape_ch(self)) { self->err = "Trailing backslash at end of input data"; return -1; } char prev_word_ch = 0; while (self->src_pos < self->src_sz) { char 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 || self->allow_empty) 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); self->allow_empty = true; break; default: write_ch(self, ch); break; } break; case STRING_WITH_ESCAPES: switch(ch) { case STRING_WITH_ESCAPES_DELIM: set_state(self, WORD); self->allow_empty = true; 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); self->allow_empty = true; break; case ESCAPE_CHAR: if (!write_ansi_escape_ch(self)) return -1; break; default: write_ch(self, ch); break; } break; } } switch (self->state) { case WORD: self->state = NORMAL; if (self->buf_pos || self->allow_empty) return get_word(self); break; case STRING_WITH_ESCAPES: case STRING_WITHOUT_ESCAPES: case ANSI_C_QUOTED: self->err = "Unterminated string at the end of input"; self->state = NORMAL; return -1; case NORMAL: break; } return -2; #undef write_escaped_or_fail } ================================================ FILE: kitty/launcher/single-instance.c ================================================ /* * 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 #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); size_t dlen = strlen(addr->sun_path); while (dlen && addr->sun_path[dlen-1] == '/') dlen--; 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}; char *session_path = NULL; 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); session_path = malloc(PATH_MAX + 8); if (!session_path) fail_on_errno("Failed to alloc space for session_path"); if (getcwd(session_path, PATH_MAX + 1) == NULL) fail_on_errno("Failed to getcwd()"); char *p = session_path + strlen(session_path); *(p++) = '/'; *(p++) = '-'; *p = 0; } 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); session_path = realpath(opts->session, NULL); if (session_path == NULL) fail_on_errno("Failed to call realpath() on session file"); } } 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(",\"session_arg\":"); if (opts->session && opts->session[0]) write_json_string(&output, opts->session, strlen(opts->session)); else write_json_string(&output, "", 0); w(",\"session_path\":"); if (session_path && session_path[0]) write_json_string(&output, session_path, strlen(session_path)); else write_json_string(&output, "", 0); free(session_path); 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); } ================================================ FILE: kitty/launcher/utils.h ================================================ /* * utils.h * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include static const char* home = NULL; static void ensure_home_path(void) { if (home) return; home = getenv("HOME"); if (!home || !home[0]) { struct passwd* pw = getpwuid(geteuid()); if (pw) home = pw->pw_dir; } if (!home || !home[0]) { fprintf(stderr, "Fatal error: Cannot determine home directory\n"); exit(1); } } #define safe_snprintf(buf, sz, fmt, ...) { int n = snprintf(buf, sz, fmt, __VA_ARGS__); if (n < 0 || (size_t)n >= sz) { fprintf(stderr, "Out of buffer space calling sprintf for format: %s at line: %d\n", fmt, __LINE__); exit(1); }} static const char* home_path_for(const char *username) { struct passwd* pw = getpwnam(username); if (pw) return pw->pw_dir; return NULL; } static void expand_tilde(const char* path, char *ans, size_t ans_sz) { if (path[0] != '~') { safe_snprintf(ans, ans_sz, "%s", path); return; } const char *prefix = NULL, *sep = ""; if (path[1] == '/' || path[1] == '\0') { // If the path is "~" or "~/something", get the current user's home directory ensure_home_path(); prefix = home; } else { // If the path is "~user/something", get the specified user's home directory char *slash = (char*)strchr(path, '/'); // path is temporarily modified here and then restored if (slash) { *slash = 0; prefix = home_path_for(path + 1); *slash = '/'; } else prefix = home_path_for(path + 1); if (prefix) path = slash ? slash - 1 : "a"; else { prefix = ""; path--; } } // Construct the expanded path safe_snprintf(ans, ans_sz, "%s%s%s", prefix, sep, path + 1); } static size_t clean_path(char *path) { char *write_ptr = path; char* read_ptr = path; while (*read_ptr) { if (read_ptr[0] != '/') { *write_ptr++ = *read_ptr++; continue; } // we have / if (read_ptr[1] == '/') { read_ptr++; continue; } // skip one slash of double slash if (read_ptr[1] != '.') { *write_ptr++ = *read_ptr++; continue; } // we have /. if (read_ptr[2] == '/' || !read_ptr[2]) { read_ptr += 2; continue; } // skip /./ if (read_ptr[2] != '.') { *write_ptr++ = *read_ptr++; continue; } // we have /.. if (read_ptr[3] == '/' || !read_ptr[3]) { read_ptr += 3; while (write_ptr > path) { write_ptr--; if (*write_ptr == '/') break; } } else *write_ptr++ = *read_ptr++; } // remove trailing slashes while (write_ptr > path + 1 && *(write_ptr - 1) == '/') write_ptr--; // Null-terminate the normalized path *write_ptr++ = '\0'; return write_ptr - path - 1; } static size_t lexical_absolute_path(const char* relative, char *output, size_t outsz) { size_t rlen = strlen(relative); char *limit = output + outsz; char* write_ptr = output; // Points to the location to write normalized characters #define _ensure_space(n) if (write_ptr + n + 1 >= limit) { fprintf(stderr, "Out of buffer space making absolute path for: %s with cwd: %s\n", relative, output); exit(1); } if (relative[0] != '/') { if (!getcwd(output, outsz)) { perror("Getting the current working directory failed with error"); exit(1); } size_t cwdlen = strlen(output); write_ptr = output + cwdlen; _ensure_space(cwdlen + rlen + 2); if (rlen && cwdlen && *(write_ptr - 1) != '/') *(write_ptr++) = '/'; } else { _ensure_space(rlen + 2); } #undef _ensure_space // Append the relative path memcpy(write_ptr, relative, rlen); *(write_ptr + rlen) = 0; size_t ans = clean_path(output); // Ensure the path is not empty if (output[0] == '\0') { output[0] = '/'; output[1] = 0; ans = 1; } return ans; } static bool makedirs_cleaned(char *path, int mode, struct stat *buffer) { if (stat(path, buffer) == 0) { if (S_ISDIR(buffer->st_mode)) return true; errno = ENOTDIR; return false; } if (errno == ENOTDIR) return false; char *p = strrchr(path, '/'); if (p && p > path) { p[0] = 0; bool parent_created = makedirs_cleaned(path, mode, buffer); p[0] = '/'; if (!parent_created) return false; } // Now parent exists return mkdir(path, mode) == 0; } static bool makedirs(const char *path, int mode) { struct stat buffer; char pbuf[PATH_MAX]; lexical_absolute_path(path, pbuf, sizeof(pbuf)); return makedirs_cleaned(pbuf, mode, &buffer); } static bool is_dir_ok_for_config(char *q) { size_t len = strlen(q); memcpy(q + len, "/kitty", sizeof("/kitty")); len += sizeof("/kitty") - 1; memcpy(q + len, "/kitty.conf", sizeof("/kitty.conf")); if (access(q, F_OK) != 0) return false; q[len] = 0; return access(q, W_OK) == 0; } static bool get_config_dir(char *output, size_t outputsz) { const char *q; char buf1[PATH_MAX], buf2[PATH_MAX]; #define expand(x, dest, sz) { expand_tilde(x, buf1, sizeof(buf1)); lexical_absolute_path(buf1, dest, sz); } q = getenv("KITTY_CONFIG_DIRECTORY"); if (q && q[0]) { expand(q, output, outputsz); return true; } #define check_and_ret(x) if (x && x[0]) { expand(x, output, outputsz); if (is_dir_ok_for_config(output)) return true; } q = getenv("XDG_CONFIG_HOME"); check_and_ret(q); check_and_ret("~/.config"); #ifdef __APPLE__ check_and_ret("~/Library/Preferences"); #endif q = getenv("XDG_CONFIG_DIRS"); if (q && q[0]) { safe_snprintf(buf2, sizeof(buf2), "%s", q); char *s, *token = strtok_r(buf2, ":", &s); while (token) { check_and_ret(token); token = strtok_r(NULL, ":", &s); } } q = getenv("XDG_CONFIG_HOME"); if (!q || !q[0]) q = "~/.config"; expand(q, buf2, sizeof(buf2)); safe_snprintf(output, outputsz, "%s/kitty", buf2); if (makedirs(output, 0755)) return true; return false; #undef expand #undef check_and_ret } static ssize_t safe_read_stream(void* ptr, size_t size, FILE* stream) { errno = 0; ssize_t total = 0, bytes_to_read = size; while (total < bytes_to_read) { size_t n = fread((char*)ptr + total, 1, bytes_to_read - total, stream); if (n > 0) total += n; else { if (!ferror(stream)) break; // eof if (errno != EINTR) return -1; clearerr(stream); } } return total; } static char* read_full_file(const char* filename, size_t *sz) { FILE* file = NULL; errno = EINTR; while (file == NULL && errno == EINTR) file = fopen(filename, "rb"); if (!file) return NULL; fseek(file, 0, SEEK_END); unsigned long file_size = ftell(file); rewind(file); char* buffer = (char*)malloc(file_size + 1); // +1 for the null terminator if (!buffer) { errno = EINTR; while (errno == EINTR && fclose(file) != 0); errno = ENOMEM; return NULL; } ssize_t q = safe_read_stream(buffer, file_size, file); int saved = errno; errno = EINTR; while (errno == EINTR && fclose(file) != 0); errno = saved; if (q < 0) { free(buffer); buffer = NULL; if (sz) *sz = 0; } else { if (sz) { *sz = q; } buffer[q] = 0; } return buffer; } ================================================ FILE: kitty/layout/__init__.py ================================================ ================================================ FILE: kitty/layout/base.py ================================================ #!/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, Callable, NamedTuple from kitty.borders import BorderColor from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE, Region, get_options, set_active_window, viewport_for_window from kitty.options.types import Options from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper, WindowResizeDragData from kitty.typing_compat import WindowType from kitty.window_list import WindowGroup, WindowList class BorderLine(NamedTuple): edges: Edges = Edges() color: BorderColor = BorderColor.inactive window_id: int = 0 horizontal: bool = False 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 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 effective_draw_minimal_borders(opts: Options, has_more_than_one_visible_group: bool = True) -> bool: ans = opts.draw_minimal_borders and sum(opts.window_margin_width) == 0 if not has_more_than_one_visible_group and opts.draw_window_borders_for_single_window: ans = False return ans def set_layout_options(opts: Options) -> None: lgd.draw_minimal_borders = effective_draw_minimal_borders(opts) 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) def create_window_id_map_for_unserialize(all_windows: WindowList) -> dict[int, int]: window_id_map = {} for w in all_windows: if w.serialized_id: window_id_map[w.serialized_id] = w.id return window_id_map 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(all_windows) 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) drag_resize_window = modify_size_of_window 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(all_windows) 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, all_windows: WindowList) -> None: lgd.central, tab_bar, vw, vh, lgd.cell_width, lgd.cell_height = viewport_for_window(self.os_window_id) # Update lgd.draw_minimal_borders based on the current number of visible windows # and the draw_window_borders_for_single_window option opts = get_options() lgd.draw_minimal_borders = effective_draw_minimal_borders(opts, all_windows.has_more_than_one_visible_group) def __call__(self, all_windows: WindowList) -> None: self._set_dimensions(all_windows) self.update_visibility(all_windows) self.blank_rects = [] # Set show_title_bar flag on each visible window before layout min_windows = get_options().window_title_bar_min_windows visible_groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True)) show_title_bar = min_windows > 0 and len(visible_groups) >= min_windows for wg in visible_groups: for w in wg.windows: w.show_title_bar = show_title_bar 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: 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 {} 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) -> Iterator[BorderLine]: self._set_dimensions(windows) yield from self.minimal_borders(windows) def minimal_borders(self, windows: WindowList) -> Iterator[BorderLine]: yield from () def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> bool | None: pass def layout_state(self) -> dict[str, Any]: return {} def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: return True def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, ) -> WindowResizeDragData: return WindowResizeDragData(click_window.id, bool(edges & RIGHT_EDGE), click_window.id, bool(edges & BOTTOM_EDGE)) def serialize(self, all_windows: WindowList) -> dict[str, Any]: ans = self.layout_state() ans['opts'] = self.layout_opts.serialized() ans['class'] = self.__class__.__name__ ans['all_windows'] = all_windows.serialize_layout_state() return ans def unserialize( self, s: dict[str, Any], all_windows: WindowList, window_id_mapper: Callable[[WindowList], dict[int, int]] = create_window_id_map_for_unserialize, ) -> bool: if s.get('class') != self.__class__.__name__: return False window_id_map = create_window_id_map_for_unserialize(all_windows) m = all_windows.unserialize_layout_state(s['all_windows'], window_id_map) if m is None: return False return self.set_layout_state(s, m.get) ================================================ FILE: kitty/layout/grid.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Callable, Generator, Iterator, 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, NeighborsMap, WindowMapper from kitty.typing_compat import WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutData, LayoutDimension, ListOfWindows, 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) -> Iterator[BorderLine]: 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, color: BorderColor, wid: int) -> Iterator[BorderLine]: 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 BorderLine(Edges(left, top, right, top + bw), color, -wid, True) if not last_row: yield BorderLine(Edges(left, bottom - bw, right, bottom), color, wid, True) # Vertical if not first_column: yield BorderLine(Edges(left, top, left + bw, bottom), color, -wid, False) if not last_column: yield BorderLine(Edges(right - bw, top, right, bottom), color, wid, False) for wg in all_groups_in_order: color = BorderColor.inactive if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell wid = wg.active_window_id yield from borders_for_window(wg.id, color, wid) 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 ans: NeighborsMap = {} if row: ans['top'] = neighbors(row-1, col) if bottom := neighbors(row + 1, col): ans['bottom'] = bottom if col and (left := side(row, col, -1)): ans['left'] = left if col < ncols - 1: ans['right'] = side(row, col, 1) return ans def layout_state(self) -> dict[str, Any]: return { 'biased_cols': self.biased_cols, 'biased_rows': self.biased_rows } def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: self.biased_rows = {int(k): v for k, v in layout_state['biased_rows'].items()} self.biased_cols = {int(k): v for k, v in layout_state['biased_cols'].items()} return True ================================================ FILE: kitty/layout/interface.py ================================================ #!/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] ================================================ FILE: kitty/layout/splits.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Collection, Generator, Iterator, Sequence from typing import Any, Optional, TypedDict, Union from kitty.borders import BorderColor from kitty.fast_data_types import BOTTOM_EDGE, LEFT_EDGE, RIGHT_EDGE, TOP_EDGE from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper, WindowResizeDragData from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutOpts, blank_rects_for_window, lgd, window_geometry_from_layouts class SerializedPair(TypedDict, total=False): horizontal: bool # default to True if absent bias: float # default to 0.5 if absent one: Union[int, 'SerializedPair'] # default to None if absent two: Union[int, 'SerializedPair'] # default to None if absent 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: tuple[Sequence[BorderLine], Sequence[BorderLine]] | None = None self.first_extent = self.second_extent = Edges() # not including between_borders self.border_width: int = 0 def serialize(self) -> SerializedPair: ans: SerializedPair = {} if not self.horizontal: ans['horizontal'] = False if self.bias != 0.5: ans['bias'] = self.bias if self.one is not None: ans['one'] = self.one.serialize() if isinstance(self.one, Pair) else self.one if self.two is not None: ans['two'] = self.two.serialize() if isinstance(self.two, Pair) else self.two return ans def unserialize(self, s: SerializedPair, map_window_id: WindowMapper) -> None: self.bias = s.get('bias', 0.5) self.horizontal = s.get('horizontal', True) def unserialize(x: int | SerializedPair | None) -> int | Pair | None: if x is None: return None if isinstance(x, int): return map_window_id(x) ans = Pair() ans.unserialize(x, map_window_id) return ans if ans.one or ans.two else None self.one = unserialize(s.get('one')) self.two = unserialize(s.get('two')) 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 = None self.left, self.top, self.width, self.height = left, top, width, height self.first_extent = self.second_extent = Edges(left, top, left + width, top + height) self.border_width = 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 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.apply_window_geometry(q, geom, id_window_map, layout_object) return one: list[BorderLine] = [] two: list[BorderLine] = [] self.between_borders = one, two 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 bleft = left + w1 self.first_extent = Edges(left, top, left + w1, top + height) if isinstance(self.one, Pair): self.one.layout_pair(left, top, w1, height, id_window_map, layout_object) if bw: for etop, ebottom, window_id in self.one.edge_border(RIGHT_EDGE, id_window_map): one.append(BorderLine(Edges(bleft, etop, bleft + bw, ebottom), window_id=window_id, horizontal=False)) 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) if bw: one.append(BorderLine(Edges(bleft, top, bleft + bw, top + height), window_id=wg.active_window_id, horizontal=False)) left += w1 + bw2 self.second_extent = Edges(left, top, left + w2, top + height) if isinstance(self.two, Pair): self.two.layout_pair(left, top, w2, height, id_window_map, layout_object) if bw: for etop, ebottom, window_id in self.two.edge_border(LEFT_EDGE, id_window_map): two.append(BorderLine(Edges(left - bw, etop, left, ebottom), window_id=window_id, horizontal=False)) else: wg = id_window_map[self.two] if bw: two.append(BorderLine(Edges(left - bw, top, left, top + height), window_id=-wg.active_window_id, horizontal=False)) xl = next(layout_object.xlayout(iter((wg,)), start=left, 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 btop = top + h1 self.first_extent = Edges(left, top, left + width, top + h1) if isinstance(self.one, Pair): self.one.layout_pair(left, top, width, h1, id_window_map, layout_object) if bw: for eleft, eright, window_id in self.one.edge_border(BOTTOM_EDGE, id_window_map): one.append(BorderLine(Edges(eleft, btop, eright, btop + bw), window_id=window_id, horizontal=True)) 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) if bw: one.append(BorderLine(Edges(left, btop, left + width, btop + bw), window_id=wg.active_window_id, horizontal=True)) top += bw2 + h1 self.second_extent = Edges(left, top, left + width, top + h2) if isinstance(self.two, Pair): self.two.layout_pair(left, top, width, h2, id_window_map, layout_object) if bw: for eleft, eright, window_id in self.two.edge_border(TOP_EDGE, id_window_map): two.append(BorderLine(Edges(eleft, top - bw, eright, top), window_id=window_id, horizontal=True)) else: wg = id_window_map[self.two] if bw: two.append(BorderLine(Edges(left, top - bw, left + width, top), window_id=-wg.active_window_id, horizontal=True)) 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=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 edge_border(self, which: int, id_group_map: dict[int, WindowGroup]) -> Iterator[tuple[int, int, int]]: mult = 1 if which & (RIGHT_EDGE | BOTTOM_EDGE) else -1 def edge(x: int, p: Pair) -> tuple[int, int, int]: wid = id_group_map[x].active_window_id * mult if which & (LEFT_EDGE | RIGHT_EDGE): return p.top, p.top + p.height, wid return p.left, p.left + p.width, wid def edges(x: int | Pair, parent: Pair) -> Iterator[tuple[int, int, int]]: if isinstance(x, int): yield edge(x, parent) else: yield from x.edge_border(which, id_group_map) if self.two is None or self.one is None: x = self.one or self.two if x is not None: yield from edges(x, self) return def as_pair(e: Edges, gid: int) -> Pair: g1 = Pair() g1.one = g1.two = gid g1.left, g1.top, g1.width, g1.height = e.left, e.top, e.right - e.left, e.bottom - e.top return g1 needs_vertical_edges = which in (LEFT_EDGE, RIGHT_EDGE) if self.horizontal == needs_vertical_edges: yield from edges(self.one if which in (LEFT_EDGE, TOP_EDGE) else self.two, self) else: g1 = as_pair(self.first_extent, self.one) if isinstance(self.one, int) else self.one g2 = as_pair(self.second_extent, self.two) if isinstance(self.two, int) else self.two yield from edges(self.one, g1) first_id = second_id = 0 if isinstance(self.one, int): first_id = self.one if isinstance(self.two, int): second_id = self.two if self.horizontal: start = g1.left + g1.width if isinstance(self.one, Pair): first_id = self.one.corner_group_id(which | RIGHT_EDGE) if isinstance(self.two, Pair): second_id = self.two.corner_group_id(which | LEFT_EDGE) else: start = g1.top + g1.height if isinstance(self.one, Pair): first_id = self.one.corner_group_id(which | BOTTOM_EDGE) if isinstance(self.two, Pair): second_id = self.two.corner_group_id(which | TOP_EDGE) if g := id_group_map.get(first_id): first_id = g.active_window_id if g := id_group_map.get(second_id): second_id = g.active_window_id yield start, start + self.border_width, first_id * mult yield start + self.border_width, start + 2*self.border_width, second_id * mult yield from edges(self.two, g2) def corner_group_id(self, which: int) -> int: if self.is_redundant: q = self.one or self.two elif self.horizontal: q = self.one if which & LEFT_EDGE else self.two else: q = self.one if which & TOP_EDGE else self.two if q is None: return 0 return q if isinstance(q, int) else q.corner_group_id(which) 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 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.get(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.setdefault(which, []).extend(neighbors) else: ans.setdefault(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 def is_group_on_second(self, gid: int) -> bool: if self.one == gid: return False if self.two == gid: return True if not isinstance(self.two, Pair): return False return self.two.is_group_on_second(gid) def find_window_in_tree(self, window_id: int) -> 'list[tuple[Pair, bool]] | None': # Returns list of (pair, is_in_one) from self down to the pair containing window_id. if self.one == window_id: return [(self, True)] if self.two == window_id: return [(self, False)] if isinstance(self.one, Pair): path = self.one.find_window_in_tree(window_id) if path is not None: return [(self, True)] + path if isinstance(self.two, Pair): path = self.two.find_window_in_tree(window_id) if path is not None: return [(self, False)] + path return None def path_from_root(self, target: 'Pair') -> 'list[str] | None': if self is target: return [] if isinstance(self.one, Pair): sub = self.one.path_from_root(target) if sub is not None: return ['one'] + sub if isinstance(self.two, Pair): sub = self.two.path_from_root(target) if sub is not None: return ['two'] + sub return None def pair_at_path(self, path: 'list[str]') -> 'Pair | None': current: Pair = self for step in path: child = current.one if step == 'one' else current.two if not isinstance(child, Pair): return None current = child return current 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()) root = self.pairs_root all_present_group_ids = {g.id for g in groups} already_placed_group_ids = frozenset(root.all_window_ids()) if groups_to_remove := already_placed_group_ids - all_present_group_ids: self.remove_windows(*groups_to_remove) if groups_to_add := all_present_group_ids - already_placed_group_ids: id_idx_map = {g.id: i for i, g in enumerate(groups)} for gid in sorted(groups_to_add, key=id_idx_map.__getitem__): root.balanced_add(gid) if len(groups) == 1: self.layout_single_window_group(groups[0]) else: id_group_map = {g.id: g for g in groups} root.layout_pair(lgd.central.left, lgd.central.top, lgd.central.width, lgd.central.height, id_group_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) g = all_windows.group_for_window(window) assert g is not None p = self.pairs_root.balanced_add(g.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) -> Iterator[BorderLine]: groups = tuple(all_windows.iter_all_layoutable_groups()) window_count = len(groups) if not lgd.draw_minimal_borders or window_count < 2: return 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 border_color_map = {} for grp_id, needs_borders in needs_borders_map.items(): if needs_borders: wid = g.active_window_id if (g := all_windows.group_for_id(grp_id)) else 0 if wid: color = BorderColor.active if grp_id is active_group_id else BorderColor.bell border_color_map[wid] = color for pair in self.pairs_root.self_and_descendants(): if pair.between_borders: for which in pair.between_borders: for bb in which: yield bb._replace(color=border_color_map.get(abs(bb.window_id), BorderColor.inactive)) 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 = {} 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: pair.horizontal = horizontal 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 elif action_name == 'maximize': args = args or ('horizontal',) axis = args[0] is_horizontal = axis == 'horizontal' wg = all_windows.active_group if wg is not None: key = (wg.id, is_horizontal) maximized_biases: dict[tuple[int, bool], list[tuple[Pair, float]]] = getattr(self, '_maximized_biases', {}) if key in maximized_biases: # Already maximized along this axis for this window — toggle back current_pair_ids = {id(p) for p in self.pairs_root.self_and_descendants()} for pair_ref, saved_bias in maximized_biases.pop(key): if id(pair_ref) in current_pair_ids: pair_ref.bias = saved_bias self._maximized_biases = maximized_biases return True else: # Undo any existing maximize along the same axis (different window) stale_keys = [k for k in maximized_biases if k[1] == is_horizontal] if stale_keys: current_pair_ids = {id(p) for p in self.pairs_root.self_and_descendants()} for k in stale_keys: for pair_ref, saved_bias in maximized_biases.pop(k): if id(pair_ref) in current_pair_ids: pair_ref.bias = saved_bias # Maximize: set biases along the path to give maximum space to active window # Only adjust pairs whose split axis matches the requested direction: # horizontal maximize expands width (affects horizontal/side-by-side splits), # vertical maximize expands height (affects vertical/top-bottom splits). tree_path = self.pairs_root.find_window_in_tree(wg.id) if tree_path is not None: saved_biases: list[tuple[Pair, float]] = [] for pair, is_in_one in tree_path: if pair.horizontal == is_horizontal and not pair.is_redundant: saved_biases.append((pair, pair.bias)) pair.bias = 1.0 if is_in_one else 0.0 if saved_biases: maximized_biases[key] = saved_biases self._maximized_biases = maximized_biases return True return None def drag_resize_window(self, all_windows: WindowList, pair_id: int, increment: float, is_horizontal: bool = True) -> bool: for pair in self.pairs_root.self_and_descendants(): if id(pair) == pair_id: new_bias = max(0, min(pair.bias + increment, 1)) if new_bias != pair.bias: pair.bias = new_bias return True break return False def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, ) -> WindowResizeDragData: is_right, is_bottom = bool(edges & RIGHT_EDGE), bool(edges & BOTTOM_EDGE) is_leading_edge = not (is_right or is_bottom) ans = WindowResizeDragData(None, is_right, None, is_bottom) if (wg := all_windows.group_for_window(click_window)) is None or (pair := self.pairs_root.pair_for_window(wg.id)) is None: return ans pair_parent_map = {} for p in self.pairs_root.self_and_descendants(): if isinstance(p.one, Pair): pair_parent_map[p.one] = p if isinstance(p.two, Pair): pair_parent_map[p.two] = p p = pair def size_increases_forwards(p: Pair) -> bool: in_leading_half = not p.is_group_on_second(wg.id) if p is pair: return is_leading_edge != in_leading_half parent = pair_parent_map.get(p) or Pair() if parent.horizontal != p.horizontal and is_leading_edge: return True return not in_leading_half def ancestor_with_neighboring_border_of_same_orientation(p: Pair) -> Pair | None: horizontal = bool(edges & (LEFT_EDGE | RIGHT_EDGE)) while (q := pair_parent_map.get(p)): if q.horizontal == horizontal: if q.between_borders: return q break p = q return None def pair_or_parent(p: Pair) -> tuple[Pair, bool]: in_leading_half = not p.is_group_on_second(wg.id) if is_leading_edge == in_leading_half and p is pair and (parent := ancestor_with_neighboring_border_of_same_orientation(p)): # special case for leading edge of one or trailing edge of two with parent being same orientation return parent, True return p, size_increases_forwards(p) while ans.horizontal_id is None or ans.vertical_id is None: if p.is_redundant: continue if ans.horizontal_id is None and p.horizontal: p, fwd = pair_or_parent(p) ans = ans._replace(horizontal_id=id(p), width_increases_rightwards=fwd) if ans.vertical_id is None and not p.horizontal: p, fwd = pair_or_parent(p) ans = ans._replace(vertical_id=id(p), height_increases_downwards=fwd) if (parent := pair_parent_map.get(p)) is None: break p = parent return ans def layout_state(self) -> dict[str, Any]: ans: dict[str, Any] = {'pairs': self.pairs_root.serialize()} maximized_biases: dict[tuple[int, bool], list[tuple[Pair, float]]] = getattr(self, '_maximized_biases', {}) if maximized_biases: serialized_maximized = [] for (window_id, is_horizontal), saved_biases_list in maximized_biases.items(): entries = [] for pair_ref, saved_bias in saved_biases_list: path = self.pairs_root.path_from_root(pair_ref) if path is not None: entries.append({'path': path, 'bias': saved_bias}) if entries: serialized_maximized.append({ 'window_id': window_id, 'is_horizontal': is_horizontal, 'saved_biases': entries, }) if serialized_maximized: ans['maximized'] = serialized_maximized return ans def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: new_root = Pair() new_root.unserialize(layout_state['pairs'], map_group_id) before = frozenset(self.pairs_root.all_window_ids()) if before == frozenset(new_root.all_window_ids()): self.pairs_root = new_root self.layout_opts = SplitsLayoutOpts(layout_state['opts']) if 'maximized' in layout_state: maximized_biases: dict[tuple[int, bool], list[tuple[Pair, float]]] = {} for entry in layout_state['maximized']: new_window_id = map_group_id(entry['window_id']) if new_window_id is None: continue is_horizontal: bool = entry['is_horizontal'] saved_biases_list: list[tuple[Pair, float]] = [] for saved in entry['saved_biases']: pair = new_root.pair_at_path(saved['path']) if pair is not None: saved_biases_list.append((pair, saved['bias'])) if saved_biases_list: maximized_biases[(new_window_id, is_horizontal)] = saved_biases_list if maximized_biases: self._maximized_biases = maximized_biases return True return False ================================================ FILE: kitty/layout/stack.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from kitty.types import NeighborsMap from kitty.typing_compat import WindowType from kitty.window_list import WindowList from .base import Layout 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} ================================================ FILE: kitty/layout/tall.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import sys from collections.abc import Generator, Iterator, Sequence from itertools import islice, repeat from typing import Any from kitty.borders import BorderColor from kitty.conf.utils import to_bool from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE from kitty.types import Edges, NeighborsMap, WindowMapper, WindowResizeDragData from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList from .base import ( BorderLine, Layout, LayoutData, LayoutDimension, LayoutOpts, lgd, normalize_biases, safe_increment_bias, ) from .vertical import borders def drag_resize_target_windows( click_window: WindowType, edges: int, x: float, y: float, num_full_size_windows: int, all_windows: WindowList, main_is_horizontal: bool = True ) -> WindowResizeDragData: groups = tuple(all_windows.iter_all_layoutable_groups()) horizontal = vertical = click_window min_dist = float(sys.maxsize) height_increases_downwards = bool(edges & BOTTOM_EDGE) width_increases_rightwards = bool(edges & RIGHT_EDGE) for gr in groups[num_full_size_windows:]: if gr.windows: w = gr.windows[-1] g = w.geometry if main_is_horizontal: if (dist := min(abs(g.top - y), abs(g.bottom - y))) < min_dist: min_dist = dist vertical = w height_increases_downwards = y > g.top + (g.bottom - g.top) / 2 else: if (dist := min(abs(g.left - x), abs(g.right - x))) < min_dist: min_dist = dist horizontal = w width_increases_rightwards = x > g.left + (g.right - g.left) / 2 return WindowResizeDragData(horizontal.id, width_increases_rightwards, vertical.id, height_increases_downwards) 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 = {} 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 rv: bool | str = data.get('mirrored', 'false') self.mirrored = to_bool(rv) if isinstance(rv, str) else bool(rv) def serialized(self) -> dict[str, Any]: return {'full_size': self.full_size, 'bias': self.bias, 'mirrored': 'y' if self.mirrored else 'n'} 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) -> Iterator[BorderLine]: 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 wid = wg.active_window_id mult = 1 if mirrored else -1 def h(left: int, right: int, top: int, mult: int) -> None: e = Edges(left, top, right, top + bw) perp_borders.append(BorderLine(e, color, mult*wid, True)) def v(top: int, bottom: int, left: int, mult: int) -> None: e = Edges(left, top, left + bw, bottom) perp_borders.append(BorderLine(e, color, mult*wid, False)) if self.main_is_horizontal: h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos - yl.space_before, -1) v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, xl.content_pos + ((xl.content_size + xl.space_after - bw) if mirrored else -xl.space_before), mult) h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after - bw, 1) else: v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, xl.content_pos - xl.space_before, -1) h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + ((yl.content_size + yl.space_after - bw) if mirrored else -yl.space_before), mult) v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, xl.content_pos + xl.content_size + xl.space_after - bw, 1) 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 { 'main_bias': self.main_bias, 'biased_map': self.biased_map } def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: self.main_bias = layout_state['main_bias'] self.biased_map = {int(k): v for k, v in layout_state['biased_map'].items()} self.layout_opts = TallLayoutOpts(layout_state['opts']) return True def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, ) -> WindowResizeDragData: return drag_resize_target_windows(click_window, edges, x, y, self.num_full_size_windows, all_windows, self.main_is_horizontal) class Fat(Tall): name = 'fat' main_is_horizontal = False main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout ================================================ FILE: kitty/layout/vertical.py ================================================ #!/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, NeighborsMap, WindowMapper from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutData, LayoutDimension, 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, -wg.active_window_id, not is_horizontal)) borders.append(BorderLine(e2, color, wg.active_window_id, not is_horizontal)) 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: after = [groups[(idx - 1 + lg) % lg].id] before = [groups[(idx + 1) % lg].id] else: before, after = [], [] ans: NeighborsMap = {} akey: EdgeLiteral = 'top' bkey: EdgeLiteral = 'bottom' if self.main_is_horizontal: akey, bkey = 'left', 'right' if before: ans[bkey] = before if after: ans[akey] = after return ans def layout_state(self) -> dict[str, Any]: return {'biased_map': self.biased_map} def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: self.biased_map = {int(k): v for k, v in layout_state['biased_map'].items()} return True class Horizontal(Vertical): name = 'horizontal' main_is_horizontal = True main_axis_layout = Layout.xlayout perp_axis_layout = Layout.ylayout ================================================ FILE: kitty/line-buf.c ================================================ /* * 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); } ================================================ FILE: kitty/line-buf.h ================================================ /* * 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*); ================================================ FILE: kitty/line.c ================================================ /* * 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 index_type text_in_cell_ansi(ANSILineState *s, const CPUCell *c, TextCache *tc, bool skip_multiline_non_zero_lines) { index_type num_cells_to_skip_for_tab = 0; if (c->is_multicell) { if (c->x || (skip_multiline_non_zero_lines && c->y)) return num_cells_to_skip_for_tab; 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': { index_type n = s->output_buf->len - pos; if (n > 1) { num_cells_to_skip_for_tab = s->output_buf->buf[s->output_buf->len - n + 1]; s->output_buf->len -= n - 1; } } break; } } return num_cells_to_skip_for_tab; } 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) { if (s->output_buf->active_hyperlink_id && start_at == 0 && !self->cpu_cells[0].hyperlink_id) write_hyperlink_to_ansi_buf(s, 0); 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); } index_type num_cells_to_skip_for_tab = text_in_cell_ansi( s, self->cpu_cells + s->pos, self->text_cache, skip_multiline_non_zero_lines); s->prev_gpu_cell = cell; const CPUCell *next = self->cpu_cells + s->pos + 1; while (num_cells_to_skip_for_tab && s->pos + 1 < s->limit && cell_is_char(next, ' ')) { num_cells_to_skip_for_tab--; s->pos++; next++; } } close_multicell(s); if (s->output_buf->active_hyperlink_id && s->limit < self->xnum && !self->cpu_cells[s->limit].hyperlink_id) write_hyperlink_to_ansi_buf(s, 0); 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->sgr.fg & COL_MASK), bg = cursor->sgr.bg & COL_MASK; color_type dfg = cursor->sgr.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->sgr.fg = self->gpu_cells[x].fg; ans->sgr.bg = self->gpu_cells[x].bg; ans->sgr.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->sgr.fg & COL_MASK; g->bg = cursor->sgr.bg & COL_MASK; g->decoration_fg = cursor->sgr.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 (CA.blink != PA.blink) P(CA.blink ? "5;" : "25;"); 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, x + 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; } ================================================ FILE: kitty/line.h ================================================ /* * 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 blink: 1; uint16_t mark : 2; uint32_t : 21; }; 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 { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ char_type ch_or_idx: sizeof(char_type) * 8 - 1; char_type ch_is_idx: 1; #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ char_type ch_is_idx: 1; char_type ch_or_idx: sizeof(char_type) * 8 - 1; #else #error "Unsupported endianness" #endif 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->sgr.decoration, .bold=c->sgr.bold, .italic=c->sgr.italic, .reverse=c->sgr.reverse, .strike=c->sgr.strikethrough, .dim=c->sgr.dim, .blink=c->sgr.blink}; return ans; } static inline void attrs_to_cursor(const CellAttrs attrs, Cursor *c) { c->sgr.decoration = attrs.decoration; c->sgr.bold = attrs.bold; c->sgr.italic = attrs.italic; c->sgr.reverse = attrs.reverse; c->sgr.strikethrough = attrs.strike; c->sgr.dim = attrs.dim; c->sgr.blink = attrs.blink; } #define cursor_as_gpu_cell(cursor) {.attrs=cursor_to_attrs(cursor), .fg=(cursor->sgr.fg & COL_MASK), .bg=(cursor->sgr.bg & COL_MASK), .decoration_fg=cursor->sgr.decoration_fg & COL_MASK} ================================================ FILE: kitty/linear2srgb.glsl ================================================ float 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)); } vec3 linear2srgb(vec3 x) { vec3 lower = 12.92 * x; vec3 upper = 1.055 * pow(x, vec3(1.0f / 2.4f)) - 0.055f; return mix(lower, upper, step(0.0031308f, x)); } vec3 srgb2linear(vec3 c) { return vec3(srgb2linear(c.r), srgb2linear(c.g), srgb2linear(c.b)); } ================================================ FILE: kitty/lineops.h ================================================ /* * 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); ================================================ FILE: kitty/logging.c ================================================ /* * 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(unsigned char, arena, calloc(size, sizeof(char))); if (!arena) return; va_start(ar, fmt); n = vsnprintf((char*)arena, size, fmt, ar); va_end(ar); unsigned char *sanbuf = arena + n + 1; char utf8buf[4]; START_ALLOW_CASE_RANGE size_t j = 0; for (unsigned char *x = arena; x < arena + n; x++) { switch(*x) { case C0_EXCEPT_NL_SPACE_TAB_DEL: { 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; case 0x7f: sanbuf[j++] = 0xe2; sanbuf[j++] = 0x90; sanbuf[j++] = 0xa1; // U+2421 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())); } // To see os_log messages from kitty, use: // log show --predicate 'processImagePath contains "kitty" and messageType == error' #ifdef __APPLE__ if (use_os_log) os_log_error(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; return true; } ================================================ FILE: kitty/loop-utils.c ================================================ /* * 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; } ================================================ FILE: kitty/loop-utils.h ================================================ /* * 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; } } ================================================ FILE: kitty/macos_process_info.c ================================================ /* * 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); } static PyObject* abspath_of_process(PyObject *self UNUSED, PyObject *pid_) { if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); return NULL; } pid_t pid = PyLong_AsLong(pid_); if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); return NULL; } char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } else if (ret == 0) { errno = EINVAL; PyErr_SetFromErrno(PyExc_OSError); return NULL; } return PyUnicode_FromString(pathbuf); } // 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, ""}, {"abspath_of_process", (PyCFunction)abspath_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; } ================================================ FILE: kitty/main.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import json import locale import os import shutil import sys from collections.abc import Generator, Sequence from contextlib import contextmanager, suppress from gettext import gettext as _ 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, glfw_path, is_macos, is_quick_access_terminal_app, is_wayland, kitten_exe, kitty_exe, launched_by_launch_services, 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_get_monitor_names, glfw_get_monitor_workarea, glfw_init, glfw_terminate, grab_keyboard, is_layer_shell_supported, load_png_data, mask_kitty_signals_process_wide, run_at_exit_cleanup_functions, set_custom_cursor, set_default_window_icon, set_options, set_use_os_log, ) 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, expandvars, get_custom_window_icon, log_error, parse_os_window_state, read_shell_environment, safe_mtime, 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() -> None: try: load_shader_programs() 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', 'macos_cycle_through_os_windows', 'macos_cycle_through_os_windows_backwards', 'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit', 'search_scrollback'): 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, 'clear_terminal last_command active', lookup_name='clear_last_command') if val is not None: global_shortcuts['clear_last_command'] = 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 _is_panel_kitten = False def is_panel_kitten() -> bool: return _is_panel_kitten def list_monitors(json_output: bool = False) -> None: monitor_names = glfw_get_monitor_names() has_descriptions = False for (name, desc) in monitor_names: if desc: has_descriptions = True break if json_output: if has_descriptions: monitors_list_of_dict = [{'name': name, 'description': desc} for name, desc in monitor_names] else: monitors_list_of_dict = [{'name': name} for name, _ in monitor_names] json.dump(monitors_list_of_dict, sys.stdout, indent=2, sort_keys=True) print() return isatty = sys.stdout.isatty() for (name, desc) in monitor_names: if isatty: name = f'\x1b[32m{name}\x1b[39m' # ]] print(name) if desc: print(f'\t{desc}') if has_descriptions: print() def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None: global _is_panel_kitten _is_panel_kitten = run_app.cached_values_name == 'panel' if _is_panel_kitten and run_app.layer_shell_config and run_app.layer_shell_config.output_name in ('list', 'listjson'): list_monitors(run_app.layer_shell_config.output_name == 'listjson') return 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() if _is_panel_kitten and not is_layer_shell_supported(): raise SystemExit('Cannot create panels as the window manager/compositor does not support the necessary protocols') pos_x, pos_y = None, None if args.grab_keyboard: grab_keyboard(True) with cached_values_for(run_app.cached_values_name) as cached_values: if not _is_panel_kitten and not is_wayland(): if opts.remember_window_position: cached_workarea = tuple(tuple(x) for x in cached_values.get('monitor-workarea', ())) if cached_workarea and glfw_get_monitor_workarea() == tuple(cached_workarea): pos_x, pos_y = cached_values.get('window-pos', (None, None)) if args.position: pos_x, pos_y = map(int, args.position.lower().partition('x')[::2]) startup_session_error: tuple[Exception, str] | None = None try: startup_sessions = tuple(create_sessions(opts, args, default_session=opts.startup_session)) except Exception as e: startup_session_error = (e, (getattr(args, 'session', '') or opts.startup_session or '')) if getattr(args, 'session', ''): args.session = '' startup_sessions = tuple(create_sessions(opts, args)) 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, x=pos_x, y=pos_y) 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 = [] if startup_session_error: boss.show_error(_('The startup session was invalid'), _( 'Loading the start session file {0} failed, with error:\n{1}').format(startup_session_error[1], startup_session_error[0])) 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: if theme_colors.refresh(): theme_colors.patch_opts(opts, args.debug_rendering) set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback) 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 expand_listen_on(listen_on: str, from_config_file: bool, env: dict[str, str]) -> str: if from_config_file and listen_on == 'none': return '' listen_on = expandvars(listen_on, env) 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 vars := opts.env.pop('read_from_shell', ''): import fnmatch import re senv = read_shell_environment(opts) patterns = tuple(re.compile(fnmatch.translate(x.strip())) for x in vars.split() if x.strip()) if patterns: for k, v in senv.items(): for pat in patterns: if pat.match(k) is not None: opts.env[k] = v break if cli_opts.listen_on: cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file, opts.env) 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 kitty_main(called_from_panel: bool = False) -> None: running_in_kitty(True) args = sys.argv[1:] try: cwd_ok = os.path.isdir(os.getcwd()) except Exception: cwd_ok = False if not cwd_ok: os.chdir(os.path.expanduser('~')) cli_flags = None 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: if not called_from_panel: cli_flags = getattr(sys, 'kitty_run_data', {}).get('cli_flags', None) usage = msg = appname = None cli_opts, rest = parse_args(args=args, result_class=CLIOptions, usage=usage, message=msg, appname=appname, preparsed_from_c=cli_flags) if getattr(sys, 'cmdline_args_for_open', False): setattr(sys, 'cmdline_args_for_open', rest) cli_opts.args = [] else: cli_opts.args = rest talk_fd = -1 if cli_opts.single_instance: si_data = os.environ.pop('KITTY_SI_DATA', '') if si_data: talk_fd = int(si_data) if cli_opts.detach: if cli_opts.session == '-': from .session import PreReadSession cli_opts.session = PreReadSession(sys.stdin.read(), os.environ, '-', os.path.join(os.getcwd(), '-')) if cli_opts.replay_commands: from kitty.client import main as client_main client_main(cli_opts.replay_commands) return bad_lines: list[BadLine] = [] opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines) if is_quick_access_terminal_app: opts.macos_hide_from_tasks = True 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(called_from_panel: bool = False) -> None: global redirected_for_quick_access try: if is_macos and launched_by_launch_services and not called_from_panel: with suppress(OSError): os.chdir(os.path.expanduser('~')) if is_quick_access_terminal_app: # we were started by launch services, use the kitten to read # the config and re-run os.execl(kitten_exe(), kitten_exe(), 'quick-access-terminal') set_use_os_log(True) kitty_main(called_from_panel) 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() ================================================ FILE: kitty/marks.py ================================================ #!/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}') ================================================ FILE: kitty/modes.h ================================================ /* * 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) // Paste events mode https://rockorager.dev/misc/bracketed-paste-mime/ #define PASTE_EVENTS (5522 << 5) // Handle Ctrl-C/Ctrl-Z mode #define HANDLE_TERMIOS_SIGNALS (19997 << 5) ================================================ FILE: kitty/monotonic.c ================================================ /* * 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" ================================================ FILE: kitty/monotonic.h ================================================ /* * 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 ================================================ FILE: kitty/mouse.c ================================================ /* * mouse.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "screen.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, LEAVE } 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) #define LEAVE_INDICATOR (1 << 8) 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); switch (action) { case MOVE: if (cb == UINT_MAX) cb = 3; cb += 32; break; case LEAVE: if (mouse_tracking_protocol != SGR_PIXEL_PROTOCOL) return 0; cb = LEAVE_INDICATOR | MOTION_INDICATOR; break; default: if (cb == UINT_MAX) return 0; break; } 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 void update_scrollbar_hover_state(Window *w, bool hovering) { if (!w) return; bool changed = w->scrollbar.is_hovering != hovering; w->scrollbar.is_hovering = hovering; if (changed && global_state.callback_os_window) { global_state.callback_os_window->needs_render = true; request_tick_callback(); } } static void set_currently_hovered_window(id_type window_id, int modifiers) { if (global_state.mouse_hover_in_window != window_id) { Window *left_window = window_for_id(global_state.mouse_hover_in_window); global_state.mouse_hover_in_window = window_id; if (left_window) { if (left_window->scrollbar.is_hovering) update_scrollbar_hover_state(left_window, false); if (left_window->render_data.screen) screen_mark_url(left_window->render_data.screen, 0, 0, 0, 0); int sz = encode_mouse_event(left_window, 0, LEAVE, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(left_window->render_data.screen, ESC_CSI, mouse_event_buf); debug("Sent mouse leave event to window: %llu\n", left_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->render_data.geometry.left - w->padding.left; } static unsigned int window_right(Window *w) { return w->render_data.geometry.right + w->padding.right; } static unsigned int window_top(Window *w) { return w->render_data.geometry.top - w->padding.top; } static unsigned int window_bottom(Window *w) { return w->render_data.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 bool border_contains_mouse(BorderRect *br, double tolerance, Edge *edges) { bool ans = false; double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; if ((int)br->px.left - tolerance <= x && x < (int)br->px.right + tolerance && (int)br->px.top - tolerance <= y && y < (int)br->px.bottom + tolerance) { ans = true; if (!br->horizontal) *edges |= br->border_type < 0 ? LEFT_EDGE : RIGHT_EDGE; else *edges |= br->border_type < 0 ? TOP_EDGE : BOTTOM_EDGE; } return ans; } 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->render_data.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 - screen->pixel_scroll_offset_y) / 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->render_data.geometry.top + margin); if (upwards || y >= w->render_data.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; } // Scrollbar {{{ typedef enum { SCROLLBAR_HIT_NONE, SCROLLBAR_HIT_TRACK, SCROLLBAR_HIT_THUMB } ScrollbarHitType; typedef struct { double left, right, top, bottom; double width, gap, hitbox_expansion; } ScrollbarGeometry; static bool validate_scrollbar_state(const Window *w) { return w && w->render_data.screen && w->render_data.screen->historybuf && w->render_data.screen->historybuf->count > 0; } static ScrollbarGeometry calculate_scrollbar_geometry(const Window *w) { ScrollbarGeometry geom = {0}; if (!w || !w->render_data.screen) return geom; const WindowGeometry *g = &w->render_data.geometry; unsigned cell_width = w->render_data.screen->cell_size.width; geom.width = (double)OPT(scrollbar_width) * cell_width; if (w->scrollbar.is_hovering) geom.width = (double)OPT(scrollbar_hover_width) * cell_width; geom.gap = (double)OPT(scrollbar_gap) * cell_width; geom.hitbox_expansion = (double)OPT(scrollbar_hitbox_expansion) * cell_width; double right_edge = g->right + g->spaces.right; geom.left = right_edge - geom.gap - geom.width - geom.hitbox_expansion; geom.right = right_edge + geom.gap; geom.top = g->top - g->spaces.top; geom.bottom = g->bottom + g->spaces.bottom; return geom; } static ScrollbarHitType get_scrollbar_hit_type(const Window *w, double mouse_x, double mouse_y) { if (!w || !validate_scrollbar_state(w)) return SCROLLBAR_HIT_NONE; ScrollbarGeometry geom = calculate_scrollbar_geometry(w); if (mouse_x < geom.left || mouse_x > geom.right || mouse_y < geom.top || mouse_y > geom.bottom) { return SCROLLBAR_HIT_NONE; } OSWindow *os_window = global_state.callback_os_window; if (!os_window) return SCROLLBAR_HIT_TRACK; double mouse_window_fraction = mouse_y / os_window->viewport_height; unsigned cell_width = w->render_data.screen->cell_size.width; double hitbox_expansion_fraction = (double)(OPT(scrollbar_hitbox_expansion) * cell_width) / os_window->viewport_height; if (mouse_window_fraction >= (w->scrollbar.thumb_top - hitbox_expansion_fraction) && mouse_window_fraction <= (w->scrollbar.thumb_bottom + hitbox_expansion_fraction)) { return SCROLLBAR_HIT_THUMB; } return SCROLLBAR_HIT_TRACK; } static void handle_scrollbar_track_click(Window *w, double mouse_y) { if (!w) return; Screen *screen = w->render_data.screen; if (!validate_scrollbar_state(w)) return; if (OPT(scrollbar_jump_on_click)) { ScrollbarGeometry geom = calculate_scrollbar_geometry(w); double scrollbar_height = geom.bottom - geom.top; double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; double target_scrolled_by = screen->historybuf->count * (1.0 - mouse_pane_fraction); screen_history_scroll_to_absolute(screen, target_scrolled_by); } else { OSWindow *os_window = global_state.callback_os_window; if (!os_window) return; double mouse_window_fraction = mouse_y / os_window->viewport_height; bool click_above_thumb = mouse_window_fraction < w->scrollbar.thumb_top; screen_history_scroll(screen, SCROLL_PAGE, click_above_thumb); } } 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; w->scrollbar.is_dragging = false; if (global_state.callback_os_window && get_scrollbar_hit_type(w, global_state.callback_os_window->mouse_x, global_state.callback_os_window->mouse_y ) == SCROLLBAR_HIT_NONE) { mouse_cursor_shape = TEXT_POINTER; set_mouse_cursor(mouse_cursor_shape); } 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}); } } static void start_scrollbar_drag(Window *w, double mouse_y) { if (!w) return; Screen *screen = w->render_data.screen; if (!validate_scrollbar_state(w)) return; ScrollbarGeometry geom = calculate_scrollbar_geometry(w); double scrollbar_height = geom.bottom - geom.top; double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; w->scrollbar.is_dragging = true; w->scrollbar.drag_start_y = mouse_pane_fraction; w->scrollbar.drag_start_scrolled_by = screen->scrolled_by; } static void handle_scrollbar_drag(Window *w, double mouse_y) { if (!w || !w->scrollbar.is_dragging || !validate_scrollbar_state(w)) return; Screen *screen = w->render_data.screen; ScrollbarGeometry geom = calculate_scrollbar_geometry(w); double scrollbar_height = geom.bottom - geom.top; double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; double delta_y = mouse_pane_fraction - w->scrollbar.drag_start_y; double visible_fraction = (double)screen->lines / (screen->lines + screen->historybuf->count); unsigned cell_height = screen->cell_size.height; double min_thumb_height_fraction = ((double)OPT(scrollbar_min_handle_height) * cell_height) / scrollbar_height; double thumb_height = MAX(min_thumb_height_fraction, visible_fraction); double available_space = 1.0 - thumb_height; if (available_space > 0) { double scroll_fraction = delta_y / available_space; double target = w->scrollbar.drag_start_scrolled_by - scroll_fraction * screen->historybuf->count; double new_scrolled_by; if (target < 0) new_scrolled_by = 0; else if (target > screen->historybuf->count) new_scrolled_by = screen->historybuf->count; else new_scrolled_by = target; screen_history_scroll_to_absolute(screen, new_scrolled_by); } } static const MouseShape scrollbar_drag_mouse_cursor = NS_RESIZE_POINTER; static bool handle_scrollbar_mouse(Window *w, int button, MouseAction action, int modifiers UNUSED) { if (!w || !OPT(scrollbar_interactive) || !global_state.callback_os_window) return false; double mouse_x = global_state.callback_os_window->mouse_x; double mouse_y = global_state.callback_os_window->mouse_y; if (action == MOVE && w->scrollbar.is_dragging) { handle_scrollbar_drag(w, mouse_y); mouse_cursor_shape = scrollbar_drag_mouse_cursor; set_mouse_cursor(mouse_cursor_shape); return true; } if (global_state.active_drag_in_window == w->id || global_state.tracked_drag_in_window == w->id) return false; ScrollbarHitType hit_type = get_scrollbar_hit_type(w, mouse_x, mouse_y); bool hovering = (hit_type != SCROLLBAR_HIT_NONE); update_scrollbar_hover_state(w, hovering); if (!hovering) return false; mouse_cursor_shape = DEFAULT_POINTER; set_mouse_cursor(mouse_cursor_shape); if (button == GLFW_MOUSE_BUTTON_LEFT && action != MOVE) { bool is_release = (action == RELEASE); if (is_release) { if (w->scrollbar.is_dragging) { end_drag(w); } else if (hit_type == SCROLLBAR_HIT_TRACK) { handle_scrollbar_track_click(w, mouse_y); } } else { if (hit_type == SCROLLBAR_HIT_THUMB) { start_scrollbar_drag(w, mouse_y); global_state.active_drag_in_window = w->id; global_state.active_drag_button = button; } } } return true; } // }}} HANDLER(handle_move_event) { modifiers &= ~GLFW_LOCK_MASK; if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) return; 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_in_active_tab, "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)) { if (w->scrollbar.is_hovering) { update_scrollbar_hover_state(w, false); } 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; bool is_relative; if (screen_prompt_supports_click_events(screen, &is_relative)) { MousePosition mpos = w->mouse_pos; if (is_relative) mpos.cell_y -= y; int sz = encode_mouse_event_impl(&mpos, 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; OSWindow *osw = global_state.callback_os_window; if (!osw) return; Tab *t = osw->tabs + osw->active_tab; bool is_release = !osw->mouse_button_pressed[button]; if (button == GLFW_MOUSE_BUTTON_LEFT && osw->suppress_left_mouse_release) { osw->suppress_left_mouse_release = false; if (is_release) return; } if (handle_scrollbar_mouse(w, button, is_release ? RELEASE : PRESS, modifiers)) return; bool suppress_child_forwarding = false; if (osw->is_focused && window_idx != t->active_window && !is_release) { call_boss(switch_focus_to_in_active_tab, "K", t->windows[window_idx].id); if (button == GLFW_MOUSE_BUTTON_LEFT) { // Treat split-focus transfer clicks as focus-only for child processes: // suppress forwarding the left press and matching release to the child // to avoid release-without-press reports. Still allow kitty to process // the event internally (e.g., start text selection via click-and-drag). osw->suppress_left_mouse_release = true; suppress_child_forwarding = true; } } 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 (!suppress_child_forwarding && 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); set_currently_hovered_window(w->id, modifiers); 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_window_title_bar_mouse(Window *w, int button, int modifiers, int action) { OSWindow *osw = global_state.callback_os_window; if (osw && button > -1) { call_boss(handle_window_title_bar_mouse, "KKiii", osw->id, w->id, button, modifiers, action); } } static void handle_tab_bar_mouse(int button, int modifiers, int action) { set_currently_hovered_window(0, modifiers); OSWindow *w = global_state.callback_os_window; // dont report motion events, as they are expensive and useless if (w && (button > -1 || global_state.tab_being_dragged.id)) { call_boss(handle_tab_bar_mouse, "Kddiii", w->id, w->mouse_x, w->mouse_y, 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 unsigned num_visible_windows(Tab *t) { unsigned ans = t->num_windows; for (unsigned i = 0; i < t->num_windows; i++) if (!t->windows[i].visible) ans--; return ans; } typedef struct MouseRegion { unsigned window_idx; bool in_tab_bar; bool in_title_bar; Edge window_border; Window *window; } MouseRegion; static MouseRegion mouse_region(bool detect_borders, bool detect_title_bar) { MouseRegion ans = {0}; Region central, tab_bar; const OSWindow* w = global_state.callback_os_window; os_window_regions(w, ¢ral, &tab_bar); const bool in_central = mouse_in_region(¢ral); if (!in_central) { if ( (tab_bar.top < central.top && w->mouse_y < central.top) || (tab_bar.bottom > central.bottom && w->mouse_y >= central.bottom) ) ans.in_tab_bar = true; } if (in_central && w->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; if (detect_borders && num_visible_windows(t) > 1) { id_type window_id = 0; double dpi = (w->fonts_data->logical_dpi_x + w->fonts_data->logical_dpi_y) / 2.; double tolerance = ((long)round((OPT(window_drag_tolerance) * (dpi / 72.0)))); BorderRect *closest_vert = NULL, *closest_horiz = NULL; double closest_vert_dist = (double)UINT_MAX, closest_horiz_dist = (double)UINT_MAX; bool is_within_border_without_tolerance = false; for (unsigned i = 0; i < t->border_rects.num_border_rects; i++) { BorderRect *br = t->border_rects.rect_buf + i; if (!br->border_type) continue; Edge edges = 0; if (border_contains_mouse(br, 0, &edges)) { ans.window_border |= edges; is_within_border_without_tolerance = true; if (edges & (LEFT_EDGE | RIGHT_EDGE)) { closest_vert_dist = -1; closest_vert = NULL; } else { closest_horiz_dist = -1; closest_horiz = NULL; } window_id = br->border_type < 0 ? -br->border_type : br->border_type; } else if (border_contains_mouse(br, tolerance, &edges)) { unsigned width = br->px.right - br->px.left, height = br->px.bottom - br->px.top; if (!br->horizontal) { double d = br->px.left + width/2. - w->mouse_x; d = d*d; if (d < closest_vert_dist) { closest_vert_dist = d; closest_vert = br; } } else { double d = br->px.top + height/2. - w->mouse_y; d = d*d; if (d < closest_horiz_dist) { closest_horiz_dist = d; closest_horiz = br; } } } } if (!is_within_border_without_tolerance) { if (closest_vert && border_contains_mouse(closest_vert, tolerance, &ans.window_border) && !window_id) window_id = closest_vert->border_type < 0 ? -closest_vert->border_type : closest_vert->border_type; if (closest_horiz && border_contains_mouse(closest_horiz, tolerance, &ans.window_border) && !window_id) window_id = closest_horiz->border_type < 0 ? -closest_horiz->border_type : closest_horiz->border_type; } if (ans.window_border) { if (window_id) { for (unsigned int i = 0; i < t->num_windows; i++) if (t->windows[i].id == window_id) { ans.window = t->windows + i; ans.window_idx = i; break; } } return ans; } } for (unsigned int i = 0; i < t->num_windows; i++) { Window *win = t->windows + i; if (contains_mouse(win) && win->render_data.screen) { ans.window_idx = i; ans.window = win; break; } else if (detect_title_bar && win->visible) { const WindowRenderData *trd = &win->window_title_render_data; if (trd->screen && trd->geometry.right > trd->geometry.left && trd->geometry.bottom > trd->geometry.top) { if (w->mouse_x >= trd->geometry.left && w->mouse_x < trd->geometry.right && w->mouse_y >= trd->geometry.top && w->mouse_y < trd->geometry.bottom) { ans.in_title_bar = true; ans.window = win; ans.window_idx = i; break; } } } } } return ans; } 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 mouse_cursor_shape = TEXT_POINTER; MouseRegion r = mouse_region(false, false); if (r.window && r.window->render_data.screen) { screen_mark_url(r.window->render_data.screen, 0, 0, 0, 0); set_mouse_cursor_for_screen(r.window->render_data.screen); } set_mouse_cursor(mouse_cursor_shape); } void update_mouse_pointer_shape(void) { mouse_cursor_shape = TEXT_POINTER; MouseRegion r = mouse_region(false, true); if (r.in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; } else if (r.in_title_bar) { mouse_cursor_shape = POINTER_POINTER; } else if (r.window) { if (handle_scrollbar_mouse(r.window, -1, MOVE, 0)) { mouse_cursor_shape = scrollbar_drag_mouse_cursor; } else if (r.window->render_data.screen) { screen_mark_url(r.window->render_data.screen, 0, 0, 0, 0); set_mouse_cursor_for_screen(r.window->render_data.screen); } } set_mouse_cursor(mouse_cursor_shape); } void leave_event(int modifiers) { if (global_state.redirect_mouse_handling || global_state.active_drag_in_window || global_state.tracked_drag_in_window) return; set_currently_hovered_window(0, modifiers); } 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; MouseRegion r = mouse_region(false, false); Window *w = r.window; set_currently_hovered_window(w ? w->id : 0, modifiers); if (!w || r.in_tab_bar || r.in_title_bar) return; if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) return; bool mouse_cell_changed = false, 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); } } 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, MOUSE_SELECTION_UPTO_SURROUNDING_WHITESPACE, } 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; case MOUSE_SELECTION_UPTO_SURROUNDING_WHITESPACE: // TODO: Implement me for people migrating from urxvt break; } set_mouse_cursor_when_dragging(screen); #undef S } static const char* border_name(int edges) { switch(edges) { case 0: return "none"; case LEFT_EDGE: return "left"; case RIGHT_EDGE: return "right"; case TOP_EDGE: return "top"; case BOTTOM_EDGE: return "bottom"; case LEFT_EDGE | TOP_EDGE: return "top-left"; case LEFT_EDGE | BOTTOM_EDGE: return "bottom-left"; case RIGHT_EDGE | TOP_EDGE: return "top-right"; case RIGHT_EDGE | BOTTOM_EDGE: return "bottom-right"; } return "unknown"; } void mouse_event(const int button, int modifiers, int action) { MouseShape old_cursor = mouse_cursor_shape; unsigned int window_idx = 0; Window *w = NULL; OSWindow *osw = global_state.callback_os_window; if (OPT(debug_keyboard)) { if (button < 0) { debug("%s x: %.1f y: %.1f ", "\x1b[36mMove\x1b[m", osw->mouse_x, osw->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) { MouseRegion r= mouse_region(false, false); w = r.window; call_boss(mouse_event, "OK iiii dd", (r.in_tab_bar ? Py_True : Py_False), (w ? w->id : 0), action, modifiers, button, currently_pressed_button(), osw->mouse_x, osw->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 = osw->tabs + osw->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); // Clear any stale suppress flag that was set during a focus-transfer // press, since the drag release bypasses handle_button_event where // it would normally be cleared. if (osw) osw->suppress_left_mouse_release = false; 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 = osw->tabs + osw->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 = osw->tabs + osw->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; } } } if (global_state.active_drag_resize) { if (button < 0) { call_boss(drag_resize_update, "dd", osw->mouse_x, osw->mouse_y); debug("drag resize updated\n"); } else if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) { call_boss(drag_resize_end, ""); global_state.active_drag_resize = 0; mouse_cursor_shape = DEFAULT_POINTER; set_mouse_cursor(mouse_cursor_shape); debug("drag resize ended\n"); } return; } MouseRegion r = mouse_region(true, true); w = r.window; window_idx = r.window_idx; set_currently_hovered_window(w && !r.window_border && !r.in_title_bar ? w->id : 0, modifiers); if (r.in_tab_bar || global_state.tab_being_dragged.id) { mouse_cursor_shape = POINTER_POINTER; handle_tab_bar_mouse(button, modifiers, action); debug("handled by tab bar\n"); } else if (r.in_title_bar && r.window) { mouse_cursor_shape = POINTER_POINTER; handle_window_title_bar_mouse(r.window, button, modifiers, action); debug("handled by window title bar\n"); } else if (r.window_border) { debug("window border: %s window id: %llu\n", border_name(r.window_border), w ? w->id : 0); if (r.window_border & LEFT_EDGE) { if (r.window_border & TOP_EDGE) mouse_cursor_shape = NWSE_RESIZE_POINTER; else if (r.window_border & BOTTOM_EDGE) mouse_cursor_shape = NESW_RESIZE_POINTER; else mouse_cursor_shape = EW_RESIZE_POINTER; } else if (r.window_border & RIGHT_EDGE) { if (r.window_border & TOP_EDGE) mouse_cursor_shape = NESW_RESIZE_POINTER; else if (r.window_border & BOTTOM_EDGE) mouse_cursor_shape = NWSE_RESIZE_POINTER; else mouse_cursor_shape = EW_RESIZE_POINTER; } else if (r.window_border & (TOP_EDGE | BOTTOM_EDGE)) mouse_cursor_shape = NS_RESIZE_POINTER; if (w && button == GLFW_MOUSE_BUTTON_LEFT && w->render_data.screen) { RAII_PyObject(retval, PyObject_CallMethod( global_state.boss, "drag_resize_start", "iddKII", r.window_border, osw->mouse_x, osw->mouse_y, w->id, w->render_data.screen->cell_size.width, w->render_data.screen->cell_size.height)); if (retval == NULL) { PyErr_Print(); return; } if (PyObject_IsTrue(retval)) global_state.active_drag_resize = w->id; } } 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 && osw->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 { mouse_cursor_shape = DEFAULT_POINTER; debug("\n"); } if (mouse_cursor_shape != old_cursor) set_mouse_cursor(mouse_cursor_shape); } static int scale_scroll(MouseTrackingMode mouse_tracking_mode, double offset, GLFWOffsetType offset_type, 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; switch (offset_type) { case GLFW_SCROLL_OFFEST_HIGHRES: { 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; } break; case GLFW_SCROLL_OFFEST_V120: { SCALE_SCROLL(wheel_scroll_multiplier); const double offset_lines = offset / 120.; const double pixels = *pending_scroll_pixels + offset_lines * cell_size; if (fabs(pixels) < cell_size) { *pending_scroll_pixels = pixels; return 0; } s = (int)round(pixels) / cell_size; *pending_scroll_pixels = pixels - s * cell_size; } break; case GLFW_SCROLL_OFFSET_LINES: { 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; } break; } return s; #undef SCALE_SCROLL } static const char* scroll_offset_type(GLFWOffsetType t) { switch(t) { case GLFW_SCROLL_OFFSET_LINES: return "lines"; case GLFW_SCROLL_OFFEST_V120: return "v120"; case GLFW_SCROLL_OFFEST_HIGHRES: return "highres"; } return ""; } static const char* scroll_phase(GLFWMomentumType t) { switch(t) { case GLFW_NO_MOMENTUM_DATA: return "none"; case GLFW_MOMENTUM_PHASE_MAY_BEGIN: return "may_begin"; case GLFW_MOMENTUM_PHASE_BEGAN: return "began"; case GLFW_MOMENTUM_PHASE_ACTIVE: return "active"; case GLFW_MOMENTUM_PHASE_STATIONARY: return "stationary"; case GLFW_MOMENTUM_PHASE_CANCELED: return "cancelled"; case GLFW_MOMENTUM_PHASE_ENDED: return "ended"; } return ""; } static inline bool pixel_scroll_enabled_for_screen(const Screen *screen) { return OPT(pixel_scroll) && screen->linebuf == screen->main_linebuf; } void scroll_event(const GLFWScrollEvent *ev) { debug("\x1b[36mScroll\x1b[m type=%s x: %f y: %f momentum: %s modifiers: %s\n", scroll_offset_type(ev->offset_type), ev->x_offset, ev->y_offset, scroll_phase(ev->momentum_type), format_mods(ev->keyboard_modifiers)); static id_type window_for_momentum_scroll = 0; static bool main_screen_for_momentum_scroll = false; // 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; } MouseRegion r = mouse_region(false, true); Window *w = r.window; if (!w && !r.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; switch(ev->momentum_type) { case GLFW_NO_MOMENTUM_DATA: break; case GLFW_MOMENTUM_PHASE_BEGAN: window_for_momentum_scroll = w->id; main_screen_for_momentum_scroll = screen->linebuf == screen->main_linebuf; break; case GLFW_MOMENTUM_PHASE_STATIONARY: case GLFW_MOMENTUM_PHASE_ACTIVE: if (window_for_momentum_scroll != w->id || main_screen_for_momentum_scroll != (screen->linebuf == screen->main_linebuf)) return; break; case GLFW_MOMENTUM_PHASE_ENDED: case GLFW_MOMENTUM_PHASE_CANCELED: window_for_momentum_scroll = 0; break; case GLFW_MOMENTUM_PHASE_MAY_BEGIN: break; } if (ev->y_offset != 0.0) { if (screen->modes.mouse_tracking_mode == NO_TRACKING && pixel_scroll_enabled_for_screen(screen) && (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES || ev->offset_type == GLFW_SCROLL_OFFEST_V120)) { double delta_pixels; if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) { delta_pixels = ev->y_offset * OPT(touch_scroll_multiplier); } else { const double offset_lines = (ev->y_offset / 120.) * OPT(wheel_scroll_multiplier); delta_pixels = offset_lines * global_state.callback_os_window->fonts_data->fcm.cell_height; } screen->pending_scroll_pixels_y = 0.0; if (screen_apply_pixel_scroll(screen, delta_pixels) && screen->selections.in_progress) update_drag(w); } else { int s = scale_scroll(screen->modes.mouse_tracking_mode, ev->y_offset, ev->offset_type, &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, ev->keyboard_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 (ev->x_offset != 0.0) { int s = scale_scroll(screen->modes.mouse_tracking_mode, ev->x_offset, ev->offset_type, &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, ev->keyboard_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); PyModule_AddIntMacro(module, MOUSE_SELECTION_UPTO_SURROUNDING_WHITESPACE); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } ================================================ FILE: kitty/multiprocessing.py ================================================ #!/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 import sys 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 collections.abc import Buffer from typing import SupportsIndex, SupportsInt if sys.version_info[:2] >= (3, 14): ArgsType = Sequence[Union[str, Buffer, SupportsInt, SupportsIndex]] else: 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() ================================================ FILE: kitty/notifications.py ================================================ #!/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_compat 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(f'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(f'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(f'Ignoring invalid notification type in notification: {v!r}') elif k == 'w': try: self.timeout = max(-1, int(v)) except Exception: self.log(f'Ignoring invalid timeout in notification: {v!r}') elif k == 's': try: self.sound_name = base64_decode(v).decode('utf-8') except Exception: self.log(f'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 ================================================ FILE: kitty/open_actions.py ================================================ #!/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_compat 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()) ================================================ FILE: kitty/options/__init__.py ================================================ ================================================ FILE: kitty/options/definition.py ================================================ #!/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') definition.add_deprecation('deprecated_scrollback_indicator_opacity', 'scrollback_indicator_opacity') 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_thickness 200% 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`. This setting also controls blinking text, which blinks in exact rhythm with the cursor. ''') 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. This setting also controls blinking text, which blinks in exact rhythm with the cursor. ''') 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. ''') opt('cursor_trail_color', 'none', option_type='to_color_or_none', ctype='!cursor_trail_color', long_text=''' Set the color of the cursor trail when :opt:`cursor_trail` is enabled. If set to 'none' (the default), the cursor trail will use the cursor's background color. Otherwise, specify a color value (e.g., #ff0000 for red, or a named color like 'red'). This allows you to customize the appearance of the cursor trail independently of the cursor color. ''') 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('scrollbar', 'scrolled', ctype='scrollbar', choices=( 'scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered'), long_text='''\ Control when the scrollbar is displayed. :code:`scrolled` means when the scrolling backwards has started. :code:`hovered` means when the mouse is hovering on the right edge of the window. :code:`scrolled-and-hovered` means when the mouse is over the scrollbar region *and* scrolling backwards has started. :code:`always` means whenever any scrollback is present :code:`never` means disable the scrollbar. ''') opt('scrollbar_interactive', 'yes', option_type='to_bool', ctype='bool', long_text=''' If disabled, the scrollbar will not be controllable via the mouse and all mouse events will pass through the scrollbar.''') opt('scrollbar_jump_on_click', 'yes', option_type='to_bool', ctype='bool', long_text=''' When enabled clicking in the scrollbar track will cause the scroll position to jump to the clicked location, otherwise the scroll position will only move towards the position by a single screenful, which is how traditional scrollbars behave.''') opt('scrollbar_width', '0.5', option_type='positive_float', ctype='float', long_text=''' The width of the scroll bar in units of cell width. ''') opt('scrollbar_hover_width', '1', option_type='positive_float', ctype='float', long_text=''' The width of the scroll bar when the mouse is hovering over it, in units of cell width. ''') opt('scrollbar_handle_opacity', '0.5', option_type='positive_float', ctype='float', long_text=''' The opacity of the scrollbar handle, 0 being fully transparent and 1 being full opaque. ''') opt('scrollbar_radius', '0.3', option_type='positive_float', ctype='float', long_text=''' The radius (curvature) of the scrollbar handle in units of cell width. Should be less than :opt:`scrollbar_width`. ''') opt('scrollbar_gap', '0.1', option_type='positive_float', ctype='float', long_text=''' The gap between the scrollbar and the window edge in units of cell width. ''') opt('scrollbar_min_handle_height', '1', option_type='positive_float', ctype='float', long_text=''' The minimum height of the scrollbar handle in units of cell height. Prevents the handle from becoming too small when there is a lot of scrollback.''') opt('scrollbar_hitbox_expansion', '0.25', option_type='positive_float', ctype='float', long_text=''' The extra area around the handle to allow easier grabbing of the scollbar in units of cell width.''') opt('scrollbar_track_opacity', '0', option_type='positive_float', ctype='float', long_text=''' The opacity of the scrollbar track, 0 being fully transparent and 1 being full opaque. ''') opt('scrollbar_track_hover_opacity', '0.1', option_type='positive_float', ctype='float', long_text=''' The opacity of the scrollbar track when the mouse is over the scrollbar, 0 being fully transparent and 1 being full opaque. ''') opt('scrollbar_handle_color', 'foreground', option_type='scrollbar_color', ctype='uint', long_text=''' The color of the scrollbar handle. A value of :code:`foreground` means to use the current foreground text color, a value of :code:`selection_background` means to use the current selection background color. Also, you can use an arbitrary color, such as :code:`#12af59` or :code:`red`. ''') opt('scrollbar_track_color', 'foreground', option_type='scrollbar_color', ctype='uint', long_text=''' The color of the scrollbar track. A value of :code:`foreground` means to use the current foreground text color, a value of :code:`selection_background` means to use the current selection background color. Also, you can use an arbitrary color, such as :code:`#12af59` or :code:`red`. ''') 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. If you would rather use neovim to view the scrollback, use something like this:: scrollback_pager nvim --cmd 'set eventignore=FileType' +'nnoremap q ZQ' +'call nvim_open_term(0, {})' +'set nomodified nolist' +'$' - The above works for neovim 0.12 and newer. There is also a dedicated plugin :link:`kitty-scrollback.nvim ` you can use with more features that works with older neovim as well. ''' ) 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. ''' ) opt('pixel_scroll', 'yes', option_type='to_bool', ctype='bool', long_text=''' Enable per-pixel scrolling, in the kitty scrollback buffer, when using high precision input devices (for example touchpads). When enabled, kitty's own scrollback will move by sub-line increments instead of only whole lines. This does not affect applications running inside the terminal (for example full-screen TUIs) that handle scrolling themselves. ''') opt('momentum_scroll', '0.96', option_type='unit_float', ctype='float', long_text=''' The amount of friction to apply to slow down momentum (inertial) scrolling. A number from 0 to 1, with 0 meaning no momentum scrolling and 1 meaning infinite scrolling. Note that this setting only applies on platforms such as Wayland, that do not provide native momentum scrolling. On macOS, the native OS based momentum scrolling is used. Also, momentum scrolling only applies to "finger" based devices such as touchpads and touchscreens. Changes to this setting only take effect after a kitty restart. ''') egr() # }}} # mouse {{{ agr('mouse', 'Mouse') opt('mouse_hide_wait', '3.0', macos_default='0.0', option_type='mouse_hide_wait', ctype='!mouse_hide_wait', 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. By default, once the cursor is hidden, it is immediately unhidden on any further mouse events. Two formats are supported: - :code:`` - :code:` ` To change the unhide behavior, the optional parameters :code:``, :code:``, and :code:`` may be set. :code:`` Waits for the specified number of seconds after mouse events before unhiding the mouse cursor. Set to zero to unhide mouse cursor immediately on mouse activity. This is useful to prevent the mouse cursor from unhiding on accidental swipes on the trackpad. :code:`` Sets the threshold of mouse activity required to unhide the mouse cursor, when the option is non-zero. When is zero, this has no effect. For example, if :code:`` is 40 and :code:`` is 2.5, when kitty detects a mouse event, it records the number of mouse events in the next 2.5 seconds, and checks if that exceeds 40 * 2.5 = 100. If it does, then the mouse cursor is unhidden, otherwise nothing happens. :code:`` Controls what mouse events may unhide the mouse cursor. If enabled, both scroll and movement events may unhide the cursor. If disabled, only mouse movements can unhide the cursor. Examples of valid values: - :code:`0.0` - :code:`1.0` - :code:`-1.0` - :code:`0.1 3.0 40 yes` ''' ) 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 or dropping 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('Extend the current selection with shift', 'extend_selection_shift shift+left 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 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('macos_dock_badge_on_bell', 'yes', option_type='to_bool', ctype='bool', long_text=''' Show a badge on kitty's dock icon when a bell occurs and kitty is not the active application (macOS only). The badge is automatically cleared when kitty regains focus. ''' ) 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). Relative paths are resolved with respect to the kitty config directory. ''' ) opt('linux_bell_theme', '__custom', ctype='!bell_theme', long_text=''' The XDG Sound Theme kitty will use to play the bell sound. On Wayland, when the compositor supports it, it is asked to play the system default bell sound, and this setting has no effect. Note that Hyprland claims to support this protocol, but :link:`does not actually play a sound `. This setting 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('remember_window_position', 'no', option_type='to_bool', long_text=''' If enabled, the :term:`OS Window ` position will be remembered so that new instances of kitty will have the same position as the previous instance. If disabled, the :term:`OS Window ` will be placed by the window manager. Note that remembering of position only works if the underlying desktop environment/window manager supports it. It never works on Wayland. See also :option:`kitty --position` to specify the position when launching kitty. ''' ) 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('draw_window_borders_for_single_window', 'no', option_type='to_bool', long_text=''' Draw borders around a window even when there is only a single window visible. When enabled and there is only a single window, full borders are drawn around it (as if :opt:`draw_minimal_borders` is false). The border will show in the active color when the window is focused and the OS window has focus, and in the inactive color when the OS window loses focus. This provides a clear visual indicator of whether the kitty window is focused. When there are multiple windows visible, this option has no effect and normal border drawing rules apply. ''' ) 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. On Wayland, :code:`titlebar-only` can be used to hide the titlebar while keeping the window shadow for resizing. On compositors that have server-side decorations (such as anything but GNOME), both :code:`yes` and :code:`titlebar-only` force client-side decoration mode. 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` on macOS, 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. ''') opt('window_drag_tolerance', '2', option_type='float', ctype='double', long_text=''' Control dragging window borders to resize kitty windows. This is the tolerance in pts for the region around window borders where pressing the left mouse button will start the dragging of window borders. Use a large negative value such as -200 to disable dragging of borders. Note that because kitty uses layouts, dragging borders does not actually resize the window itself, but instead, the layout row/column/slot, which can result in multiple windows getting resized. ''') opt('window_title_bar', 'top', choices=('top', 'bottom'), long_text=''' Control the position of the window title bar relative to the window content. Use :opt:`window_title_bar_min_windows` to control when title bars are shown. Use :opt:`window_title_template` to format the displayed window title. ''' ) opt('window_title_bar_min_windows', '0', option_type='positive_int', long_text=''' The minimum number of visible windows in a tab before window title bars are shown. A value of :code:`0` means never show. :code:`1` means always show. :code:`2` or more means show only when at least that many windows are visible. Similar to :opt:`tab_bar_min_tabs` for the tab bar. ''' ) opt('window_title_template', '"{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.window}{progress_percent}{title}"', option_type='tab_title_template', long_text=''' A template to render the window title bar text. Uses the same template syntax as :opt:`tab_title_template`. Available variables include: :code:`{title}`, :code:`{bell_symbol}`, :code:`{activity_symbol}`, :code:`{progress_percent}`, :code:`{custom}`, :code:`{fmt}`, :code:`{is_active}`. You can also provide a custom :code:`draw_window_title(data)` function in :file:`window_title_bar.py` in the kitty config directory, exposed as :code:`{custom}`. ''' ) opt('active_window_title_template', 'none', option_type='tab_title_template', long_text=''' Template to use for the active window title bar. If not set (the value :code:`none`), the :opt:`window_title_template` is used. ''' ) opt('window_title_bar_active_foreground', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Foreground color for the active window title bar. Defaults to the corresponding tab bar color (:code:`active_tab_foreground`) when set to :code:`none`. ''' ) opt('window_title_bar_active_background', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Background color for the active window title bar. Defaults to the corresponding tab bar color (:code:`active_tab_background`) when set to :code:`none`. ''' ) opt('window_title_bar_inactive_foreground', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Foreground color for inactive window title bars. Defaults to the corresponding tab bar color (:code:`inactive_tab_foreground`) when set to :code:`none`. ''' ) opt('window_title_bar_inactive_background', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Background color for inactive window title bars. Defaults to the corresponding tab bar color (:code:`inactive_tab_background`) when set to :code:`none`. ''' ) opt('window_title_bar_align', 'center', choices=('left', 'center', 'right'), long_text='Horizontal alignment of the text in window title bars.' ) 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_filter', '', long_text=''' A :ref:`search expression `. Only tabs that match this expression will be shown in the tab bar. The currently active tab is :italic:`always` shown, regardless of whether it matches or not. When using this option, the tab bar may be displayed with less tabs than specified in :opt:`tab_bar_min_tabs`, as evaluating the filter is expensive and is done only at display time. This is most useful when using :ref:`sessions `. An expression of :code:`session:~ or session:^$` will show only tabs that belong to the current session or no session. The various tab navigation actions such as :ac:`goto_tab`, :ac:`next_tab`, :ac:`previous_tab`, etc. are automatically restricted to work only on matching tabs. ''') 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', 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:`session_name` The name of the kitty session file from which this tab was created, if any. :code:`active_session_name` The name of the kitty session file from which the active window in this tab was created, if any. :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. :code:`custom` This will call a function named :code:`draw_title(data)` from the file :file:`tab_bar.py` placed in the kitty config directory. The function will be passed a dictionary of data, the same data that can be used in this template. It can then perform arbitrarily complex processing and return a string. For example: :code:`tab_title_template "{custom}"` will use the output of the function as the tab title. Any print statements in the :code:`draw_title()` will print to the STDOUT of the kitty process, useful for debugging. 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, unless the window is translucent, in which case the default background is used as it looks better. ''') opt( 'tab_bar_drag_threshold', '5', option_type='positive_int', long_text=""" Control when dragging of tabs to re-order them happens. The value is the drag threshold in pixels, the distance the mouse must move before a drag begins. A value of zero disables tab dragging entirely. Dragging a tab to another kitty window moves it there, while dragging outside any kitty window detaches it into a new OS window. Note that on Wayland, :link:`because of poor design ` cancelling a drag will detach the tab. This is worked around for compositors that support :link:`xdg-toplevel-drag `. """, ) 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 terminal background color. 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 also, that this setting does not apply to the :opt:`background_image`, if any. The background image can itself have transparency via its alpha channel if desired, and that will be respected. 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 Wayland, when the compositor supports the background blur extension. ''') 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 Note that you must also set :opt:`background_opacity` to something less than 1 for this setting to work properly. ''' ) 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_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.' ' Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.' ) 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. Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect. ''' ) opt('background_image_linear', 'no', option_type='to_bool', ctype='bool', long_text='When background image is scaled, whether linear interpolation should be used.' ' Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.' ) 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_image` is set. Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect. ''' ) 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. Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect. ''' ) 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() # }}} # colors.wide_gamut {{{ agr('colors.wide_gamut', 'Wide gamut color formats', ''' kitty supports modern wide gamut color formats including OKLCH and CIE LAB for precise color specification. These formats can be used anywhere a color value is accepted (foreground, background, color0-color255, etc.). For detailed documentation on wide gamut color formats, syntax, and examples, see :doc:`/wide-gamut-colors`. ''') 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`. Use the special value :code:`read_from_shell` to have kitty read the specified variables from your :opt:`login shell ` configuration. Useful if your shell startup files setup a bunch of environment variables that you want available to kitty and in kitty session files. Each variable name is treated as a glob pattern to match. For example: :code:`env read_from_shell=PATH LANG LC_* XDG_* EDITOR VISUAL`. Note that these variables are only read after the configuration is fully processed, thus they are not available for recursive expansion and they will override any variables set by other :opt:`env` directives. ''', ) 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:`notify-bell` Send a desktop notification and ring the terminal bell. The arguments are the same as for `notify`. :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`. ''' ) 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 You can browse and trigger these actions by pressing :sc:`command_palette` to run the command palette. 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('map_timeout', '0.0', option_type='positive_float', long_text=''' The default timeout (in seconds) for multi-key mappings and modal keyboard modes. If you press the first key(s) of a multi-key mapping and don't press the next key within this timeout, the mapping is cancelled and the mode is exited. A value of zero disables the timeout. This can be overridden for specific modes using the :code:`--timeout` option when creating a keyboard mode with :code:`--new-mode`. For example:: # 2 second timeout for all mappings map_timeout 2.0 # This mode will have a 5 second timeout (overrides the global 2 second timeout) map --new-mode resize --timeout 5.0 kitty_mod+r ''') 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. The :ac:`copy_or_noop` action will copy if there is a selection and pass the key through to the application running in the terminal if there is no selection. ''' ) map('Copy to clipboard or pass through', 'copy_or_noop cmd+c copy_or_noop', 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. ''' ) map('Search the scrollback within a pager', 'search_scrollback kitty_mod+/ search_scrollback', long_text=''' Search for currently selected text in the scrollback using the configured :opt:`scrollback_pager`. Assumes that pressing the :kbd:`/` key triggers search mode in the pager. If you want to create a manual mapping with a special pager for this, you can use something like: map f1 combine : launch --stdin-source=@screen_scrollback --stdin-add-formatting --type=overlay mypager : send_key / For more sophisticated control, such as using the current selection, use :ac:`remote_control_script`. ''') map('Search the scrollback within a pager', 'search_scrollback cmd+f search_scrollback', only='macos') 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 To setup shortcuts to multiply/divide the font size:: map kitty_mod+f6 change_font_size all *2.0 map kitty_mod+f6 change_font_size all /2.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 chosen file', 'insert_chosen_file kitty_mod+p>c kitten choose-files', long_text=''' Select a file using the :doc:`choose-files ` kitten and insert it into the terminal. ''' ) map('Insert chosen directory', 'insert_chosen_directory kitty_mod+p>d kitten choose-files --mode=dir', long_text=''' Select a directory using the :doc:`choose-files ` kitten and insert it into the terminal. ''' ) 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('Command palette', 'command_palette kitty_mod+f3 command_palette') 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('macOS Cycle through OS Windows', 'macos_cycle_through_os_windows cmd+` macos_cycle_through_os_windows', only='macos') map('macOS Cycle through OS Windows backwards', 'macos_cycle_through_os_windows_backwards cmd+shift+` macos_cycle_through_os_windows_backwards', 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 # Erase the last command and its output (needs shell integration to work) map f1 clear_terminal last_command 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 the last command', 'clear_last_command cmd+l clear_terminal last_command 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() # }}} ================================================ FILE: kitty/options/parse.py ================================================ # 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_scrollback_indicator_opacity, 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, mouse_hide_wait, 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, scrollbar_color, 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 active_window_title_template(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_window_title_template'] = 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_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_trail_color'] = to_color_or_none(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 draw_window_borders_for_single_window(self, val: str, ans: dict[str, typing.Any]) -> None: ans['draw_window_borders_for_single_window'] = 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_dock_badge_on_bell(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_dock_badge_on_bell'] = 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 map_timeout(self, val: str, ans: dict[str, typing.Any]) -> None: ans['map_timeout'] = positive_float(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 momentum_scroll(self, val: str, ans: dict[str, typing.Any]) -> None: ans['momentum_scroll'] = unit_float(val) def mouse_hide_wait(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mouse_hide_wait'] = mouse_hide_wait(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 pixel_scroll(self, val: str, ans: dict[str, typing.Any]) -> None: ans['pixel_scroll'] = to_bool(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_position(self, val: str, ans: dict[str, typing.Any]) -> None: ans['remember_window_position'] = to_bool(val) 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_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 scrollbar(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_scrollbar: raise ValueError(f"The value {val} is not a valid choice for scrollbar") ans["scrollbar"] = val choices_for_scrollbar = frozenset(('scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered')) def scrollbar_gap(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_gap'] = positive_float(val) def scrollbar_handle_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_handle_color'] = scrollbar_color(val) def scrollbar_handle_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_handle_opacity'] = positive_float(val) def scrollbar_hitbox_expansion(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_hitbox_expansion'] = positive_float(val) def scrollbar_hover_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_hover_width'] = positive_float(val) def scrollbar_interactive(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_interactive'] = to_bool(val) def scrollbar_jump_on_click(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_jump_on_click'] = to_bool(val) def scrollbar_min_handle_height(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_min_handle_height'] = positive_float(val) def scrollbar_radius(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_radius'] = positive_float(val) def scrollbar_track_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_track_color'] = scrollbar_color(val) def scrollbar_track_hover_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_track_hover_opacity'] = positive_float(val) def scrollbar_track_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_track_opacity'] = positive_float(val) def scrollbar_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollbar_width'] = positive_float(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_drag_threshold(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_drag_threshold'] = positive_int(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_filter(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_filter'] = str(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_drag_tolerance(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_drag_tolerance'] = float(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 window_title_bar(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_window_title_bar: raise ValueError(f"The value {val} is not a valid choice for window_title_bar") ans["window_title_bar"] = val choices_for_window_title_bar = frozenset(('top', 'bottom')) def window_title_bar_active_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_bar_active_background'] = to_color_or_none(val) def window_title_bar_active_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_bar_active_foreground'] = to_color_or_none(val) def window_title_bar_align(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_window_title_bar_align: raise ValueError(f"The value {val} is not a valid choice for window_title_bar_align") ans["window_title_bar_align"] = val choices_for_window_title_bar_align = choices_for_tab_bar_align def window_title_bar_inactive_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_bar_inactive_background'] = to_color_or_none(val) def window_title_bar_inactive_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_bar_inactive_foreground'] = to_color_or_none(val) def window_title_bar_min_windows(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_bar_min_windows'] = positive_int(val) def window_title_template(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_title_template'] = tab_title_template(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 scrollback_indicator_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_scrollback_indicator_opacity('scrollback_indicator_opacity', 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 ================================================ FILE: kitty/options/to-c-generated.h ================================================ // 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_cursor_trail_color(PyObject *val, Options *opts) { cursor_trail_color(val, opts); } static void convert_from_opts_cursor_trail_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_trail_color"); if (ret == NULL) return; convert_from_python_cursor_trail_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar(PyObject *val, Options *opts) { opts->scrollbar = scrollbar(val); } static void convert_from_opts_scrollbar(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar"); if (ret == NULL) return; convert_from_python_scrollbar(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_interactive(PyObject *val, Options *opts) { opts->scrollbar_interactive = PyObject_IsTrue(val); } static void convert_from_opts_scrollbar_interactive(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_interactive"); if (ret == NULL) return; convert_from_python_scrollbar_interactive(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_jump_on_click(PyObject *val, Options *opts) { opts->scrollbar_jump_on_click = PyObject_IsTrue(val); } static void convert_from_opts_scrollbar_jump_on_click(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_jump_on_click"); if (ret == NULL) return; convert_from_python_scrollbar_jump_on_click(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_width(PyObject *val, Options *opts) { opts->scrollbar_width = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_width(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_width"); if (ret == NULL) return; convert_from_python_scrollbar_width(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_hover_width(PyObject *val, Options *opts) { opts->scrollbar_hover_width = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_hover_width(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_hover_width"); if (ret == NULL) return; convert_from_python_scrollbar_hover_width(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_handle_opacity(PyObject *val, Options *opts) { opts->scrollbar_handle_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_handle_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_handle_opacity"); if (ret == NULL) return; convert_from_python_scrollbar_handle_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_radius(PyObject *val, Options *opts) { opts->scrollbar_radius = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_radius(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_radius"); if (ret == NULL) return; convert_from_python_scrollbar_radius(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_gap(PyObject *val, Options *opts) { opts->scrollbar_gap = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_gap(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_gap"); if (ret == NULL) return; convert_from_python_scrollbar_gap(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_min_handle_height(PyObject *val, Options *opts) { opts->scrollbar_min_handle_height = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_min_handle_height(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_min_handle_height"); if (ret == NULL) return; convert_from_python_scrollbar_min_handle_height(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_hitbox_expansion(PyObject *val, Options *opts) { opts->scrollbar_hitbox_expansion = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_hitbox_expansion(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_hitbox_expansion"); if (ret == NULL) return; convert_from_python_scrollbar_hitbox_expansion(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_track_opacity(PyObject *val, Options *opts) { opts->scrollbar_track_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_track_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_opacity"); if (ret == NULL) return; convert_from_python_scrollbar_track_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_track_hover_opacity(PyObject *val, Options *opts) { opts->scrollbar_track_hover_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_scrollbar_track_hover_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_hover_opacity"); if (ret == NULL) return; convert_from_python_scrollbar_track_hover_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_handle_color(PyObject *val, Options *opts) { opts->scrollbar_handle_color = PyLong_AsUnsignedLong(val); } static void convert_from_opts_scrollbar_handle_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_handle_color"); if (ret == NULL) return; convert_from_python_scrollbar_handle_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollbar_track_color(PyObject *val, Options *opts) { opts->scrollbar_track_color = PyLong_AsUnsignedLong(val); } static void convert_from_opts_scrollbar_track_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_color"); if (ret == NULL) return; convert_from_python_scrollbar_track_color(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_pixel_scroll(PyObject *val, Options *opts) { opts->pixel_scroll = PyObject_IsTrue(val); } static void convert_from_opts_pixel_scroll(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "pixel_scroll"); if (ret == NULL) return; convert_from_python_pixel_scroll(ret, opts); Py_DECREF(ret); } static void convert_from_python_momentum_scroll(PyObject *val, Options *opts) { opts->momentum_scroll = PyFloat_AsFloat(val); } static void convert_from_opts_momentum_scroll(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "momentum_scroll"); if (ret == NULL) return; convert_from_python_momentum_scroll(ret, opts); Py_DECREF(ret); } static void convert_from_python_mouse_hide_wait(PyObject *val, Options *opts) { mouse_hide_wait(val, opts); } 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_macos_dock_badge_on_bell(PyObject *val, Options *opts) { opts->macos_dock_badge_on_bell = PyObject_IsTrue(val); } static void convert_from_opts_macos_dock_badge_on_bell(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_dock_badge_on_bell"); if (ret == NULL) return; convert_from_python_macos_dock_badge_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_window_drag_tolerance(PyObject *val, Options *opts) { opts->window_drag_tolerance = PyFloat_AsDouble(val); } static void convert_from_opts_window_drag_tolerance(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_drag_tolerance"); if (ret == NULL) return; convert_from_python_window_drag_tolerance(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_title_bar_active_foreground(PyObject *val, Options *opts) { opts->window_title_bar_active_foreground = color_or_none_as_int(val); } static void convert_from_opts_window_title_bar_active_foreground(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_title_bar_active_foreground"); if (ret == NULL) return; convert_from_python_window_title_bar_active_foreground(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_title_bar_active_background(PyObject *val, Options *opts) { opts->window_title_bar_active_background = color_or_none_as_int(val); } static void convert_from_opts_window_title_bar_active_background(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_title_bar_active_background"); if (ret == NULL) return; convert_from_python_window_title_bar_active_background(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_title_bar_inactive_foreground(PyObject *val, Options *opts) { opts->window_title_bar_inactive_foreground = color_or_none_as_int(val); } static void convert_from_opts_window_title_bar_inactive_foreground(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_title_bar_inactive_foreground"); if (ret == NULL) return; convert_from_python_window_title_bar_inactive_foreground(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_title_bar_inactive_background(PyObject *val, Options *opts) { opts->window_title_bar_inactive_background = color_or_none_as_int(val); } static void convert_from_opts_window_title_bar_inactive_background(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_title_bar_inactive_background"); if (ret == NULL) return; convert_from_python_window_title_bar_inactive_background(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_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_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_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_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_cursor_trail_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_interactive(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_jump_on_click(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_width(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_hover_width(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_handle_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_radius(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_gap(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_min_handle_height(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_hitbox_expansion(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_track_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_track_hover_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_handle_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollbar_track_color(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_pixel_scroll(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_momentum_scroll(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_macos_dock_badge_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_window_drag_tolerance(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_title_bar_active_foreground(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_title_bar_active_background(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_title_bar_inactive_foreground(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_title_bar_inactive_background(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_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_dynamic_background_opacity(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_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; } ================================================ FILE: kitty/options/to-c.h ================================================ /* * 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 ScrollbarVisibilityPolicy scrollbar(PyObject *src) { const char *q = PyUnicode_AsUTF8(src); switch (q[0]) { case 'a': return SCROLLBAR_ALWAYS; case 'n': return SCROLLBAR_NEVER; case 'h': return SCROLLBAR_ON_HOVERED; case 's': return strcmp(q, "scrolled") == 0 ? SCROLLBAR_ON_SCROLLED : SCROLLBAR_ON_SCROLL_AND_HOVER; } return SCROLLBAR_ON_SCROLLED; } 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 mouse_hide_wait(PyObject *val, Options *opts) { if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 4) { PyErr_SetString(PyExc_TypeError, "mouse_hide_wait is not a 4-item tuple"); return; } opts->mouse_hide.hide_wait = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(val, 0)); opts->mouse_hide.unhide_wait = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(val, 1)); opts->mouse_hide.unhide_threshold = PyLong_AsLong(PyTuple_GET_ITEM(val, 2)); opts->mouse_hide.scroll_unhide = PyObject_IsTrue(PyTuple_GET_ITEM(val, 3)); } 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 inline void cursor_trail_color(PyObject *src, Options *opts) { opts->cursor_trail_color = color_or_none_as_int(src); } 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); else zero_at_ptr(&opts->which); } 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_composition_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_composition_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 } ================================================ FILE: kitty/options/types.py ================================================ # 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, MouseHideWait, 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_scrollbar = typing.Literal['scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered'] 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 choices_for_window_title_bar = typing.Literal['top', 'bottom'] choices_for_window_title_bar_align = choices_for_tab_bar_align option_names = ( 'action_alias', 'active_border_color', 'active_tab_background', 'active_tab_font_style', 'active_tab_foreground', 'active_tab_title_template', 'active_window_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_color', 'cursor_trail_decay', 'cursor_trail_start_threshold', 'cursor_underline_thickness', 'default_pointer_shape', 'detect_urls', 'dim_opacity', 'disable_ligatures', 'draw_minimal_borders', 'draw_window_borders_for_single_window', '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_dock_badge_on_bell', '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', 'map_timeout', 'mark1_background', 'mark1_foreground', 'mark2_background', 'mark2_foreground', 'mark3_background', 'mark3_foreground', 'menu_map', 'modify_font', 'momentum_scroll', 'mouse_hide_wait', 'mouse_map', 'narrow_symbols', 'notify_on_cmd_finish', 'open_url_with', 'paste_actions', 'pixel_scroll', 'placement_strategy', 'pointer_shape_when_dragging', 'pointer_shape_when_grabbed', 'remember_window_position', 'remember_window_size', 'remote_control_password', 'repaint_delay', 'resize_debounce_time', 'resize_in_steps', 'scrollback_fill_enlarged_window', 'scrollback_lines', 'scrollback_pager', 'scrollback_pager_history_size', 'scrollbar', 'scrollbar_gap', 'scrollbar_handle_color', 'scrollbar_handle_opacity', 'scrollbar_hitbox_expansion', 'scrollbar_hover_width', 'scrollbar_interactive', 'scrollbar_jump_on_click', 'scrollbar_min_handle_height', 'scrollbar_radius', 'scrollbar_track_color', 'scrollbar_track_hover_opacity', 'scrollbar_track_opacity', 'scrollbar_width', '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_drag_threshold', 'tab_bar_edge', 'tab_bar_filter', '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_drag_tolerance', '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', 'window_title_bar', 'window_title_bar_active_background', 'window_title_bar_active_foreground', 'window_title_bar_align', 'window_title_bar_inactive_background', 'window_title_bar_inactive_foreground', 'window_title_bar_min_windows', 'window_title_template', ) 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 active_window_title_template: str = '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_color: kitty.fast_data_types.Color | None = None 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 draw_window_borders_for_single_window: bool = False 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_dock_badge_on_bell: bool = True 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 map_timeout: float = 0 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) momentum_scroll: float = 0.96 mouse_hide_wait: MouseHideWait = MouseHideWait(hide_wait=0.0, show_wait=0.0, show_threshold=40, scroll_show=True) if is_macos else MouseHideWait(hide_wait=3.0, show_wait=0.0, show_threshold=40, scroll_show=True) 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'}) pixel_scroll: bool = True 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_position: bool = False 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_lines: int = 2000 scrollback_pager: list[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER'] scrollback_pager_history_size: int = 0 scrollbar: choices_for_scrollbar = 'scrolled' scrollbar_gap: float = 0.1 scrollbar_handle_color: int = 0 scrollbar_handle_opacity: float = 0.5 scrollbar_hitbox_expansion: float = 0.25 scrollbar_hover_width: float = 1.0 scrollbar_interactive: bool = True scrollbar_jump_on_click: bool = True scrollbar_min_handle_height: float = 1.0 scrollbar_radius: float = 0.3 scrollbar_track_color: int = 0 scrollbar_track_hover_opacity: float = 0.1 scrollbar_track_opacity: float = 0 scrollbar_width: float = 0.5 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_drag_threshold: int = 5 tab_bar_edge: int = 8 tab_bar_filter: str = '' 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_drag_tolerance: float = 2.0 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 window_title_bar: choices_for_window_title_bar = 'top' window_title_bar_active_background: kitty.fast_data_types.Color | None = None window_title_bar_active_foreground: kitty.fast_data_types.Color | None = None window_title_bar_align: choices_for_window_title_bar_align = 'center' window_title_bar_inactive_background: kitty.fast_data_types.Color | None = None window_title_bar_inactive_foreground: kitty.fast_data_types.Color | None = None window_title_bar_min_windows: int = 0 window_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.window}{progress_percent}{title}' 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'), # search_scrollback KeyDefinition(trigger=SingleKey(mods=256, key=47), definition='search_scrollback'), # 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_chosen_file KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=99),), definition='kitten choose-files'), # insert_chosen_directory KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=100),), definition='kitten choose-files --mode=dir'), # 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'), # command_palette KeyDefinition(trigger=SingleKey(mods=256, key=57366), definition='command_palette'), # 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_or_noop')) 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=102), definition='search_scrollback')) 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=8, key=96), definition='macos_cycle_through_os_windows')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=96), definition='macos_cycle_through_os_windows_backwards')) 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=8, key=108), definition='clear_terminal last_command 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'), # extend_selection_shift MouseMapping(mods=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_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', 'cursor_trail_color', 'visual_bell_color', 'active_border_color', 'window_title_bar_active_foreground', 'window_title_bar_active_background', 'window_title_bar_inactive_foreground', 'window_title_bar_inactive_background', 'tab_bar_background', 'tab_bar_margin_color', 'selection_foreground', 'selection_background' }) special_colors = frozenset({ 'scrollbar_handle_color', 'scrollbar_track_color', 'wayland_titlebar_color', 'macos_titlebar_color' }) secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass') ================================================ FILE: kitty/options/utils.py ================================================ #!/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', 'goto_session', 'save_as_session', 'close_session', ) 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', 'simulate_color_scheme_preference_change', ) 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', 'last_command'): 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', '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 return func, [num] @func_with_args('scroll_to_prompt') def scroll_to_prompt(func: str, rest: str) -> FuncArgsType: vals = rest.strip().split() args = [-1, 0] if len(vals) > 2: log_error('scroll_to_prompt needs one or two arguments, using defaults') else: try: args[0] = int(vals[0]) except Exception: log_error(f'{vals[0]} is not a valid number of prompts to jump for scroll_to_prompt') if len(vals) == 2: try: args[1] = int(vals[1]) except Exception: log_error(f'{vals[1]} is not a valid scroll offset for scroll_to_prompt') return func, args @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, 'upto_surrounding_whitespace': defines.MOUSE_SELECTION_UPTO_SURROUNDING_WHITESPACE, } 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', 'notify-bell', 'command'): raise ValueError(f'Unknown notify_on_cmd_finish action: {parts[2]}') action = parts[2] if action.startswith('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 color_with_special_values(x: str, special_values: dict[str, int], error_msg: str) -> int: x = x.strip('"') if (ans := special_values.get(x)) is not None: return ans & 0xff try: return (color_as_int(to_color(x)) << 8) | len(special_values) except Exception: log_error(error_msg.format(x=x)) return 0 def titlebar_color(x: str) -> int: return color_with_special_values( x, {'system': 0, 'background': 1}, 'Ignoring invalid title bar color: {x}' ) def scrollbar_color(x: str) -> int: return color_with_special_values( x, {'foreground': 0, 'selection_background': 1}, 'Ignoring invalid scroll bar color: {x}' ) 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 numeric 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]: if not defn: return 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)) timeout: float | None = None 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 timeout: float = 0.0 timeout_timer_id: int | None = None def __init__(self, name: str = '') -> None: self.name = name self.keymap: KeyMap = defaultdict(list) KeyboardModeMap = dict[str, KeyboardMode] key_map_option_converters: defaultdict[str, Callable[[str], Any]] = defaultdict(lambda: (lambda x: x)) key_map_option_converters['timeout'] = float 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, key_map_option_converters[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, key_map_option_converters[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) class MouseHideWait(NamedTuple): hide_wait: float show_wait: float show_threshold: int scroll_show: bool def mouse_hide_wait(x: str) -> MouseHideWait: parts = x.split(maxsplit=3) if len(parts) != 1 and len(parts) != 4: log_error(f'Invalid mouse_hide_wait: {x}, ignoring') return MouseHideWait(3.0, 0.0, 40, True) if len(parts) == 1: return MouseHideWait(float(parts[0]), 0.0, 40, True) else: return MouseHideWait(float(parts[0]), float(parts[1]), int(parts[2]), to_bool(parts[3])) 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)) def deprecated_scrollback_indicator_opacity(key: str, val: str, ans: dict[str, Any]) -> None: if not hasattr(deprecated_scrollback_indicator_opacity, key): setattr(deprecated_scrollback_indicator_opacity, key, True) log_error(f'The option {key} is deprecated. Use scrollbar instead.') op = unit_float(val) if op <= 0.001: ans['scrollbar'] = 'never' else: ans['scrollbar_handle_opacity'] = op ================================================ FILE: kitty/os_window_size.py ================================================ #!/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_compat 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 ================================================ FILE: kitty/parse-graphics-command.h ================================================ // 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); } ================================================ FILE: kitty/parse-multicell-command.h ================================================ // 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); } ================================================ FILE: kitty/png-reader.c ================================================ /* * 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, int max_image_dimension) { 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); // libpng uses too much memory for overly large images if (d->width > max_image_dimension || d->height > max_image_dimension) { ABRT(ENOMEM, "PNG image is too large"); } 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; } // Structure to hold memory write state typedef struct { unsigned char* buffer; size_t size, capacity; } png_memory_write_state; // Custom write function for writing PNG data to memory static void png_write_to_memory(png_structp png_ptr, png_bytep data, png_size_t length) { png_memory_write_state* state = (png_memory_write_state*)png_get_io_ptr(png_ptr); if (state->size + length > state->capacity) { // Double the capacity or add enough space for the new data, whichever is larger size_t new_capacity = state->capacity * 2; if (new_capacity < state->size + length) new_capacity = state->size + length; unsigned char* new_buffer = realloc(state->buffer, new_capacity); if (!new_buffer) { png_error(png_ptr, "Failed to allocate memory for PNG buffer"); return; } state->buffer = new_buffer; state->capacity = new_capacity; } // Copy the data to the buffer memcpy(state->buffer + state->size, data, length); state->size += length; } static void png_flush_memory(png_structp png_ptr) { (void)png_ptr; } static const char* create_png_from_data(const char *data, size_t width, size_t height, size_t stride, size_t *out_size, bool flip_vertically, int color_type) { *out_size = 0; png_memory_write_state state = {.capacity=width*height * sizeof(uint32_t)}; state.buffer = malloc(state.capacity); if (!state.buffer) return "Out of memory"; png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { free(state.buffer); return "Failed to create PNG write struct"; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { free(state.buffer); png_destroy_write_struct(&png_ptr, NULL); return "Failed to create PNG info struct"; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); free(state.buffer); return("Error during PNG creation\n"); } png_set_write_fn(png_ptr, &state, png_write_to_memory, png_flush_memory); png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Allocate memory for row pointers png_bytep *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); if (!row_pointers) { png_destroy_write_struct(&png_ptr, &info_ptr); free(state.buffer); return ("Failed to allocate memory for row pointers"); } if (flip_vertically) for (size_t y = 0; y < height; y++) row_pointers[height - 1 - y] = (png_byte*)&data[y * stride]; else for (size_t y = 0; y < height; y++) row_pointers[y] = (png_byte*)&data[y * stride]; png_write_info(png_ptr, info_ptr); png_write_image(png_ptr, row_pointers); png_write_end(png_ptr, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); free(row_pointers); *out_size = state.size; return (char*)state.buffer; } const char* png_from_32bit_rgba(const char *data, size_t width, size_t height, size_t *out_size, bool flip_vertically) { return create_png_from_data(data, width, height, 4 * width, out_size, flip_vertically, PNG_COLOR_TYPE_RGBA); } PyObject* png_from_32bit_rgba_data(PyObject *self UNUSED, PyObject *args) { int flip_vertically = 0; const char* data; Py_ssize_t len; unsigned width, height; if (!PyArg_ParseTuple(args, "y#II|p", &data, &len, &width, &height, &flip_vertically)) return NULL; size_t out_size; const char *out = create_png_from_data(data, width, height, 4 * width, &out_size, flip_vertically, PNG_COLOR_TYPE_RGBA); PyObject *ans = PyBytes_FromStringAndSize(out, out_size); free((void*)out); return ans; } const char* png_from_24bit_rgb(const char *data, size_t width, size_t height, size_t *out_size, bool flip_vertically) { return create_png_from_data(data, width, height, 3 * width, out_size, flip_vertically, PNG_COLOR_TYPE_RGB); } 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, 10000); 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), METHODB(png_from_32bit_rgba_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; } ================================================ FILE: kitty/png-reader.h ================================================ /* * 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, int max_image_dimension); const char* png_from_32bit_rgba(const char *data, size_t width, size_t height, size_t *out_size, bool flip_vertically); const char* png_from_24bit_rgb(const char *data, size_t width, size_t height, size_t *out_size, bool flip_vertically); ================================================ FILE: kitty/print-graphics.h ================================================ /* * Copyright (C) 2026 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include #include "base64.h" // Write the kitty graphics protocol escape codes for a 32-bit RGBA image to fp. static inline void fprint_rgba32(FILE *fp, const uint32_t *rgba, uint32_t width, uint32_t height) { const uint8_t *data = (const uint8_t *)rgba; const size_t total_bytes = (size_t)width * height * sizeof(uint32_t); uint8_t b64_buf[4096]; size_t offset = 0; bool first = true; while (offset < total_bytes) { size_t chunk = total_bytes - offset; if (chunk > 3072) chunk = 3072; size_t b64_len = sizeof(b64_buf); base64_encode8(data + offset, chunk, b64_buf, &b64_len, false); offset += chunk; const bool last = (offset >= total_bytes); if (first) { fprintf(fp, "\x1b_Ga=T,q=2,f=32,s=%u,v=%u,m=%d;", width, height, last ? 0 : 1); first = false; } else fprintf(fp, "\x1b_Gm=%d;", last ? 0 : 1); fwrite(b64_buf, 1, b64_len, fp); fputs("\x1b\\", fp); } } static inline void fprint_argb32(FILE *fp, const uint32_t *rgba, uint32_t width, uint32_t height) { const size_t total_bytes = (size_t)width * height * sizeof(uint32_t); uint8_t b64_buf[4096]; uint32_t rgba_buf[3072/4]; size_t offset = 0; bool first = true; while (offset < total_bytes) { size_t chunk = total_bytes - offset; if (chunk > sizeof(rgba_buf)) chunk = sizeof(rgba_buf); size_t b64_len = sizeof(b64_buf); for (size_t i = 0; i < chunk/4; i++) { uint32_t argb = rgba[offset/4 + i]; rgba_buf[i] = ((argb & 0x00FFFFFF) << 8) | ((argb & 0xFF000000) >> 24); } base64_encode8((const uint8_t*)rgba_buf, chunk, b64_buf, &b64_len, false); offset += chunk; const bool last = (offset >= total_bytes); if (first) { fprintf(fp, "\x1b_Ga=T,q=2,f=32,s=%u,v=%u,m=%d;", width, height, last ? 0 : 1); first = false; } else fprintf(fp, "\x1b_Gm=%d;", last ? 0 : 1); fwrite(b64_buf, 1, b64_len, fp); fputs("\x1b\\", fp); } } static inline void fprint_abgr32(FILE *fp, const uint32_t *src, uint32_t width, uint32_t height) { const size_t total_bytes = (size_t)width * height * sizeof(uint32_t); uint8_t b64_buf[4096]; uint32_t rgba_buf[3072/4]; size_t offset = 0; bool first = true; while (offset < total_bytes) { size_t chunk = total_bytes - offset; if (chunk > sizeof(rgba_buf)) chunk = sizeof(rgba_buf); size_t b64_len = sizeof(b64_buf); for (size_t i = 0; i < chunk/4; i++) { uint32_t abgr = src[offset/4 + i]; rgba_buf[i] = __builtin_bswap32(abgr); } base64_encode8((const uint8_t*)rgba_buf, chunk, b64_buf, &b64_len, false); offset += chunk; const bool last = (offset >= total_bytes); if (first) { fprintf(fp, "\x1b_Ga=T,q=2,f=32,s=%u,v=%u,m=%d;", width, height, last ? 0 : 1); first = false; } else fprintf(fp, "\x1b_Gm=%d;", last ? 0 : 1); fwrite(b64_buf, 1, b64_len, fp); fputs("\x1b\\", fp); } } static inline void print_rgba32(const uint32_t *rgba, uint32_t width, uint32_t height) { fprint_rgba32(stdout, rgba, width, height); } static inline void print_argb32(const uint32_t *rgba, uint32_t width, uint32_t height) { fprint_argb32(stdout, rgba, width, height); } static inline void print_abgr32(const uint32_t *rgba, uint32_t width, uint32_t height) { fprint_abgr32(stdout, rgba, width, height); } ================================================ FILE: kitty/progress.py ================================================ #!/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 ================================================ FILE: kitty/rc/__init__.py ================================================ ================================================ FILE: kitty/rc/action.py ================================================ #!/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() ================================================ FILE: kitty/rc/base.py ================================================ #!/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 get_defaults_from_seq, parse_args from kitty.cli_stub import RCOptions as R from kitty.constants import list_kitty_resources, running_in_kitty from kitty.simple_cli_definitions import CompletionSpec, parse_option_spec 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" ext: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`, :code:`session` 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`. The field :code:`session` matches windows that were created in the specified session. Use the expression :code:`^$` to match windows that were not created in a session and :code:`.` to match the currently active session and :code:`~` to match either the currently active session or the last active session when no session is active. 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:`focused_os_window`, :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:`focused_os_window` matches all windows in the currently focused OS window. 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`, :code:`session` 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. The field :code:`session` matches tabs that were created in the specified session. Use the expression :code:`^$` to match windows that were not created in a session and :code:`.` to match the currently active session and :code:`~` to match either the currently active session or the last active session when no session is active. 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`, :code:`parent_focused` and :code:`focused_os_window`. 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. The value :code:`focused_os_window` matches all tabs in the currently focused OS window. 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): if q := window.tabref(): 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'))}) ================================================ FILE: kitty/rc/close_tab.py ================================================ #!/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() ================================================ FILE: kitty/rc/close_window.py ================================================ #!/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() ================================================ FILE: kitty/rc/create_marker.py ================================================ #!/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() ================================================ FILE: kitty/rc/detach_tab.py ================================================ #!/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() ================================================ FILE: kitty/rc/detach_window.py ================================================ #!/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() ================================================ FILE: kitty/rc/disable_ligatures.py ================================================ #!/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() ================================================ FILE: kitty/rc/env.py ================================================ #!/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() ================================================ FILE: kitty/rc/focus_tab.py ================================================ #!/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: window = tab.active_window if window: boss.set_active_window(window, switch_os_window_if_needed=True) else: boss.set_active_tab(tab) break return None focus_tab = FocusTab() ================================================ FILE: kitty/rc/focus_window.py ================================================ #!/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 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: boss.set_active_window(window, switch_os_window_if_needed=True) break return None focus_window = FocusWindow() ================================================ FILE: kitty/rc/get_colors.py ================================================ #!/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() ================================================ FILE: kitty/rc/get_text.py ================================================ #!/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() ================================================ FILE: kitty/rc/goto_layout.py ================================================ #!/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() ================================================ FILE: kitty/rc/kitten.py ================================================ #!/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() ================================================ FILE: kitty/rc/last_used_layout.py ================================================ #!/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() ================================================ FILE: kitty/rc/launch.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os 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 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 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 add_to_session/str: Session name to add created window/tab to env/list.str: List of environment variables of the form NAME=VALUE var/list.str: List of user variables of the form NAME=VALUE os_panel/list.str: List of panel settings tab_title/str: Title for the new tab type/choices.window.tab.os-window.os-panel.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 wait_for_child_to_exit/bool: Boolean indicating whether to wait and return child exit code hold_after_ssh/bool: Boolean indicating whether to run a local shell after exiting the ssh session cloned via cwd=current or similar ''' 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' + '''\ --wait-for-child-to-exit type=bool-set Wait until the launched program exits and print out its exit code. The exit code is printed out instead of the window id. If the program exited normally its exit code is printed, which is always greater than or equal to zero. If the program was killed by a signal, the symbolic name of the SIGNAL is printed, if available, otherwise the signal number with a leading minus sign is printed. --response-timeout type=float default=86400 The time in seconds to wait for the started process to exit, when using the :option:`--wait-for-child-to-exit` option. Defaults to one day. --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')) is_asynchronous = True 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 # ans['wait_for_child_to_exit'] = opts.wait_for_child_to_exit return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: # responder.send_data(getattr(w, 'id', 0)) 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] # Create responder before defining callback to avoid closure issue responder = None if payload_get('wait_for_child_to_exit') and not payload_get('no_response'): responder = self.create_async_responder(payload_get, window) def on_child_death(exit_status: int, exc: Exception | None) -> None: code = os.waitstatus_to_exitcode(exit_status) ans = str(code) if code < 0: try: from signal import Signals ans = Signals(-code).name except ValueError: pass if responder is not None: responder.send_data(ans) w = do_launch( boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window, base_env=base_env, child_death_callback=on_child_death if payload_get('wait_for_child_to_exit') and not payload_get('no_response') else None) if payload_get('no_response'): return None if not payload_get('wait_for_child_to_exit'): return str(0 if w is None else w.id) return AsyncResponse() def cancel_async_request(self, boss: 'Boss', window: Window | None, payload_get: PayloadGetType) -> None: pass launch = Launch() ================================================ FILE: kitty/rc/load_config.py ================================================ #!/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() ================================================ FILE: kitty/rc/ls.py ================================================ #!/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 output_format/str: Output in json or session format ''' 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. --output-format type=choices choices=json,session default=json Output in JSON or kitty session format ''' + '\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 elif payload_get('output_format') == 'session': return "\n".join(boss.serialize_state_as_session()) 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() ================================================ FILE: kitty/rc/new_window.py ================================================ #!/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() ================================================ FILE: kitty/rc/remove_marker.py ================================================ #!/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() ================================================ FILE: kitty/rc/resize_os_window.py ================================================ #!/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 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.toggle-visibility.hide.show.os-panel: The action to perform 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 os_panel/list.str: Settings for modifying the OS Panel ''' short_desc = 'Resize/show/hide/etc. the specified OS Windows' desc = ( 'Resize (or other operations) on the specified OS Windows.' ' Note that some window managers/environments do not allow applications to resize' ' their windows, for example, tiling window managers.\n\nTo modify OS Panels created with the' ' panel kitten, use :option:`--action`=:code:`os-panel`. Specify the modifications in the same syntax as used' ' by the panel kitten, without the leading dashes. Use the :option:`--incremental` option to only change' ' the specified panel settings. For example, move the panel to bottom edge and make it two lines tall:' ' :code:`--action=os-panel --incremental lines=2 edge=bottom`' ) args = RemoteCommand.Args(spec='[OS Panel settings ...]', json_field='os_panel', special_parse='escape_list_of_strings(args), nil') options_spec = MATCH_WINDOW_OPTION + '''\n --action default=resize choices=resize,toggle-fullscreen,toggle-maximized,toggle-visibility,hide,show,os-panel 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. When using :option:`--action`=:code:`os-panel`, only the specified settings are changed, otherwise non-specified settings keep their current value. --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, 'os_panel': args, } def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.fast_data_types import ( get_os_window_size, layer_shell_config_for_os_window, set_layer_shell_config, toggle_fullscreen, toggle_os_window_visibility, ) 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}: metrics = get_os_window_size(os_window_id) if metrics is None: raise RemoteControlErrorWithoutTraceback(f'The OS Window {os_window_id} does not exist') panels = payload_get('os_panel') is_panel = metrics['is_layer_shell'] if ac == 'os-panel': if not is_panel: raise RemoteControlErrorWithoutTraceback( f'The OS Window {os_window_id} is not a panel you should not use the --action=resize option to resize it') if not panels: raise RemoteControlErrorWithoutTraceback('Must specify at least one panel setting') if payload_get('incremental'): existing = layer_shell_config_for_os_window(os_window_id) if existing is None: raise RemoteControlErrorWithoutTraceback( f'The OS Window {os_window_id} has no panel configuration') from kittens.panel.main import incrementally_update_layer_shell_config try: lsc = incrementally_update_layer_shell_config(existing, panels) except Exception as e: raise RemoteControlErrorWithoutTraceback(str(e)) else: from kitty.launch import layer_shell_config_from_panel_opts try: lsc = layer_shell_config_from_panel_opts(panels) except Exception as e: raise RemoteControlErrorWithoutTraceback( f'Invalid panel options specified: {e}') if not set_layer_shell_config(os_window_id, lsc): raise RemoteControlErrorWithoutTraceback(f'Failed to change panel configuration for OS Window {os_window_id}') elif ac == 'toggle-visibility': toggle_os_window_visibility(os_window_id) elif ac == 'hide': toggle_os_window_visibility(os_window_id, False) elif ac == 'show': toggle_os_window_visibility(os_window_id, True) elif ac == 'toggle-fullscreen': if not toggle_fullscreen(os_window_id): raise RemoteControlErrorWithoutTraceback( f'The OS Window {os_window_id} is a desktop panel that cannot be made fullscreen') elif is_panel: raise RemoteControlErrorWithoutTraceback( f'The OS Window {os_window_id} is a desktop panel, no actions other than resizing are supported for it') elif 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'), metrics=metrics, ) elif ac == 'toggle-maximized': boss.toggle_maximized(os_window_id) return None resize_os_window = ResizeOSWindow() ================================================ FILE: kitty/rc/resize_window.py ================================================ #!/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() ================================================ FILE: kitty/rc/run.py ================================================ #!/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() ================================================ FILE: kitty/rc/scroll_window.py ================================================ #!/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 to 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 unspecified, :code:`l` is the default.' ' For example, :code:`30.5` will scroll down 30 and a half 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 :code:`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 (unscroll or prompt) 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'): (window.scroll_home if amt[0] == 'start' else window.scroll_end)() else: amt, unit = amt match unit: case 'u': window.screen.reverse_scroll(int(abs(amt)), True) case 'r': window.scroll_to_prompt(int(amt)) case 'l': window.scroll_fractional_lines(amt) case 'p': if 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() ================================================ FILE: kitty/rc/select_window.py ================================================ #!/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() ================================================ FILE: kitty/rc/send_key.py ================================================ #!/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() ================================================ FILE: kitty/rc/send_text.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_background_image.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_background_opacity.py ================================================ #!/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) # GLFW represents opacity as a float internally, but python's # "float" type has double precision, so we can't rely on precise # equality here if current is not None and abs(current - val) <= 0.0001: val = opts.background_opacity boss._set_os_window_background_opacity(os_window_id, val) return None set_background_opacity = SetBackgroundOpacity() ================================================ FILE: kitty/rc/set_colors.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_enabled_layouts.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_font_size.py ================================================ #!/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:`+`, :code:`-`, :code:`*` or :code:`/` changes 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() ================================================ FILE: kitty/rc/set_spacing.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_tab_color.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_tab_title.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_user_vars.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_window_logo.py ================================================ #!/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() ================================================ FILE: kitty/rc/set_window_title.py ================================================ #!/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() ================================================ FILE: kitty/rc/signal_child.py ================================================ #!/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() ================================================ FILE: kitty/remote_control.py ================================================ #!/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_compat 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.get(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) ================================================ FILE: kitty/render_cache.py ================================================ #!/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() ================================================ FILE: kitty/resize.c ================================================ /* * 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; } static void nuke_in_line(CPUCell *cp, GPUCell *gp, index_type start, index_type x_limit) { for (index_type x = start; x < x_limit; x++) { cell_set_char(cp + x, 0); cp[x].is_multicell = false; clear_sprite_position(gp[x]); } } static void nuke_multicell_char_at(LineBuf *lb, index_type x_, index_type y_) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(lb, y_, &cp, &gp); index_type num_lines_above = cp[x_].y; index_type y_max_limit = MIN(lb->ynum, y_ + cp[x_].scale - num_lines_above); while (cp[x_].x && x_ > 0) x_--; index_type x_limit = MIN(lb->xnum, x_ + mcd_x_limit(&cp[x_])); for (index_type y = y_; y < y_max_limit; y++) { linebuf_init_cells(lb, y, &cp, &gp); nuke_in_line(cp, gp, x_, x_limit); } for (int y = (int)y_ - 1; y > -1 && num_lines_above; y--, num_lines_above--) { linebuf_init_cells(lb, y, &cp, &gp); nuke_in_line(cp, gp, x_, x_limit); } } ResizeResult resize_screen_buffer_without_rewrap(LineBuf *lb, index_type lines, index_type columns, TrackCursor *cursors) { ResizeResult ans = {0}; ans.lb = alloc_linebuf(lines, columns, lb->text_cache); if (!ans.lb) return ans; Rewrap r = { .src = {.lb=lb},}; exclude_empty_lines_at_bottom(&r); ans.num_content_lines_before = r.num_content_lines_before; ans.num_content_lines_after = MIN(lines, r.num_content_lines_before); index_type xcommon = MIN(lb->xnum, ans.lb->xnum); for (index_type y = 0; y < ans.num_content_lines_after; y++) { linebuf_init_line(lb, y); linebuf_init_line(ans.lb, y); ans.lb->line_attrs[y] = lb->line_attrs[y]; ans.lb->line_attrs[y].has_dirty_text = true; memcpy(ans.lb->line->cpu_cells, lb->line->cpu_cells, xcommon * sizeof(lb->line->cpu_cells[0])); memcpy(ans.lb->line->gpu_cells, lb->line->gpu_cells, xcommon * sizeof(lb->line->gpu_cells[0])); if (xcommon > lb->line->xnum) { // extend the colors/styles of the last cell to edge GPUCell e = lb->line->gpu_cells[xcommon-1]; clear_sprite_position(e); for (index_type x = xcommon; x < ans.lb->line->xnum; x++) ans.lb->line->gpu_cells[x] = e; } else if (xcommon < lb->line->xnum) { // remove multicell chars that were split at the right edge index_type last_x = xcommon - 1; CPUCell *c = ans.lb->line->cpu_cells + last_x; if (c->is_multicell && c->x + 1u < mcd_x_limit(c)) { while (ans.lb->line->cpu_cells[last_x].x && last_x > 0) last_x--; nuke_in_line(ans.lb->line->cpu_cells, ans.lb->line->gpu_cells, last_x, ans.lb->line->xnum); } } } // Set bg color for extra lines at bottom if (ans.num_content_lines_before < lines) { linebuf_init_line(lb, lb->ynum-1); GPUCell *g = lb->line->gpu_cells; for (index_type y = ans.num_content_lines_after; y < ans.lb->ynum; y++) { linebuf_init_line(ans.lb, y); for (index_type x = 0; x < ans.lb->xnum; x++) ans.lb->line->gpu_cells[x].bg = g->bg; } } else if (ans.num_content_lines_after < ans.num_content_lines_before) { // delete multicell chars split at the bottom linebuf_init_line(ans.lb, ans.num_content_lines_after-1); for (index_type x = 0; x < ans.lb->xnum; x++) { CPUCell *c = ans.lb->line->cpu_cells + x; if (c->is_multicell && c->y < c->scale-1) nuke_multicell_char_at(ans.lb, x, ans.num_content_lines_after-1); } } for (TrackCursor *tc = cursors; !tc->is_sentinel; tc++) { tc->dest_x = MIN(tc->x, ans.lb->xnum-1); tc->dest_y = MIN(tc->y, ans.lb->ynum-1); } ans.ok = true; return ans; } ================================================ FILE: kitty/resize.h ================================================ /* * 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); ResizeResult resize_screen_buffer_without_rewrap(LineBuf *lb, index_type lines, index_type columns, TrackCursor *cursors); ================================================ FILE: kitty/rgb.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal 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 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: if (val := Color.parse_color(raw)) is None and validate: raise ValueError(f'Invalid color name: {raw!r}') return val ================================================ FILE: kitty/rounded_rect_fragment.glsl ================================================ #pragma kitty_include_shader #pragma kitty_include_shader in vec2 dimensions; out vec4 output_color; uniform vec4 rect; uniform vec2 params; uniform vec4 color; uniform vec4 background_color; // Signed distance function for a rounded rectangle float rounded_rectangle_sdf(vec2 p, vec2 b, float r) { // signed distance field // first term is used for points outside the rectangle vec2 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; } void main() { vec2 size = rect.ba, origin = rect.xy; float thickness = params[0], corner_radius = params[1]; // Position must be relative to the center of the rectangle of (size) located at (origin) vec2 position = gl_FragCoord.xy - size / 2.0 - origin; // Calculate distance to rounded rectangle float dist = rounded_rectangle_sdf(position, size*0.5 - corner_radius, corner_radius); // The below is for a filled rounded rect // float alpha = 1.0 - smoothstep(0.0, 1.0, dist); // vec4 ans = color; ans.a *= alpha; // output_color = alpha_blend(ans, background_color); // The border is outer - inner rects float outer_edge = -dist, inner_edge = outer_edge - thickness; // Smooth borders (anti-alias) const float step_size = 1.0; // controls how blurred the aliasing causes the rect to be float alpha = smoothstep(-step_size, step_size, outer_edge) - smoothstep(-step_size, step_size, inner_edge); vec4 ans = color; ans.a *= alpha; // pre-multiplied output output_color = alpha_blend(ans, background_color); } ================================================ FILE: kitty/rounded_rect_vertex.glsl ================================================ #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) ); const vec4 dest_rect = vec4(-1, 1, 1, -1); void main() { ivec2 pos = vertex_pos_map[gl_VertexID]; gl_Position = vec4(dest_rect[pos.x], dest_rect[pos.y], 0, 1); } ================================================ FILE: kitty/rowcolumn-diacritics.c ================================================ // Unicode data, built from the Unicode Standard 17.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 ================================================ FILE: kitty/safe-wrappers.h ================================================ /* * 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; } ================================================ FILE: kitty/screen.c ================================================ /* * 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 #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->extra_cursors.count = 0; zero_at_ptr(&self->extra_cursors.color); self->extra_cursors.dirty = true; 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, 111, NULL); // does default_bg_changed processing colorprofile_reset(self->color_profile); CALLBACK("on_reset", 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_buffer_without_rewrap(screen->alt_linebuf, lines, columns, cursors); if (!ar.ok) { Py_DecRef((PyObject*)ar.lb); 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 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 index_selection(const Screen *self, Selections *selections, bool up, index_type top, index_type bottom) { const bool needs_special_handling = self->linebuf == self->alt_linebuf && (top > 0 || bottom < self->lines - 1); for (size_t i = 0; i < selections->count; i++) { Selection *s = selections->items + i; if (needs_special_handling) { if (is_selection_empty(s)) continue; int start = (int)s->start.y - s->start_scrolled_by; int end = (int)s->end.y - s->end_scrolled_by; int stop = MIN(start, end); int sbottom = MAX(start, end); if (stop < (int)top) { if (sbottom < (int)top) continue; clear_selection(selections); return; } else { if (stop > (int)bottom) continue; if (sbottom > (int)bottom) { clear_selection(selections); return; } } } 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, top, bottom); \ 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 - 1; } } } 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 and blank lines 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); free(self->extra_cursors.locations); free(self->paused_rendering.extra_cursors.locations); 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; GraphemeSegmentationResult seg; struct { index_type x, y; CPUCell *cc; } prev; } 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 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_prev_cell(Screen *self, text_loop_state *s) { zero_at_ptr(&s->prev); if (self->cursor->x) { s->prev.y = self->cursor->y; s->prev.x = self->cursor->x - 1; s->prev.cc = linebuf_cpu_cell_at(self->linebuf, s->prev.x, s->prev.y); } else if (self->cursor->y) { s->prev.y = self->cursor->y - 1; s->prev.x = self->columns - 1; s->prev.cc = linebuf_cpu_cell_at(self->linebuf, s->prev.x, s->prev.y); if (!s->prev.cc->next_char_was_wrapped) s->prev.cc = NULL; } } static void init_segmentation_state(Screen *self, text_loop_state *s) { init_prev_cell(self, s); grapheme_segmentation_reset(&s->seg); if (s->prev.cc) { text_in_cell(s->prev.cc, self->text_cache, self->lc); for (index_type i = 0; i < self->lc->count; i++) s->seg = grapheme_segmentation_step(s->seg, char_props_for(self->lc->chars[i])); } } 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; init_segmentation_state(self, s); } 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_--; const index_type ws = mcd_x_limit(&cp[x_]); const index_type x_limit = MIN(self->columns, x_ + ws); const index_type half_x_limit = MIN(self->columns, x_ + ws / 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 = x_; 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 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 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, text_loop_state *s, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) { index_type before = self->cursor->y; 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}; if (self->cursor->y == before) init_segmentation_state(self, s); else init_text_loop_line(self, s); } 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) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, s->prev.y, &cp, &gp); index_type xpos = s->prev.x; while (xpos && cp[xpos].is_multicell && cp[xpos].x) xpos--; if (!add_combining_char(self, ch, xpos, s->prev.y) || 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, s, cpu_cell, gpu_cell, xpos, s->prev.y); return; } nuke_multicell_char_at(self, xpos + 1, s->prev.y, 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, s, cpu_cell, gpu_cell, xpos, s->prev.y); } } } 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, s->prev.y)) { self->cursor->x -= deltax; init_segmentation_state(self, s); } } } } 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: { index_type before = self->cursor->y; screen_backspace(self); if (before == self->cursor->y) init_segmentation_state(self, s); else init_text_loop_line(self, s); } 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); init_segmentation_state(self, s); 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); init_segmentation_state(self, s); 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); int char_width; for (size_t i = 0; i < num_chars; i++) { uint32_t ch = map_char(self, chars[i]); if (ch < DEL && s->seg.grapheme_break <= GBP_None) { // fast path for printable ASCII if (ch < ' ') { draw_control_char(self, s, ch); continue; } char_width = 1; s->seg = (GraphemeSegmentationResult){.grapheme_break=GBP_None}; } else { CharProps cp = char_props_for(ch); if (cp.is_invalid) { if (ch < ' ') draw_control_char(self, s, ch); continue; } s->seg = grapheme_segmentation_step(s->seg, cp); if (UNLIKELY(s->seg.add_to_current_cell && s->prev.cc)) { draw_combining_char(self, s, ch); continue; } char_width = wcwidth_std(cp); if (UNLIKELY(char_width < 1)) { if (char_width == 0) { // Preserve zero width chars as combining chars even though // they were not added to the prev cell by grapheme segmentation. // Zero width chars can only be represented as combining chars. if (s->prev.cc) draw_combining_char(self, s, ch); continue; } char_width = 1; } } if (self->cursor->x < self->columns && s->cp[self->cursor->x].is_multicell) { 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); } 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, .hyperlink_id=s->cc.hyperlink_id}; *second = *fc; second->x = 1; s->gp[self->cursor->x + 1] = s->gp[self->cursor->x]; s->prev.y = self->cursor->y; s->prev.x = self->cursor->x; s->prev.cc = fc; self->cursor->x += 2; } else { zero_cells(s, fc, s->gp + self->cursor->x); cell_set_char(fc, ch); s->prev.y = self->cursor->y; s->prev.x = self->cursor->x; s->prev.cc = fc; 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->sgr.fg & COL_MASK, .bg=self->cursor->sgr.bg & COL_MASK, \ .decoration_fg=force_underline ? ((OPT(url_color) & COL_MASK) << 8) | 2 : self->cursor->sgr.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); } 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); GraphemeSegmentationResult s; grapheme_segmentation_reset(&s); 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 ((s = grapheme_segmentation_step(s, cp)).add_to_current_cell || (wcwidth_std(cp) == 0 && lc.count)) lc.chars[lc.count++] = ch; else { if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc); switch(wcwidth_std(cp)) { case 0: case -1: lc.count = 0; break; default: lc.chars[0] = ch; lc.count = 1; break; } } } 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); self->sgr_blink_was_used |= self->cursor->sgr.blink; } } 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; } static inline void reset_pixel_scroll(Screen *self, unsigned val) { self->pixel_scroll_offset_y = val; } // 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; reset_pixel_scroll(self, 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); if (self->extra_cursors.count) { self->extra_cursors.count = 0; self->extra_cursors.dirty = true; } 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(PASTE_EVENTS) 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 val, unsigned val2) { // 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. if ( self->test_child == Py_None && !screen_current_key_encoding_flags(self) && val == 4 && val2 != INT_MAX && val2 != 0 ) { 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_move(self, 1, -1, true); } 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_move(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/, bool allow_move_to_previous_line) { if (count == 0) count = 1; bool in_margins = cursor_within_margins(self); if (move_direction > 0) { self->cursor->x += count; screen_ensure_bounds(self, false, in_margins); } else { index_type top = in_margins && self->modes.mDECOM ? self->margin_top : 0; while (count > 0) { if (count <= self->cursor->x) { self->cursor->x -= count; count = 0; } else { if (self->cursor->x > 0) { count -= self->cursor->x; self->cursor->x = 0; } else { if (self->cursor->y == top || !allow_move_to_previous_line) count = 0; else { count--; self->cursor->y--; self->cursor->x = self->columns-1; } } } } } } void screen_cursor_forward(Screen *self, unsigned int count/*=1*/) { screen_cursor_move(self, count, 1, false); } 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, top, bottom); \ 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(PASTE_EVENTS) 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, PASTE_EVENTS, 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, bool *is_relative) { *is_relative = (bool) self->prompt_settings.relative_click_events; 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); reset_pixel_scroll(self, 0); 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: if (self->extra_cursors.count) { self->extra_cursors.count = 0; self->extra_cursors.dirty = true; } 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); } static void screen_delete_lines_impl(Screen *self, index_type start, index_type count, index_type top, index_type bottom) { index_type y = start; 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, start, bottom); self->is_dirty = true; clear_all_selections(self); } 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) { screen_delete_lines_impl(self, self->cursor->y, count, top, bottom); 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: CALLBACK("on_da1", NULL); 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 which, unsigned modifier) { char buf[32] = {0}; unsigned code = 0, width = 0, height = 0; switch(which) { case 14: code = 4; width = self->cell_size.width * self->columns; height = self->cell_size.height * self->lines; if (modifier == 2 && self->window_id) { OSWindow *osw = os_window_for_kitty_window(self->window_id); if (osw) { int w, h, fw, fh; get_os_window_size(osw, &w, &h, &fw, &fh); width = fw; height = fh; } } 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); KNOWN_MODE(PASTE_EVENTS); #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; } self->cursor->shape = shape; self->cursor->non_blinking = !blink; break; } } #define NAME multi_cursor_map #define KEY_TY index_type #define VAL_TY uint8_t #include "kitty-verstable.h" unsigned screen_multi_cursor_count(const Screen *self) { return self->paused_rendering.expires_at ? self->paused_rendering.extra_cursors.count : self->extra_cursors.count; } void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params) { // printf("%d;", queried_shape); for (unsigned i = 0; i < num_params; i++) {printf("%d:", params[i]);} printf("\n"); if (!num_params) { #define pr(...) { int n = snprintf(p, sz - (p - buf), __VA_ARGS__); if (n >= 0 && (unsigned)n <= (sz - (p - buf))) p += n; } if (params == NULL) { write_escape_code_to_child(self, ESC_CSI, ">1;2;3;29;30;40;100;101 q"); } else if (queried_shape == 100) { size_t sz = self->extra_cursors.count * 32 + 64; RAII_ALLOC(char, buf, malloc(sz)); sz -= 4; if (buf) { char *p = buf + snprintf(buf, sz, ">100;"); for (unsigned i = 0; i < self->extra_cursors.count; i++) { index_type cell = self->extra_cursors.locations[i].cell, shape = self->extra_cursors.locations[i].shape; index_type y = cell / self->columns, x = cell - (y * self->columns); pr("%d:2:%u:%u;", shape > 3 ? 29 : (int)shape, y+1, x+1); } if (*(p-1) == ';') p--; *(p++) = ' '; *(p++) = 'q'; *(p++) = 0; write_escape_code_to_child(self, ESC_CSI, buf); } } else if (queried_shape == 101) { char buf[64], *p = buf; size_t sz = sizeof(buf); pr(">101;30:"); DynamicColor ecc = self->extra_cursors.color.text; #define o() switch(ecc.type) { \ case COLOR_NOT_SET: pr("0"); break; \ case COLOR_IS_SPECIAL: pr("1"); break; \ case COLOR_IS_INDEX: pr("5:%u", ecc.rgb & 0xff); break; \ case COLOR_IS_RGB: pr("2:%u:%u:%u", (ecc.rgb >> 16) & 0xff, (ecc.rgb >> 8) & 0xff, ecc.rgb & 0xff); break; \ } \ o(); pr(";40:"); ecc = self->extra_cursors.color.cursor; o(); #undef o pr(" q"); write_escape_code_to_child(self, ESC_CSI, buf); } return; #undef pr } if (queried_shape == 30 || queried_shape == 40) { DynamicColor *ecc = queried_shape == 40 ? &self->extra_cursors.color.cursor : &self->extra_cursors.color.text; self->extra_cursors.dirty = true; switch (params[0]) { case 0: ecc->type = COLOR_NOT_SET; break; case 1: ecc->type = COLOR_IS_SPECIAL; break; case 2: if (num_params > 3) { ecc->type = COLOR_IS_RGB; ecc->rgb = ((params[1] & 0xff) << 16) | ((params[2] & 0xff) << 8) | (params[3] & 0xff); } break; case 5: if (num_params > 1) { ecc->type = COLOR_IS_INDEX; ecc->rgb = params[1] & 0xff; } break; } return; } uint8_t shape = 0; switch(queried_shape) { case 29: shape = 4; break; case 0: case 1: case 2: case 3: shape = queried_shape; break; default: return; } self->extra_cursors.dirty = true; int type = params[0]; params++; num_params--; int extra[2]; switch (type) { case 0: extra[0] = MIN(self->cursor->y, self->lines-1) + 1; extra[1] = MIN(self->cursor->x, self->columns-1) + 1; params = extra; num_params = 2; /* fallthrough */ case 2: { multi_cursor_map s; vt_init(&s); for (unsigned i = 0; i < self->extra_cursors.count; i++) { vt_insert(&s, self->extra_cursors.locations[i].cell, self->extra_cursors.locations[i].shape); } for (unsigned i = 0; i+1 < num_params; i+=2) { index_type y = params[i]-1, x = params[i+1]-1; if (!shape) { vt_erase(&s, y * self->columns + x); } else if (y < self->lines && x < self->columns) vt_insert(&s, y * self->columns + x, shape); } self->extra_cursors.count = vt_size(&s); ensure_space_for(&self->extra_cursors, locations, ExtraCursor, self->extra_cursors.count, capacity, 20 * 80, false); self->extra_cursors.count = 0; vt_create_for_loop(multi_cursor_map_itr, i, &s) { self->extra_cursors.locations[self->extra_cursors.count++] = (ExtraCursor){ .shape = i.data->val, .cell = i.data->key}; } vt_cleanup(&s); } break; case 4: { if (num_params < 4) { // full screen switch(shape) { default: self->extra_cursors.count = 0; break; case 1: case 2: case 3: case 4: ensure_space_for(&self->extra_cursors, locations, ExtraCursor, self->lines * self->columns, capacity, 20 * 80, false); self->extra_cursors.count = self->lines * self->columns; for (index_type cell = 0; cell < self->lines * self->columns; cell++) { self->extra_cursors.locations[cell].shape = shape; self->extra_cursors.locations[cell].cell = cell; } break; } break; } unsigned count = 0; for (unsigned i = 0; i < self->extra_cursors.count; i++) { bool in_some_region = false; index_type y = self->extra_cursors.locations[i].cell / self->columns, x = self->extra_cursors.locations[i].cell - (self->columns * y); for (unsigned i = 0; i + 3 < num_params && !in_some_region; i += 4) { index_type top = params[i]-1, left = params[i+1]-1, bottom = params[i+2]-1, right = params[i+3]-1; in_some_region = top <= y && y <= bottom && left <= x && x <= right; } if (!in_some_region) self->extra_cursors.locations[count++] = self->extra_cursors.locations[i]; } self->extra_cursors.count = count; if (shape) { for (unsigned i = 0; i + 3 < num_params; i += 4) { index_type top = params[i]-1, left = params[i+1]-1, bottom = params[i+2]-1, right = params[i+3]-1; bottom = MIN(bottom, self->lines-1); right = MIN(right, self->columns -1); if (right < left || bottom < top) continue; size_t xnum = right + 1 - left, ynum = bottom + 1 - top; ensure_space_for(&self->extra_cursors, locations, ExtraCursor, self->extra_cursors.count + xnum * ynum, capacity, 20 * 80, false); for (index_type y = top; y <= bottom; y++) { for (index_type x = left; x <= right; x++) { self->extra_cursors.locations[self->extra_cursors.count++] = (ExtraCursor){ .shape=shape, .cell=y*self->columns + x}; } } } } } break; } } void set_title(Screen *self, PyObject *title) { CALLBACK("title_changed", "O", title); } void osc_context(Screen *self, PyObject *ctx) { CALLBACK("osc_context", "O", ctx); } 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; self->prompt_settings.relative_click_events = 0; } else if (strcmp(token, "click_events=2") == 0) { self->prompt_settings.supports_click_events = 1; self->prompt_settings.relative_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, int scroll_offset) { 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; y += scroll_offset; 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--; } } y -= scroll_offset; #undef ensure_y_ok self->scrolled_by = y >= 0 ? 0 : -y; screen_set_last_visited_prompt(self, 0); } if (old != self->scrolled_by) { reset_pixel_scroll(self, 0); 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 const char *s = cursor_as_sgr(self->cursor); if (s && s[0]) shape = snprintf(buf, sizeof(buf), "1$r0;%sm", s); else shape = snprintf(buf, sizeof(buf), "1$rm"); } 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; self->extra_cursors.dirty = true; // free grman data grman_pause_rendering(NULL, self->paused_rendering.grman); // free extra cursors free(self->paused_rendering.extra_cursors.locations); zero_at_ptr(&self->paused_rendering.extra_cursors); 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); if (self->extra_cursors.count) { self->paused_rendering.extra_cursors.locations = calloc(self->extra_cursors.count, sizeof(self->extra_cursors.locations[0])); if (self->paused_rendering.extra_cursors.locations) { self->paused_rendering.extra_cursors.count = self->extra_cursors.count; self->paused_rendering.extra_cursors.dirty = self->extra_cursors.dirty; memcpy(self->paused_rendering.extra_cursors.locations, self->extra_cursors.locations, sizeof(self->extra_cursors.locations[0]) * self->extra_cursors.count); } } 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 update_line_data_blank(unsigned xnum, unsigned int dest_y, uint8_t *data) { const size_t sz = xnum * sizeof(GPUCell); memset(data + sz * dest_y, 0, sz); } static Line* render_line_for_virtual_y(Screen *self, int y, Line *line, index_type *lnum, bool *is_history) { if (y < (int)self->scrolled_by) { int idx = (int)self->scrolled_by - 1 - y; if (idx >= 0 && (unsigned)idx < self->historybuf->count) { historybuf_init_line(self->historybuf, idx, line); line->xnum = self->columns; line->ynum = (index_type)MIN(MAX(y, 0), (int)self->lines - 1); *lnum = (index_type)idx; *is_history = true; return line; } return NULL; } y -= self->scrolled_by; if (y >= 0 && y < (int)self->lines) { linebuf_init_line_at(self->linebuf, (index_type)y, line); *lnum = (index_type)y; *is_history = false; return line; } return NULL; } 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 = 0; 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; const unsigned int render_lines = render_lines_for_screen(self); const int render_row_offset = pixel_scroll_enabled(self); Line line = {.text_cache = self->text_cache}; for (unsigned int render_row = 0; render_row < render_lines; render_row++) { const int virtual_y = (int)render_row - render_row_offset; bool is_history = false; Line *linep = render_line_for_virtual_y(self, virtual_y, &line, &lnum, &is_history); if (linep) { screen_render_line_graphics(self, linep, virtual_y - (int)self->scrolled_by); if (linep->attrs.has_dirty_text) { if (is_history) historybuf_mark_line_clean(self->historybuf, lnum); else 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; screen_reset_dirty(self); update_overlay_position(self); const bool force_history_render = pixel_scroll_enabled(self) && self->scroll_changed; if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); self->scroll_changed = false; const unsigned int render_lines = render_lines_for_screen(self); const int render_row_offset = pixel_scroll_enabled(self); Line line = {.text_cache = self->text_cache}; for (unsigned int render_row = 0; render_row < render_lines; render_row++) { const int virtual_y = (int)render_row - render_row_offset; bool is_history = false; index_type lnum = 0; Line *linep = render_line_for_virtual_y(self, virtual_y, &line, &lnum, &is_history); if (linep == NULL) { update_line_data_blank(self->columns, render_row, address); continue; } if (is_history) { // 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, linep, virtual_y - (int)self->scrolled_by); if (force_history_render || linep->attrs.has_dirty_text) { render_line(fonts_data, linep, lnum, self->cursor, self->disable_ligatures, self->lc); if (screen_has_marker(self)) mark_text_in_line(self->marker, linep, &self->as_ansi_buf); historybuf_mark_line_clean(self->historybuf, lnum); } } else { if (linep->attrs.has_dirty_text || (cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor.y == lnum))) { render_line(fonts_data, linep, lnum, self->cursor, self->disable_ligatures, self->lc); screen_render_line_graphics(self, linep, virtual_y - (int)self->scrolled_by); if (linep->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line( self->marker, linep, &self->as_ansi_buf); if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, linep, fonts_data); linebuf_mark_line_clean(self->linebuf, lnum); } } update_line_data(linep, render_row, 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, int extra_leading_rows) { iteration_data(s, &s->last_rendered, self->columns, -self->historybuf->count, 0); Line *line; const int y_min = MAX(-extra_leading_rows - (int)self->scrolled_by, s->last_rendered.y), y_limit = MIN(s->last_rendered.y_limit, (int)self->lines - (int)self->scrolled_by); 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 = checked_range_line(self, y); if (!line) continue; } const int y_in_data = (y + extra_leading_rows + self->scrolled_by); uint8_t *line_start = data + self->columns * y_in_data; 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_in_data - c->y); ym < y_in_data; ym++) data[self->columns * ym + x] |= set_mask; for (int ym = y_in_data + 1; ym < MIN((int)self->lines + extra_leading_rows, y_in_data + 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) { uint8_t *address = address_; memset(address, 0, size); const int offset = pixel_scroll_enabled(self); 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, offset); 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, offset); } sel->last_rendered_count = sel->count; address += offset * self->columns; size -= offset * self->columns; ExtraCursors *ec = self->paused_rendering.expires_at ? &self->paused_rendering.extra_cursors : &self->extra_cursors; for (unsigned i = 0; i < ec->count; i++) { if (ec->locations[i].cell < size) address[ec->locations[i].cell] |= (ec->locations[i].shape & 7) << 2; } ec->dirty = false; } 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")); RAII_PyObject(empty_string, PyUnicode_FromString("")); if (!ans || !nl || !empty_string) return NULL; bool has_escape_codes = false; bool need_newline = false; for (int i = 0, y = min_y; y < y_limit && i < PyTuple_GET_SIZE(ans) - 1; 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; PyTuple_SET_ITEM(ans, i, Py_NewRef(need_newline ? nl : empty_string)); } 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 y = idata.y; y < idata.y_limit && y < (int)self->lines; y++) { 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++ < 20) { 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; reset_pixel_scroll(self, 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->sgr.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->sgr.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 int render_row_offset = pixel_scroll_enabled(self); const size_t base = sizeof(GPUCell) * (self->overlay_line.ynum + self->scrolled_by + render_row_offset) * 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) { 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) { if (direction == 0) { found_prompt = true; break; } found_next_prompt = true; end = y1; } else if (line && line->attrs.prompt_kind == OUTPUT_START) { 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_output) { // skip fetching wrapped prompt lines while (range_line_is_continued(self, y2)) { y2++; } } else if (!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* erase_last_command(Screen *self, PyObject *args) { int include_prompt = 1; if (!PyArg_ParseTuple(args, "|p", &include_prompt)) return NULL; OutputOffset oo = {.screen=self}; if (self->linebuf != self->main_linebuf || !find_cmd_output(self, &oo, self->cursor->y + self->scrolled_by, self->scrolled_by, -1, false)) Py_RETURN_FALSE; if (include_prompt) { int y = oo.start - 1; Line *line; while ((line = checked_range_line(self, y))) { oo.start--; oo.num_lines++; y--; if (line->attrs.prompt_kind == PROMPT_START) break; } } index_type num_lines_to_erase_in_screen = oo.start >= 0 ? oo.num_lines : oo.num_lines + oo.start; num_lines_to_erase_in_screen = MIN(self->cursor->y, num_lines_to_erase_in_screen); if (num_lines_to_erase_in_screen) { screen_delete_lines_impl(self, self->cursor->y - num_lines_to_erase_in_screen, num_lines_to_erase_in_screen, 0, self->lines - 1); self->cursor->y -= num_lines_to_erase_in_screen; } if (oo.num_lines > num_lines_to_erase_in_screen) { index_type num_of_lines_to_erase_from_history = oo.num_lines - num_lines_to_erase_in_screen; historybuf_delete_newest_lines(self->historybuf, num_of_lines_to_erase_from_history); } Py_RETURN_TRUE; } 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)) { 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_move, 1, -1, true) 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(paste_events, PASTE_EVENTS) 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, ' '); } static void screen_selection_range_for_line_(Line *line, index_type *start, index_type *end) { 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; } bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) { if (y >= self->lines) { return false; } screen_selection_range_for_line_(visual_line_(self, y), start, end); 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 } void screen_history_scroll_to_absolute(Screen *self, double target_scrolled_by) { if (self->linebuf != self->main_linebuf) return; index_type target_scrolled_by_line = (index_type)target_scrolled_by; unsigned pixel_scroll_offset_y = (unsigned)((target_scrolled_by - target_scrolled_by_line) * self->cell_size.height); if (!OPT(pixel_scroll)) pixel_scroll_offset_y = 0; if (target_scrolled_by_line > self->historybuf->count) target_scrolled_by_line = self->historybuf->count; if (target_scrolled_by_line >= self->historybuf->count) pixel_scroll_offset_y = 0; if (target_scrolled_by_line != self->scrolled_by || self->pixel_scroll_offset_y != pixel_scroll_offset_y) { self->scrolled_by = target_scrolled_by_line; reset_pixel_scroll(self, pixel_scroll_offset_y); dirty_scroll(self); } } bool screen_apply_pixel_scroll(Screen *self, double delta_pixels) { if (!pixel_scroll_enabled(self)) return false; if (!self->historybuf->count) return false; const double cell_height = (double)self->cell_size.height; if (cell_height <= 0.0 || delta_pixels == 0.0) return false; double total = self->pixel_scroll_offset_y + (double)self->scrolled_by * cell_height + delta_pixels; const double max_total = (double)self->historybuf->count * cell_height; if (total < 0.0) total = 0.0; if (total > max_total) total = max_total; const unsigned int new_scrolled_by = (unsigned int)floor(total / cell_height); const unsigned offset = (unsigned)(total - (double)new_scrolled_by * cell_height); bool changed = false; if (new_scrolled_by != self->scrolled_by) { self->scrolled_by = new_scrolled_by; changed = true; } if (offset != self->pixel_scroll_offset_y) { self->pixel_scroll_offset_y = offset; changed = true; } if (changed) dirty_scroll(self); return changed; } 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; } unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count); if (new_scroll != self->scrolled_by || (new_scroll == 0 && self->pixel_scroll_offset_y != 0)) { self->scrolled_by = new_scroll; reset_pixel_scroll(self, 0); dirty_scroll(self); return true; } return false; } static bool screen_fractional_scroll(Screen *self, double amt) { if (amt == 0) return false; index_type before_scrolled_by = self->scrolled_by; double before_pixels = self->pixel_scroll_offset_y; double integral_part, fractional_part = modf(amt, &integral_part); int lines = (int)integral_part; int pixels = (int)(fractional_part * self->cell_size.height); if (amt > 0) { // downwards if (fractional_part != 0) pixels = MAX(1, pixels); if (lines > (int)self->scrolled_by) { self->scrolled_by = 0; self->pixel_scroll_offset_y = 0; } else { self->scrolled_by -= lines; if (pixels <= (int)self->pixel_scroll_offset_y) self->pixel_scroll_offset_y -= pixels; else { self->pixel_scroll_offset_y = 0; if (self->scrolled_by) { self->scrolled_by--; self->pixel_scroll_offset_y = self->cell_size.height - pixels; } } } } else { if (fractional_part != 0) pixels = MIN(-1, pixels); self->pixel_scroll_offset_y -= pixels; // pixels is negative if (self->pixel_scroll_offset_y >= self->cell_size.height) { self->pixel_scroll_offset_y = 0; self->scrolled_by++; } self->scrolled_by = MIN(self->scrolled_by - lines, self->historybuf->count); if (self->scrolled_by >= self->historybuf->count) self->pixel_scroll_offset_y = 0; } if (self->scrolled_by != before_scrolled_by || self->pixel_scroll_offset_y != before_pixels) { dirty_scroll(self); return true; } return false; } static PyObject* fractional_scroll(Screen *self, PyObject *amt) { double y; if (PyFloat_Check(amt)) y = PyFloat_AS_DOUBLE(amt); else if (PyLong_Check(amt)) y = PyLong_AsDouble(amt); else { PyErr_SetString(PyExc_TypeError, "amt must be a float"); return NULL; } return Py_NewRef(screen_fractional_scroll(self, y) ? Py_True : Py_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; int scroll_offset = 0; if (!PyArg_ParseTuple(args, "|ii", &num_of_prompts, &scroll_offset)) return NULL; if (screen_history_scroll_to_prompt(self, num_of_prompts, scroll_offset)) { 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 || self->extra_cursors.dirty) 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_upwards_scrollback(Screen *self, int top_line, SelectionBoundary *start, SelectionBoundary *end) { index_type num_in_scrollback = 0; Line *line = NULL; while (range_line_is_continued(self, top_line) && (line = range_line_(self, top_line-1))) { screen_selection_range_for_line_(line, &start->x, &end->x) ; top_line--; num_in_scrollback++; } return num_in_scrollback; } 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; // extend into scrollback if needed if (top_line == 0 && self->linebuf == self->main_linebuf) { index_type num_in_scrollback = continue_line_upwards_scrollback( self, top_line, &up_start, &up_end); if (num_in_scrollback) { s->start_scrolled_by += num_in_scrollback; s->start.x = up_start.x; } } } } #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; // extend into scrollback if needed if (top_line == 0 && self->linebuf == self->main_linebuf) { index_type num_in_scrollback = continue_line_upwards_scrollback( self, top_line, &up_start, &up_end); if (num_in_scrollback) { s->start_scrolled_by += num_in_scrollback; s->start.x = up_start.x; } } } 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; reset_pixel_scroll(self, 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)render_lines_for_screen(self) * 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(erase_last_command, 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_move, 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(fractional_scroll, METH_O) 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(paste_events) 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) // }}} ================================================ FILE: kitty/screen.h ================================================ /* * 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, mPASTE_EVENTS; 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 ExtraCursor { CursorShape shape; index_type cell; } ExtraCursor; typedef struct ExtraCursors { ExtraCursor *locations; unsigned count, capacity; struct { DynamicColor cursor, text; } color; bool dirty; } ExtraCursors; typedef struct { PyObject_HEAD unsigned int columns, lines, margin_top, margin_bottom, scrolled_by, pixel_scroll_offset_y; 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 scrolled_by; index_type lines, columns; color_type cursor_bg; CursorRenderInfo cursor; } last_rendered; bool is_dirty, scroll_changed, reload_all_gpu_data, sgr_blink_was_used; 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 relative_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; ExtraCursors extra_cursors; } paused_rendering; CharsetState charset; ListOfChars *lc; monotonic_t parsing_at; ExtraCursors extra_cursors; } Screen; #define pixel_scroll_enabled(screen) (OPT(pixel_scroll) && !screen->paused_rendering.expires_at && screen->linebuf == screen->main_linebuf) #define render_lines_for_screen(screen) (screen->lines + pixel_scroll_enabled(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_move(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/, bool allow_move_to_previous_line); 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 osc_context(Screen *self, PyObject *ctx); 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); unsigned screen_multi_cursor_count(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); void screen_history_scroll_to_absolute(Screen *self, double target_scrolled_by); bool screen_apply_pixel_scroll(Screen *self, double delta_pixels); 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 which, unsigned modifier); 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, unsigned); 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 *is_relative); 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); void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params); #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 ================================================ FILE: kitty/screenshot_fragment.glsl ================================================ #pragma kitty_include_shader uniform sampler2D image; uniform vec2 src_size; // Source texture size in pixels in vec2 texcoord; out vec4 output_color; void main() { // The input texture contains sRGB colors with premultiplied alpha. // We need to output unpremultiplied sRGB colors with proper downscaling. // For proper downscaling, we need to: // 1. Sample neighboring pixels // 2. Convert from sRGB to linear (unpremultiplying first) // 3. Average in linear space // 4. Convert back to sRGB // 5. Output unpremultiplied // Calculate the texel size vec2 texel_size = 1.0 / src_size; // Sample a 2x2 grid for better quality downscaling // This provides basic bilinear-like filtering in linear space vec2 tc = texcoord; vec4 s00 = texture(image, tc + vec2(-0.25, -0.25) * texel_size); vec4 s10 = texture(image, tc + vec2( 0.25, -0.25) * texel_size); vec4 s01 = texture(image, tc + vec2(-0.25, 0.25) * texel_size); vec4 s11 = texture(image, tc + vec2( 0.25, 0.25) * texel_size); // Unpremultiply and convert to linear for each sample vec3 linear00 = s00.a > 0.0 ? srgb2linear(s00.rgb / s00.a) : vec3(0.0); vec3 linear10 = s10.a > 0.0 ? srgb2linear(s10.rgb / s10.a) : vec3(0.0); vec3 linear01 = s01.a > 0.0 ? srgb2linear(s01.rgb / s01.a) : vec3(0.0); vec3 linear11 = s11.a > 0.0 ? srgb2linear(s11.rgb / s11.a) : vec3(0.0); // Average the alpha values float avg_alpha = (s00.a + s10.a + s01.a + s11.a) * 0.25; // For proper downsampling with transparency, weight colors by their alpha // This ensures partially transparent pixels contribute proportionally vec3 weighted_sum = linear00 * s00.a + linear10 * s10.a + linear01 * s01.a + linear11 * s11.a; float total_weight = s00.a + s10.a + s01.a + s11.a; // Calculate the weighted average color in linear space vec3 avg_linear = total_weight > 0.0 ? weighted_sum / total_weight : vec3(0.0); // Convert back to sRGB vec3 srgb_color = linear2srgb(avg_linear); // Output unpremultiplied sRGB color output_color = vec4(srgb_color, avg_alpha); } ================================================ FILE: kitty/screenshot_vertex.glsl ================================================ uniform vec4 src_rect, dest_rect; #pragma kitty_include_shader ================================================ FILE: kitty/search_query_parser.py ================================================ #!/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) ================================================ FILE: kitty/session.py ================================================ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import json import os import re import shlex import sys from collections.abc import Callable, Generator, Iterator, Mapping from contextlib import suppress from functools import partial from gettext import gettext as _ from typing import TYPE_CHECKING, Any, Optional, Sequence, Union from .cli_stub import CLIOptions, GotoSessionOptions, SaveAsSessionOptions from .constants import config_dir, unserialize_launch_flag from .fast_data_types import get_options 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_compat import BossType, SpecialWindowInstance, WindowType 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'], serialized_id: int = 0, run_command_at_shell_startup: Sequence[str] | str = () ): self.launch_spec = launch_spec self.resize_spec: ResizeSpec | None = None self.focus_matching_window_spec: str = '' self.is_background_process = False self.serialized_id = serialized_id self.run_command_at_shell_startup = run_command_at_shell_startup 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 = -1 self.enabled_layouts = opts.enabled_layouts self.layout = (self.enabled_layouts or ['tall'])[0] self.layout_state: dict[str, Any] | None = None 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: session_name: str = '' num_of_windows_in_definition: int = 0 def __init__(self, default_title: str | None = None): self.tabs: list[Tab] = [] self.active_tab_idx = 0 self.focus_tab_spec: str | None = None 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.os_window_title: 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 set_layout_state(self, val: str) -> None: self.tabs[-1].layout_state = json.loads(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)) serialize_data: dict[str, Any] = {'id': 0, 'cmd_at_shell_startup': ()} if cmd and cmd[0].startswith(unserialize_launch_flag): serialize_data = json.loads(cmd[0][len(unserialize_launch_flag):]) del cmd[0] 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, serialized_id=serialize_data['id'], run_command_at_shell_startup=serialize_data.get('cmd_at_shell_startup', ()))) 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 focus_tab(self, spec: str) -> None: self.focus_tab_spec = spec 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, session_base_dir: str) -> None: if val: val = os.path.expanduser(val) if not os.path.isabs(val): val = os.path.abspath(os.path.join(session_base_dir, val)) self.tabs[-1].cwd = val elif session_base_dir: self.tabs[-1].cwd = session_base_dir SESSION_FILE_EXTENSIONS = {'session', 'kitty-session', 'kitty_session'} def has_session_extension(path: str) -> bool: name = os.path.basename(path) return name.rpartition('.')[2] in SESSION_FILE_EXTENSIONS def session_arg_to_name(session_arg: str) -> str: if session_arg in ('-', '/dev/stdin', 'none'): session_arg = '' session_name = os.path.basename(session_arg) if has_session_extension(session_name): session_name = session_name.rpartition('.')[0] return session_name def resolve_session_arg_path(path: str) -> str: path = os.path.expanduser(path) if not os.path.isabs(path): path = os.path.join(config_dir, path) return os.path.abspath(path) def parse_session( raw: str, opts: Options, environ: Mapping[str, str] | None = None, session_arg: str = '', session_path: str = '' ) -> Generator[Session, None, None]: session_name = session_arg_to_name(session_arg) if session_path: session_base_dir = os.path.dirname(os.path.abspath(session_path)) if session_name: seen_session_paths[session_name] = session_path append_to_session_history(session_name) else: session_base_dir = os.getcwd() def finalize_session(ans: Session) -> Session: ans.session_name = session_name ans.num_of_windows_in_definition = sum(len(t.windows) for t in ans.tabs) 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 not in ('launch', 'set_layout_state'): 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_tab': ans.focus_tab(rest) 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, session_base_dir) 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_title': ans.os_window_title = 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) elif cmd == 'set_layout_state': ans.set_layout_state(rest) else: raise ValueError(f'Unknown command in session file: {cmd}') yield finalize_session(ans) class PreReadSession(str): associated_environ: Mapping[str, str] session_arg: str session_path: str def __new__(cls, val: str, associated_environ: Mapping[str, str], session_arg: str, session_path: str) -> 'PreReadSession': ans: PreReadSession = str.__new__(cls, val) ans.associated_environ = associated_environ ans.session_arg = session_arg ans.session_path = session_path 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, env_when_no_session: dict[str, str] | None = None, ) -> Iterator[Session]: if args and args.session: if args.session == "none": default_session = "none" else: session_arg = args.session session_path = '' environ: Mapping[str, str] | None = None if isinstance(args.session, PreReadSession): session_data = '' + str(args.session) environ = args.session.associated_environ session_arg = args.session.session_arg session_path = args.session.session_path else: if args.session == '-': f = sys.stdin else: f = open(resolve_custom_file(args.session)) with f: session_data = f.read() session_path = f.name yield from parse_session(session_data, opts, environ=environ, session_arg=session_arg, session_path=session_path) return if default_session and default_session != 'none' and not getattr(args, 'args', None): session_arg = session_arg_to_name(default_session) try: with open(default_session) as f: session_data = f.read() session_path = f.name except OSError: log_error(f'Failed to read from session file, ignoring: {default_session}') else: yield from parse_session(session_data, opts, session_arg=session_arg, session_path=session_path) 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 args is not None: ans.os_window_class = args.cls ans.os_window_name = args.name ans.os_window_title = args.title 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, env=env_when_no_session, hold=bool(args and args.hold)) ans.add_special_window(special_window) yield ans def window_for_session_name(boss: BossType, session_name: str) -> WindowType | None: windows = [w for w in boss.all_windows if w.created_in_session_name == session_name] if not windows: tabs = (t for t in boss.all_tabs if t.created_in_session_name == session_name) windows = [t.active_window for t in tabs if t.active_window] if not windows: os_windows = (tm for tm in boss.all_tab_managers if tm.created_in_session_name == session_name) windows = [tm.active_window for tm in os_windows if tm.active_window] if windows: def skey(w: WindowType) -> float: return w.last_focused_at windows.sort(key=skey, reverse=True) return windows[0] return None seen_session_paths: dict[str, str] = {} def create_session(boss: BossType, path: str) -> tuple[str, bool]: session_name = '' created_new_os_window = False for i, s in enumerate(create_sessions(get_options(), default_session=path)): if i == 0: session_name = s.session_name if s.num_of_windows_in_definition == 0: # leading new_os_window continue tm = boss.active_tab_manager if tm is None: os_window_id = boss.add_os_window(s) created_new_os_window = True else: os_window_id = tm.os_window_id tm.add_tabs_from_session(s, session_name) else: os_window_id = boss.add_os_window(s) created_new_os_window = True if s.focus_os_window: boss.focus_os_window(os_window_id) return session_name, created_new_os_window goto_session_history: list[str] = [] def append_to_session_history(name: str) -> None: with suppress(ValueError): goto_session_history.remove(name) goto_session_history.append(name) def most_recent_session() -> str: return goto_session_history[-1] if goto_session_history else '' def switch_to_session(boss: BossType, session_name: str) -> bool: w = window_for_session_name(boss, session_name) if w is not None: append_to_session_history(session_name) boss.set_active_window(w, switch_os_window_if_needed=True) return True return False def resolve_session_path_and_name(path: str) -> tuple[str, str]: path = resolve_session_arg_path(path) return path, session_arg_to_name(path) def get_all_known_sessions() -> dict[str, str]: opts = get_options() all_known_sessions = seen_session_paths.copy() for km in opts.keyboard_modes.values(): for kdefs in km.keymap.values(): for kd in kdefs: for key_action in opts.alias_map.resolve_aliases(kd.definition, 'map'): if key_action.func == 'goto_session': path = '' for x in key_action.args: if isinstance(x, str) and not x.startswith('-'): path = x break if path: path, session_name = resolve_session_path_and_name(path) if session_name not in all_known_sessions: all_known_sessions[session_name] = path return all_known_sessions def close_session_with_confirm(boss: BossType, cmdline: Sequence[str]) -> None: if not cmdline: names = sorted(boss.all_loaded_session_names, key=lambda x: x.lower()) if not names: boss.ring_bell_if_allowed() return if len(names) == 1: return close_session_with_confirm(boss, names) def chosen(name: str | None) -> None: if name: close_session_with_confirm(boss, (name,)) boss.choose_entry( _('Select a session to close'), ((name, name) for name in names), chosen) return if len(cmdline) != 1: boss.show_error(_('Invalid close_session specification'), _('{} is not a valid argument to close_session').format(shlex.join(cmdline))) return path_or_name = cmdline[0] if path_or_name == '.': if name := boss.active_session: close_session_with_confirm(boss, (name,)) else: boss.ring_bell_if_allowed() return if '/' in path_or_name: path_to_name = {v: k for k, v in get_all_known_sessions().items()} name = path_to_name.get(path_or_name, '') if not name: boss.ring_bell_if_allowed() return else: name = path_or_name windows = tuple(w for w in boss.all_windows if w.created_in_session_name == name) if not windows: return msg, num_active_windows = boss.close_windows_with_confirmation_msg(windows, boss.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) def do_close(confirmed: bool) -> None: if confirmed: boss.close_windows_no_confirm(windows) if needs_confirmation: msg = msg or _('It has {} windows?').format(num) msg = _('Are you sure you want to close this session?') + ' ' + msg boss.confirm(msg, do_close, window=boss.active_window, title=_('Close session?')) else: do_close(True) def choose_session_from_map( boss: BossType, opts: GotoSessionOptions, session_map: Mapping[str, str], title: str ) -> bool: if not session_map: return False hmap = {n: len(goto_session_history)-i for i, n in enumerate(goto_session_history)} if opts.sort_by == 'alphabetical': def skey(name: str) -> tuple[int, str]: return 0, name.lower() else: def skey(name: str) -> tuple[int, str]: return hmap.get(name, len(goto_session_history)), name.lower() names = sorted(session_map, key=skey) def chosen(name: str | None) -> None: if name: goto_session(boss, (session_map[name],)) boss.choose_entry(title, ((name, name) for name in names), chosen) return True def choose_session(boss: BossType, opts: GotoSessionOptions) -> None: all_known_sessions = get_all_known_sessions() if opts.active_only: all_active_sessions = boss.all_loaded_session_names all_known_sessions = {name: all_known_sessions[name] for name in all_active_sessions if name in all_known_sessions} choose_session_from_map(boss, opts, all_known_sessions, _('Select a session to activate')) def choose_session_in_directory(boss: BossType, opts: GotoSessionOptions, directory_path: str) -> None: try: with os.scandir(directory_path) as entries: session_map = { session_arg_to_name(entry.path): entry.path for entry in entries if entry.is_file() and has_session_extension(entry.name) } except OSError as e: boss.show_error( _('Failed to list sessions'), _('Could not list session files in {0} with error: {1}').format(directory_path, e)) return session_map = {name: path for name, path in session_map.items() if name} if not choose_session_from_map( boss, opts, session_map, _('Select a session to activate from {0}').format(directory_path) ): boss.show_error( _('No session files found'), _('No session files were found inside {0}').format(directory_path)) def parse_goto_session_cmdline(args: list[str]) -> tuple[GotoSessionOptions, list[str]]: from kitty.cli import cached_parse_cmdline ans = GotoSessionOptions() leftover_args = cached_parse_cmdline(goto_session_options(), args, ans) return ans, leftover_args def goto_session_options() -> str: return ''' --sort-by choices=recent,alphabetical default=recent When interactively choosing sessions from a list, how to sort the list. --active-only type=bool-set Only consider active sessions. ''' def goto_previous_session(boss: BossType, idx: int) -> None: if boss.active_session: nidx = max(0, len(goto_session_history) - 1 + idx) if nidx < len(goto_session_history): switch_to_session(boss, goto_session_history[nidx]) return else: if goto_session_history: switch_to_session(boss, goto_session_history[-1]) return boss.ring_bell_if_allowed() def goto_session(boss: BossType, cmdline: Sequence[str]) -> None: if len(cmdline) == 1 and re.match(r'-\d+', cmdline[0]) is not None: # special case for backwards compat goto_session -1 return goto_previous_session(boss, int(cmdline[0])) try: opts, cmdline = parse_goto_session_cmdline(list(cmdline)) except Exception as e: boss.show_error(_('Invalid goto_session command'), _( 'The command goto_session {0} is invalid with error: {1}').format(shlex.join(cmdline), e)) return if not cmdline: choose_session(boss, opts) return path = cmdline[0] if len(cmdline) == 1: # goto_session -- -1 try: idx = int(path) except Exception: idx = 0 if idx < 0: return goto_previous_session(boss, idx) resolved_path = resolve_session_arg_path(path) if os.path.isdir(resolved_path): choose_session_in_directory(boss, opts, resolved_path) return path, session_name = resolve_session_path_and_name(resolved_path) if not session_name: boss.show_error(_('Invalid session'), _('{} is not a valid path for a session').format(path)) return if switch_to_session(boss, session_name): return try: session_name, created_new_os_window = create_session(boss, path) except Exception: import traceback tb = traceback.format_exc() boss.show_error(_('Failed to create session'), _('Could not create session from {0} with error:\n{1}').format(path, tb)) else: # Ensure newly created session is focused needed when it doesn't create its own OS Windows. if not created_new_os_window: switch_to_session(boss, session_name) save_as_session_message = '''\ Save the current state of kitty as a session file for easy re-use. If the path at which to save the session file is not specified, kitty will prompt you for one. If the path is :code:`.` it will save the session to the path of the currently active session, if there is one, otherwise prompt you for a path. ''' def save_as_session_options() -> str: return ''' --save-only type=bool-set Only save the specified session file, dont open it in an editor to review after saving. --use-foreground-process type=bool-set When saving windows that were started with the default shell but are currently running some other process inside that shell, save that process so that when the session is used both the shell **and** the process running inside it are re-started. This is most useful when you have opened programs like editors or similar inside windows that started out running the shell and you want to preserve that. WARNING: Be careful when using this option, if you are running some dangerous command like :file:`rm` or :file:`mv` or similar in a shell, it will be re-run when the session is executed if you use this option. Note that this option requires :ref:`shell_integration` to work. --relocatable type=bool-set When saving the working directory for windows, do so as paths relative to the directory in which the session file will be saved. This allows the session file to work even when its containing directory is moved elsewhere. --match If specified, only save all windows (and their parent tabs/OS Windows) that match the specified search expression. See :ref:`search_syntax` for details on the search language. In particular if you want to only save windows that are present in the currently active session, use :code:`--match=session:.`. --base-dir When specified, relative session filenames will be saved to this directory instead of the current working directory. This is useful when kitty is launched from locations where the working directory is not your home directory, such as from system-wide shortcuts. Note that :code:`--relocatable` is typically not used with :code:`--base-dir`, since relocatable is meant for session files that are co-located with their project directories. ''' def save_as_session_part2(boss: BossType, opts: SaveAsSessionOptions, path: str) -> None: if not path: return from .config import atomic_save if opts.base_dir and not os.path.isabs(path): base_dir = os.path.abspath(os.path.expanduser(opts.base_dir)) path = os.path.join(base_dir, path) path = os.path.abspath(os.path.expanduser(path)) session = '\n'.join(boss.serialize_state_as_session(path, opts)) os.makedirs(os.path.dirname(path), exist_ok=True) atomic_save(session.encode(), path) if not opts.save_only: boss.edit_file(path) def parse_save_as_options_spec_args(args: list[str]) -> tuple[SaveAsSessionOptions, list[str]]: from kitty.cli import cached_parse_cmdline ans = SaveAsSessionOptions() leftover_args = cached_parse_cmdline(save_as_session_options(), args, ans) return ans, leftover_args def default_save_as_session_opts() -> SaveAsSessionOptions: return parse_save_as_options_spec_args([])[0] def save_as_session(boss: BossType, cmdline: Sequence[str]) -> None: opts, args = parse_save_as_options_spec_args(list(cmdline)) path = args[0] if args else '' if path == '.': sn = boss.active_session path = seen_session_paths.get(sn) or '' if path: save_as_session_part2(boss, opts, path) else: boss.get_save_filepath(_( 'Enter the path at which to save the session, usually session files are given the .kitty-session file extension'), partial(save_as_session_part2, boss, opts)) ================================================ FILE: kitty/shaders.c ================================================ /* * 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 #include "text-cache.h" #include "window_logo.h" #include "srgb_gamma.h" #include "uniforms_generated.h" #include "state.h" enum { CELL_PROGRAM, CELL_FG_PROGRAM, CELL_BG_PROGRAM, CELL_PROGRAM_SENTINEL, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, TRAIL_PROGRAM, BLIT_PROGRAM, SCREENSHOT_PROGRAM, ROUNDED_RECT_PROGRAM, NUM_PROGRAMS }; enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; typedef struct UIRenderData { unsigned screen_width, screen_height, cell_width, cell_height, screen_left, screen_top, full_framebuffer_width, full_framebuffer_height; Window *window; Screen *screen; OSWindow *os_window; GraphicsRenderData grd; WindowLogoRenderData *window_logo; float bg_alpha, inactive_text_alpha; bool has_background_image; color_type background_color; // RGB only } UIRenderData; static inline float row_offset_for_screen(const Screen *screen) { if (!pixel_scroll_enabled(screen) || !screen->cell_size.height) return 0.f; return -1.f + (float)(screen->pixel_scroll_offset_y / (double)screen->cell_size.height); } static inline float scroll_offset_lines_for_screen(const Screen *screen) { if (!pixel_scroll_enabled(screen) || !screen->cell_size.height) return 0.f; return (float)(screen->pixel_scroll_offset_y / (double)screen->cell_size.height); } // 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); } static void color_vec4(GLint location, color_type color, GLfloat alpha) { glUniform4f(location, srgb_lut[(color >> 16) & 0xFF], srgb_lut[(color >> 8) & 0xFF], srgb_lut[color & 0xFF], alpha); } static void clear_current_framebuffer(void) { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); } 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->decorations_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); free_texture(&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); free_texture(&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); } // }}} // Rounded rect {{{ typedef struct { Rounded_rectUniforms uniforms; } RoundedRectProgramLayout; static RoundedRectProgramLayout rounded_rect_program_layout; static double thickness_as_float(const OSWindow *os_window, unsigned level) { level = MIN(level, arraysz(OPT(box_drawing_scale))); double pts = OPT(box_drawing_scale)[level]; double dpi = (os_window->fonts_data->logical_dpi_x + os_window->fonts_data->logical_dpi_y) / 2.0; return pts * dpi / 72.0; } static void draw_rounded_rect( const OSWindow *os_window, Viewport rect, unsigned framebuffer_height, unsigned thickness_level, unsigned corner_radius_px, color_type srgb_color, color_type srgb_background, float bg_alpha ) { float thickness = (float)thickness_as_float(os_window, thickness_level); bind_program(ROUNDED_RECT_PROGRAM); color_vec4(rounded_rect_program_layout.uniforms.color, srgb_color, 1.f); color_vec4(rounded_rect_program_layout.uniforms.background_color, srgb_background, bg_alpha); // y co-ord has to be changed to co-ord system with origin at bottom left float y = (float)framebuffer_height - (float)(rect.top + rect.height); glUniform4f(rounded_rect_program_layout.uniforms.rect, rect.left, y, rect.width, rect.height); glUniform2f(rounded_rect_program_layout.uniforms.params, thickness, corner_radius_px); save_viewport_using_top_left_origin(rect.left, rect.top, rect.width, rect.height, framebuffer_height); draw_quad(true, 0); restore_viewport(); } // }}} // Cell {{{ 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; typedef struct { TrailUniforms uniforms; } TrailProgramLayout; static TrailProgramLayout trail_program_layout; typedef struct { BlitUniforms uniforms; } BlitProgramLayout; static BlitProgramLayout blit_program_layout; typedef struct { ScreenshotUniforms uniforms; } ScreenshotProgramLayout; static ScreenshotProgramLayout screenshot_program_layout; static void init_cell_program(void) { for (int i = CELL_PROGRAM; i < CELL_PROGRAM_SENTINEL; 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 < CELL_PROGRAM_SENTINEL; 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); get_uniform_locations_trail(TRAIL_PROGRAM, &trail_program_layout.uniforms); get_uniform_locations_blit(BLIT_PROGRAM, &blit_program_layout.uniforms); get_uniform_locations_screenshot(SCREENSHOT_PROGRAM, &screenshot_program_layout.uniforms); get_uniform_locations_rounded_rect(ROUNDED_RECT_PROGRAM, &rounded_rect_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(color_type cell_fg, color_type cell_bg, color_type *cursor_fg, color_type *cursor_bg, color_type default_fg, color_type default_bg) { ARGB32 fg, bg, dfg, dbg; 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 bool has_bgimage(OSWindow *w) { return w->bgimage && w->bgimage->texture_id > 0; } static color_type cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, const CursorRenderInfo *cursor, OSWindow *os_window, float inactive_text_alpha, float bg_alpha) { struct GPUCellRenderData { GLfloat use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg; GLuint default_fg, highlight_fg, highlight_bg, main_cursor_fg, main_cursor_bg, url_color, url_style, inverted, extra_cursor_fg, extra_cursor_bg; GLuint columns, lines, sprites_xnum, sprites_ynum, cursor_shape, cell_width, cell_height; GLuint cursor_x1, cursor_x2, cursor_y1, cursor_y2; GLfloat cursor_opacity, inactive_text_alpha, dim_opacity, blink_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 ColorProfile *cp = screen->paused_rendering.expires_at ? &screen->paused_rendering.color_profile : screen->color_profile; const bool color_table_needs_upload = cp->dirty || screen->reload_all_gpu_data; const unsigned sz = color_table_needs_upload ? cell_program_layouts[CELL_PROGRAM].render_data.size : cell_program_layouts[CELL_PROGRAM].color_table.offset; struct GPUCellRenderData *rd = (struct GPUCellRenderData*)map_vao_buffer_for_write_only(vao_idx, uniform_buffer, 0, sz); if (color_table_needs_upload) { 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->extra_cursor_fg = screen->extra_cursors.color.text.val; rd->extra_cursor_bg = screen->extra_cursors.color.cursor.val; rd->bg_colors0 = COLOR(default_bg); rd->bg_opacities0 = bg_alpha; #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 Line *line_for_cursor = NULL; rd->cursor_opacity = MAX(0, MIN(cursor->cursor_opacity, 1)); rd->blink_opacity = MAX(0, MIN(cursor->text_blink_opacity, 1)); if (rd->cursor_opacity != 0 && cursor->is_visible) { rd->cursor_x1 = cursor->x, rd->cursor_y1 = cursor->y; rd->cursor_x2 = cursor->x, rd->cursor_y2 = cursor->y; if (pixel_scroll_enabled(screen)) { rd->cursor_y1 += 1; rd->cursor_y2 += 1; } CursorShape cs = (cursor->is_focused || OPT(cursor_shape_unfocused) == NO_CURSOR_SHAPE) ? cursor->shape : OPT(cursor_shape_unfocused); rd->cursor_shape = cs; 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 you change the following algorithm remember to change it in the cell shader for extra cursors too if (IS_SPECIAL_COLOR(cursor_color)) { if (line_for_cursor) pick_cursor_color(cell_fg, cell_bg, &rd->main_cursor_fg, &rd->main_cursor_bg, rd->default_fg, rd->bg_colors0); else { rd->main_cursor_fg = rd->bg_colors0; rd->main_cursor_bg = rd->default_fg; } if (cell_bg == cell_fg) { rd->main_cursor_fg = rd->bg_colors0; rd->main_cursor_bg = rd->default_fg; } else { rd->main_cursor_fg = cell_bg; rd->main_cursor_bg = cell_fg; } } else { rd->main_cursor_bg = COLOR(cursor_color); if (IS_SPECIAL_COLOR(cursor_text_color)) rd->main_cursor_fg = cell_bg; else rd->main_cursor_fg = COLOR(cursor_text_color); } // store last rendered cursor color for trail rendering screen->last_rendered.cursor_bg = rd->main_cursor_bg; } else { rd->cursor_shape = 0; rd->cursor_x1 = screen->columns + 1; rd->cursor_x2 = screen->columns; rd->cursor_y1 = screen->lines + 1; rd->cursor_y2 = screen->lines; } rd->columns = screen->columns; rd->lines = screen->lines; 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_width = os_window->fonts_data->fcm.cell_width; rd->cell_height = os_window->fonts_data->fcm.cell_height; rd->inactive_text_alpha = inactive_text_alpha; rd->dim_opacity = OPT(dim_opacity); #undef COLOR rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); color_type default_bg = rd->bg_colors0; unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL; return default_bg; } static bool cell_prepare_to_render(ssize_t vao_idx, Screen *screen, 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 { \ const unsigned int render_lines = render_lines_for_screen(screen); \ sz = sizeof(GPUCell) * render_lines * screen->columns; \ address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, true); \ 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; #define update_selection_data { \ const unsigned int render_lines = render_lines_for_screen(screen); \ sz = (size_t)render_lines * screen->columns; \ address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, true); \ 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, scroll_offset_lines_for_screen(screen), -1.f, 1.f, 2.f/screen->columns, 2.f/screen->lines, 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; screen->last_rendered.cursor = screen->cursor_render_info; return changed; } static void draw_graphics(int program, ImageRenderData *data, GLuint start, GLuint count, float extra_alpha) { bind_program(program); if (program != GRAPHICS_ALPHA_MASK_PROGRAM) glUniform1f(graphics_program_layouts[program].uniforms.extra_alpha, extra_alpha); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); GraphicsUniforms *u = &graphics_program_layouts[program].uniforms; 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); draw_quad(true, 0); } } } 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 setup_texture_as_render_target(unsigned width, unsigned height, GLuint *texture_id, GLuint *framebuffer_id) { glGenTextures(1, texture_id); glGenFramebuffers(1, framebuffer_id); glBindTexture(GL_TEXTURE_2D, *texture_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // We use GL_RGBA16 to avoid incorrect colors due to quantization loss when // blending, see https://github.com/kovidgoyal/kitty/issues/8953 static struct { bool ok; int fmt; } status = { false, GL_RGBA16}; glTexImage2D(GL_TEXTURE_2D, 0, status.fmt, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); bind_framebuffer_for_output(*framebuffer_id); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture_id, 0); if (!status.ok) { if (check_framebuffer_status() == NULL) { status.ok = true; } else { if (status.fmt == GL_RGBA16) { // Driver does not support 16 bit FBO so let it choose the // format. It will probably end up choosing 8 bit but // inaccurate colors are better than completely broken rendering. // See https://github.com/kovidgoyal/kitty/issues/9068 status.fmt = GL_RGBA; free_framebuffer(framebuffer_id); free_texture(texture_id); setup_texture_as_render_target(width, height, texture_id, framebuffer_id); log_error("WARNING: Your GPU driver does not support 16bit textures as framebuffer targets, some colors may be slightly inaccurate."); } else { fatal("Your GPU driver does not support indirect rendering to a GL_RGBA texture via a framebuffer"); } } } } static void set_cell_uniforms(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_ALPHA_MASK_PROGRAM; i++) { bind_program(i); glUniform1i(graphics_program_layouts[i].uniforms.image, GRAPHICS_UNIT); } for (int i = CELL_PROGRAM; i < CELL_PROGRAM_SENTINEL; i++) { bind_program(i); const CellUniforms *cu = &cell_program_layouts[i].uniforms; glUniform1i(cu->sprites, SPRITE_MAP_UNIT); glUniform1i(cu->sprite_decorations_map, SPRITE_DECORATIONS_MAP_UNIT); glUniform1f(cu->text_contrast, text_contrast); glUniform1f(cu->text_gamma_adjustment, text_gamma_adjustment); } bind_program(BLIT_PROGRAM); glUniform1i(blit_program_layout.uniforms.image, GRAPHICS_UNIT); bind_program(SCREENSHOT_PROGRAM); glUniform1i(screenshot_program_layout.uniforms.image, GRAPHICS_UNIT); constants_set = true; } } // UI Layer {{{ static Animation *default_visual_bell_animation = NULL; static bool has_visual_bell(Screen *screen) { return screen->start_visual_bell_at > 0; } 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; } static void draw_visual_bell_flash(GLfloat intensity, const color_type flash) { bind_program(TINT_PROGRAM); GLfloat attenuation = 0.4f; #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, -1, 1, 1, -1); draw_quad(true, 0); } static void draw_visual_bell(const UIRenderData *ui) { if (!has_visual_bell(ui->screen)) return; Screen *screen = ui->screen; float intensity = get_visual_bell_intensity(screen); if (intensity <= 0) return; #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) color_type flash = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg); draw_visual_bell_flash(intensity, flash); #undef COLOR } static bool has_scrollbar(Window *w, Screen *screen) { if (screen->linebuf != screen->main_linebuf || !screen->historybuf->count) return false; switch (OPT(scrollbar)) { case SCROLLBAR_NEVER: return false; case SCROLLBAR_ALWAYS: return true; case SCROLLBAR_ON_SCROLLED: return screen->scrolled_by > 0; case SCROLLBAR_ON_HOVERED: return w->scrollbar.is_hovering; case SCROLLBAR_ON_SCROLL_AND_HOVER: return screen->scrolled_by > 0 && w->scrollbar.is_hovering; } return false; } static unsigned render_a_bar(const UIRenderData *ui, WindowBarData *bar, PyObject *title, bool along_bottom) { unsigned border_width = (unsigned)ceil(thickness_as_float(ui->os_window, 1)); unsigned bar_height = ui->cell_height + 2; unsigned bar_width = ui->screen_width - 2 * border_width; 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; } #define RGBCOL(which, fallback) ( 0xff000000 | colorprofile_to_color_with_fallback(ui->screen->color_profile, ui->screen->color_profile->overridden.which, ui->screen->color_profile->configured.which, ui->screen->color_profile->overridden.fallback, ui->screen->color_profile->configured.fallback)) color_type fg = RGBCOL(default_fg, default_fg), bg = RGBCOL(default_bg, default_bg); #undef RGBCOL 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)); if (!draw_window_title(ui->os_window->fonts_data->font_sz_in_pts, ui->os_window->fonts_data->logical_dpi_y, titlebuf, fg, bg, bar->buf, bar_width, bar_height)) return 0; Py_CLEAR(bar->last_drawn_title_object_id); bar->last_drawn_title_object_id = Py_NewRef(title); } static ImageRenderData data = {.group_count=1}; gpu_data_for_image(&data, -1, 1, 1, -1); 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); bind_program(GRAPHICS_PROGRAM); Viewport border_rect = { .height=bar_height + 2 * border_width, .left=ui->screen_left, .width=ui->screen_width, .top=ui->screen_top}; if (along_bottom) border_rect.top += ui->screen_height - border_rect.height; const unsigned sh = ui->full_framebuffer_height; // first blank the area to be drawn to background enable_scissor_using_top_left_origin(border_rect, sh); blank_canvas(ui->bg_alpha, bg, false); disable_scissor(); // then draw the rendered text save_viewport_using_top_left_origin( border_rect.left + border_width, border_rect.top + border_width, bar_width, bar_height, sh); draw_graphics(GRAPHICS_PROGRAM, &data, 0, 1, 1.f); restore_viewport(); free_texture(&data.texture_id); // finally draw border with transparent bg draw_rounded_rect(ui->os_window, border_rect, sh, 1, ui->cell_width, fg, bg, 0.f); return border_rect.height; } static bool has_hyperlink_target(OSWindow *os_window, Window *w, Screen *screen) { return OPT(show_hyperlink_targets) && screen->current_hyperlink_under_mouse.id && w && !is_mouse_hidden(os_window) && global_state.mouse_hover_in_window == w->id; } static void draw_hyperlink_target(const UIRenderData *ui) { if (!has_hyperlink_target(ui->os_window, ui->window, ui->screen)) return; Screen *screen = ui->screen; const bool along_bottom = screen->current_hyperlink_under_mouse.y < 3; Window *window = ui->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_display_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; PyObject *ref = Py_NewRef(bd->last_drawn_title_object_id); // render_a_bar clears bd->last_drawn_title_object_id render_a_bar(ui, &window->title_bar_data, bd->last_drawn_title_object_id, along_bottom); Py_DECREF(ref); } static bool has_window_number(Window *w, Screen *screen) { return w != NULL && screen->display_window_char != 0; } static void draw_window_number(const UIRenderData *ui) { if (!has_window_number(ui->window, ui->screen)) return; unsigned title_bar_height = 0, requested_height = ui->screen_height; if (ui->window->title && PyUnicode_Check(ui->window->title) && (requested_height > (ui->cell_height + 1) * 2)) { title_bar_height = render_a_bar(ui, &ui->window->title_bar_data, ui->window->title, false); } unsigned height_for_letter = ui->screen_height - title_bar_height - ui->cell_height; unsigned width_for_letter = ui->screen_width - ui->cell_width; requested_height = MIN(12 * ui->cell_height, MIN(height_for_letter, width_for_letter)); if (requested_height < 4) return; #define lr ui->screen->last_rendered_window_char if (!lr.canvas || lr.ch != ui->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(ui->screen->display_window_char, &lr.width_px, &lr.height_px); if (lr.height_px < 4 || lr.width_px < 4 || !lr.canvas) return; lr.ch = ui->screen->display_window_char; } unsigned letter_x = 0, letter_y = title_bar_height; if (lr.width_px < ui->screen_width) letter_x = (ui->screen_width - lr.width_px) / 2; if (lr.height_px + title_bar_height < ui->screen_height) letter_y += (ui->screen_height - lr.height_px - title_bar_height) / 2; bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); ImageRenderData *ird = load_alpha_mask_texture(lr.width_px, lr.height_px, lr.canvas); gpu_data_for_image(ird, -1, 1, 1, -1); glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); color_type digit_color = colorprofile_to_color_with_fallback(ui->screen->color_profile, ui->screen->color_profile->overridden.highlight_bg, ui->screen->color_profile->configured.highlight_bg, ui->screen->color_profile->overridden.default_fg, ui->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); save_viewport_using_top_left_origin( ui->screen_left + letter_x, ui->screen_top + letter_y, lr.width_px, lr.height_px, ui->full_framebuffer_height); draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, ird, 0, 1, 1.f); restore_viewport(); #undef lr } // Helper function to extract and apply opacity to color components static void set_color_uniform_with_opacity(color_type color, float opacity) { float r = srgb_color((color >> 16) & 0xFF) * opacity; float g = srgb_color((color >> 8) & 0xFF) * opacity; float b = srgb_color(color & 0xFF) * opacity; glUniform4f(tint_program_layout.uniforms.tint_color, r, g, b, opacity); } static color_type scrollbar_color(Screen *screen, unsigned val) { switch (val & 0xff) { #define C(which) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which).rgb case 0: return C(default_fg); case 1: return C(highlight_bg); #undef C default: return val >> 8; } } static void draw_scrollbar(const UIRenderData *ui) { Screen *screen = ui->screen; Window *window = ui->window; if (!window || !screen || !has_scrollbar(window, screen)) return; color_type bar_color = scrollbar_color(screen, OPT(scrollbar_handle_color)), track_color = scrollbar_color(screen, OPT(scrollbar_track_color)); double cell_frac = (double)screen->pixel_scroll_offset_y / screen->cell_size.height; if (!OPT(pixel_scroll)) cell_frac = 0; float bar_frac = (float)(screen->scrolled_by + cell_frac) / MAX(1u, (float)screen->historybuf->count); float opacity = OPT(scrollbar_handle_opacity); float track_opacity = window->scrollbar.is_hovering ? OPT(scrollbar_track_hover_opacity) : OPT(scrollbar_track_opacity); GLsizei scrollbar_width_px = (GLsizei)(OPT(scrollbar_width) * ui->cell_width); if (window->scrollbar.is_hovering) scrollbar_width_px = (GLsizei)(OPT(scrollbar_hover_width) * ui->cell_width); GLsizei scrollbar_gap_px = (GLsizei)(OPT(scrollbar_gap) * ui->cell_width); unsigned scrollbar_radius = (unsigned)(OPT(scrollbar_radius) * ui->cell_width); // Calculate window boundaries including padding GLsizei window_right_edge = ui->screen_left + ui->screen_width + window->render_data.geometry.spaces.right; GLsizei window_top_edge = ui->screen_top - window->render_data.geometry.spaces.top; GLsizei window_height = ui->screen_height + window->render_data.geometry.spaces.top + window->render_data.geometry.spaces.bottom; // Position scrollbar on right side with gap GLsizei scrollbar_left = window_right_edge - scrollbar_width_px - scrollbar_gap_px; GLsizei scrollbar_top = window_top_edge + scrollbar_gap_px; GLsizei scrollbar_height = window_height - 2 * scrollbar_gap_px; // Calculate thumb size and position float visible_fraction = (float)screen->lines / (float)(screen->lines + screen->historybuf->count); float min_thumb_height_fraction = (OPT(scrollbar_min_handle_height) * ui->cell_height) / (float)window_height; float thumb_height_fraction = MAX(min_thumb_height_fraction, visible_fraction); // Convert to OpenGL coordinates (range -1.0 to 1.0, total span = 2.0) const float GL_COORD_SPAN = 2.0f; float thumb_height_gl = thumb_height_fraction * GL_COORD_SPAN; float available_space = GL_COORD_SPAN - thumb_height_gl; float thumb_bottom_gl = -1.0f + available_space * bar_frac; float thumb_top_gl = thumb_bottom_gl + thumb_height_gl; // Store thumb position for mouse interaction (normalized window coordinates) float scrollbar_top_in_window = (float)(window_top_edge + scrollbar_gap_px) / (float)ui->full_framebuffer_height; float scrollbar_height_in_window = (float)(window_height - 2 * scrollbar_gap_px) / (float)ui->full_framebuffer_height; float thumb_top_fraction = (1.0f - thumb_top_gl) / 2.0f; float thumb_bottom_fraction = (1.0f - thumb_bottom_gl) / 2.0f; window->scrollbar.thumb_top = scrollbar_top_in_window + thumb_top_fraction * scrollbar_height_in_window; window->scrollbar.thumb_bottom = scrollbar_top_in_window + thumb_bottom_fraction * scrollbar_height_in_window; // Set viewport for scrollbar area save_viewport_using_top_left_origin( scrollbar_left, scrollbar_top, scrollbar_width_px, scrollbar_height, ui->full_framebuffer_height ); // Draw scrollbar track (background) if (track_opacity > 0) { bind_program(TINT_PROGRAM); set_color_uniform_with_opacity(track_color, track_opacity); glUniform4f(tint_program_layout.uniforms.edges, -1.f, 1.f, 1.f, -1.f); draw_quad(true, 0); } // Draw scrollbar thumb (handle) if (scrollbar_radius > 0) { // Rounded thumb - use separate viewport and rounded rect program GLsizei thumb_height_px = (GLsizei)(thumb_height_fraction * scrollbar_height); GLsizei thumb_top_px = scrollbar_top + (GLsizei)(thumb_top_fraction * scrollbar_height); restore_viewport(); bind_program(ROUNDED_RECT_PROGRAM); color_vec4(rounded_rect_program_layout.uniforms.color, bar_color, opacity); color_vec4(rounded_rect_program_layout.uniforms.background_color, 0, 0.0f); float y = (float)ui->full_framebuffer_height - (float)(thumb_top_px + thumb_height_px); glUniform4f(rounded_rect_program_layout.uniforms.rect, (float)scrollbar_left, y, (float)scrollbar_width_px, (float)thumb_height_px); float thickness = (float)MAX(scrollbar_width_px, thumb_height_px); glUniform2f(rounded_rect_program_layout.uniforms.params, thickness, (float)scrollbar_radius); save_viewport_using_top_left_origin(scrollbar_left, thumb_top_px, scrollbar_width_px, thumb_height_px, ui->full_framebuffer_height); draw_quad(true, 0); restore_viewport(); } else { set_color_uniform_with_opacity(bar_color, opacity); glUniform4f(tint_program_layout.uniforms.edges, -1.f, thumb_top_gl, 1.f, thumb_bottom_gl); draw_quad(true, 0); restore_viewport(); } } static void draw_window_logo(const UIRenderData *ui) { struct { unsigned width, height; int left, top; } w; WindowLogoRenderData *wl = ui->window_logo; w.height = wl->instance->height; w.width = wl->instance->width; if (OPT(window_logo_scale.width) > 0 || OPT(window_logo_scale.height) > 0) { unsigned scaled_wl_width = ui->screen_width, scaled_wl_height = ui->screen_height; // [sx] Scales logo to sx % of the viewports shortest dimension, preserving aspect ratio if (OPT(window_logo_scale.height) < 0) { if (ui->screen_height < ui->screen_width) { scaled_wl_height = (int)(ui->screen_height * OPT(window_logo_scale.width) / 100); scaled_wl_width = wl->instance->width * scaled_wl_height / wl->instance->height; } else { scaled_wl_width = (int)(ui->screen_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); } w.width = scaled_wl_width; w.height = scaled_wl_height; } w.left = (int)(ui->screen_width * wl->position.canvas_x - w.width * wl->position.image_x); w.top = (int)(ui->screen_height * wl->position.canvas_y - w.height * wl->position.image_y); float left = gl_pos_x(w.left, ui->screen_width), top = gl_pos_y(w.top, ui->screen_height); ImageRenderData d = {.texture_id = wl->instance->texture_id}; gpu_data_for_image(&d, left, top, left + gl_size(w.width, ui->screen_width), top - gl_size(w.height, ui->screen_height)); draw_graphics(GRAPHICS_PROGRAM, &d, 0, 1, ui->inactive_text_alpha * OPT(window_logo_alpha)); } bool screen_needs_rendering_in_layers(OSWindow *os_window, Window *w, Screen *screen) { const bool has_ui = w && (has_visual_bell(screen) || has_scrollbar(w, screen) || has_hyperlink_target(os_window, w, screen) || has_window_number(w, screen) || w->window_logo.id); GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; return has_ui || grman_has_images(grman); } bool current_framebuffer_is_ok(void) { return check_framebuffer_status() == NULL; } // }}} enum { DRAW_NEITHER_BG = 0, DRAW_DEFAULT_BG = 1, DRAW_NON_DEFAULT_BG = 2, DRAW_BOTH_BG = 3}; static void call_cell_program(int program, const UIRenderData *ui, ssize_t vao_idx, bool for_final_output, unsigned draw_bg_bitfield) { bind_program(program); CELL_BUFFERS; bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[program].render_data.index); glUniform1ui(cell_program_layouts[program].uniforms.draw_bg_bitfield, draw_bg_bitfield); glUniform1f(cell_program_layouts[program].uniforms.row_offset, row_offset_for_screen(ui->screen)); if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB); draw_quad(!for_final_output, render_lines_for_screen(ui->screen) * ui->screen->columns); if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB); } static void draw_cells_without_layers(const UIRenderData *ui, ssize_t vao_idx) { call_cell_program(CELL_PROGRAM, ui, vao_idx, true, DRAW_BOTH_BG); } static void draw_tint(const UIRenderData *ui) { bind_program(TINT_PROGRAM); color_vec4_premult(tint_program_layout.uniforms.tint_color, ui->background_color, OPT(background_tint)); glUniform4f(tint_program_layout.uniforms.edges, -1, 1, 1, -1); draw_quad(true, 0); } static void draw_cells_with_layers(const UIRenderData *ui, ssize_t vao_idx) { if (ui->has_background_image && OPT(background_tint) > 0) draw_tint(ui); const bool has_content_between_background_and_foreground = ui->window_logo != NULL || ui->grd.num_of_below_refs > 0 || ui->grd.num_of_negative_refs > 0; if (has_content_between_background_and_foreground) { if (!ui->has_background_image) call_cell_program(CELL_BG_PROGRAM, ui, vao_idx, false, DRAW_DEFAULT_BG); if (ui->window_logo != NULL) draw_window_logo(ui); if (ui->grd.num_of_below_refs > 0) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, 0, ui->grd.num_of_below_refs, ui->inactive_text_alpha); call_cell_program(CELL_BG_PROGRAM, ui, vao_idx, false, DRAW_NON_DEFAULT_BG); if (ui->grd.num_of_negative_refs) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_below_refs, ui->grd.num_of_negative_refs, ui->inactive_text_alpha); call_cell_program(CELL_FG_PROGRAM, ui, vao_idx, false, DRAW_NEITHER_BG); } else call_cell_program(CELL_PROGRAM, ui, vao_idx, false, ui->has_background_image ? DRAW_NON_DEFAULT_BG : DRAW_BOTH_BG); if (ui->grd.num_of_positive_refs > 0) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_below_refs + ui->grd.num_of_negative_refs, ui->grd.num_of_positive_refs, ui->inactive_text_alpha); draw_visual_bell(ui); draw_scrollbar(ui); draw_hyperlink_target(ui); draw_window_number(ui); } void blank_canvas(float background_opacity, color_type color_in_srgb, bool for_final_output) { if (for_final_output) { // we need to write pre-multiplied sRGB color to framebuffer #define C(shift) ((((color_in_srgb >> shift) & 0xFF) / 255.f) * background_opacity) glClearColor(C(16), C(8), C(0), background_opacity); #undef C } else { // we need to write pre-multiplied linear color to framebuffer #define C(shift) srgb_color((color_in_srgb >> shift) & 0xFF) * background_opacity 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, Screen *screen, OSWindow *os_window) { bool changed = false; if (os_window->fonts_data) { if (cell_prepare_to_render(vao_idx, screen, os_window->fonts_data)) changed = true; } return changed; } void draw_cells(const WindowRenderData *srd, OSWindow *os_window, bool is_active_window, bool is_tab_bar, bool is_single_window, Window *window) { Screen *screen = srd->screen; CELL_BUFFERS; bind_vertex_array(srd->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); float bg_alpha = effective_os_window_alpha(os_window); color_type default_bg = cell_update_uniform_block( srd->vao_idx, screen, uniform_buffer, &screen->cursor_render_info, os_window, current_inactive_text_alpha, bg_alpha); set_cell_uniforms(screen->reload_all_gpu_data); WindowLogoRenderData *wl; if (window && (wl = &window->window_logo) && wl->id && (wl->instance = find_window_logo(global_state.all_window_logos, wl->id)) && wl->instance->load_from_disk_ok) { if (!window->window_logo.instance->texture_id) { set_on_gpu_state(window->window_logo.instance, true); } } else wl = NULL; GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; UIRenderData ui = { .screen_width = srd->geometry.right - srd->geometry.left, .screen_height = srd->geometry.bottom - srd->geometry.top, .cell_width = os_window->fonts_data->fcm.cell_width, .cell_height = os_window->fonts_data->fcm.cell_height, .screen_left = srd->geometry.left, .screen_top = srd->geometry.top, .full_framebuffer_width = os_window->viewport_width, .full_framebuffer_height = os_window->viewport_height, .window = window, .screen = screen, .os_window = os_window, .grd = grman_render_data(grman), .window_logo = wl, .inactive_text_alpha = current_inactive_text_alpha, .has_background_image = has_bgimage(os_window), .background_color = default_bg, .bg_alpha=effective_os_window_alpha(os_window), }; screen->reload_all_gpu_data = false; save_viewport_using_top_left_origin( ui.screen_left, ui.screen_top, ui.screen_width, ui.screen_height, ui.full_framebuffer_height); if (ui.os_window->needs_layers) draw_cells_with_layers(&ui, srd->vao_idx); else draw_cells_without_layers(&ui, srd->vao_idx); restore_viewport(); } // }}} // 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, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) { float background_opacity = effective_os_window_alpha(w); if (!num_border_rects) return; bind_program(BORDERS_PROGRAM); bind_vertex_array(vao_idx); const bool has_background_image = has_bgimage(w); if (has_background_image) background_opacity = OPT(background_tint) * OPT(background_tint_gaps); 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, false); 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); if (!w->needs_layers) glEnable(GL_FRAMEBUFFER_SRGB); draw_quad(has_background_image, num_border_rects); if (!w->needs_layers) glDisable(GL_FRAMEBUFFER_SRGB); unbind_program(); unbind_vertex_array(); } // }}} // Cursor Trail {{{ void draw_cursor_trail(CursorTrail *trail, Window *active_window) { bind_program(TRAIL_PROGRAM); 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_type trail_color = OPT(cursor_trail_color); if (trail_color == 0) { // 0 means "none" was specified trail_color = active_window ? active_window->render_data.screen->last_rendered.cursor_bg : OPT(foreground); } color_vec3(trail_program_layout.uniforms.trail_color, trail_color); glUniform1f(trail_program_layout.uniforms.trail_opacity, trail->opacity); draw_quad(true, 0); unbind_program(); } // }}} // OSWindow {{{ static void draw_bg_image(OSWindow *os_window, Tab *tab) { if (!has_bgimage(os_window)) return; BackgroundImageRenderSettings s = { .os_window.width = os_window->viewport_width, .os_window.height = os_window->viewport_height, .instance_id = os_window->bgimage->id, .layout=OPT(background_image_layout), .linear=OPT(background_image_linear), .bgcolor=OPT(background), .opacity=effective_os_window_alpha(os_window), }; GLfloat iwidth = os_window->bgimage->width, iheight = os_window->bgimage->height; GLfloat vwidth = s.os_window.width, vheight = s.os_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; } } 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; } bind_program(BGIMAGE_PROGRAM); // altough we dont use this VO we need to ensure *some* VAO is bound at this point. bind_vertex_array(tab->border_rects.vao_idx); glUniform4f(bgimage_program_layout.uniforms.sizes, vwidth, vheight, iwidth, iheight); glUniform1f(bgimage_program_layout.uniforms.tiled, tiled); glUniform4f(bgimage_program_layout.uniforms.positions, left, top, right, bottom); glUniform1i(bgimage_program_layout.uniforms.image, GRAPHICS_UNIT); color_vec4(bgimage_program_layout.uniforms.background, s.bgcolor, s.opacity); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); glBindTexture(GL_TEXTURE_2D, os_window->bgimage->texture_id); draw_quad(false, 0); unbind_program(); } 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); } static void draw_centered_alpha_mask(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); draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, data, 0, 1, 1.0); } 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 by %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(width, height, rendered.width, rendered.height, rendered.canvas, MAX(0.2f, MIN(OPT(background_opacity), 0.8f))); free(rendered.canvas); } } } 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(effective_os_window_alpha(osw), color, true); } static void start_os_window_rendering(OSWindow *os_window, Tab *tab) { // note that during live resize rendering is done in layers if (os_window->live_resize.in_progress) blank_os_window(os_window); if (os_window->needs_layers) { // Ensure the global shared texture is large enough for this window if (global_state.layers_render_texture.width < os_window->viewport_width || global_state.layers_render_texture.height < os_window->viewport_height) { if (global_state.layers_render_texture.texture_id) free_texture(&global_state.layers_render_texture.texture_id); if (global_state.layers_render_texture.framebuffer_id) free_framebuffer(&global_state.layers_render_texture.framebuffer_id); unsigned new_w = (unsigned)MAX(global_state.layers_render_texture.width, os_window->viewport_width); unsigned new_h = (unsigned)MAX(global_state.layers_render_texture.height, os_window->viewport_height); setup_texture_as_render_target(new_w, new_h, &global_state.layers_render_texture.texture_id, &global_state.layers_render_texture.framebuffer_id); global_state.layers_render_texture.width = (int)new_w; global_state.layers_render_texture.height = (int)new_h; global_state.layers_render_texture.texture_generation++; } // Create per-window framebuffer if needed and attach the global texture to it if (!os_window->indirect_output.framebuffer_id) glGenFramebuffers(1, &os_window->indirect_output.framebuffer_id); if (os_window->indirect_output.attached_texture_generation != global_state.layers_render_texture.texture_generation) { bind_framebuffer_for_output(os_window->indirect_output.framebuffer_id); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, global_state.layers_render_texture.texture_id, 0); os_window->indirect_output.attached_texture_generation = global_state.layers_render_texture.texture_generation; } set_framebuffer_to_use_for_output(os_window->indirect_output.framebuffer_id); bind_framebuffer_for_output(0); save_viewport_using_bottom_left_origin(0, 0, os_window->viewport_width, os_window->viewport_height); clear_current_framebuffer(); draw_bg_image(os_window, tab); } } static void stop_os_window_rendering(OSWindow *os_window, Tab *tab, Window *active_window) { if (OPT(cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail, active_window); if (os_window->needs_layers) { set_framebuffer_to_use_for_output(0); bind_framebuffer_for_output(0); bind_program(BLIT_PROGRAM); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); glBindTexture(GL_TEXTURE_2D, global_state.layers_render_texture.texture_id); float sx = global_state.layers_render_texture.width > 0 ? (float)os_window->viewport_width / (float)global_state.layers_render_texture.width : 1.f; float sy = global_state.layers_render_texture.height > 0 ? (float)os_window->viewport_height / (float)global_state.layers_render_texture.height : 1.f; glUniform4f(blit_program_layout.uniforms.src_rect, 0, sy, sx, 0); glUniform4f(blit_program_layout.uniforms.dest_rect, -1, 1, 1, -1); restore_viewport(); if (os_window->live_resize.in_progress) save_viewport_using_top_left_origin( 0, 0, os_window->viewport_width, os_window->viewport_height, os_window->live_resize.height); draw_quad(false, 0); if (os_window->live_resize.in_progress) { restore_viewport(); draw_resizing_text(os_window); } } } void setup_os_window_for_rendering(OSWindow *os_window, Tab *tab, Window *active_window, bool start) { if (start) start_os_window_rendering(os_window, tab); else stop_os_window_rendering(os_window, tab, active_window); } // Take a screenshot of the OS Window, must be called immediately after // the OSWindow is rendered into the back buffer and before the buffers // are swapped. If thumb_w or thumb_h are zero the are set to the corresponding // dimension of the source region (viewport or central region without tab bar). // Takes a screenshot of a rectangular region of the OSWindow's framebuffer. // The region parameter specifies which part of the framebuffer to capture. // Scaling is performed on the GPU using the SCREENSHOT_PROGRAM shader for better performance. // The shader properly handles sRGB color space conversion and downscaling. // Setting the thumbnail dimensions to zero disables scaling. void take_screenshot_of_rectangular_region(OSWindow *os_window, Region region, unsigned char *dst_buf, unsigned *thumb_w, unsigned *thumb_h) { unsigned vw = os_window->viewport_width; unsigned vh = os_window->viewport_height; // Calculate the source region dimensions unsigned src_height = region.bottom - region.top; unsigned src_width = region.right - region.left; if (!*thumb_w) *thumb_w = src_width; if (!*thumb_h) *thumb_h = src_height; *thumb_w = MIN(src_width, *thumb_w); *thumb_h = MIN(src_height, *thumb_h); // Create a texture to hold the current framebuffer content GLuint src_texture = 0; glGenTextures(1, &src_texture); glBindTexture(GL_TEXTURE_2D, src_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Copy the current framebuffer to the texture glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, vw, vh, 0); // Create a temporary framebuffer for GPU-based scaling GLuint temp_texture = 0, temp_framebuffer = 0; setup_texture_as_render_target(*thumb_w, *thumb_h, &temp_texture, &temp_framebuffer); // Save current state GLint current_framebuffer; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_framebuffer); // Bind our temporary framebuffer for rendering bind_framebuffer_for_output(temp_framebuffer); save_viewport_using_bottom_left_origin(0, 0, *thumb_w, *thumb_h); // Use the screenshot program to render the scaled framebuffer with proper color space handling bind_program(SCREENSHOT_PROGRAM); // Set source rectangle (normalized coordinates: 0 to 1) // Note: OpenGL texture origin is bottom-left, but Region uses top-left origin // Convert from screen coordinates (top-left origin) to OpenGL texture coordinates (bottom-left origin) float src_left_norm = (float)region.left / (float)vw; float src_right_norm = (float)region.right / (float)vw; float src_bottom_norm = (float)(vh - region.bottom) / (float)vh; float src_top_norm = (float)(vh - region.top) / (float)vh; glUniform4f(screenshot_program_layout.uniforms.src_rect, src_left_norm, src_top_norm, src_right_norm, src_bottom_norm); // Set destination rectangle (NDC coordinates: -1 to 1) glUniform4f(screenshot_program_layout.uniforms.dest_rect, -1.0f, -1.0f, 1.0f, 1.0f); // Set the source texture size for proper downscaling glUniform2f(screenshot_program_layout.uniforms.src_size, (float)vw, (float)vh); // Bind the source texture glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); glBindTexture(GL_TEXTURE_2D, src_texture); // Draw the scaled quad draw_quad(false, 0); // Read the scaled result glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, *thumb_w, *thumb_h, GL_RGBA, GL_UNSIGNED_BYTE, dst_buf); glPixelStorei(GL_PACK_ALIGNMENT, 4); // Restore previous state bind_framebuffer_for_output(current_framebuffer); restore_viewport(); // Clean up temporary resources free_texture(&src_texture); free_texture(&temp_texture); free_framebuffer(&temp_framebuffer); } // }}} // Python API {{{ static PyObject* pygpu_driver_version_string(PyObject *self UNUSED, PyObject *args UNUSED) { return PyUnicode_FromString(global_state.gl_version ? gl_version_string() : ""); } 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) 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(gpu_driver_version_string, 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), {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_FG_PROGRAM); C(CELL_BG_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(BLIT_PROGRAM); C(SCREENSHOT_PROGRAM); C(ROUNDED_RECT_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_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; } // }}} ================================================ FILE: kitty/shaders.py ================================================ #!/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, BLINK, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, COLOR_IS_INDEX, COLOR_IS_RGB, COLOR_IS_SPECIAL, COLOR_NOT_SET, DECORATION, DECORATION_MASK, DIM, GLSL_VERSION, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, MARK, MARK_MASK, REVERSE, ROUNDED_RECT_PROGRAM, SCREENSHOT_PROGRAM, STRIKETHROUGH, TINT_PROGRAM, TRAIL_PROGRAM, compile_program, get_options, init_cell_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 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(allow_recompile=True) def __call__(self, allow_recompile: bool = False) -> None: 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, BLINK_SHIFT=BLINK, DECORATION_SHIFT=DECORATION, MARK_SHIFT=MARK, MARK_MASK=MARK_MASK, DECORATION_MASK=DECORATION_MASK, COLOR_NOT_SET=COLOR_NOT_SET, COLOR_IS_SPECIAL=COLOR_IS_SPECIAL, COLOR_IS_INDEX=COLOR_IS_INDEX, COLOR_IS_RGB=COLOR_IS_RGB, ) def resolve_cell_defines(only_fg: int, only_bg: int, src: str) -> str: r = self.cell_program_replacer.replacements r['ONLY_FOREGROUND'] = str(only_fg) r['ONLY_BACKGROUND'] = str(only_bg) 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 prog, (only_fg, only_bg) in { CELL_PROGRAM: (0, 0), CELL_FG_PROGRAM: (1, 0), CELL_BG_PROGRAM: (0, 1), }.items(): fn = partial(resolve_cell_defines, only_fg, only_bg) cell.apply_to_sources(vertex=fn, frag=fn) cell.compile(prog, allow_recompile) graphics = program_for('graphics') def resolve_graphics_fragment_defines(which: str, is_premult: bool, f: str) -> str: ans = f.replace('#define ALPHA_TYPE', f'#define {which}', 1) return ans.replace('TEXTURE_IS_NOT_PREMULTIPLIED', '0' if is_premult else '1') for p, (which, is_premult) in { GRAPHICS_PROGRAM: ('IMAGE', False), GRAPHICS_ALPHA_MASK_PROGRAM: ('ALPHA_MASK', False), GRAPHICS_PREMULT_PROGRAM: ('IMAGE', True), }.items(): graphics.apply_to_sources(frag=partial(resolve_graphics_fragment_defines, which, is_premult)) graphics.compile(p, allow_recompile) program_for('bgimage').compile(BGIMAGE_PROGRAM, allow_recompile) program_for('tint').compile(TINT_PROGRAM, allow_recompile) program_for('trail').compile(TRAIL_PROGRAM, allow_recompile) program_for('blit').compile(BLIT_PROGRAM, allow_recompile) program_for('screenshot').compile(SCREENSHOT_PROGRAM, allow_recompile) program_for('rounded_rect').compile(ROUNDED_RECT_PROGRAM, allow_recompile) init_cell_program() load_shader_programs = LoadShaderPrograms() ================================================ FILE: kitty/shell_integration.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import os import re import subprocess from collections.abc import Callable from contextlib import suppress from typing import Iterable from .constants import shell_integration_dir from .fast_data_types import get_options from .options.types import Options, defaults from .types import run_once 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, } QUOTERES = { 'fish': as_fish_str_literal } 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) @run_once def unsafe_pat() -> re.Pattern[str]: return re.compile(r'[^\w@%+=:,./-]', re.ASCII) def join(path: str, cmd: Iterable[str]) -> str: name = get_supported_shell_name(path) _find_unsafe = unsafe_pat().search if not name: raise ValueError(f'{path} is not a supported shell') q = QUOTERES.get(name, as_str_literal) def quote(x: str) -> str: return x if _find_unsafe(x) is None else q(x) return ' '.join(map(quote, cmd)) 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}') ================================================ FILE: kitty/shlex.c ================================================ /* * shlex.c * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "unicodeobject.h" #include "launcher/shlex.h" typedef struct { PyObject_HEAD ShlexState state; PyObject *src; bool yielded; void *data; int kind; size_t unicode_pos, src_pos_at_last_unicode_pos; } Shlex; static PyObject * new_shlex_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Shlex *self; self = (Shlex *)type->tp_alloc(type, 0); if (self) { const char *src; Py_ssize_t sz; int support_ansi_c_quoting; if (!PyArg_ParseTuple(args, "s#|p", &src, &sz, &support_ansi_c_quoting)) return NULL; if (!alloc_shlex_state(&self->state, src, sz, support_ansi_c_quoting != 0)) return PyErr_NoMemory(); self->src = PyTuple_GetItem(args, 0); self->data = PyUnicode_DATA(self->src); self->kind = PyUnicode_KIND(self->src); Py_INCREF(self->src); } return (PyObject*) self; } static void dealloc(Shlex* self) { Py_CLEAR(self->src); dealloc_shlex_state(&self->state); } static size_t advance_unicode_pos(Shlex *self) { ssize_t num_bytes = self->state.word_start - self->src_pos_at_last_unicode_pos; self->src_pos_at_last_unicode_pos = self->state.word_start; char buf[8]; while (num_bytes > 0) { Py_UCS4 ch = PyUnicode_READ(self->kind, self->data, self->unicode_pos); num_bytes -= encode_utf8(ch, buf); self->unicode_pos++; } return self->unicode_pos; } static PyObject* next_word_with_position(Shlex *self, PyObject *args UNUSED) { ssize_t len = next_word(&self->state); unsigned long pos = advance_unicode_pos(self); switch(len) { case -1: PyErr_SetString(PyExc_ValueError, self->state.err); return NULL; case -2: if (self->yielded) return Py_BuildValue("is#", -1, self->state.buf, 0); len = 0; /* fallthrough */ default: self->yielded = true; return Py_BuildValue("ks#", pos, self->state.buf, (Py_ssize_t)len); } } static PyObject* next(PyObject *self_) { Shlex *self = (Shlex*)self_; ssize_t len = next_word(&self->state); switch(len) { case -1: PyErr_SetString(PyExc_ValueError, self->state.err); return NULL; case -2: if (self->yielded) { PyErr_SetNone(PyExc_StopIteration); return NULL; } len = 0; /* fallthrough */ default: self->yielded = true; return PyUnicode_FromStringAndSize(self->state.buf, (Py_ssize_t)len); } } static PyObject* iter(PyObject *s) { return Py_NewRef(s); } static PyMethodDef methods[] = { {"next_word", (PyCFunction)next_word_with_position, 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_iternext = next, .tp_new = new_shlex_object, .tp_iter = iter, .tp_methods = methods, }; INIT_TYPE(Shlex) ================================================ FILE: kitty/shm.py ================================================ #!/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 typing import Literal, cast 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, cast(Literal[0, 1, 2, 3, 4], max(0, min(whence, 4)))) 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 = '' ================================================ FILE: kitty/short_uuid.py ================================================ #!/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() ================================================ FILE: kitty/simd-string-128.c ================================================ /* * 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" ================================================ FILE: kitty/simd-string-256.c ================================================ /* * 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" ================================================ FILE: kitty/simd-string-impl.h ================================================ /* * 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), four = set1_epi8(4), 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 by whether they may be the start of a 2-byte, 3-byte, or 4-byte sequence. // This is only an initial, potential classification. // 0xC0 and 0xC1 are initially classified as potential starter bytes of 2-byte sequences. // And 0xF5..0xFF are initially classified as potential starter bytes of 4-byte sequences. // They will be marked as actually invalid later in the chunk_is_invalid checks. integer_t state = set1_epi8(0x80); const integer_t vec_signed = add_epi8(vec, state); // needed because cmplt_epi8 works only on signed chars // state now has 0x80 on all bytes 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; } // The next section performs detailed validation of the chunk's byte sequences. // It accumulates validation errors into a chunk_is_invalid vector. // When chunk_is_invalid has any non-zero byte, then the chunk contains invalid UTF-8. // chunk_is_invalid is a vector, and not a bitmask or boolean, // because the or_si SIMD operation is empirically faster than movemask_epi8 with |= or ||=. integer_t chunk_is_invalid; // Only bytes within the ASCII range should have counts[i] == 0, and vice versa. // Detect any mismatch between the two conditions for each chunk byte. // If there is any mismatch, then the chunk has invalid UTF-8, so set all bytes in chunk_is_invalid to 0xFF; // otherwise the chunk might be valid, so set all bytes in chunk_is_invalid to 0x00. // Without this, "\x80" would incorrectly be decoded as a "\x00". // This also validates that continuation bytes' positions do not have ASCII bytes (< 0x80). // Without this, "\xe0\xa0\x7f\x01" would incorrectly be decoded as "\x00\x01". // In that example, 0x7F has an ascii_mask bit of 0 (i.e., it is within 0x00..0x7F), // but it has a counts value of 1, not 0 (i.e., it is the last remaining byte of a multi-byte sequence). // Therefore there is a count mismatch, indicating that the chunk is ill-formed UTF-8. // (If the following "\x01" were absent, and the "\x7f" were the last byte of the chunk, // then the `check_for_trailing_bytes` validation above detects the error as a trailing incomplete sequence.) const int ascii_sequence_count_mismatches = ascii_mask ^ movemask_epi8(cmpgt_epi8(counts, zero)); chunk_is_invalid = set1_epi8(ascii_sequence_count_mismatches ? 0xff : 0x00); // Validate 2-byte sequence starter bytes: 0xC0..0xC1 are invalid (overlong encodings for U+0000..U+007F). // Without this, "\xc0\x80" would incorrectly be decoded as a "\x00". chunk_is_invalid = or_si(chunk_is_invalid, and_si(bytes_indicating_start_of_two_byte_sequence, cmplt_epi8(vec, set1_epi8(0xc2)))); // Validate 4-byte sequence starter bytes: 0xF5..0xFF are invalid (out of Unicode codespace). // Without this, "\xff\x80\x80\x80" would incorrectly be decoded as an ill-formed "\U003C0000". chunk_is_invalid = or_si(chunk_is_invalid, and_si(bytes_indicating_start_of_four_byte_sequence, cmpgt_epi8(vec, set1_epi8(0xf4)))); // Validate that all continuation bytes' positions do not have non-ASCII starter bytes (>=0xC0). // If counts[i] > count[i], the chunk byte at i is in the middle of a previous sequence but also classified as a starter byte. // Without this, "\xf0\x90\xc2\x80" would have overlapping sequences, and it would be incorrectly decoded elsewhere as an empty string. chunk_is_invalid = or_si(chunk_is_invalid, andnot_si(cmplt_epi8(vec, set1_epi8(0xc0)), cmpgt_epi8(counts, count))); // Validate second bytes of E0-starting 3-byte sequences. // 0xE0 must be followed by 0xA0..0xBF (not 0x80..0x9F) to avoid overlong encodings. // Without this, "\xe0\x80\x80" would incorrectly be decoded as a "\x00". const integer_t e0_starter_bytes = cmpeq_epi8(vec, set1_epi8(0xe0)); const integer_t e0_first_follower_bytes = shift_right_by_one_byte(e0_starter_bytes); chunk_is_invalid = or_si(chunk_is_invalid, and_si(e0_first_follower_bytes, cmplt_epi8(and_si(e0_first_follower_bytes, vec), set1_epi8(0xa0)))); // Validate second bytes of ED-starting 3-byte sequences. // 0xED must be followed by 0x80..0x9F (not 0xA0..0xBF) to avoid UTF-16 surrogates. // Without this, "\xed\xa0\x80" would incorrectly be decoded as an isolated surrogate "\uD800". const integer_t ed_starter_bytes = cmpeq_epi8(vec, set1_epi8(0xed)); const integer_t ed_first_follower_bytes = shift_right_by_one_byte(ed_starter_bytes); chunk_is_invalid = or_si(chunk_is_invalid, and_si(ed_first_follower_bytes, cmpgt_epi8(and_si(ed_first_follower_bytes, vec), set1_epi8(0x9f)))); // Validate second bytes of F0-starting 4-byte sequences. // F0 must be followed by 0x90..0xBF (not 0x80..0x8F) to avoid overlong encodings. // Without this, "\xf0\x80\x80\x80" would incorrectly be decoded as a "\x0000". const integer_t f0_starter_bytes = cmpeq_epi8(vec, set1_epi8(0xf0)); const integer_t f0_first_follower_bytes = shift_right_by_one_byte(f0_starter_bytes); chunk_is_invalid = or_si(chunk_is_invalid, and_si(f0_first_follower_bytes, cmplt_epi8(and_si(f0_first_follower_bytes, vec), set1_epi8(0x90)))); // Validate second bytes of F4-starting 4-byte sequences. // F4 must be followed by 0x80..0x8F (not 0x90..0xBF) to stay within the Unicode codespace. // Without this, "\xf4\x90\x80\x80" would incorrectly be decoded as an ill-formed "\U00110000". const integer_t f4_starter_bytes = cmpeq_epi8(vec, set1_epi8(0xf4)); const integer_t f4_first_follower_bytes = shift_right_by_one_byte(f4_starter_bytes); chunk_is_invalid = or_si(chunk_is_invalid, and_si(f4_first_follower_bytes, cmpgt_epi8(and_si(f4_first_follower_bytes, vec), set1_epi8(0x8f)))); // Check for any accumulated validation errors and, if found, // fall back to slow scalar decoding of this chunk, // which handles replacement of invalid sequences with U+FFFD if (!is_zero(chunk_is_invalid)) { 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, four); // 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 ================================================ FILE: kitty/simd-string.c ================================================ /* * 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; } ================================================ FILE: kitty/simd-string.h ================================================ /* * 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); ================================================ FILE: kitty/simple_cli_definitions.py ================================================ #!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal # This module must be runnable by a vanilla python interpreter # as it is used to generate C code when building kitty import re import sys from enum import Enum, auto from functools import lru_cache from typing import Any, Iterator, NamedTuple, Sequence if getattr(sys, 'running_from_setup', False): is_macos = 'darwin' in sys.platform.lower() from shlex import split as psplit def shlex_split(text: str) -> Iterator[str]: yield from psplit(text) else: from kitty.constants import appname, is_macos from kitty.utils import shlex_split as ksplit def shlex_split(text: str) -> Iterator[str]: yield from ksplit(text) def serialize_as_go_string(x: str) -> str: return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"') class CompletionType(Enum): file = auto() directory = auto() keyword = auto() special = auto() none = auto() class CompletionRelativeTo(Enum): cwd = auto() config_dir = auto() class CompletionSpec(NamedTuple): 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': typ = CompletionType.none kwds: tuple[str, ...] = () extensions: tuple[str, ...] = () mime_patterns: tuple[str, ...] = () group = '' relative_to = CompletionRelativeTo.cwd for x in shlex_split(raw): ck, vv = x.split(':', 1) if ck == 'type': typ = getattr(CompletionType, vv) elif ck == 'kwds': kwds += tuple(vv.split(',')) elif ck == 'ext': extensions += tuple(vv.split(',')) elif ck == 'group': group = vv elif ck == 'mime': mime_patterns += tuple(vv.split(',')) elif ck == 'relative': if vv == 'conf': relative_to = CompletionRelativeTo.config_dir else: raise ValueError(f'Unknown completion relative to value: {vv}') else: raise KeyError(f'Unknown completion property: {ck}') return CompletionSpec( type=typ, kwds=kwds, extensions=extensions, mime_patterns=mime_patterns, group=group, relative_to=relative_to) 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 OptionDefinition(NamedTuple): dest: str = '' name: str = '' aliases: tuple[str, ...] = () help: str = '' choices: tuple[str, ...] = () type: str = '' default: str | None = None condition: bool = False completion: CompletionSpec = CompletionSpec() OptionSpecSeq = Sequence[str | OptionDefinition] @lru_cache(64) def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpecSeq]: if spec is None: spec = kitty_options_spec() NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP' state = NORMAL lines = spec.splitlines() prev_line = '' prev_indent = 0 seq: list[str | OptionDefinition] = [] disabled: list[str | OptionDefinition] = [] mpat = re.compile('([a-z]+)=(.+)') current_cmd = empty_cmd = OptionDefinition() 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 = OptionDefinition(dest=defdest, aliases=tuple(parts), name=defdest, condition=True) 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 = current_cmd._replace(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(',')) if not current_cmd.type: current_cmd = current_cmd._replace(type='choices') if current_cmd.type != 'choices': raise ValueError(f'Cannot specify choices for an option of type: {current_cmd.type}') current_cmd = current_cmd._replace(choices=tuple(vals)) if current_cmd.default is None: current_cmd = current_cmd._replace(default=vals[0]) else: if k == 'default': current_cmd = current_cmd._replace(default=v) elif k == 'type': if v == 'choice': v = 'choices' current_cmd = current_cmd._replace(type=v) elif k == 'dest': current_cmd = current_cmd._replace(dest=v) elif k == 'condition': current_cmd = current_cmd._replace(condition=bool(eval(v))) elif k == 'completion': current_cmd = current_cmd._replace(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 = current_cmd._replace(help=current_cmd.help + '\n') else: line = line.strip() prev_indent = current_indent spc = '' if current_cmd.help.endswith('\n') else ' ' current_cmd = current_cmd._replace(help=current_cmd.help + spc + line) else: prev_indent = 0 if prev_line: h = '\n' if current_cmd.help.endswith('::') else '\n\n' current_cmd = current_cmd._replace(help=current_cmd.help + h) else: state = NORMAL (seq if current_cmd.condition else disabled).append(current_cmd) current_cmd = empty_cmd prev_line = line if current_cmd is not empty_cmd: (seq if current_cmd.condition else disabled).append(current_cmd) return seq, disabled def defval_for_opt(opt: OptionDefinition) -> Any: dv: Any = opt.default typ = opt.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 = list(shlex_split(dv)) if dv else [] elif typ in ('int', 'float'): dv = (int if typ == 'int' else float)(dv or 0) return dv def get_option_maps(seq: OptionSpecSeq) -> tuple[dict[str, OptionDefinition], dict[str, OptionDefinition], dict[str, Any]]: names_map: dict[str, OptionDefinition] = {} alias_map: dict[str, OptionDefinition] = {} values_map: dict[str, Any] = {} for opt in seq: if isinstance(opt, str): continue for alias in opt.aliases: alias_map[alias] = opt name = opt.dest names_map[name] = opt values_map[name] = defval_for_opt(opt) return names_map, alias_map, values_map def c_str(x: str) -> str: x = x.replace('\\', r'\\') return f'"{x}"' def add_list_values(*values: str) -> Iterator[str]: yield f'\tflag.defval.listval.items = alloc_for_cli(spec, {len(values)} * sizeof(flag.defval.listval.items[0]));' yield '\tif (!flag.defval.listval.items) OOM;' yield f'\tflag.defval.listval.count = {len(values)};' yield f'\tflag.defval.listval.capacity = {len(values)};' for n, value in enumerate(values): yield f'\tflag.defval.listval.items[{n}] = {c_str(value)};' def generate_c_for_opt(name: str, defval: Any, opt: OptionDefinition) -> Iterator[str]: yield f'\tflag = (FlagSpec){{.dest={c_str(name)},}};' match opt.type: case 'bool-set' | 'bool-reset': yield '\tflag.defval.type = CLI_VALUE_BOOL;' yield f'\tflag.defval.boolval = {"true" if defval else "false"};' case 'int': yield '\tflag.defval.type = CLI_VALUE_INT;' yield f'\tflag.defval.intval = {defval};' case 'float': yield '\tflag.defval.type = CLI_VALUE_FLOAT;' yield f'\tflag.defval.floatval = {defval};' case 'list': yield '\tflag.defval.type = CLI_VALUE_LIST;' if defval: yield from add_list_values(*defval) case 'choices': yield '\tflag.defval.type = CLI_VALUE_CHOICE;' yield f'\tflag.defval.strval = {c_str(defval)};' yield from add_list_values(*opt.choices) case _: yield '\tflag.defval.type = CLI_VALUE_STRING;' if defval is not None: yield f'\tflag.defval.strval = {c_str(defval)};' def generate_c_parser_for(funcname: str, spec: str) -> Iterator[str]: seq, disabled = parse_option_spec(spec) names_map, _, defaults_map = get_option_maps(seq) if 'help' not in names_map: names_map['help'] = OptionDefinition(type='bool-set', aliases=('--help', '-h')) defaults_map['help'] = False if 'version' not in names_map: names_map['version'] = OptionDefinition(type='bool-set', aliases=('--version', '-v')) defaults_map['version'] = False yield f'static void\nparse_cli_for_{funcname}(CLISpec *spec, int argc, char **argv) {{' # }} yield '\tFlagSpec flag;' for name, opt in names_map.items(): for alias in opt.aliases: yield f'\tif (vt_is_end(vt_insert(&spec->alias_map, {c_str(alias)}, {c_str(name)}))) OOM;' yield from generate_c_for_opt(name, defaults_map[name], opt) yield '\tif (vt_is_end(vt_insert(&spec->flag_map, flag.dest, flag))) OOM;' for d in disabled: if not isinstance(d, str): yield from generate_c_for_opt(d.dest, defval_for_opt(d), d) yield '\tif (vt_is_end(vt_insert(&spec->disabled_map, flag.dest, flag))) OOM;' yield '\tparse_cli_loop(spec, true, argc, argv);' yield '}' def generate_c_parsers() -> Iterator[str]: yield '#pragma once' yield '// generated by simple_cli_definitions.py do NOT edit!' yield '#include "cli-parser.h"' yield from generate_c_parser_for('kitty', kitty_options_spec()) yield '' yield '' yield from generate_c_parser_for('panel_kitten', build_panel_cli_spec({})) yield '' # kitty CLI spec {{{ grab_keyboard_docs = """\ Grab the keyboard. This means global shortcuts defined in the OS will be passed to kitty instead. Useful if you want to create an OS modal window. How well this works depends on the OS/window manager/desktop environment. On Wayland it works only if the compositor implements the :link:`inhibit-keyboard-shortcuts protocol `. On macOS Apple doesn't allow applications to grab the keyboard without special permissions, so it doesn't work. """ listen_on_defn = f'''\ --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`. ''' wait_for_single_instance_defn = f'''\ --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. ''' 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 kitty_options_spec() -> str: if not hasattr(kitty_options_spec, 'ans'): OPTIONS = """ --class --app-id dest=cls default={appname} condition=not is_macos On Wayland set the :italic:`application id`. On X11 set the class part of the :italic:`WM_CLASS` window property. --name --os-window-tag condition=not is_macos On Wayland, set the :italic:`window tag`, when specified. On X11, set the name part of the :italic:`WM_CLASS` property, when unset, 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 Detach from the controlling terminal, if any. On macOS use :code:`open -a kitty.app -n` instead. --detached-log Path to a log file to store STDOUT/STDERR when using :option:`--detach` --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_defn} {listen_on_defn} To start in headless mode, without an actual window, use :option:`{appname} --start-as`=hidden. --start-as type=choices default=normal choices=normal,fullscreen,maximized,minimized,hidden Control how the initial kitty OS window is created. Note that this is applies to all OS Windows if you use the :option:`{appname} --session` option to create multiple OS Windows. Any OS Windows state specified in the session file gets overriden. --position The position, for example 10x20, on screen at which to place the first kitty OS Window. This may or may not work depending on the policies of the desktop environment/window manager. It never works on Wayland. See also :opt:`remember_window_position` to have kitty automatically try to restore the previous window position. --grab-keyboard type=bool-set {grab_keyboard_docs} # 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(kitty_options_spec, 'ans', OPTIONS.format( appname=appname, conf_name=appname, listen_on_defn=listen_on_defn, grab_keyboard_docs=grab_keyboard_docs, wait_for_single_instance_defn=wait_for_single_instance_defn, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname ))) ans: str = getattr(kitty_options_spec, 'ans') return ans # }}} # panel CLI spec {{{ panel_defaults = { 'lines': '1', 'columns': '1', 'margin_left': '0', 'margin_top': '0', 'margin_right': '0', 'margin_bottom': '0', 'edge': 'top', 'layer': 'bottom', 'override': '', 'cls': f'{appname}-panel', 'focus_policy': 'not-allowed', 'exclusive_zone': '-1', 'override_exclusive_zone': 'no', 'single_instance': 'no', 'instance_group': '', 'toggle_visibility': 'no', 'start_as_hidden': 'no', 'detach': 'no', 'detached_log': '', } def build_panel_cli_spec(defaults: dict[str, str]) -> str: d = panel_defaults.copy() d.update(defaults) return r''' --lines default={lines} The number of lines shown in the panel. Ignored for background, centered, and vertical panels. If it has the suffix :code:`px` then it sets the height of the panel in pixels instead of lines. --columns default={columns} The number of columns shown in the panel. Ignored for background, centered, and horizontal panels. If it has the suffix :code:`px` then it sets the width of the panel in pixels instead of columns. --margin-top type=int default={margin_top} Set the top margin for the panel, in pixels. Has no effect for bottom edge panels. Only works on macOS and Wayland compositors that supports the wlr layer shell protocol. --margin-left type=int default={margin_left} Set the left margin for the panel, in pixels. Has no effect for right edge panels. Only works on macOS and Wayland compositors that supports the wlr layer shell protocol. --margin-bottom type=int default={margin_bottom} Set the bottom margin for the panel, in pixels. Has no effect for top edge panels. Only works on macOS and Wayland compositors that supports the wlr layer shell protocol. --margin-right type=int default={margin_right} Set the right margin for the panel, in pixels. Has no effect for left edge panels. Only works on macOS and Wayland compositors that supports the wlr layer shell protocol. --edge choices=top,bottom,left,right,background,center,center-sized,none default={edge} 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". 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 three more values: :code:`center`, :code:`center-sized` and :code:`none`. The value :code:`center` anchors the panel to all sides and covers the entire display (on macOS the part of the display not covered by titlebar and dock). The panel can be shrunk and placed using the margin parameters. The value :code:`none` anchors the panel to the top left corner and should be placed using the margin parameters. Its size is set by :option:`--lines` and :option:`--columns`. The value :code:`center-sized` is just like :code:`none` except that the panel is centered instead of in the top left corner and the margins have no effect. --layer choices=background,bottom,top,overlay default={layer} 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`. On macOS, maps these to appropriate NSWindow *levels*. --config -c type=list Path to config file to use for kitty when drawing the panel. --override -o type=list default={override} 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 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. Use the special value :code:`list` to get a list of available outputs. Use :code:`listjson` for a json encoded output. Note that on Wayland the output can only be set at panel creation time, it cannot be changed after creation, nor is there anyway to display a single panel on all outputs. Please complain to the Wayland developers about this. --class --app-id dest=cls default={cls} condition=not is_macos On Wayland set the :italic:`namespace` of the layer shell surface. On X11 set the class part of the :italic:`WM_CLASS` window property. --name --os-window-tag condition=not is_macos On X11 sets the name part of the :italic:`WM_CLASS` property on X11, when unspecified uses the value from :option:`{appname} --class` on X11. --focus-policy choices=not-allowed,exclusive,on-demand default={focus_policy} 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. Note that different Wayland compositors behave very differently with :code:`exclusive`, your mileage may vary. On macOS, :code:`exclusive` and :code:`on-demand` are currently the same. --hide-on-focus-loss type=bool-set Automatically hide the panel window when it loses focus. Using this option will force :option:`--focus-policy` to :code:`on-demand`. Note that on Wayland, depending on the compositor, this can result in the window never becoming visible. --grab-keyboard type=bool-set {grab_keyboard_docs} --exclusive-zone type=int default={exclusive_zone} 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. Ignored on X11 and macOS. --override-exclusive-zone type=bool-set default={override_exclusive_zone} 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`. Ignored on X11 and macOS. --single-instance -1 type=bool-set default={single_instance} If specified only a single instance of the panel will run. New invocations will instead create a new top-level window in the existing panel instance. --instance-group default={instance_group} Used in combination with the :option:`--single-instance` option. All panel invocations with the same :option:`--instance-group` will result in new panels being created in the first panel instance within that group. {wait_for_single_instance_defn} {listen_on_defn} --toggle-visibility type=bool-set default={toggle_visibility} When set and using :option:`--single-instance` will toggle the visibility of the existing panel rather than creating a new one. --move-to-active-monitor type=bool-set default=false When set and using :option:`--toggle-visibility` to show an existing panel, the panel is moved to the active monitor (typically the monitor with the mouse on it). This works only if the underlying OS supports it. It is currently supported on macOS only. --start-as-hidden type=bool-set default={start_as_hidden} Start in hidden mode, useful with :option:`--toggle-visibility`. --detach type=bool-set default={detach} Detach from the controlling terminal, if any, running in an independent child process, the parent process exits immediately. --detached-log default={detached_log} Path to a log file to store STDOUT/STDERR when using :option:`--detach` --debug-rendering type=bool-set For internal debugging use. --debug-input type=bool-set For internal debugging use. '''.format( appname=appname, listen_on_defn=listen_on_defn, grab_keyboard_docs=grab_keyboard_docs, wait_for_single_instance_defn=wait_for_single_instance_defn, **d) def panel_options_spec() -> str: return build_panel_cli_spec(panel_defaults) # }}} ================================================ FILE: kitty/srgb_gamma.h ================================================ // Generated by gen-srgb-lut.py DO NOT edit static const GLfloat srgb_lut[256] = { 0.00000000f, 0.00030353f, 0.00060705f, 0.00091058f, 0.00121411f, 0.00151763f, 0.00182116f, 0.00212469f, 0.00242822f, 0.00273174f, 0.00303527f, 0.00334654f, 0.00367651f, 0.00402472f, 0.00439144f, 0.00477695f, 0.00518152f, 0.00560539f, 0.00604883f, 0.00651209f, 0.00699541f, 0.00749903f, 0.00802319f, 0.00856813f, 0.00913406f, 0.00972122f, 0.01032982f, 0.01096009f, 0.01161225f, 0.01228649f, 0.01298303f, 0.01370208f, 0.01444384f, 0.01520851f, 0.01599629f, 0.01680738f, 0.01764195f, 0.01850022f, 0.01938236f, 0.02028856f, 0.02121901f, 0.02217388f, 0.02315337f, 0.02415763f, 0.02518686f, 0.02624122f, 0.02732089f, 0.02842604f, 0.02955683f, 0.03071344f, 0.03189603f, 0.03310477f, 0.03433981f, 0.03560131f, 0.03688945f, 0.03820437f, 0.03954624f, 0.04091520f, 0.04231141f, 0.04373503f, 0.04518620f, 0.04666509f, 0.04817182f, 0.04970657f, 0.05126946f, 0.05286065f, 0.05448028f, 0.05612849f, 0.05780543f, 0.05951124f, 0.06124605f, 0.06301002f, 0.06480327f, 0.06662594f, 0.06847817f, 0.07036010f, 0.07227185f, 0.07421357f, 0.07618538f, 0.07818742f, 0.08021982f, 0.08228271f, 0.08437621f, 0.08650046f, 0.08865559f, 0.09084171f, 0.09305896f, 0.09530747f, 0.09758735f, 0.09989873f, 0.10224173f, 0.10461648f, 0.10702310f, 0.10946171f, 0.11193243f, 0.11443537f, 0.11697067f, 0.11953843f, 0.12213877f, 0.12477182f, 0.12743768f, 0.13013648f, 0.13286832f, 0.13563333f, 0.13843162f, 0.14126329f, 0.14412847f, 0.14702727f, 0.14995979f, 0.15292615f, 0.15592646f, 0.15896084f, 0.16202938f, 0.16513219f, 0.16826940f, 0.17144110f, 0.17464740f, 0.17788842f, 0.18116424f, 0.18447499f, 0.18782077f, 0.19120168f, 0.19461783f, 0.19806932f, 0.20155625f, 0.20507874f, 0.20863687f, 0.21223076f, 0.21586050f, 0.21952620f, 0.22322796f, 0.22696587f, 0.23074005f, 0.23455058f, 0.23839757f, 0.24228112f, 0.24620133f, 0.25015828f, 0.25415209f, 0.25818285f, 0.26225066f, 0.26635560f, 0.27049779f, 0.27467731f, 0.27889426f, 0.28314874f, 0.28744084f, 0.29177065f, 0.29613827f, 0.30054379f, 0.30498731f, 0.30946892f, 0.31398871f, 0.31854678f, 0.32314321f, 0.32777810f, 0.33245154f, 0.33716362f, 0.34191442f, 0.34670406f, 0.35153260f, 0.35640014f, 0.36130678f, 0.36625260f, 0.37123768f, 0.37626212f, 0.38132601f, 0.38642943f, 0.39157248f, 0.39675523f, 0.40197778f, 0.40724021f, 0.41254261f, 0.41788507f, 0.42326767f, 0.42869050f, 0.43415364f, 0.43965717f, 0.44520119f, 0.45078578f, 0.45641102f, 0.46207700f, 0.46778380f, 0.47353150f, 0.47932018f, 0.48514994f, 0.49102085f, 0.49693300f, 0.50288646f, 0.50888132f, 0.51491767f, 0.52099557f, 0.52711513f, 0.53327640f, 0.53947949f, 0.54572446f, 0.55201140f, 0.55834039f, 0.56471151f, 0.57112483f, 0.57758044f, 0.58407842f, 0.59061884f, 0.59720179f, 0.60382734f, 0.61049557f, 0.61720656f, 0.62396039f, 0.63075714f, 0.63759687f, 0.64447968f, 0.65140564f, 0.65837482f, 0.66538730f, 0.67244316f, 0.67954247f, 0.68668531f, 0.69387176f, 0.70110189f, 0.70837578f, 0.71569350f, 0.72305513f, 0.73046074f, 0.73791041f, 0.74540421f, 0.75294222f, 0.76052450f, 0.76815115f, 0.77582222f, 0.78353779f, 0.79129794f, 0.79910274f, 0.80695226f, 0.81484657f, 0.82278575f, 0.83076988f, 0.83879901f, 0.84687323f, 0.85499261f, 0.86315721f, 0.87136712f, 0.87962240f, 0.88792312f, 0.89626935f, 0.90466117f, 0.91309865f, 0.92158186f, 0.93011086f, 0.93868573f, 0.94730654f, 0.95597335f, 0.96468625f, 0.97344529f, 0.98225055f, 0.99110210f, 1.00000000f }; ================================================ FILE: kitty/state.c ================================================ /* * 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(const 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, const 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.alpha = 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(); w->window_title_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; if (w->window_title_render_data.vao_idx > -1) remove_vao(w->window_title_render_data.vao_idx); w->window_title_render_data.vao_idx = -1; Py_CLEAR(w->window_title_render_data.screen); } 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); w->scrollbar.is_hovering = false; 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); if (title) window->title = Py_NewRef(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_CLEAR(os_window->window_title); os_window->window_title = Py_NewRef(w->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; return; } } } if (tab->active_window >= tab->num_windows) tab->active_window = 0; } 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); if (tab->active_window >= tab->num_windows) tab->active_window = tab->num_windows ? tab->num_windows - 1 : 0; 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); zero_at_ptr(&w->bgimage); if (w->indirect_output.framebuffer_id) free_framebuffer(&w->indirect_output.framebuffer_id); } 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(); if (global_state.num_os_windows == 0) { if (global_state.layers_render_texture.texture_id) free_texture(&global_state.layers_render_texture.texture_id); if (global_state.layers_render_texture.framebuffer_id) free_framebuffer(&global_state.layers_render_texture.framebuffer_id); global_state.layers_render_texture.width = 0; global_state.layers_render_texture.height = 0; } } 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_TAB(os_window_id, tab_id) tab->active_window = 0; for (unsigned w = 0; w < tab->num_windows; w++) { if (tab->windows[w].id == window_id) { tab->active_window = w; break; } } osw->needs_render = true; set_os_window_chrome(osw); END_WITH_TAB; } 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 PyObject* pyreorder_tabs(PyObject *self UNUSED, PyObject *args) { if (PyTuple_GET_SIZE(args) < 2) Py_RETURN_NONE; id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(args, 0)); WITH_OS_WINDOW(os_window_id) if (PyTuple_GET_SIZE(args) != (Py_ssize_t)(os_window->num_tabs + 1)) { PyErr_SetString(PyExc_ValueError, "number of tabs not correct"); return NULL; } if (!os_window->num_tabs) Py_RETURN_NONE; RAII_ALLOC(Tab, tabs, calloc(os_window->capacity, sizeof(Tab))); RAII_ALLOC(char, used, calloc(os_window->num_tabs, sizeof(char))); if (!tabs || !used) { return PyErr_NoMemory(); } for (Py_ssize_t i = 1; i < PyTuple_GET_SIZE(args); i++) { id_type tab_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(args, i)); bool found = false; for (size_t t = 0; t < os_window->num_tabs; t++) { if (os_window->tabs[t].id == tab_id) { tabs[i-1] = os_window->tabs[t]; found = true; if (used[t]) { PyErr_SetString(PyExc_ValueError, "duplicate tab ids found"); return NULL; } used[t] = 1; break; } } if (!found) { PyErr_SetString(PyExc_ValueError, "tab id not found"); return NULL; } } free(os_window->tabs); os_window->tabs = tabs; tabs = NULL; END_WITH_OS_WINDOW Py_RETURN_NONE; } static PyObject* pyset_borders_rects(PyObject *self UNUSED, PyObject *args) { id_type os_window_id, tab_id; PyObject *rects; if (!PyArg_ParseTuple(args, "KKO!", &os_window_id, &tab_id, &PyList_Type, &rects)) return NULL; WITH_TAB(os_window_id, tab_id) BorderRects *br = &tab->border_rects; br->is_dirty = true; br->num_border_rects = PyList_GET_SIZE(rects); ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false); for (unsigned i = 0; i < br->num_border_rects; i++) { PyObject *pr = PyList_GET_ITEM(rects, i); unsigned long color; long long border_type; BorderRect *r = br->rect_buf + i; int horizontal; if (!PyArg_ParseTuple( pr, "IIIIkLp", &r->px.left, &r->px.top, &r->px.right, &r->px.bottom, &color, &border_type, &horizontal )) return NULL; r->left = gl_pos_x(r->px.left, osw->viewport_width); r->top = gl_pos_y(r->px.top, osw->viewport_height); r->right = r->left + gl_size(r->px.right - r->px.left, osw->viewport_width); r->bottom = r->top - gl_size(r->px.bottom - r->px.top, osw->viewport_height); r->color = color; r->border_type = border_type; r->horizontal = horizontal; } END_WITH_TAB Py_RETURN_NONE; } void os_window_regions(const OSWindow *os_window, Region *central, Region *tab_bar) { if (!OPT(tab_bar_hidden) && os_window->num_tabs && !os_window->has_too_few_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); central->left = 0; central->right = os_window->viewport_width; unsigned tab_bar_height = os_window->fonts_data->fcm.cell_height + margin_inner + margin_outer; switch(OPT(tab_bar_edge)) { case TOP_EDGE: central->top = tab_bar_height; central->bottom = os_window->viewport_height; central->top = MIN(central->top, central->bottom); tab_bar->top = margin_outer; break; default: central->top = 0; long bottom = os_window->viewport_height - tab_bar_height; central->bottom = MAX(0, bottom); tab_bar->top = central->bottom + 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; } else { zero_at_ptr(tab_bar); central->left = 0; central->top = 0; central->right = os_window->viewport_width; central->bottom = os_window->viewport_height; } } 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; } #ifdef __APPLE__ global_state.is_apple = true; global_state.has_render_frames = true; #endif global_state.is_wayland = is_wayland ? true : false; 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(WindowRenderData *d, const WindowGeometry g, Screen *screen) { d->geometry = g; Py_CLEAR(d->screen); d->screen = (Screen*)Py_NewRef(screen); } PYWRAP1(set_tab_bar_render_data) { WindowGeometry g; id_type os_window_id; Screen *screen; PA("KOIIII", &os_window_id, &screen, &g.left, &g.top, &g.right, &g.bottom); WITH_OS_WINDOW(os_window_id) init_window_render_data(&os_window->tab_bar_render_data, g, screen); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(set_window_title_bar_render_data) { WindowGeometry g = {0}; id_type os_window_id, tab_id, window_id; Screen *screen; PA("KKKOIIII", &os_window_id, &tab_id, &window_id, &screen, &g.left, &g.top, &g.right, &g.bottom); WITH_WINDOW(os_window_id, tab_id, window_id) init_window_render_data(&window->window_title_render_data, g, screen); screen->reload_all_gpu_data = true; END_WITH_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)); PyStructSequence_SET_ITEM(ans, 5, PyLong_FromUnsignedLong(r->bottom - r->top)); } 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; int should_be_shown; PA("Kp", &os_window_id, &should_be_shown); WITH_OS_WINDOW(os_window_id) os_window->has_too_few_tabs = !should_be_shown; os_window->tab_bar_data_updated = false; END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(is_tab_bar_visible) { id_type os_window_id; PA("K", &os_window_id); if (!OPT(tab_bar_hidden)) { WITH_OS_WINDOW(os_window_id) return (os_window->num_tabs == 0 || os_window->has_too_few_tabs) ? Py_NewRef(Py_False) : Py_NewRef(Py_True); END_WITH_OS_WINDOW } Py_RETURN_FALSE; } 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.alpha = opacity; if (!os_window->redraw_count) os_window->redraw_count++; set_os_window_chrome(os_window); // on macOS titlebar opacity can depend on background_opacity 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)effective_os_window_alpha(os_window)); 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 B(name) &(g.name) #define S(name) &(g.spaces.name) id_type os_window_id, tab_id, window_id; WindowGeometry g = {0}; Screen *screen; PA("KKKOIIIIIIII", &os_window_id, &tab_id, &window_id, &screen, B(left), B(top), B(right), B(bottom), S(left), S(top), S(right), S(bottom)); WITH_WINDOW(os_window_id, tab_id, window_id); init_window_render_data(&window->render_data, g, screen); END_WITH_WINDOW; Py_RETURN_NONE; #undef B #undef S } 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)) { on_os_window_font_size_change(os_window, new_sz); 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); } } // 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 sO}", "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, "is_layer_shell", os_window->is_layer_shell ? Py_True : Py_False); 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.alpha = OPT(background_opacity); set_os_window_chrome(os_window); 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); P(macos_titlebar_color); P(wayland_titlebar_color); P(scrollbar_handle_color); P(scrollbar_track_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, PyObject *kw) { const char *path; PyObject *layout_name = NULL, *pylinear = NULL, *pytint = NULL, *pytint_gaps = NULL; PyObject *os_window_ids; int configured = 0; char *png_data = NULL; Py_ssize_t png_data_size = 0; static char *kwds[] = {"path", "os_window_ids", "configured", "layout_name", "png_data", "linear", "tint", "tint_gaps", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "zO!|pOy#OOO", kwds, &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size, &pylinear, &pytint, &pytint_gaps)) return NULL; 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; } static uint32_t bgimage_id_counter = 0; bgimage->id = ++bgimage_id_counter; 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; if (pylinear && pylinear != Py_None) convert_from_python_background_image_linear(pylinear, &global_state.opts); if (pytint && pytint != Py_None) convert_from_python_background_tint(pytint, &global_state.opts); if (pytint_gaps && pytint_gaps != Py_None) convert_from_python_background_tint_gaps(pytint_gaps, &global_state.opts); } 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(get_window_logo_settings_if_not_default) { 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); if (window->window_logo.instance != NULL && window->window_logo.id && !window->window_logo.using_default) { WindowLogo *wl = find_window_logo(global_state.all_window_logos, window->window_logo.id); if (wl != NULL && wl->load_from_disk_ok) { const char *path = window_logo_path_for_id(global_state.all_window_logos, window->window_logo.id); if (path) { ImageAnchorPosition *p = &window->window_logo.position; return Py_BuildValue("sfN", path, window->window_logo.alpha, Py_BuildValue( "ffff", p->image_x, p->image_y, p->canvas_x, p->canvas_y)); } } } 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) 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 tbd global_state.tab_being_dragged static PyObject* set_tab_being_dragged(PyObject *self UNUSED, PyObject *args) { zero_at_ptr(&tbd); if (!PyArg_ParseTuple(args, "|Kpdd", &tbd.id, &tbd.drag_started, &tbd.x, &tbd.y)) return NULL; Py_RETURN_NONE; } static PyObject* get_tab_being_dragged(PyObject *self UNUSED, PyObject *args UNUSED) { return Py_BuildValue("KOdd", tbd.id, tbd.drag_started ? Py_True : Py_False, tbd.x, tbd.y); } #undef tbd static PyObject* request_callback_with_thumbnail(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id, window_id = 0; const char *callback; int include_tab_bar = 0; double scale = 0.25; unsigned max_width = 480; if (!PyArg_ParseTuple(args, "sK|KpdI", &callback, &os_window_id, &window_id, &include_tab_bar, &scale, &max_width)) return NULL; WITH_OS_WINDOW(os_window_id) global_state.thumbnail_callback.os_window = os_window->id; global_state.thumbnail_callback.window = window_id; global_state.thumbnail_callback.include_tab_bar = include_tab_bar; snprintf(global_state.thumbnail_callback.callback, arraysz(global_state.thumbnail_callback.callback), "%s", callback); global_state.thumbnail_callback.max_width = max_width; global_state.thumbnail_callback.scale = scale; mark_os_window_dirty(os_window_id); END_WITH_OS_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), M(request_callback_with_thumbnail, METH_VARARGS), M(set_tab_being_dragged, METH_VARARGS), M(get_tab_being_dragged, METH_NOARGS), 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(get_window_logo_settings_if_not_default, 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(reorder_tabs, METH_VARARGS), MW(set_borders_rects, METH_VARARGS), MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_title_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_VARARGS), MW(is_tab_bar_visible, METH_VARARGS), 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), {"set_background_image", (PyCFunction)(void (*) (void))pyset_background_image, METH_VARARGS | METH_KEYWORDS, ""}, 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_drag_source(); Py_CLEAR(global_state.drop_dest.data); zero_at_ptr(&global_state.drop_dest); 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_HIDDEN); PyModule_AddIntMacro(module, WINDOW_MINIMIZED); PyModule_AddIntMacro(module, LEFT_EDGE); PyModule_AddIntMacro(module, RIGHT_EDGE); PyModule_AddIntMacro(module, TOP_EDGE); PyModule_AddIntMacro(module, BOTTOM_EDGE); register_at_exit_cleanup_func(STATE_CLEANUP_FUNC, finalize); return true; } // }}} ================================================ FILE: kitty/state.h ================================================ /* * 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, WINDOW_HIDDEN } WindowState; typedef struct UrlPrefix { 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 Options { monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, click_interval; struct { monotonic_t hide_wait, unhide_wait; int unhide_threshold; bool scroll_unhide; } mouse_hide; double wheel_scroll_multiplier, touch_scroll_multiplier; int wheel_scroll_min_lines; bool pixel_scroll; 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; color_type cursor_trail_color; 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, window_title_bar_active_foreground, window_title_bar_active_background, window_title_bar_inactive_foreground, window_title_bar_inactive_background; 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; ScrollbarVisibilityPolicy scrollbar; bool scrollbar_interactive, scrollbar_jump_on_click; float scrollbar_width, scrollbar_radius, scrollbar_gap, scrollbar_min_handle_height, scrollbar_hitbox_expansion; float scrollbar_hover_width, scrollbar_handle_opacity, scrollbar_track_opacity, scrollbar_track_hover_opacity; color_type scrollbar_handle_color, scrollbar_track_color; 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; 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 macos_dock_badge_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]; double momentum_scroll; double window_drag_tolerance; } Options; typedef struct WindowLogoRenderData { window_logo_id_t id; WindowLogo *instance; ImageAnchorPosition position; float alpha; bool using_default; } WindowLogoRenderData; typedef struct { unsigned int left, top, right, bottom; struct { unsigned int left, top, right, bottom; } spaces; } WindowGeometry; typedef struct WindowRenderData { ssize_t vao_idx; WindowGeometry geometry; Screen *screen; } WindowRenderData; typedef struct Click { monotonic_t at; int button, modifiers; double x, y; unsigned long num; } Click; #define CLICK_QUEUE_SZ 3 typedef struct ClickQueue { 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 Window { id_type id; bool visible; PyObject *title; WindowRenderData render_data; WindowRenderData window_title_render_data; WindowLogoRenderData window_logo; MousePosition mouse_pos; struct { unsigned int left, top, right, bottom; } padding; 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; struct { double thumb_top, thumb_bottom; bool is_dragging; double drag_start_y; double drag_start_scrolled_by; bool is_hovering; } scrollbar; } Window; typedef struct BorderRect { float left, top, right, bottom; struct { unsigned left, top, right, bottom; } px; uint32_t color; long long border_type; bool horizontal; } BorderRect; typedef struct BorderRects { BorderRect *rect_buf; unsigned int num_border_rects, capacity; bool is_dirty; ssize_t vao_idx; } BorderRects; typedef struct CursorTrail { 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 Tab { 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 LiveResizeInfo { 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 BackgroundImageRenderSettings { struct { unsigned width, height; } os_window; unsigned instance_id; BackgroundImageLayout layout; bool linear; uint32_t bgcolor; float opacity; } BackgroundImageRenderSettings; typedef struct OSWindow { 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; struct { uint32_t framebuffer_id, attached_texture_generation; } indirect_output; unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id; bool focused_at_last_render, needs_render, needs_layers; unsigned keep_rendering_till_swap; 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, mouse_activate_deadline; int mouse_show_threshold; bool has_received_cursor_pos_event; double mouse_x, mouse_y; bool mouse_button_pressed[32]; bool has_too_few_tabs; bool suppress_left_mouse_release; 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, shown_once, ignore_resize_events; unsigned int redraw_count; WindowChromeState last_window_chrome; struct { float alpha; bool os_forces_opaque, supports_transparency; } 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; bool is_layer_shell, hide_on_focus_loss; struct { int x, y; } last_drag_event; } OSWindow; static inline float effective_os_window_alpha(OSWindow *w) { return (!w->background_opacity.supports_transparency || w->background_opacity.os_forces_opaque) ? 1.f : w->background_opacity.alpha; } typedef struct GlobalState { 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, is_apple; 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, mouse_hover_in_window, active_drag_resize; int active_drag_button, tracked_drag_button; CloseRequest quit_request; bool redirect_mouse_handling; WindowLogoTable *all_window_logos; int gl_version; bool supports_framebuffer_srgb; PyObject *options_object; struct { PyObject *data; id_type os_window_id; double x, y; size_t num_left; } drop_dest; struct { bool is_active, was_dropped, was_canceled, needs_toplevel_on_wayland; char *accepted_mime_type; int action, thumbnail_idx; PyObject *drag_data, *thumbnails; } drag_source; struct { id_type os_window, window; char callback[32]; bool include_tab_bar; double scale; unsigned max_width; } thumbnail_callback; struct { id_type id; bool drag_started; double x, y; } tab_being_dragged; struct { uint32_t texture_id, framebuffer_id, texture_generation; int width, height; } layers_render_texture; } 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); 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(const 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, 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, Screen *, OSWindow *); void draw_cells(const WindowRenderData*, OSWindow *, bool, bool, bool, Window*); bool update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window); void set_gpu_viewport(unsigned w, unsigned h); 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, bool); 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 on_os_window_font_size_change(OSWindow *window, double new_sz); 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(double, double, 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 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); bool screen_needs_rendering_in_layers(OSWindow *os_window, Window *w, Screen *screen); void setup_os_window_for_rendering(OSWindow*, Tab*, Window*, bool); void swap_window_buffers(OSWindow *w); void take_screenshot_of_rectangular_region(OSWindow *os_window, Region region, unsigned char *dst_buf, unsigned *thumb_w, unsigned *thumb_h); bool current_framebuffer_is_ok(void); ================================================ FILE: kitty/systemd.c ================================================ /* * 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; } ================================================ FILE: kitty/tab_bar.py ================================================ #!/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, background_opacity_of, 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_compat 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 os_window_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 session_name: str active_session_name: str 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 os_window_id: 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 + '"""', '