diff options
author | James McCoy <jamessan@debian.org> | 2019-12-15 21:47:10 -0500 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2019-12-15 21:47:10 -0500 |
commit | 0bcfee27861d8e0144fb261c4f0d87a7b77d68ff (patch) | |
tree | 912eed4e954fab8b668f8ab291d8fcefeea8f6a2 | |
parent | 0e4dcf278892f0ee7580e2391678abc9f317ef3b (diff) | |
parent | 804f72b31a7fabb9f8a484e2ada296c2a75628e0 (diff) |
Merge tag 'v0.15.0' into debian/sid
version-0.15.0
Signed-off-by: James McCoy <jamessan@debian.org>
100 files changed, 3900 insertions, 1409 deletions
diff --git a/.gitattributes b/.gitattributes index d4967bd8..765052f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,11 +3,11 @@ kitty/emoji.h linguist-generated=true kitty/keys.h linguist-generated=true kitty/charsets.c linguist-generated=true kitty/key_encoding.py linguist-generated=true -kitty/unicode-data.c +kitty/unicode-data.c linguist-generated=true kitty/rgb.py linguist-generated=true kitty/gl-wrapper.* linguist-generated=true -kitty/khrplatform.h linguist-generated=true kitty/glfw-wrapper.* linguist-generated=true +kitty/parse-graphics-command.h linguist-generated=true glfw/*.c linguist-vendored=true glfw/*.h linguist-vendored=true kittens/unicode_input/names.h linguist-generated=true diff --git a/README.asciidoc b/README.asciidoc index 8f93af43..e5bed3d2 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -1,5 +1,5 @@ = kitty - the fast, featureful, GPU based, terminal emulator -See https://sw.kovidgoyal.net/kitty +See https://sw.kovidgoyal.net/kitty/ image:https://circleci.com/gh/kovidgoyal/kitty.svg?style=svg["Build status", link="https://circleci.com/gh/kovidgoyal/kitty"] diff --git a/__main__.py b/__main__.py index 0741561e..870664f0 100644 --- a/__main__.py +++ b/__main__.py @@ -92,8 +92,10 @@ def setup_openssl_environment(): if 'SSL_CERT_FILE' not in os.environ and 'SSL_CERT_DIR' not in os.environ: if os.access('/etc/pki/tls/certs/ca-bundle.crt', os.R_OK): os.environ['SSL_CERT_FILE'] = '/etc/pki/tls/certs/ca-bundle.crt' + sys.kitty_ssl_env_var = 'SSL_CERT_FILE' elif os.path.isdir('/etc/ssl/certs'): os.environ['SSL_CERT_DIR'] = '/etc/ssl/certs' + sys.kitty_ssl_env_var = 'SSL_CERT_DIR' def main(): diff --git a/count-lines-of-code b/count-lines-of-code index dac06d76..c1b2bc84 100755 --- a/count-lines-of-code +++ b/count-lines-of-code @@ -1,2 +1,27 @@ -#!/usr/bin/env bash -cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens +#!/usr/bin/env python + +import subprocess + +files_to_exclude = '''\ +kitty/wcwidth-std.h +kitty/glfw.c +kitty/keys.h +kitty/charsets.c +kitty/unicode-data.c +kitty/key_encoding.py +kitty/rgb.py +kitty/gl.h +kitty/gl-wrapper.h +kitty/gl-wrapper.c +kitty/glfw-wrapper.h +kitty/glfw-wrapper.c +kitty/emoji.h +kittens/unicode_input/names.h +kitty/parse-graphics-command.h +''' + +p = subprocess.Popen([ + 'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens' +], stdin=subprocess.PIPE) +p.communicate(files_to_exclude.encode('utf-8')) +raise SystemExit(p.wait()) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index a3d4aa9a..d93818df 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -78,6 +78,8 @@ body div.document { div.sphinxsidebar { font-size: inherit; line-height: inherit; + max-height: 100%; + overflow-y: auto; } #sidebartoc li { diff --git a/docs/build.rst b/docs/build.rst index bdc5f115..673aca87 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -29,7 +29,7 @@ Build-time dependencies: * gcc or clang * pkg-config * For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for: - ``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, ``libcanberra-dev`` and ``libpython-dev``. + ``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, and ``libpython-dev``, if they are not already installed by your distro. Install and run from source diff --git a/docs/changelog.rst b/docs/changelog.rst index d482ce7a..7918d708 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,56 @@ Changelog |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. To update |kitty|, :doc:`follow the instructions <binary>`. +0.15.0 [2019-11-27] +-------------------- + +- Add a new action :ref:`detach_window <detach_window>` that can be used to move the current + window into a different tab (:iss:`1310`) + +- Add a new action :doc:`launch <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 <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] --------------------- diff --git a/docs/conf.py b/docs/conf.py index decc3d72..c0d20060 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- +# vim:fileencoding=utf-8 # # Configuration file for the Sphinx documentation builder. # @@ -243,7 +243,17 @@ def add_html_context(app, pagename, templatename, context, *args): # CLI docs {{{ def write_cli_docs(all_kitten_names): + from kitty.launch import options_spec as launch_options_spec from kitty.cli import option_spec_as_rst + 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')) diff --git a/docs/faq.rst b/docs/faq.rst index 46050564..29f97629 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -109,8 +109,13 @@ How do I change the colors in a running kitty instance? You can either use the `OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands>`_ -to set colors or you can enable :doc:`remote control <remote-control>` -for |kitty| and use :ref:`at_set-colors`. +to set colors or you can 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 <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`. A list of pre-made color themes for kitty is available at: `kitty-themes <https://github.com/dexpota/kitty-themes>`_ @@ -184,6 +189,11 @@ following :file:`~/.config/fontconfig/fonts.conf`:: </match> </fontconfig> +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 ``kitty list-fonts``. diff --git a/docs/index.rst b/docs/index.rst index caa1ed62..7bb7839f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,7 +59,7 @@ See the :doc:`binary install instructions </binary>`. You can also You can also use your favorite package manager to install the |kitty| package. |kitty| packages are available for: `macOS with Homebrew (Cask) <https://formulae.brew.sh/cask/kitty>`_, -`macOS and Linux with Nix <https://nixos.org/nixos/packages.html#kitty>`_, +`macOS and Linux with Nix <https://nixos.org/nixos/packages.html?channel=nixpkgs-unstable&query=kitty>`_, `Ubuntu <https://launchpad.net/ubuntu/+source/kitty>`_, `Debian <https://packages.debian.org/buster/kitty>`_, `openSUSE <https://build.opensuse.org/package/show/X11:terminals/kitty>`_, @@ -173,6 +173,19 @@ You can also define a shortcut to switch to the previously active window:: ``nth_window`` will focus the nth window for positive numbers and the previously active windows for negative numbers. +.. _detach_window: + +You can define shortcuts to detach the current window and +move it to another tab or another OS window:: + + map ctrl+f2 detach_window # moves the window into a new OS window + map ctrl+f3 detach_window new-tab # moves the window into a new Tab + map ctrl+f4 detach_window ask # asks which tab to move the window into + +Similarly, you can detach the current tab, with:: + + map ctrl+f2 detach_tab # moves the tab into a new OS window + map ctrl+f4 detach_tab ask # asks which OS Window to move the tab into Other keyboard shortcuts ---------------------------------- @@ -402,7 +415,7 @@ comfortably within the pager. Additionally, you can pipe the contents of the scrollback buffer to an arbitrary, command running in a new window, tab or overlay, for example:: - map f1 pipe @ansi window less +G -R + map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting less +G -R Would open the scrollback buffer in a new window when you press the :kbd:`F1` key. See :sc:`show_scrollback` for details. @@ -478,7 +491,8 @@ completions happens after the call to :file:`compinit`. fish ~~~~~~~~ -Add the following to your :file:`~/.config/fish/config.fish` +For versions of fish earlier than 3.0.0, add the following to your +:file:`~/.config/fish/config.fish`. Later versions source completions by default. .. code-block:: sh diff --git a/docs/kittens/custom.rst b/docs/kittens/custom.rst index b4fe7e33..6e3dbc1c 100644 --- a/docs/kittens/custom.rst +++ b/docs/kittens/custom.rst @@ -128,7 +128,27 @@ layout, by simply adding the line:: boss.toggle_fullscreen() -to the ``handle_result()`` function, above. +To the ``handle_result()`` function, above. + + +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. .. _external_kittens: diff --git a/docs/kittens/hints.rst b/docs/kittens/hints.rst index f68304d8..7347d9c3 100644 --- a/docs/kittens/hints.rst +++ b/docs/kittens/hints.rst @@ -23,6 +23,56 @@ 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 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 arbitrary data. + 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. + + Command Line Interface ------------------------- diff --git a/docs/kittens/unicode-input.rst b/docs/kittens/unicode-input.rst index e29f8c60..e3f46380 100644 --- a/docs/kittens/unicode-input.rst +++ b/docs/kittens/unicode-input.rst @@ -22,3 +22,8 @@ In :guilabel:`Name` mode you instead type words from the character name and use keys/tab to select the character from the displayed matches. You can also type a leading period and the index for the match if you don't like to use arrow keys. + +Command Line Interface +------------------------- + +.. include:: ../generated/cli-kitten-unicode_input.rst diff --git a/docs/launch.rst b/docs/launch.rst new file mode 100644 index 00000000..f5c9d77f --- /dev/null +++ b/docs/launch.rst @@ -0,0 +1,56 @@ +Launching programs in new windows/tabs +======================================== + +.. program:: launch + + +|kitty| has a :code:`launch` action that can be used to run arbitrary programs +in news windows/tabs. It can be mapped to user defined shortcuts in 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 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. + +The piping environment +-------------------------- + +When using :option:`launch --stdin-source`, 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. + + +Syntax reference +------------------ + +.. include:: /generated/launch.rst diff --git a/docs/pipe.rst b/docs/pipe.rst index 4e9e2821..8f1289cd 100644 --- a/docs/pipe.rst +++ b/docs/pipe.rst @@ -1,6 +1,9 @@ Working with the screen and history buffer contents ====================================================== +.. warning:: + The pipe action has been deprecated in favor of the + :doc:`launch <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 @@ -56,6 +59,9 @@ Input placeholders There are various different kinds of placeholders +``@selection`` + Plain text, currently selected text + ``@text`` Plain text, current screen + scrollback buffer diff --git a/docs/remote-control.rst b/docs/remote-control.rst index 83df27dd..212df731 100644 --- a/docs/remote-control.rst +++ b/docs/remote-control.rst @@ -126,7 +126,7 @@ If you do not want to allow all programs running in |kitty| to control it, you c enable remote control for only some |kitty| windows. Simply create a shortcut such as:: - map ctrl+k new_window @ some_program + map ctrl+k launch --allow-remote-control some_program Then programs running in windows created with that shortcut can use ``kitty @`` to control kitty. Note that any program with the right level of permissions can diff --git a/gen-wcwidth.py b/gen-wcwidth.py index 1242e8de..c7498f7c 100755 --- a/gen-wcwidth.py +++ b/gen-wcwidth.py @@ -43,6 +43,7 @@ def get_data(fname, folder='UCD'): # Map of class names to set of codepoints in class class_maps = {} +all_symbols = set() name_map = {} word_search_map = defaultdict(set) zwj = 0x200d @@ -89,6 +90,8 @@ def parse_ucd(): not_assigned.discard(codepoint) if category.startswith('M'): marks.add(codepoint) + elif category.startswith('S'): + all_symbols.add(codepoint) # Some common synonyms word_search_map['bee'] |= word_search_map['honeybee'] @@ -194,6 +197,7 @@ def gen_emoji(): p('\t\tdefault: return false;') p('\t}') p('\treturn false;\n}') + p('static inline bool\nis_emoji_modifier(char_type code) {') p('\tswitch(code) {') for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])): @@ -203,6 +207,15 @@ def gen_emoji(): p('\t}') p('\treturn false;\n}') + p('static inline bool\nis_symbol(char_type code) {') + p('\tswitch(code) {') + for spec in get_ranges(list(all_symbols)): + write_case(spec, p) + p('\t\t\treturn true;') + p('\t\tdefault: return false;') + p('\t}') + p('\treturn false;\n}') + def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()): static = 'static inline ' if static else '' diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index 9058c6c3..536dbdb1 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -22,19 +22,6 @@ #define ppoll pollts #endif -static inline double -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 (((double)ts.tv_nsec) / 1e9) + (double)ts.tv_sec; -} - void update_fds(EventLoopData *eld) { for (nfds_t i = 0; i < eld->watches_count; i++) { @@ -109,7 +96,7 @@ update_timers(EventLoopData *eld) { } id_type -addTimer(EventLoopData *eld, const char *name, double interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free) { +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; @@ -117,7 +104,7 @@ addTimer(EventLoopData *eld, const char *name, double interval, int enabled, boo Timer *t = eld->timers + eld->timers_count++; t->interval = interval; t->name = name; - t->trigger_at = enabled ? monotonic() + interval : DBL_MAX; + t->trigger_at = enabled ? monotonic() + interval : MONOTONIC_T_MAX; t->repeats = repeats; t->callback = cb; t->callback_data = cb_data; @@ -144,7 +131,7 @@ 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) { - double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX; + 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); @@ -155,7 +142,7 @@ toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) { } void -changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) { +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; @@ -165,11 +152,11 @@ changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) { } -double -prepareForPoll(EventLoopData *eld, double timeout) { +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 == DBL_MAX) return timeout; - double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at; + 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; } @@ -177,10 +164,8 @@ prepareForPoll(EventLoopData *eld, double timeout) { } int -pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) { - const long seconds = (long) timeout; - const long nanoseconds = (long) ((timeout - seconds) * 1e9); - struct timespec tv = { seconds, nanoseconds }; +pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout) { + struct timespec tv = calc_time(timeout); return ppoll(fds, nfds, &tv, NULL); } @@ -198,10 +183,10 @@ dispatchEvents(EventLoopData *eld) { unsigned dispatchTimers(EventLoopData *eld) { - if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0; + 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; - double now = monotonic(); + 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; @@ -299,12 +284,12 @@ finalizePollData(EventLoopData *eld) { } int -pollForEvents(EventLoopData *eld, double timeout) { +pollForEvents(EventLoopData *eld, monotonic_t timeout) { int read_ok = 0; timeout = prepareForPoll(eld, timeout); - EVDBG("pollForEvents final timeout: %.3f", timeout); + EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout)); int result; - double end_time = monotonic() + timeout; + monotonic_t end_time = monotonic() + timeout; eld->wakeup_fd_ready = false; while(1) { diff --git a/glfw/backend_utils.h b/glfw/backend_utils.h index 15445817..3e1ff2ab 100644 --- a/glfw/backend_utils.h +++ b/glfw/backend_utils.h @@ -25,6 +25,7 @@ //======================================================================== #pragma once +#include "../kitty/monotonic.h" #include <poll.h> #include <unistd.h> #include <stdbool.h> @@ -55,7 +56,7 @@ typedef struct { typedef struct { id_type id; - double interval, trigger_at; + monotonic_t interval, trigger_at; timer_callback_func callback; void *callback_data; GLFWuserdatafreefun free; @@ -82,14 +83,14 @@ 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, double interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free); +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, double interval); -double prepareForPoll(EventLoopData *eld, double timeout); -int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout); -int pollForEvents(EventLoopData *eld, double timeout); +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); unsigned dispatchTimers(EventLoopData *eld); void finalizePollData(EventLoopData *eld); bool initPollData(EventLoopData *eld, int display_fd); diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index d8b12057..a6b6ea13 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -27,9 +27,13 @@ //======================================================================== #include "internal.h" +#include "../kitty/monotonic.h" #include <sys/param.h> // For MAXPATHLEN #include <pthread.h> +// Needed for _NSGetProgname +#include <crt_externs.h> + // Change to our application bundle's resources directory, if present // static void changeToResourcesDirectory(void) @@ -66,14 +70,119 @@ static void changeToResourcesDirectory(void) 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]; +} + // Create key code translation tables // static void createKeyTables(void) { - int scancode; + int keycode; memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes)); - memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes)); + memset(_glfw.ns.key_to_keycode, -1, sizeof(_glfw.ns.key_to_keycode)); _glfw.ns.keycodes[0x1D] = GLFW_KEY_0; _glfw.ns.keycodes[0x12] = GLFW_KEY_1; @@ -190,11 +299,11 @@ static void createKeyTables(void) _glfw.ns.keycodes[0x43] = GLFW_KEY_KP_MULTIPLY; _glfw.ns.keycodes[0x4E] = GLFW_KEY_KP_SUBTRACT; - for (scancode = 0; scancode < 256; scancode++) + for (keycode = 0; keycode < 256; keycode++) { // Store the reverse translation for faster key name lookup - if (_glfw.ns.keycodes[scancode] >= 0) - _glfw.ns.scancodes[_glfw.ns.keycodes[scancode]] = scancode; + if (_glfw.ns.keycodes[keycode] >= 0) + _glfw.ns.key_to_keycode[_glfw.ns.keycodes[keycode]] = keycode; } } @@ -276,6 +385,17 @@ static bool initializeTIS(void) return updateUnicodeDataNS(); } +static void +display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED) +{ + if (flags & kCGDisplayBeginConfigurationFlag) { + return; + } + if (flags & kCGDisplaySetModeFlag) { + // GPU possibly changed + } +} + @interface GLFWHelper : NSObject @end @@ -294,6 +414,103 @@ static bool initializeTIS(void) @end // GLFWHelper +// Delegate for application related notifications {{{ + +@interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate> +@end + +@implementation GLFWApplicationDelegate + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + (void)sender; + _GLFWwindow* window; + + for (window = _glfw.windowListHead; window; window = window->next) + _glfwInputWindowCloseRequest(window); + + return NSTerminateCancel; +} + +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 above and + // finishLaunching below, 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(); +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification +{ + (void)notification; + [NSApp stop:nil]; + + CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL); + _glfwCocoaPostEmptyEvent(); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ + (void)aNotification; + CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL); +} + +- (void)applicationDidHide:(NSNotification *)notification +{ + (void)notification; + int i; + + for (i = 0; i < _glfw.monitorCount; i++) + _glfwRestoreVideoModeNS(_glfw.monitors[i]); +} + +@end // GLFWApplicationDelegate +// }}} + + @interface GLFWApplication : NSApplication - (void)tick_callback; - (void)render_frame_received:(id)displayIDAsID; @@ -335,6 +552,18 @@ is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) { return false; } +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(void) { @autoreleasepool { @@ -345,8 +574,21 @@ int _glfwPlatformInit(void) 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]; + NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event) { NSEventModifierFlags modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; @@ -385,6 +627,10 @@ int _glfwPlatformInit(void) if (_glfw.hints.init.ns.chdir) changeToResourcesDirectory(); + // Press and Hold prevents some keys from emitting repeated characters + NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO}; + [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; + [[NSNotificationCenter defaultCenter] addObserver:_glfw.ns.helper selector:@selector(selectedKeyboardInputSourceChanged:) @@ -531,7 +777,7 @@ typedef struct { NSTimer *os_timer; unsigned long long id; bool repeats; - double interval; + monotonic_t interval; GLFWuserdatafun callback; void *callback_data; GLFWuserdatafun free_callback_data; @@ -551,7 +797,7 @@ remove_timer_at(size_t idx) { } static void schedule_timer(Timer *t) { - t->os_timer = [NSTimer scheduledTimerWithTimeInterval:t->interval repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) { + 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); @@ -562,7 +808,7 @@ static void schedule_timer(Timer *t) { }]; } -unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) { +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"); @@ -588,7 +834,7 @@ void _glfwPlatformRemoveTimer(unsigned long long timer_id) { } } -void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled) { +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; diff --git a/glfw/cocoa_monitor.m b/glfw/cocoa_monitor.m index 09e5fe64..50dd535b 100644 --- a/glfw/cocoa_monitor.m +++ b/glfw/cocoa_monitor.m @@ -328,7 +328,7 @@ void _glfwPollMonitorsNS(void) if (!name) name = _glfw_strdup("Unknown"); - _GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height); + _GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height); monitor->ns.displayID = displays[i]; monitor->ns.unitNumber = unitNumber; createDisplayLink(monitor->ns.displayID); @@ -354,8 +354,9 @@ void _glfwPollMonitorsNS(void) void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired) { GLFWvidmode current; - const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired); _glfwPlatformGetVideoMode(monitor, ¤t); + + const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired); if (_glfwCompareVideoModes(¤t, best) == 0) return; @@ -454,13 +455,13 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, const NSRect frameRect = [monitor->ns.screen visibleFrame]; if (xpos) - *xpos = frameRect.origin.x; + *xpos = (int)frameRect.origin.x; if (ypos) - *ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1); + *ypos = (int)_glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1); if (width) - *width = frameRect.size.width; + *width = (int)frameRect.size.width; if (height) - *height = frameRect.size.height; + *height = (int)frameRect.size.height; } @@ -505,7 +506,6 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode) { CVDisplayLinkRef link; - CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link); CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID); diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index da6e72bc..7bd3afaa 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -36,6 +36,9 @@ typedef void* id; typedef void* CVDisplayLinkRef; #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 @@ -64,8 +67,9 @@ typedef void* CVDisplayLinkRef; typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long); -typedef int (* GLFWapplicationshouldhandlereopenfun)(int); -typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); +typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); +typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef struct VkMacOSSurfaceCreateInfoMVK @@ -144,7 +148,7 @@ typedef struct _GLFWDisplayLinkNS { CVDisplayLinkRef displayLink; CGDirectDisplayID displayID; - double lastRenderFrameRequestedAt; + monotonic_t lastRenderFrameRequestedAt; } _GLFWDisplayLinkNS; // Cocoa-specific global data @@ -153,6 +157,7 @@ typedef struct _GLFWlibraryNS { CGEventSourceRef eventSource; id delegate; + bool finishedLaunching; bool cursorHidden; TISInputSourceRef inputSource; IOHIDManagerRef hidManager; @@ -160,11 +165,12 @@ typedef struct _GLFWlibraryNS id helper; id keyUpMonitor; id keyDownMonitor; + id nibObjects; char keyName[64]; char text[256]; short int keycodes[256]; - short int scancodes[GLFW_KEY_LAST + 1]; + short int key_to_keycode[GLFW_KEY_LAST + 1]; char* clipboardString; CGPoint cascadePoint; // Where to place the cursor when re-enabled diff --git a/glfw/cocoa_time.c b/glfw/cocoa_time.c index 4bf646c8..cba8de27 100644 --- a/glfw/cocoa_time.c +++ b/glfw/cocoa_time.c @@ -42,7 +42,7 @@ void _glfwInitTimerNS(void) mach_timebase_info_data_t info; mach_timebase_info(&info); - _glfw.timer.ns.frequency = (info.denom * 1e9) / info.numer; + _glfw.timer.ns.frequency = (unsigned long long)((info.denom * 1e9) / info.numer); } @@ -59,4 +59,3 @@ uint64_t _glfwPlatformGetTimerFrequency(void) { return _glfw.timer.ns.frequency; } - diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 292659db..22045bb3 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -27,13 +27,11 @@ //======================================================================== #include "internal.h" +#include "../kitty/monotonic.h" #include <float.h> #include <string.h> -// Needed for _NSGetProgname -#include <crt_externs.h> - #define PARAGRAPH_UTF_8 0xc2a7 // § #define MASCULINE_UTF_8 0xc2ba // º @@ -93,15 +91,14 @@ // static NSUInteger getStyleMask(_GLFWwindow* window) { - NSUInteger styleMask = 0; + NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; if (window->monitor || !window->decorated) styleMask |= NSWindowStyleMaskBorderless; else { styleMask |= NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable; + NSWindowStyleMaskClosable; if (window->resizable) styleMask |= NSWindowStyleMaskResizable; @@ -120,7 +117,7 @@ CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { } static unsigned long long display_link_shutdown_timer = 0; -#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL 30 +#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll) void _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) { @@ -147,7 +144,7 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { } else { display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL); } - double now = glfwGetTime(); + monotonic_t now = glfwGetTime(); for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i]; if (dl->displayID == displayID) { @@ -329,8 +326,8 @@ format_text(const char *src) { } static const char* -safe_name_for_scancode(unsigned int scancode) { - const char *ans = _glfwPlatformGetScancodeName(scancode); +safe_name_for_keycode(unsigned int keycode) { + const char *ans = _glfwPlatformGetNativeKeyName(keycode); if (!ans) return "<noname>"; if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>"; return ans; @@ -343,7 +340,7 @@ static int translateKey(unsigned int key, bool apply_keymap) { if (apply_keymap) { // Look for the effective key name after applying any keyboard layouts/mappings - const char *name_chars = _glfwPlatformGetScancodeName(key); + const char *name_chars = _glfwPlatformGetNativeKeyName(key); uint32_t name = 0; if (name_chars) { for (int i = 0; i < 4; i++) { @@ -480,17 +477,6 @@ static int translateKey(unsigned int key, bool apply_keymap) return _glfw.ns.keycodes[key]; } -static void -display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED) -{ - if (flags & kCGDisplayBeginConfigurationFlag) { - return; - } - if (flags & kCGDisplaySetModeFlag) { - // GPU possibly changed - } -} - // Translate a GLFW keycode to a Cocoa modifier flag // static NSUInteger translateKeyToModifierFlag(int key) @@ -572,17 +558,17 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + 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 = contentRect.size.width; - window->ns.height = contentRect.size.height; - _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.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); } } @@ -661,77 +647,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } } - @end // }}} -// Delegate for application related notifications {{{ - -@interface GLFWApplicationDelegate : NSObject -@end - -@implementation GLFWApplicationDelegate - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender -{ - (void)sender; - _GLFWwindow* window; - - for (window = _glfw.windowListHead; window; window = window->next) - _glfwInputWindowCloseRequest(window); - - return NSTerminateCancel; -} - -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(); -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notification -{ - (void)notification; - [NSApp stop:nil]; - - CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL); - _glfwCocoaPostEmptyEvent(); -} - -- (void)applicationWillTerminate:(NSNotification *)aNotification -{ - (void)aNotification; - CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL); -} - -- (void)applicationDidHide:(NSNotification *)notification -{ - (void)notification; - int i; - - for (i = 0; i < _glfw.monitorCount; i++) - _glfwRestoreVideoModeNS(_glfw.monitors[i]); -} - -@end -// }}} // Content view class for the GLFW window {{{ @@ -960,9 +877,9 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { - window->ns.fbWidth = fbRect.size.width; - window->ns.fbHeight = fbRect.size.height; - _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + 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; @@ -1047,14 +964,16 @@ is_ascii_control_char(char x) { - (void)keyDown:(NSEvent *)event { - const unsigned int scancode = [event keyCode]; + const unsigned int keycode = [event keyCode]; const NSUInteger flags = [event modifierFlags]; const int mods = translateFlags(flags); - const int key = translateKey(scancode, true); - const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode, flags) != 1; + const int key = translateKey(keycode, true); + const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1; const bool previous_has_marked_text = [self hasMarkedText]; [self unmarkText]; _glfw.ns.text[0] = 0; + GLFWkeyevent glfw_keyevent; + _glfwInitializeKeyEvent(&glfw_keyevent, key, keycode, GLFW_PRESS, 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 @@ -1069,7 +988,7 @@ is_ascii_control_char(char x) { const bool in_compose_sequence = window->ns.deadKeyState != 0; if (UCKeyTranslate( [(NSData*) _glfw.ns.unicodeData bytes], - scancode, + keycode, kUCKeyActionDown, convert_cocoa_to_carbon_modifiers(flags), LMGetKbdType(), @@ -1079,30 +998,32 @@ is_ascii_control_char(char x) { &char_count, text ) != noErr) { - debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%@) %@\n", - scancode, @(safe_name_for_scancode(scancode)), @(format_mods(mods))); + debug_key(@"UCKeyTranslate failed for keycode: 0x%x (%@) %@\n", + keycode, @(safe_name_for_keycode(keycode)), @(format_mods(mods))); window->ns.deadKeyState = 0; return; } - debug_key(@"scancode: 0x%x (%@) %@char_count: %lu deadKeyState: %u repeat: %d", - scancode, @(safe_name_for_scancode(scancode)), @(format_mods(mods)), char_count, window->ns.deadKeyState, event.ARepeat); + debug_key(@"keycode: 0x%x (%@) %@char_count: %lu deadKeyState: %u repeat: %d", + keycode, @(safe_name_for_keycode(keycode)), @(format_mods(mods)), char_count, window->ns.deadKeyState, event.ARepeat); if (process_text) { // this will call insertText which will fill up _glfw.ns.text [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } else { window->ns.deadKeyState = 0; } - if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) { + 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: %@ markedText: %@).\n", @(format_text(_glfw.ns.text)), markedText); - _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, - [[markedText string] UTF8String], 1); // update pre-edit text + glfw_keyevent.text = [[markedText string] UTF8String]; + glfw_keyevent.ime_state = 1; + _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text return; } if (in_compose_sequence) { debug_key(@"Clearing pre-edit text at end of compose sequence\n"); - _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, - NULL, 1); // clear pre-edit text + glfw_keyevent.text = NULL; + glfw_keyevent.ime_state = 1; + _glfwInputKeyboard(window, &glfw_keyevent); // 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 @@ -1110,18 +1031,22 @@ is_ascii_control_char(char x) { @(format_text(_glfw.ns.text)), @(_glfwGetKeyName(key)), markedText); if (!window->ns.deadKeyState) { if ([self hasMarkedText]) { - _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, - [[markedText string] UTF8String], 1); // update pre-edit text + glfw_keyevent.text = [[markedText string] UTF8String]; + glfw_keyevent.ime_state = 1; + _glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text } else if (previous_has_marked_text) { - _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, - NULL, 1); // clear pre-edit text + glfw_keyevent.text = NULL; + glfw_keyevent.ime_state = 1; + _glfwInputKeyboard(window, &glfw_keyevent); // 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 return; } } - _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0); + glfw_keyevent.text = _glfw.ns.text; + glfw_keyevent.ime_state = 0; + _glfwInputKeyboard(window, &glfw_keyevent); } - (void)flagsChanged:(NSEvent *)event @@ -1143,14 +1068,19 @@ is_ascii_control_char(char x) { else action = GLFW_RELEASE; - _glfwInputKeyboard(window, key, [event keyCode], action, mods, "", 0); + GLFWkeyevent glfw_keyevent; + _glfwInitializeKeyEvent(&glfw_keyevent, key, [event keyCode], action, mods); + _glfwInputKeyboard(window, &glfw_keyevent); } - (void)keyUp:(NSEvent *)event { const int key = translateKey([event keyCode], true); const int mods = translateFlags([event modifierFlags]); - _glfwInputKeyboard(window, key, [event keyCode], GLFW_RELEASE, mods, "", 0); + + GLFWkeyevent glfw_keyevent; + _glfwInitializeKeyEvent(&glfw_keyevent, key, [event keyCode], GLFW_RELEASE, mods); + _glfwInputKeyboard(window, &glfw_keyevent); } - (void)scrollWheel:(NSEvent *)event @@ -1341,7 +1271,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, @end // }}} -// GLFW Window class {{{ +// GLFW window class {{{ @interface GLFWWindow : NSWindow { _GLFWwindow* glfw_window; @@ -1394,154 +1324,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, @end // }}} -// 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]; -} - -// Initialize the Cocoa Application Kit -// -static bool initializeAppKit(void) -{ - if (_glfw.ns.delegate) - return true; - - // There can only be one application delegate, but we allocate it the - // first time a window is created to keep all window code in this file - _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]; - - 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 above and - // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain - - // disabled by Kovid - /* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */ - /* [NSApp loadMainMenu]; */ - /* else */ - createMenuBar(); - } - - [NSApp run]; - - // Press and Hold prevents some keys from emitting repeated characters - NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO}; - - [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; - - return true; -} // Create the Cocoa window // @@ -1589,7 +1371,7 @@ static bool createNativeWindow(_GLFWwindow* window, [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; else { - [(NSWindow*)window->ns.object center]; + [(NSWindow*) window->ns.object center]; _glfw.ns.cascadePoint = NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: NSPointFromCGPoint(_glfw.ns.cascadePoint)]); @@ -1646,8 +1428,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWfbconfig* fbconfig) { window->ns.deadKeyState = 0; - if (!initializeAppKit()) - return false; + if (!_glfw.ns.finishedLaunching) + { + [NSApp run]; + _glfw.ns.finishedLaunching = true; + } if (!createNativeWindow(window, wndconfig, fbconfig)) return false; @@ -1734,9 +1519,9 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; if (xpos) - *xpos = contentRect.origin.x; + *xpos = (int)contentRect.origin.x; if (ypos) - *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); + *ypos = (int)_glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) @@ -1752,9 +1537,9 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) const NSRect contentRect = [window->ns.view frame]; if (width) - *width = contentRect.size.width; + *width = (int)contentRect.size.width; if (height) - *height = contentRect.size.height; + *height = (int)contentRect.size.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) @@ -1817,15 +1602,15 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; if (left) - *left = contentRect.origin.x - frameRect.origin.x; + *left = (int)(contentRect.origin.x - frameRect.origin.x); if (top) - *top = frameRect.origin.y + frameRect.size.height - - contentRect.origin.y - contentRect.size.height; + *top = (int)(frameRect.origin.y + frameRect.size.height - + contentRect.origin.y - contentRect.size.height); if (right) - *right = frameRect.origin.x + frameRect.size.width - - contentRect.origin.x - contentRect.size.width; + *right = (int)(frameRect.origin.x + frameRect.size.width - + contentRect.origin.x - contentRect.size.width); if (bottom) - *bottom = contentRect.origin.y - frameRect.origin.y; + *bottom = (int)(contentRect.origin.y - frameRect.origin.y); } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, @@ -1840,9 +1625,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, *yscale = (float) (pixels.size.height / points.size.height); } -double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) +monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { - return [NSEvent doubleClickInterval]; + return s_double_to_monotonic_t([NSEvent doubleClickInterval]); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) @@ -2110,14 +1895,14 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED) updateCursorMode(window); } -const char* _glfwPlatformGetScancodeName(int scancode) +const char* _glfwPlatformGetNativeKeyName(int keycode) { UInt32 deadKeyState = 0; UniChar characters[8]; UniCharCount characterCount = 0; if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], - scancode, + keycode, kUCKeyActionDisplay, 0, LMGetKbdType(), @@ -2137,9 +1922,9 @@ const char* _glfwPlatformGetScancodeName(int scancode) return _glfw.ns.keyName; } -int _glfwPlatformGetKeyScancode(int key) +int _glfwPlatformGetNativeKeyForKey(int key) { - return _glfw.ns.scancodes[key]; + return _glfw.ns.key_to_keycode[key]; } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, @@ -2389,12 +2174,6 @@ GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWw return previous; } -GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) { - GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback; - handle_reopen_callback = callback; - return previous; -} - GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) { requestRenderFrame((_GLFWwindow*)w, callback); } diff --git a/glfw/dbus_glfw.c b/glfw/dbus_glfw.c index 2ec4407a..5213800b 100644 --- a/glfw/dbus_glfw.c +++ b/glfw/dbus_glfw.c @@ -27,6 +27,7 @@ #include "internal.h" #include "dbus_glfw.h" +#include "../kitty/monotonic.h" #include <stdlib.h> #include <string.h> @@ -107,7 +108,7 @@ on_dbus_timer_ready(id_type timer_id UNUSED, void *data) { static dbus_bool_t add_dbus_timeout(DBusTimeout *timeout, void *data) { int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0; - double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.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; diff --git a/glfw/glfw.py b/glfw/glfw.py index 38876e8a..f3a351ec 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -165,13 +165,14 @@ def generate_wrappers(glfw_header): GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback) GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback) GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) + GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods) void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) void* glfwGetX11Display(void) int32_t glfwGetX11Window(GLFWwindow* window) void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string) const char* glfwGetPrimarySelectionString(GLFWwindow* window, void) - int glfwGetXKBScancode(const char* key_name, int case_sensitive) + int glfwGetNativeKeyForName(const char* key_name, int case_sensitive) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) @@ -185,15 +186,23 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac 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 <stddef.h> #include <stdint.h> +#include "monotonic.h" {} typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); -typedef int (* GLFWapplicationshouldhandlereopenfun)(int); -typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); +typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); +typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 4c9036f9..1305e867 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -96,11 +96,32 @@ extern "C" { #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 <stddef.h> + +/* Include because it is needed by Vulkan and related functions. + * Include it unconditionally to avoid surprising side-effects. + */ +#include <stdint.h> + +#include <stdbool.h> + +#if defined(GLFW_INCLUDE_VULKAN) + #include <vulkan/vulkan.h> +#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. */ -#ifndef APIENTRY - #ifdef _WIN32 +#if !defined(APIENTRY) + #if defined(_WIN32) #define APIENTRY __stdcall #else #define APIENTRY @@ -122,19 +143,6 @@ extern "C" { #define GLFW_CALLBACK_DEFINED #endif /* CALLBACK */ -/* 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 <stddef.h> - -/* Include because it is needed by Vulkan and related functions. - * Include it unconditionally to avoid surprising side-effects. - */ -#include <stdint.h> - -#include <stdbool.h> - /* Include the chosen OpenGL or OpenGL ES headers. */ #if defined(GLFW_INCLUDE_ES1) @@ -213,10 +221,6 @@ extern "C" { #endif /* OpenGL and OpenGL ES headers */ -#if defined(GLFW_INCLUDE_VULKAN) - #include <vulkan/vulkan.h> -#endif /* Vulkan header */ - #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 @@ -297,6 +301,7 @@ extern "C" { /*! @} */ /*! @defgroup hat_state Joystick hat states + * @brief Joystick hat states. * * See [joystick hat input](@ref joystick_hat) for how these are used. * @@ -1030,12 +1035,25 @@ extern "C" { * [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 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 @@ -1098,11 +1116,22 @@ typedef enum { /*! @addtogroup init * @{ */ +/*! @brief Joystick hat buttons init hint. + * + * Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS). + */ #define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001 #define GLFW_DEBUG_KEYBOARD 0x00050002 #define GLFW_ENABLE_JOYSTICKS 0x00050003 - +/*! @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 /*! @} */ @@ -1173,17 +1202,48 @@ typedef struct GLFWwindow GLFWwindow; * * @since Added in version 3.1. * - * @ingroup cursor + * @ingroup input */ typedef struct GLFWcursor GLFWcursor; -/*! @brief The function signature for error callbacks. +typedef struct GLFWkeyevent +{ + // The [keyboard key](@ref keys) that was pressed or released. + int key; + + // The platform-specific identifier of the key. + int native_key; + + // The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. + int 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 1 means the pre-edit text for the input event has been changed. + // A value of 2 means the text should be committed. + int ime_state; +} GLFWkeyevent; + +/*! @brief The function pointer type for error callbacks. * - * This is the function signature for error callback functions. + * 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 An [error code](@ref errors). + * @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 * @@ -1193,9 +1253,13 @@ typedef struct GLFWcursor GLFWcursor; */ typedef void (* GLFWerrorfun)(int,const char*); -/*! @brief The function signature for window position callbacks. +/*! @brief The function pointer type for window position callbacks. * - * This is the function signature for window position callback functions. + * 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 @@ -1212,9 +1276,13 @@ typedef void (* GLFWerrorfun)(int,const char*); */ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); -/*! @brief The function signature for window resize callbacks. +/*! @brief The function pointer type for window size callbacks. * - * This is the function signature for window size callback functions. + * 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. @@ -1230,9 +1298,13 @@ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); -/*! @brief The function signature for window close callbacks. +/*! @brief The function pointer type for window close callbacks. * - * This is the function signature for window close callback functions. + * 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. * @@ -1246,9 +1318,13 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowclosefun)(GLFWwindow*); -/*! @brief The function signature for window content refresh callbacks. +/*! @brief The function pointer type for window content refresh callbacks. * - * This is the function signature for window refresh callback functions. + * 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. * @@ -1262,9 +1338,13 @@ typedef void (* GLFWwindowclosefun)(GLFWwindow*); */ typedef void (* GLFWwindowrefreshfun)(GLFWwindow*); -/*! @brief The function signature for window focus/defocus callbacks. +/*! @brief The function pointer type for window focus callbacks. * - * This is the function signature for window focus callback functions. + * 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 @@ -1297,10 +1377,13 @@ typedef void (* GLFWwindowfocusfun)(GLFWwindow*,int); typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); -/*! @brief The function signature for window iconify/restore callbacks. +/*! @brief The function pointer type for window iconify callbacks. * - * This is the function signature for window iconify/restore callback - * functions. + * 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 @@ -1315,10 +1398,13 @@ typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); */ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); -/*! @brief The function signature for window maximize/restore callbacks. +/*! @brief The function pointer type for window maximize callbacks. * - * This is the function signature for window maximize/restore callback - * functions. + * 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] iconified `true` if the window was maximized, or @@ -1333,10 +1419,13 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); */ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); -/*! @brief The function signature for framebuffer resize callbacks. +/*! @brief The function pointer type for framebuffer size callbacks. * - * This is the function signature for framebuffer resize callback - * functions. + * 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. @@ -1351,10 +1440,13 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); */ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); -/*! @brief The function signature for window content scale callbacks. +/*! @brief The function pointer type for window content scale callbacks. * - * This is the function signature for window content scale callback - * functions. + * 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. @@ -1369,14 +1461,19 @@ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); -/*! @brief The function signature for mouse button callbacks. +/*! @brief The function pointer type for mouse button callbacks. * - * This is the function signature for mouse button callback functions. + * 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`. + * @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. * @@ -1390,9 +1487,13 @@ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); */ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); -/*! @brief The function signature for cursor position callbacks. +/*! @brief The function pointer type for cursor position callbacks. * - * This is the function signature for cursor position callback functions. + * 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 @@ -1409,12 +1510,16 @@ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); */ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); -/*! @brief The function signature for cursor enter/leave callbacks. +/*! @brief The function pointer type for cursor enter/leave callbacks. * - * This is the function signature for cursor enter/leave callback functions. + * 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 client + * @param[in] entered `true` if the cursor entered the window's content * area, or `false` if it left it. * * @sa @ref cursor_enter @@ -1426,16 +1531,20 @@ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); */ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); -/*! @brief The function signature for scroll callbacks. +/*! @brief The function pointer type for scroll callbacks. * - * This is the function signature for scroll callback functions. + * 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 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 @@ -1451,9 +1560,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); */ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); -/*! @brief The function signature for key callbacks. +/*! @brief The function pointer type for key callbacks. * - * This is the function signature for key callback functions. + * This is the function pointer type for key callbacks. A keyboard + * key callback function has the following signature: + * @code + * void function_name(GLFWwindow* window, int 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 @@ -1462,15 +1575,8 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); * the "s" key will generate text "o" and GLFW_KEY_O. * * @param[in] window The window that received the event. - * @param[in] key The [keyboard key](@ref keys) that was pressed or released. - * @param[in] scancode The system-specific scancode of the key. - * @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. - * @param[in] mods Bit field describing which [modifier keys](@ref mods) were - * held down. - * @param[in] text UTF-8 encoded text generated by this key event or empty string. - * @param[in] status Used for Input Method events. Zero for normal key events. - * A value of 1 means the pre-edit text for the input event has been changed. - * A value of 2 means the text should be committed. + * @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 @@ -1484,16 +1590,23 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); * * @ingroup input */ -typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int); +typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief The function signature for file drop callbacks. +/*! @brief The function pointer type for path drop callbacks. * - * This is the function signature for file drop callbacks. + * This is the function pointer type for path drop callbacks. A path drop + * callback function has the following signature: + * @code + * void function_name(GLFWwindow* window, int path_count, const char* paths[]) + * @endcode * * @param[in] window The window that received the event. - * @param[in] count The number of dropped files. + * @param[in] path_count The number of dropped paths. * @param[in] paths The UTF-8 encoded file and/or directory path names. * + * @pointer_lifetime The path array and its strings are valid until the + * callback function returns. + * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * @@ -1501,17 +1614,21 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, i * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**); +typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); -/*! @brief The function signature for monitor configuration callbacks. +/*! @brief The function pointer type for monitor configuration callbacks. * - * This is the function signature for monitor configuration callback functions. + * 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`. Remaining - * values reserved for future use. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future + * releases may add more events. * * @sa @ref monitor_event * @sa @ref glfwSetMonitorCallback @@ -1522,14 +1639,17 @@ typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); */ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); -/*! @brief The function signature for joystick configuration callbacks. +/*! @brief The function pointer type for joystick configuration callbacks. * - * This is the function signature for joystick configuration callback - * functions. + * 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`. Remaining - * values reserved for future use. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future + * releases may add more events. * * @sa @ref joystick_event * @sa @ref glfwSetJoystickCallback @@ -1616,6 +1736,8 @@ typedef struct GLFWgammaramp * * @since Added in version 2.1. * @glfw3 Removed format and bytes-per-pixel members. + * + * @ingroup window */ typedef struct GLFWimage { @@ -1638,6 +1760,8 @@ typedef struct GLFWimage * @sa @ref glfwGetGamepadState * * @since Added in version 3.3. + * + * @ingroup input */ typedef struct GLFWgamepadstate { @@ -1688,11 +1812,11 @@ typedef struct GLFWgamepadstate * * @ingroup init */ -GLFWAPI int glfwInit(void); +GLFWAPI int glfwInit(monotonic_t start_time); GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *callback_data); GLFWAPI void glfwStopMainLoop(void); -GLFWAPI unsigned long long glfwAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback); -GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, double interval, bool enabled); +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); /*! @brief Terminates the GLFW library. @@ -1868,10 +1992,17 @@ GLFWAPI int glfwGetError(const char** description); * Once set, the error callback remains set even after the library has been * terminated. * - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. * + * @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. @@ -1885,7 +2016,7 @@ GLFWAPI int glfwGetError(const char** description); * * @ingroup init */ -GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun); +GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun callback); /*! @brief Returns the currently connected monitors. * @@ -1965,21 +2096,23 @@ GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void); */ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos); -/*! @brief Returns the work area of the monitor. +/*! @brief Retrieves the work area of the monitor. * * This function returns the position, in screen coordinates, of the upper-left - * corner of the specified monitor alongwith the work area size in screen co-ordinates. - * The work area is defined as the area of the monitor not occluded by the operating - * system chrome (task bar, global menubar, etc.). + * 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. + * 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`. + * @param[out] height Where to store the monitor height, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. @@ -1992,7 +2125,7 @@ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos); * * @ingroup monitor */ -GLFWAPI void glfwGetMonitorWorkarea(GLFWmonitor* monitor, int* xpos, int* ypos, int *width, int *height); +GLFWAPI void glfwGetMonitorWorkarea(GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height); /*! @brief Returns the physical size of the monitor. * @@ -2032,9 +2165,11 @@ GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* widthMM, int* * * 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. If you scale all pixel dimensions by this scale then your - * content should appear at an appropriate size. This is especially important - * for text and any UI elements. + * 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 @@ -2140,11 +2275,18 @@ GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor); * currently set callback. This is called when a monitor is connected to or * disconnected from the system. * - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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(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. @@ -2155,7 +2297,7 @@ GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor); * * @ingroup monitor */ -GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun); +GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun callback); /*! @brief Returns the available video modes for the specified monitor. * @@ -2220,9 +2362,9 @@ 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. + * 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. @@ -2538,9 +2680,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value); * @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 @ref - * GLFW_X11_CLASS_NAME and @ref GLFW_X11_INSTANCE_NAME window hints to override - * this. + * 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 @@ -2822,11 +2965,11 @@ GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); * 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 client + * @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 client + * @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`. @@ -3005,9 +3148,11 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int * * 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. If you scale all pixel dimensions by this scale then your - * content should appear at an appropriate size. This is especially important - * for text and any UI elements. + * 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 @@ -3056,7 +3201,7 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* * * @ingroup window */ -GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window); +GLFWAPI monotonic_t glfwGetDoubleClickInterval(GLFWwindow* window); /*! @brief Returns the opacity of the whole window. * @@ -3128,8 +3273,9 @@ GLFWAPI void glfwSetWindowOpacity(GLFWwindow* window, float opacity); * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * - * @remark @wayland Once a window is iconified it cannot be restored - * except by the compositor, this is a design decision of xdg-shell. + * @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. * @@ -3382,9 +3528,9 @@ GLFWAPI GLFWmonitor* glfwGetWindowMonitor(GLFWwindow* window); * 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 client + * @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`. @@ -3534,15 +3680,22 @@ GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* 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. + * 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] cbfun The new callback, or `NULL` to remove the currently 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 @@ -3556,7 +3709,7 @@ GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* window); * * @ingroup window */ -GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindowposfun cbfun); +GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindowposfun callback); /*! @brief Sets the size callback for the specified window. * @@ -3565,11 +3718,18 @@ GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindow * in screen coordinates, of the content area of the window. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3581,7 +3741,7 @@ GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindow * * @ingroup window */ -GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun cbfun); +GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun callback); /*! @brief Sets the close callback for the specified window. * @@ -3595,11 +3755,18 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind * The close callback is not triggered by @ref glfwDestroyWindow. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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 @@ -3614,7 +3781,7 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind * * @ingroup window */ -GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun cbfun); +GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback); /*! @brief Sets the refresh callback for the specified window. * @@ -3627,11 +3794,18 @@ GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwi * very infrequently or never at all. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3643,7 +3817,7 @@ GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwi * * @ingroup window */ -GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GLFWwindowrefreshfun cbfun); +GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GLFWwindowrefreshfun callback); /*! @brief Sets the focus callback for the specified window. * @@ -3656,11 +3830,18 @@ GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GL * and @ref glfwSetMouseButtonCallback. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3671,7 +3852,7 @@ GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GL * * @ingroup window */ -GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwindowfocusfun cbfun); +GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwindowfocusfun callback); /*! @brief Sets the occlusion callback for the specified window. * @@ -3681,11 +3862,18 @@ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwi * moved away. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3696,7 +3884,7 @@ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwi * * @ingroup window */ -GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* window, GLFWwindowocclusionfun cbfun); +GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* window, GLFWwindowocclusionfun callback); /*! @brief Sets the iconify callback for the specified window. * @@ -3704,11 +3892,18 @@ GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* window * is called when the window is iconified or restored. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3719,7 +3914,7 @@ GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* window * * @ingroup window */ -GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GLFWwindowiconifyfun cbfun); +GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GLFWwindowiconifyfun callback); /*! @brief Sets the maximize callback for the specified window. * @@ -3727,11 +3922,18 @@ GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GL * is called when the window is maximized or restored. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3742,7 +3944,7 @@ GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GL * * @ingroup window */ -GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, GLFWwindowmaximizefun cbfun); +GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, GLFWwindowmaximizefun callback); /*! @brief Sets the framebuffer resize callback for the specified window. * @@ -3750,11 +3952,18 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, * which is called when the framebuffer of the specified window is resized. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3765,7 +3974,7 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, * * @ingroup window */ -GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun); +GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun callback); /*! @brief Sets the window content scale callback for the specified window. * @@ -3773,11 +3982,18 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window * which is called when the content scale of the specified window changes. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -3789,7 +4005,7 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window * * @ingroup window */ -GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun cbfun); +GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun callback); /*! @brief Posts an empty event to the event queue. * @@ -3843,8 +4059,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * 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 client - * area of the window but does not restrict the cursor from leaving. + * - `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. @@ -3898,9 +4114,9 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); * __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 `GLFW_KEY_UNKNOWN`, the scancode is used to identify the key, - * otherwise the scancode is ignored. If you specify a non-printable key, or - * `GLFW_KEY_UNKNOWN` and a scancode that maps to a non-printable key, this + * If the key is `GLFW_KEY_UNKNOWN`, the keycode is used to identify the key, + * otherwise the keycode is ignored. If you specify a non-printable key, or + * `GLFW_KEY_UNKNOWN` 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 @@ -3999,15 +4215,17 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); * language and should be localized along with other user interface text. * * @param[in] key The key to query, or `GLFW_KEY_UNKNOWN`. - * @param[in] scancode The scancode of the key to query. + * @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 next call to @ref - * glfwGetKeyName, or until the library is terminated. + * 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. * @@ -4017,17 +4235,17 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); * * @ingroup input */ -GLFWAPI const char* glfwGetKeyName(int key, int scancode); +GLFWAPI const char* glfwGetKeyName(int key, int native_key); -/*! @brief Returns the platform-specific scancode of the specified key. +/*! @brief Returns the platform-specific identifier of the specified key. * - * This function returns the platform-specific scancode of the specified key. + * This function returns the platform-specific identifier of the specified key. * * If the key is `GLFW_KEY_UNKNOWN` or does not exist on the keyboard this * method will return `-1`. * * @param[in] key Any [named key](@ref keys). - * @return The platform-specific scancode for the key, or `-1` if an + * @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 @@ -4041,7 +4259,7 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode); * * @ingroup input */ -GLFWAPI int glfwGetKeyScancode(int key); +GLFWAPI int glfwGetNativeKeyForKey(int key); /*! @brief Returns the last reported state of a keyboard key for the specified * window. @@ -4310,7 +4528,7 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); * * @ingroup input */ -GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun); +GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun callback); /*! @brief Notifies the OS Input Method Event system of changes to application input state * @@ -4341,11 +4559,18 @@ GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int * [window focus callback](@ref glfwSetWindowFocusCallback) has been called. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -4357,7 +4582,7 @@ GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int * * @ingroup input */ -GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmousebuttonfun cbfun); +GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmousebuttonfun callback); /*! @brief Sets the cursor position callback. * @@ -4367,11 +4592,18 @@ GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmo * content area of the window. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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. @@ -4382,20 +4614,27 @@ GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmo * * @ingroup input */ -GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursorposfun cbfun); +GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursorposfun callback); -/*! @brief Sets the cursor enter/exit 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] cbfun The new callback, or `NULL` to remove the currently 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. @@ -4406,7 +4645,7 @@ GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursor * * @ingroup input */ -GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcursorenterfun cbfun); +GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcursorenterfun callback); /*! @brief Sets the scroll callback. * @@ -4418,11 +4657,18 @@ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcu * wheel or a touchpad scrolling area. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new scroll callback, or `NULL` to remove the currently - * set callback. + * @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. @@ -4433,12 +4679,12 @@ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcu * * @ingroup input */ -GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun cbfun); +GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun callback); -/*! @brief Sets the file drop callback. +/*! @brief Sets the path drop callback. * - * This function sets the file drop callback of the specified window, which is - * called when one or more dragged files are dropped on the window. + * This function sets the path drop callback of the specified window, which is + * called when one or more dragged paths are dropped on the window. * * Because the path array and its strings may have been generated specifically * for that event, they are not guaranteed to be valid after the callback has @@ -4446,11 +4692,18 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun cb * make a deep copy. * * @param[in] window The window whose callback to set. - * @param[in] cbfun The new file drop callback, or `NULL` to remove the + * @param[in] callback The new file drop callback, or `NULL` to remove the * currently set callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * + * @callback_signature + * @code + * void function_name(GLFWwindow* window, int path_count, const char* paths[]) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWdropfun). + * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @remark @wayland File drop is currently unimplemented. @@ -4463,8 +4716,8 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun cb * * @ingroup input */ -GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun cbfun); -GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun cbfun); +GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun callback); +GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun callback); /*! @brief Returns whether the specified joystick is present. * @@ -4570,7 +4823,7 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count); * 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 @@ -4783,11 +5036,18 @@ GLFWAPI int glfwJoystickIsGamepad(int jid); * called by joystick functions. The function will then return whatever it * returns if the joystick is not present. * - * @param[in] cbfun The new callback, or `NULL` to remove the currently 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(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. @@ -4798,7 +5058,7 @@ GLFWAPI int glfwJoystickIsGamepad(int jid); * * @ingroup input */ -GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun); +GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun callback); /*! @brief Adds the specified SDL_GameControllerDB gamepad mappings. * @@ -4961,23 +5221,26 @@ GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string); */ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); -/*! @brief Returns the value of the GLFW timer. +/*! @brief Returns the GLFW time. * - * This function returns the value of the GLFW timer. Unless the timer has - * been set using @ref glfwSetTime, the timer measures time elapsed since GLFW - * was initialized. + * 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. + * + * This function and @ref glfwSetTime are helper functions on top of @ref + * glfwGetTimerFrequency and @ref glfwGetTimerValue. * * 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 value, in seconds, or zero if an + * @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 timer offset is not atomic, so it needs to be + * writing of the internal base time is not atomic, so it needs to be * externally synchronized with calls to @ref glfwSetTime. * * @sa @ref time @@ -4986,25 +5249,28 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); * * @ingroup input */ -GLFWAPI double glfwGetTime(void); +GLFWAPI monotonic_t glfwGetTime(void); -/*! @brief Sets the GLFW timer. +/*! @brief Sets the GLFW time. + * + * This function sets the current GLFW time, in seconds. The value must be + * a positive finite number less than or equal to 18446744073.0, which is + * approximately 584.5 years. * - * This function sets the value of the GLFW timer. It then continues to count - * up from that value. The value must be a positive finite number less than - * or equal to 18446744073.0, which is approximately 584.5 years. + * This function and @ref glfwGetTime are helper functions on top of @ref + * glfwGetTimerFrequency and @ref glfwGetTimerValue. * * @param[in] time The new value, in seconds. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_VALUE. * - * @remark The upper limit of the timer is calculated as + * @remark The upper limit of GLFW time is calculated as * floor((2<sup>64</sup> - 1) / 10<sup>9</sup>) and is due to implementations * storing nanoseconds in 64 bits. The limit may be increased in the future. * * @thread_safety This function may be called from any thread. Reading and - * writing of the internal timer offset is not atomic, so it needs to be + * writing of the internal base time is not atomic, so it needs to be * externally synchronized with calls to @ref glfwGetTime. * * @sa @ref time @@ -5013,7 +5279,7 @@ GLFWAPI double glfwGetTime(void); * * @ingroup input */ -GLFWAPI void glfwSetTime(double time); +GLFWAPI void glfwSetTime(monotonic_t time); /*! @brief Returns the current value of the raw timer. * @@ -5308,7 +5574,7 @@ GLFWAPI int glfwVulkanSupported(void); * * 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 contains `VK_KHR_surface`, so if you don't require any + * 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. * diff --git a/glfw/ibus_glfw.c b/glfw/ibus_glfw.c index d6caa0c0..13f7fe5e 100644 --- a/glfw/ibus_glfw.c +++ b/glfw/ibus_glfw.c @@ -107,10 +107,14 @@ get_ibus_text_from_message(DBusMessage *msg) { } static inline void -send_text(const char *text, int state) { +send_text(const char *text, int ime_state) { _GLFWwindow *w = _glfwFocusedWindow(); if (w && w->callbacks.keyboard) { - w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state); + GLFWkeyevent fake_ev; + _glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0); + fake_ev.text = text; + fake_ev.ime_state = ime_state; + w->callbacks.keyboard((GLFWwindow*) w, &fake_ev); } } @@ -393,31 +397,36 @@ ibus_key_state(unsigned int glfw_modifiers, int action) { void key_event_processed(DBusMessage *msg, const char* errmsg, void *data) { uint32_t handled = 0; - KeyEvent *ev = (KeyEvent*)data; - bool is_release = ev->action == GLFW_RELEASE; + _GLFWIBUSKeyEvent *ev = (_GLFWIBUSKeyEvent*)data; + // Restore key's text from the text embedded in the structure. + ev->glfw_ev.text = ev->__embedded_text; + bool is_release = ev->glfw_ev.action == GLFW_RELEASE; bool failed = false; if (errmsg) { _glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg); failed = true; } else { glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID); - debug("IBUS processed scancode: 0x%x release: %d handled: %u\n", ev->keycode, is_release, handled); + 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 KeyEvent *ev_, _GLFWIBUSData *ibus) { +ibus_process_key(const _GLFWIBUSKeyEvent *ev_, _GLFWIBUSData *ibus) { if (!check_connection(ibus)) return false; - KeyEvent *ev = malloc(sizeof(KeyEvent)); + _GLFWIBUSKeyEvent *ev = calloc(1, sizeof(_GLFWIBUSKeyEvent)); if (!ev) return false; - memcpy(ev, ev_, sizeof(KeyEvent)); - uint32_t state = ibus_key_state(ev->glfw_modifiers, ev->action); + 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(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_sym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32, + DBUS_TYPE_UINT32, &ev->ibus_keysym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)) { free(ev); return false; diff --git a/glfw/ibus_glfw.h b/glfw/ibus_glfw.h index a09de2e5..57d09866 100644 --- a/glfw/ibus_glfw.h +++ b/glfw/ibus_glfw.h @@ -27,6 +27,7 @@ #pragma once +#include "internal.h" #include "dbus_glfw.h" #include <xkbcommon/xkbcommon.h> @@ -38,18 +39,16 @@ typedef struct { } _GLFWIBUSData; typedef struct { - xkb_keycode_t keycode, ibus_keycode; - xkb_keysym_t keysym, ibus_sym; - unsigned int glfw_modifiers; - int action; + xkb_keycode_t ibus_keycode; + xkb_keysym_t ibus_keysym; GLFWid window_id; - int glfw_keycode; - char text[64]; -} KeyEvent; + 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 KeyEvent *ev_, _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); diff --git a/glfw/init.c b/glfw/init.c index 247ee24f..332102f5 100644 --- a/glfw/init.c +++ b/glfw/init.c @@ -27,6 +27,7 @@ // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== +#define MONOTONIC_START_MODULE #include "internal.h" #include "mappings.h" @@ -201,7 +202,7 @@ _glfwDebug(const char *format, ...) { { va_list vl; - fprintf(stderr, "[%.4f] ", glfwGetTime()); + fprintf(stderr, "[%.4f] ", monotonic_t_to_s_double(glfwGetTime())); va_start(vl, format); vfprintf(stderr, format, vl); va_end(vl); @@ -214,10 +215,11 @@ _glfwDebug(const char *format, ...) { ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// -GLFWAPI int glfwInit(void) +GLFWAPI int glfwInit(monotonic_t start_time) { if (_glfw.initialized) return true; + monotonic_start_time = start_time; memset(&_glfw, 0, sizeof(_glfw)); _glfw.hints.init = _glfwInitHints; @@ -350,13 +352,13 @@ GLFWAPI void glfwStopMainLoop(void) { } GLFWAPI unsigned long long glfwAddTimer( - double interval, bool repeats, GLFWuserdatafun callback, + 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, double interval, bool enabled) { +GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) { _glfwPlatformUpdateTimer(timer_id, interval, enabled); } diff --git a/glfw/input.c b/glfw/input.c index 66bc8fe2..afa17976 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -28,6 +28,7 @@ //======================================================================== #include "internal.h" +#include "../kitty/monotonic.h" #include <assert.h> #include <float.h> @@ -256,33 +257,44 @@ static bool parseMapping(_GLFWmapping* mapping, const char* string) ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// +void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods) +{ + ev->key = key; + ev->native_key = native_key; + ev->action = action; + ev->mods = mods; + ev->text = NULL; + ev->ime_state = 0; +} + // Notifies shared code of a physical key event // -void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state) +void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev) { - if (key >= 0 && key <= GLFW_KEY_LAST) + if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST) { bool repeated = false; - if (action == GLFW_RELEASE && window->keys[key] == GLFW_RELEASE) + if (ev->action == GLFW_RELEASE && window->keys[ev->key] == GLFW_RELEASE) return; - if (action == GLFW_PRESS && window->keys[key] == GLFW_PRESS) + if (ev->action == GLFW_PRESS && window->keys[ev->key] == GLFW_PRESS) repeated = true; - if (action == GLFW_RELEASE && window->stickyKeys) - window->keys[key] = _GLFW_STICK; + if (ev->action == GLFW_RELEASE && window->stickyKeys) + window->keys[ev->key] = _GLFW_STICK; else - window->keys[key] = (char) action; + window->keys[ev->key] = (char) ev->action; if (repeated) - action = GLFW_REPEAT; + ev->action = GLFW_REPEAT; } + // FIXME: will need to update ev->virtual_mods here too? if (window->callbacks.keyboard) { - if (!window->lockKeyMods) mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK); - window->callbacks.keyboard((GLFWwindow*) window, key, scancode, action, mods, text, state); + if (!window->lockKeyMods) ev->mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK); + window->callbacks.keyboard((GLFWwindow*) window, ev); } } @@ -744,7 +756,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); } -GLFWAPI const char* glfwGetKeyName(int key, int scancode) +GLFWAPI const char* glfwGetKeyName(int key, int native_key) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); @@ -757,13 +769,13 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode) return NULL; } - scancode = _glfwPlatformGetKeyScancode(key); + native_key = _glfwPlatformGetNativeKeyForKey(key); } - return _glfwPlatformGetScancodeName(scancode); + return _glfwPlatformGetNativeKeyName(native_key); } -GLFWAPI int glfwGetKeyScancode(int key) +GLFWAPI int glfwGetNativeKeyForKey(int key) { _GLFW_REQUIRE_INIT_OR_RETURN(-1); @@ -773,7 +785,7 @@ GLFWAPI int glfwGetKeyScancode(int key) return GLFW_RELEASE; } - return _glfwPlatformGetKeyScancode(key); + return _glfwPlatformGetNativeKeyForKey(key); } GLFWAPI int glfwGetKey(GLFWwindow* handle, int key) @@ -1466,25 +1478,23 @@ GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle UNUSED) } #endif -GLFWAPI double glfwGetTime(void) +GLFWAPI monotonic_t glfwGetTime(void) { - _GLFW_REQUIRE_INIT_OR_RETURN(0.0); - return (double) (_glfwPlatformGetTimerValue() - _glfw.timer.offset) / - _glfwPlatformGetTimerFrequency(); + _GLFW_REQUIRE_INIT_OR_RETURN(0); + return monotonic(); } -GLFWAPI void glfwSetTime(double time) +GLFWAPI void glfwSetTime(monotonic_t time) { _GLFW_REQUIRE_INIT(); - if (time != time || time < 0.0 || time > 18446744073.0) + if (time < 0) { - _glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", time); + _glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", monotonic_t_to_s_double(time)); return; } - _glfw.timer.offset = _glfwPlatformGetTimerValue() - - (uint64_t) (time * _glfwPlatformGetTimerFrequency()); + // Do nothing } GLFWAPI uint64_t glfwGetTimerValue(void) diff --git a/glfw/internal.h b/glfw/internal.h index cffc29e7..8ea3de86 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -27,6 +27,8 @@ #pragma once +#include "../kitty/monotonic.h" + #if defined(_GLFW_USE_CONFIG_H) #include "glfw_config.h" #endif @@ -638,8 +640,8 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape void _glfwPlatformDestroyCursor(_GLFWcursor* cursor); void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor); -const char* _glfwPlatformGetScancodeName(int scancode); -int _glfwPlatformGetKeyScancode(int key); +const char* _glfwPlatformGetNativeKeyName(int native_key); +int _glfwPlatformGetNativeKeyForKey(int key); void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor); void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos); @@ -687,7 +689,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* right, int* bottom); void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale); -double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window); +monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window); void _glfwPlatformIconifyWindow(_GLFWwindow* window); void _glfwPlatformRestoreWindow(_GLFWwindow* window); void _glfwPlatformMaximizeWindow(_GLFWwindow* window); @@ -716,7 +718,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); -void _glfwPlatformWaitEventsTimeout(double timeout); +void _glfwPlatformWaitEventsTimeout(monotonic_t timeout); void _glfwPlatformPostEmptyEvent(void); void _glfwPlatformGetRequiredInstanceExtensions(char** extensions); @@ -756,7 +758,8 @@ void _glfwInputWindowDamage(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); -void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state); +void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods); +void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); @@ -823,8 +826,8 @@ _GLFWwindow* _glfwFocusedWindow(void); _GLFWwindow* _glfwWindowForId(GLFWid id); void _glfwPlatformRunMainLoop(GLFWtickcallback, void*); void _glfwPlatformStopMainLoop(void); -unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback); -void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled); +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); char* _glfw_strdup(const char* source); diff --git a/glfw/linux_joystick.c b/glfw/linux_joystick.c index 0cae51f0..d29471b3 100644 --- a/glfw/linux_joystick.c +++ b/glfw/linux_joystick.c @@ -222,7 +222,8 @@ static bool openJoystickDevice(const char* path) } } - _GLFWjoystick* js = _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount); + _GLFWjoystick* js = + _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount); if (!js) { close(linjs.fd); diff --git a/glfw/main_loop.h b/glfw/main_loop.h index fdc29691..a944fd66 100644 --- a/glfw/main_loop.h +++ b/glfw/main_loop.h @@ -7,6 +7,7 @@ #pragma once #include "internal.h" +#include "../kitty/monotonic.h" #ifndef GLFW_LOOP_BACKEND #define GLFW_LOOP_BACKEND x11 @@ -36,7 +37,7 @@ void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) { EVDBG("main loop exiting"); } -unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback) { +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); } @@ -44,7 +45,7 @@ void _glfwPlatformRemoveTimer(unsigned long long timer_id) { removeTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id); } -void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled) { +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); } diff --git a/glfw/nsgl_context.h b/glfw/nsgl_context.h index 3e19a2ee..e005e0ad 100644 --- a/glfw/nsgl_context.h +++ b/glfw/nsgl_context.h @@ -32,8 +32,8 @@ // typedef struct _GLFWcontextNSGL { - id pixelFormat; - id object; + id pixelFormat; + id object; } _GLFWcontextNSGL; diff --git a/glfw/nsgl_context.m b/glfw/nsgl_context.m index 2eadf981..66dcdc6e 100644 --- a/glfw/nsgl_context.m +++ b/glfw/nsgl_context.m @@ -271,7 +271,7 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window, return false; } - NSOpenGLContext* share = NULL; + NSOpenGLContext* share = nil; if (ctxconfig->share) share = ctxconfig->share->context.nsgl.object; @@ -325,7 +325,7 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle) if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); - return NULL; + return nil; } return window->context.nsgl.object; diff --git a/glfw/null_window.c b/glfw/null_window.c index c474b031..c7a759ad 100644 --- a/glfw/null_window.c +++ b/glfw/null_window.c @@ -28,6 +28,7 @@ //======================================================================== #include "internal.h" +#include "../kitty/monotonic.h" static int createNativeWindow(_GLFWwindow* window, @@ -150,9 +151,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED, *yscale = 1.f; } -double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) +monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { - return 0.5; + return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window UNUSED) @@ -257,7 +258,7 @@ void _glfwPlatformWaitEvents(void) { } -void _glfwPlatformWaitEventsTimeout(double timeout UNUSED) +void _glfwPlatformWaitEventsTimeout(monotonic_t timeout UNUSED) { } @@ -306,12 +307,12 @@ const char* _glfwPlatformGetClipboardString(void) return NULL; } -const char* _glfwPlatformGetScancodeName(int scancode UNUSED) +const char* _glfwPlatformGetNativeKeyName(int native_key UNUSED) { return ""; } -int _glfwPlatformGetKeyScancode(int key UNUSED) +int _glfwPlatformGetNativeKeyForKey(int key UNUSED) { return -1; } diff --git a/glfw/window.c b/glfw/window.c index 2e8213f2..c693a0a0 100644 --- a/glfw/window.c +++ b/glfw/window.c @@ -29,6 +29,7 @@ //======================================================================== #include "internal.h" +#include "../kitty/monotonic.h" #include <assert.h> #include <string.h> @@ -56,8 +57,10 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused) { if (window->keys[key] == GLFW_PRESS) { - const int scancode = _glfwPlatformGetKeyScancode(key); - _glfwInputKeyboard(window, key, scancode, GLFW_RELEASE, 0, "", 0); + const int native_key = _glfwPlatformGetNativeKeyForKey(key); + GLFWkeyevent ev; + _glfwInitializeKeyEvent(&ev, key, native_key, GLFW_RELEASE, 0); + _glfwInputKeyboard(window, &ev); } } @@ -736,12 +739,12 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle, _glfwPlatformGetWindowContentScale(window, xscale, yscale); } -GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle) +GLFWAPI monotonic_t glfwGetDoubleClickInterval(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); - _GLFW_REQUIRE_INIT_OR_RETURN(0.5f); + _GLFW_REQUIRE_INIT_OR_RETURN(ms_to_monotonic_t(500ll)); return _glfwPlatformGetDoubleClickInterval(window); } diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 396972b5..8c67f7a3 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -29,6 +29,7 @@ #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" +#include "../kitty/monotonic.h" #include <assert.h> #include <errno.h> @@ -164,6 +165,7 @@ static void setCursor(GLFWCursorShape shape) wl_surface_damage(surface, 0, 0, image->width, image->height); wl_surface_commit(surface); + _glfw.wl.cursorPreviousShape = shape; } static void pointerHandleMotion(void* data UNUSED, @@ -174,43 +176,45 @@ static void pointerHandleMotion(void* data UNUSED, { _GLFWwindow* window = _glfw.wl.pointerFocus; GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR; + double x, y; if (!window) return; if (window->cursorMode == GLFW_CURSOR_DISABLED) return; - window->wl.cursorPosX = wl_fixed_to_double(sx); - window->wl.cursorPosY = wl_fixed_to_double(sy); + x = wl_fixed_to_double(sx); + y = wl_fixed_to_double(sy); switch (window->wl.decorations.focus) { case mainWindow: - _glfwInputCursorPos(window, - window->wl.cursorPosX, window->wl.cursorPosY); + window->wl.cursorPosX = x; + window->wl.cursorPosY = y; + _glfwInputCursorPos(window, x, y); return; case topDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) cursorShape = GLFW_VRESIZE_CURSOR; else cursorShape = GLFW_ARROW_CURSOR; break; case leftDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) cursorShape = GLFW_NW_RESIZE_CURSOR; else cursorShape = GLFW_HRESIZE_CURSOR; break; case rightDecoration: - if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH) + if (y < _GLFW_DECORATION_WIDTH) cursorShape = GLFW_NE_RESIZE_CURSOR; else cursorShape = GLFW_HRESIZE_CURSOR; break; case bottomDecoration: - if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH) + if (x < _GLFW_DECORATION_WIDTH) cursorShape = GLFW_SW_RESIZE_CURSOR; - else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH) + else if (x > window->wl.width + _GLFW_DECORATION_WIDTH) cursorShape = GLFW_SE_RESIZE_CURSOR; else cursorShape = GLFW_VRESIZE_CURSOR; @@ -218,7 +222,8 @@ static void pointerHandleMotion(void* data UNUSED, default: assert(0); } - setCursor(cursorShape); + if (_glfw.wl.cursorPreviousShape != cursorShape) + setCursor(cursorShape); } static void pointerHandleButton(void* data UNUSED, @@ -284,8 +289,8 @@ static void pointerHandleButton(void* data UNUSED, { xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, _glfw.wl.seat, serial, - window->wl.cursorPosX, - window->wl.cursorPosY); + (int32_t)window->wl.cursorPosX, + (int32_t)window->wl.cursorPosY); return; } } @@ -367,7 +372,7 @@ static void keyboardHandleEnter(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial UNUSED, struct wl_surface* surface, - struct wl_array* keys UNUSED) + struct wl_array* keys) { // Happens in the case we just destroyed the surface. if (!surface) @@ -383,6 +388,15 @@ static void keyboardHandleEnter(void* data UNUSED, _glfw.wl.keyboardFocus = window; _glfwInputWindowFocus(window, true); + uint32_t* key; + if (keys && _glfw.wl.keyRepeatInfo.key) { + wl_array_for_each(key, keys) { + if (*key == _glfw.wl.keyRepeatInfo.key) { + toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); + break; + } + } + } } static void keyboardHandleLeave(void* data UNUSED, @@ -397,13 +411,14 @@ static void keyboardHandleLeave(void* data UNUSED, _glfw.wl.keyboardFocus = NULL; _glfwInputWindowFocus(window, false); + toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); } static void dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) { if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return; glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT); - changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate)); + changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate)); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); } @@ -421,6 +436,7 @@ static void keyboardHandleKey(void* data UNUSED, int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action); bool repeatable = false; + _glfw.wl.keyRepeatInfo.key = 0; if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key)) { @@ -429,7 +445,7 @@ static void keyboardHandleKey(void* data UNUSED, _glfw.wl.keyRepeatInfo.keyboardFocus = window; } if (repeatable) { - changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0); + changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay); } toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0); } @@ -454,7 +470,7 @@ static void keyboardHandleRepeatInfo(void* data UNUSED, return; _glfw.wl.keyboardRepeatRate = rate; - _glfw.wl.keyboardRepeatDelay = delay; + _glfw.wl.keyboardRepeatDelay = ms_to_monotonic_t(delay); } static const struct wl_keyboard_listener keyboardListener = { @@ -607,8 +623,7 @@ static void registryHandleGlobal(void* data UNUSED, _glfwSetupWaylandDataDevice(); } } - else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 || - strcmp(interface, "gtk_primary_selection_device_manager") == 0) + else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0) { _glfw.wl.primarySelectionDeviceManager = wl_registry_bind(registry, name, @@ -728,8 +743,8 @@ int _glfwPlatformInit(void) "Wayland: Failed to initialize event loop data"); } glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData); - _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, true, dispatchPendingKeyRepeats, NULL, NULL); - _glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, true, animateCursorImage, NULL, NULL); + _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL); + _glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL); _glfw.wl.registry = wl_display_get_registry(_glfw.wl.display); wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 26f5bd8e..2718aae3 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -156,6 +156,7 @@ typedef struct _GLFWwindowWayland // We need to track the monitors the window spans on to calculate the // optimal scaling factor. int scale; + bool initial_scale_notified; _GLFWmonitor** monitors; int monitorsCount; int monitorsSize; @@ -247,10 +248,11 @@ typedef struct _GLFWlibraryWayland struct wl_cursor_theme* cursorTheme; struct wl_surface* cursorSurface; + GLFWCursorShape cursorPreviousShape; uint32_t pointerSerial; int32_t keyboardRepeatRate; - int32_t keyboardRepeatDelay; + monotonic_t keyboardRepeatDelay; struct { uint32_t key; id_type keyRepeatTimer; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 35c6e3c6..c9e94e79 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -32,6 +32,7 @@ #include "backend_utils.h" #include "memfd.h" #include "linux_notify.h" +#include "../kitty/monotonic.h" #include <stdio.h> #include <stdlib.h> @@ -54,13 +55,18 @@ static bool checkScaleChange(_GLFWwindow* window) if (_glfw.wl.compositorVersion < 3) return false; - // Get the scale factor from the highest scale monitor. + // 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 > scale) scale = m->wl.scale; + } // Only change the framebuffer size if the scale changed. if (scale != window->wl.scale) @@ -69,6 +75,10 @@ static bool checkScaleChange(_GLFWwindow* window) wl_surface_set_buffer_scale(window->wl.surface, scale); return true; } + if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) { + window->wl.initial_scale_notified = true; + return true; + } return false; } @@ -241,8 +251,8 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Creating a buffer file for %d B failed: %%m", - length); + "Wayland: Creating a buffer file for %d B failed: %s", + length, strerror(errno)); return NULL; } @@ -250,7 +260,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: mmap failed: %%m"); + "Wayland: mmap failed: %s", strerror(errno)); close(fd); return NULL; } @@ -545,9 +555,9 @@ static void xdgToplevelHandleConfigure(void* data, aspectRatio = (float)width / (float)height; targetRatio = (float)window->numer / (float)window->denom; if (aspectRatio < targetRatio) - height = width / targetRatio; + height = (int32_t)((float)width / targetRatio); else if (aspectRatio > targetRatio) - width = height * targetRatio; + width = (int32_t)((float)height * targetRatio); } } } @@ -684,7 +694,7 @@ setCursorImage(_GLFWcursorWayland* cursorWayland) image = cursorWayland->cursor->images[cursorWayland->currentImage]; buffer = wl_cursor_image_get_buffer(image); if (image->delay) { - changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ((double)image->delay) / 1000.0); + 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); @@ -745,11 +755,11 @@ abortOnFatalError(int last_error) { } static void -handleEvents(double timeout) +handleEvents(monotonic_t timeout) { struct wl_display* display = _glfw.wl.display; errno = 0; - EVDBG("starting handleEvents(%.2f)", timeout); + EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout)); while (wl_display_prepare_read(display) != 0) { while(1) { @@ -1052,9 +1062,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, *yscale = (float) window->wl.scale; } -double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) +monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { - return 0.5; + return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) @@ -1227,11 +1237,11 @@ void _glfwPlatformPollEvents(void) void _glfwPlatformWaitEvents(void) { - double timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1; + monotonic_t timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1; handleEvents(timeout); } -void _glfwPlatformWaitEventsTimeout(double timeout) +void _glfwPlatformWaitEventsTimeout(monotonic_t timeout) { if (wl_display_dispatch_pending(_glfw.wl.display) > 0) timeout = 0; handleEvents(timeout); @@ -1268,12 +1278,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED) _glfwPlatformSetCursor(window, window->wl.currentCursor); } -const char* _glfwPlatformGetScancodeName(int scancode) +const char* _glfwPlatformGetNativeKeyName(int native_key) { - return glfw_xkb_keysym_name(scancode); + return glfw_xkb_keysym_name(native_key); } -int _glfwPlatformGetKeyScancode(int key) +int _glfwPlatformGetNativeKeyForKey(int key) { return glfw_xkb_sym_for_key(key); } @@ -1462,8 +1472,8 @@ static void send_text(char *text, int fd) { if (text) { size_t len = strlen(text), pos = 0; - double start = glfwGetTime(); - while (pos < len && glfwGetTime() - start < 2.0) { + monotonic_t start = glfwGetTime(); + while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) { ssize_t ret = write(fd, text + pos, len - pos); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; @@ -1496,7 +1506,7 @@ static char* read_offer_string(int data_pipe) { struct pollfd fds; fds.fd = data_pipe; fds.events = POLLIN; - double start = glfwGetTime(); + monotonic_t start = glfwGetTime(); #define bail(...) { \ _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \ free(buf); buf = NULL; \ @@ -1504,7 +1514,7 @@ static char* read_offer_string(int data_pipe) { return NULL; \ } - while (glfwGetTime() - start < 2) { + while (glfwGetTime() - start < s_to_monotonic_t(2ll)) { int ret = poll(&fds, 1, 2000); if (ret == -1) { if (errno == EINTR) continue; @@ -2031,7 +2041,7 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) return window->wl.surface; } -GLFWAPI int glfwGetXKBScancode(const char* keyName, bool caseSensitive) { +GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } diff --git a/glfw/x11_init.c b/glfw/x11_init.c index a13c78a6..6b6a394e 100644 --- a/glfw/x11_init.c +++ b/glfw/x11_init.c @@ -433,12 +433,10 @@ static bool initExtensions(void) // void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cache) { - // NOTE: Default to the display-wide DPI as we don't currently have a policy - // for which monitor a window is considered to be on - float xdpi = DisplayWidth(_glfw.x11.display, _glfw.x11.screen) * - 25.4f / DisplayWidthMM(_glfw.x11.display, _glfw.x11.screen); - float ydpi = DisplayHeight(_glfw.x11.display, _glfw.x11.screen) * - 25.4f / DisplayHeightMM(_glfw.x11.display, _glfw.x11.screen); + // 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 @@ -468,7 +466,7 @@ void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cac if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { if (type && strcmp(type, "String") == 0) - xdpi = ydpi = atof(value.addr); + xdpi = ydpi = (float)atof(value.addr); } XrmDestroyDatabase(db); diff --git a/glfw/x11_monitor.c b/glfw/x11_monitor.c index 0b5574dd..85c08fb5 100644 --- a/glfw/x11_monitor.c +++ b/glfw/x11_monitor.c @@ -166,6 +166,16 @@ void _glfwPollMonitorsX11(void) 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 calcuate 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; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 26a17de6..8e189a22 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -31,6 +31,7 @@ #include "internal.h" #include "backend_utils.h" #include "linux_notify.h" +#include "../kitty/monotonic.h" #include <X11/cursorfont.h> #include <X11/Xmd.h> @@ -51,6 +52,10 @@ #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 @@ -61,8 +66,8 @@ static unsigned _glfwDispatchX11Events(void); static void -handleEvents(double timeout) { - EVDBG("starting handleEvents(%.2f)", timeout); +handleEvents(monotonic_t timeout) { + EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout)); int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout); EVDBG("display_read_ok: %d", display_read_ok); if (display_read_ok) { @@ -77,9 +82,9 @@ handleEvents(double timeout) { } static bool -waitForX11Event(double timeout) { +waitForX11Event(monotonic_t timeout) { // returns true if there is X11 data waiting to be read, does not run watches and timers - double end_time = glfwGetTime() + timeout; + monotonic_t end_time = glfwGetTime() + timeout; while(true) { if (timeout >= 0) { const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout); @@ -109,7 +114,7 @@ static bool waitForVisibilityNotify(_GLFWwindow* window) VisibilityNotify, &dummy)) { - if (!waitForX11Event(0.1)) + if (!waitForX11Event(ms_to_monotonic_t(100ll))) return false; } @@ -508,8 +513,8 @@ static bool createNativeWindow(_GLFWwindow* window, if (wndconfig->scaleToMonitor) { - width *= _glfw.x11.contentScaleX; - height *= _glfw.x11.contentScaleY; + width *= (int)_glfw.x11.contentScaleX; + height *= (int)_glfw.x11.contentScaleY; } // Create a colormap based on the visual used by the current context @@ -880,7 +885,7 @@ static const char* getSelectionString(Atom selection) Atom actualType; int actualFormat; unsigned long itemCount, bytesAfter; - double start = glfwGetTime(); + monotonic_t start = glfwGetTime(); XEvent notification, dummy; XConvertSelection(_glfw.x11.display, @@ -895,10 +900,10 @@ static const char* getSelectionString(Atom selection) SelectionNotify, ¬ification)) { - double time = glfwGetTime(); - if (time - start > 2) + monotonic_t time = glfwGetTime(); + if (time - start > s_to_monotonic_t(2ll)) return ""; - waitForX11Event(2.0 - (time - start)); + waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } if (notification.xselection.property == None) @@ -935,10 +940,10 @@ static const char* getSelectionString(Atom selection) isSelPropNewValueNotify, (XPointer) ¬ification)) { - double time = glfwGetTime(); - if (time - start > 2) + monotonic_t time = glfwGetTime(); + if (time - start > s_to_monotonic_t(2ll)) return ""; - waitForX11Event(2.0 - (time - start)); + waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } XFree(data); @@ -2066,7 +2071,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, isFrameExtentsEvent, (XPointer) window)) { - if (!waitForX11Event(0.5)) + 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"); @@ -2103,9 +2108,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED, *yscale = _glfw.x11.contentScaleY; } -double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) +monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { - return 0.5; + return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) @@ -2350,33 +2355,24 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED) void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled) { - if (enabled) + struct { - XDeleteProperty(_glfw.x11.display, - window->x11.handle, - _glfw.x11.MOTIF_WM_HINTS); - } - else - { - struct - { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long input_mode; - unsigned long status; - } hints; + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; + } hints = {0}; - hints.flags = 2; // Set decorations - hints.decorations = 0; // No decorations + 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)); - } + 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) @@ -2527,11 +2523,11 @@ void _glfwPlatformPollEvents(void) void _glfwPlatformWaitEvents(void) { - double timeout = _glfwDispatchX11Events() ? 0 : -1; + monotonic_t timeout = _glfwDispatchX11Events() ? 0 : -1; handleEvents(timeout); } -void _glfwPlatformWaitEventsTimeout(double timeout) +void _glfwPlatformWaitEventsTimeout(monotonic_t timeout) { if (_glfwDispatchX11Events()) timeout = 0; handleEvents(timeout); @@ -2585,13 +2581,13 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) XFlush(_glfw.x11.display); } -const char* _glfwPlatformGetScancodeName(int scancode) +const char* _glfwPlatformGetNativeKeyName(int native_key) { - return glfw_xkb_keysym_name(scancode); + return glfw_xkb_keysym_name(native_key); } -int _glfwPlatformGetKeyScancode(int key) +int _glfwPlatformGetNativeKeyForKey(int key) { return glfw_xkb_sym_for_key(key); } @@ -2870,7 +2866,7 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) return window->x11.handle; } -GLFWAPI int glfwGetXKBScancode(const char* keyName, bool caseSensitive) { +GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } diff --git a/glfw/xkb_glfw.c b/glfw/xkb_glfw.c index d61fc570..a554acd2 100644 --- a/glfw/xkb_glfw.c +++ b/glfw/xkb_glfw.c @@ -437,24 +437,22 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m } bool -glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) { +glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode) { #ifdef _GLFW_WAYLAND - scancode += 8; + keycode += 8; #endif - return xkb_keymap_key_repeats(xkb->keymap, scancode); + return xkb_keymap_key_repeats(xkb->keymap, keycode); } -static KeyEvent key_event = {0}; - static inline xkb_keysym_t -compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) { +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_event.text, sizeof(key_event.text)); + xkb_compose_state_get_utf8(composeState, key_text, n); *compose_completed = 1; return xkb_compose_state_get_one_sym(composeState); case XKB_COMPOSE_COMPOSING: @@ -534,11 +532,14 @@ glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, i } void -glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed) { +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 - window->callbacks.keyboard((GLFWwindow*) window, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, "", 1); + GLFWkeyevent fake_ev; + _glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0); + fake_ev.ime_state = 1; + 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 @@ -547,53 +548,58 @@ glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed) { // 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->action == GLFW_RELEASE; - debug("From IBUS: scancode: 0x%x name: %s is_release: %d\n", ev->keycode, glfw_xkb_keysym_name(ev->keysym), is_release); - if (window && !handled_by_ime && !(is_release && ev->keycode == prev_handled_press)) { + bool is_release = ev->glfw_ev.action == GLFW_RELEASE; + debug("From IBUS: native_key: 0x%x name: %s is_release: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release); + 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_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym), - (ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")), - format_mods(ev->glfw_modifiers), ev->text + 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 ); - _glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0); + + ev->glfw_ev.ime_state = 0; + _glfwInputKeyboard(window, &ev->glfw_ev); } else debug("↳ discarded\n"); - if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode; + if (!is_release && handled_by_ime) + last_handled_press_keycode = ev->glfw_ev.native_key; } void -glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) { +glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t xkb_keycode, int action) { + static char key_text[64] = {0}; const xkb_keysym_t *syms, *clean_syms, *default_syms; - xkb_keysym_t glfw_sym; - xkb_keycode_t code_for_sym = scancode; - key_event.ibus_keycode = scancode; + xkb_keysym_t xkb_sym; + xkb_keycode_t code_for_sym = xkb_keycode, ibus_keycode = xkb_keycode; + GLFWkeyevent glfw_ev; + _glfwInitializeKeyEvent(&glfw_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0); // init with default values #ifdef _GLFW_WAYLAND code_for_sym += 8; #else - key_event.ibus_keycode -= 8; + ibus_keycode -= 8; #endif - debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode); + debug("%s xkb_keycode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", 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_event.text[0] = 0; + 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; } - glfw_sym = clean_syms[0]; + xkb_sym = clean_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; - glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed); - if (glfw_sym == XKB_KEY_NoSymbol && !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(glfw_sym)); - if (glfw_sym == syms[0]) { // composed sym is the same as non-composed sym + 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 @@ -601,37 +607,58 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t 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 glfw_sym = clean_syms[0]; + else 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) & sg->modifiers) == 0) xkb_state_key_get_utf8(sg->state, code_for_sym, key_event.text, sizeof(key_event.text)); + if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) { + xkb_state_key_get_utf8(sg->state, code_for_sym, key_text, sizeof(key_text)); + } text_type = "text"; } - if ((1 <= key_event.text[0] && key_event.text[0] <= 31) || key_event.text[0] == 127) key_event.text[0] = 0; // don't send text for ascii control codes - if (key_event.text[0]) { debug("%s: %s ", text_type, key_event.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); } } - int glfw_keycode = glfw_key_for_sym(glfw_sym); + int glfw_sym = glfw_key_for_sym(xkb_sym); bool is_fallback = false; - if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.text[0]) { + if (glfw_sym == GLFW_KEY_UNKNOWN && !key_text[0]) { int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms); if (num_default_syms > 0) { - glfw_sym = default_syms[0]; - glfw_keycode = glfw_key_for_sym(glfw_sym); + xkb_sym = default_syms[0]; + glfw_sym = glfw_key_for_sym(xkb_sym); is_fallback = true; } } debug( "%s%s: %d (%s) xkb_key: %d (%s)\n", format_mods(sg->modifiers), - is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_keycode, _glfwGetKeyName(glfw_keycode), - glfw_sym, glfw_xkb_keysym_name(glfw_sym) + is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_sym, _glfwGetKeyName(glfw_sym), + xkb_sym, glfw_xkb_keysym_name(xkb_sym) ); - key_event.action = action; key_event.glfw_modifiers = sg->modifiers; - key_event.keycode = scancode; key_event.keysym = glfw_sym; - key_event.window_id = window->id; key_event.glfw_keycode = glfw_keycode; - key_event.ibus_sym = syms[0]; - if (ibus_process_key(&key_event, &xkb->ibus)) { - debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", key_event.ibus_keycode, key_event.ibus_sym, glfw_xkb_keysym_name(key_event.ibus_sym), format_mods(key_event.glfw_modifiers)); + + // 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_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0); + _glfwInputKeyboard(window, &glfw_ev); } } diff --git a/glfw/xkb_glfw.h b/glfw/xkb_glfw.h index 99e9ea8c..eea09fba 100644 --- a/glfw/xkb_glfw.h +++ b/glfw/xkb_glfw.h @@ -87,10 +87,10 @@ 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 scancode); +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(int key); -void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action); +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, int which, int a, int b, int c, int d); -void glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed); +void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed); diff --git a/kittens/diff/main.py b/kittens/diff/main.py index 848411b9..a6c1cbcc 100644 --- a/kittens/diff/main.py +++ b/kittens/diff/main.py @@ -2,14 +2,17 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> +import atexit import os import signal +import subprocess import sys +import tempfile import warnings from collections import defaultdict +from contextlib import suppress from functools import partial from gettext import gettext as _ -from contextlib import suppress from kitty.cli import CONFIG_HELP, parse_args from kitty.constants import appname @@ -506,7 +509,7 @@ def showwarning(message, category, filename, lineno, file=None, line=None): showwarning.warnings = [] -help_text = 'Show a side-by-side diff of the specified files/directories' +help_text = 'Show a side-by-side diff of the specified files/directories. You can also use ssh:hostname:remote-file-path to diff remote files.' usage = 'file_or_directory_left file_or_directory_right' @@ -516,6 +519,20 @@ def terminate_processes(processes): os.kill(pid, signal.SIGKILL) +def get_remote_file(path): + if path.startswith('ssh:'): + parts = path.split(':', 2) + if len(parts) == 3: + hostname, rpath = parts[1:] + with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(rpath), prefix='remote:', delete=False) as tf: + atexit.register(os.remove, tf.name) + p = subprocess.Popen(['ssh', hostname, 'cat', rpath], stdout=tf) + if p.wait() != 0: + raise SystemExit(p.returncode) + return tf.name + return path + + def main(args): warnings.showwarning = showwarning args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff') @@ -528,6 +545,7 @@ def main(args): opts = init_config(args) set_diff_command(opts.diff_cmd) lines_for_path.replace_tab_by = opts.replace_tab_by + left, right = map(get_remote_file, (left, right)) for f in left, right: if not os.path.exists(f): raise SystemExit('{} does not exist'.format(f)) diff --git a/kittens/hints/main.py b/kittens/hints/main.py index 09f51594..6b549070 100644 --- a/kittens/hints/main.py +++ b/kittens/hints/main.py @@ -26,11 +26,12 @@ screen_size = screen_size_function() class Mark: - __slots__ = ('index', 'start', 'end', 'text') + __slots__ = ('index', 'start', 'end', 'text', 'groupdict') - def __init__(self, index, start, end, text): + def __init__(self, index, start, end, text, groupdict): self.index, self.start, self.end = index, start, end self.text = text + self.groupdict = groupdict @lru_cache(maxsize=2048) @@ -94,6 +95,14 @@ class Hints(Handler): self.chosen = [] self.reset() + @property + def text_matches(self): + return [m.text + self.match_suffix for m in self.chosen] + + @property + def groupdicts(self): + return [m.groupdict for m in self.chosen] + def get_match_suffix(self, args): if args.add_trailing_space == 'always': return ' ' @@ -126,7 +135,7 @@ class Hints(Handler): if encode_hint(idx, self.alphabet).startswith(self.current_input) ] if len(matches) == 1: - self.chosen.append(matches[0].text + self.match_suffix) + self.chosen.append(matches[0]) if self.multiple: self.ignore_mark_indices.add(matches[0].index) self.reset() @@ -144,7 +153,7 @@ class Hints(Handler): elif key_event is enter_key and self.current_input: try: idx = decode_hint(self.current_input, self.alphabet) - self.chosen.append(self.index_map[idx].text + self.match_suffix) + self.chosen.append(self.index_map[idx]) self.ignore_mark_indices.add(idx) except Exception: self.current_input = '' @@ -176,12 +185,13 @@ class Hints(Handler): def regex_finditer(pat, minimum_match_length, text): + has_named_groups = bool(pat.groupindex) for m in pat.finditer(text): - s, e = m.span(pat.groups) + s, e = m.span(0 if has_named_groups else pat.groups) while e > s + 1 and text[e-1] == '\0': e -= 1 if e - s >= minimum_match_length: - yield s, e + yield s, e, m.groupdict() closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"} @@ -240,21 +250,23 @@ def quotes(text, s, e): def mark(pattern, post_processors, text, args): pat = re.compile(pattern) - for idx, (s, e) in enumerate(regex_finditer(pat, args.minimum_match_length, text)): + for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)): for func in post_processors: s, e = func(text, s, e) mark_text = text[s:e].replace('\n', '').replace('\0', '') - yield Mark(idx, s, e, mark_text) + yield Mark(idx, s, e, mark_text, groupdict) -def run_loop(args, text, all_marks, index_map): +def run_loop(args, text, all_marks, index_map, extra_cli_args=()): loop = Loop() handler = Hints(text, all_marks, index_map, args) loop.loop(handler) if handler.chosen and loop.return_code == 0: - return {'match': handler.chosen, 'programs': args.program, - 'multiple_joiner': args.multiple_joiner, - 'type': args.type} + return { + 'match': handler.text_matches, 'programs': args.program, + 'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing, + 'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args + } raise SystemExit(loop.return_code) @@ -311,11 +323,29 @@ def parse_input(text): return convert_text(text, cols) -def run(args, text): +def load_custom_processor(customize_processing): + from kitty.constants import config_dir + customize_processing = os.path.expandvars(os.path.expanduser(customize_processing)) + if os.path.isabs(customize_processing): + custom_path = customize_processing + else: + custom_path = os.path.join(config_dir, customize_processing) + import runpy + return runpy.run_path(custom_path, run_name='__main__') + + +def run(args, text, extra_cli_args=()): try: - pattern, post_processors = functions_for(args) text = parse_input(text) - all_marks = tuple(mark(pattern, post_processors, text, args)) + pattern, post_processors = functions_for(args) + if args.customize_processing: + m = load_custom_processor(args.customize_processing) + if 'mark' in m: + all_marks = tuple(m['mark'](text, args, Mark, extra_cli_args)) + else: + all_marks = tuple(mark(pattern, post_processors, text, args)) + else: + all_marks = tuple(mark(pattern, post_processors, text, args)) if not all_marks: input(_('No {} found, press Enter to quit.').format( 'URLs' if args.type == 'url' else 'matches' @@ -333,7 +363,7 @@ def run(args, text): input('Press Enter to quit.') raise SystemExit(1) - return run_loop(args, text, all_marks, index_map) + return run_loop(args, text, all_marks, index_map, extra_cli_args) # CLI {{{ @@ -356,11 +386,14 @@ The type of text to search for. --regex default=(?m)^\s*(.+)\s*$ The regular expression to use when :option:`kitty +kitten hints --type`=regex. -The regular expression is in python syntax. If you specify a group in +The regular expression is in python syntax. If you specify a numbered group in the regular expression only the group will be matched. This allow you to match text ignoring a prefix/suffix, as needed. The default expression matches lines. To match text over multiple lines you should prefix the regular expression with :code:`(?ms)`, which turns on MULTILINE and DOTALL modes for the regex engine. +If you specify named groups and a :option:`kitty +kitten hints --program` then +the program will be passed arguments corresponding to each named group of +the form key=value. --url-prefixes @@ -418,6 +451,13 @@ unless you specify the hints offset as zero the first match will be highlighted the second character you specify. +--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. See https://sw.kovidgoyal.net/kitty/kittens/hints.html +for details. You can also specify absolute paths to load the script from elsewhere. + + '''.format(','.join(sorted(URL_PREFIXES))).format help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.' usage = '' @@ -444,15 +484,23 @@ def main(args): print(e.args[0], file=sys.stderr) input(_('Press Enter to quit')) return - if items: + if items and not args.customize_processing: print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr) input(_('Press Enter to quit')) - return run(args, text) + return run(args, text, items) def handle_result(args, data, target_window_id, boss): + if data['customize_processing']: + m = load_custom_processor(data['customize_processing']) + if 'handle_result' in m: + return m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args']) + programs = data['programs'] or ('default',) - matches = tuple(filter(None, data['match'])) + matches, 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(joiner) @@ -489,7 +537,11 @@ def handle_result(args, data, target_window_id, boss): if w is not None: cwd = w.cwd_of_child program = None if program == 'default' else program - for m in matches: + for m, groupdict in zip(matches, groupdicts): + if groupdict: + m = [] + for k, v in groupdict.items(): + m.append('{}={}'.format(k, v or '')) boss.open_url(m, program, cwd=cwd) diff --git a/kittens/icat/main.py b/kittens/icat/main.py index 038f4f96..f7fc67bd 100755 --- a/kittens/icat/main.py +++ b/kittens/icat/main.py @@ -22,7 +22,6 @@ from ..tui.images import ( ) from ..tui.operations import clear_images_on_screen, serialize_gr_command -screen_size = None OPTIONS = '''\ --align type=choices @@ -97,6 +96,21 @@ Do not print out anything to stdout during operation. ''' +screen_size = None + + +def get_screen_size_function(): + global screen_size + if screen_size is None: + screen_size = screen_size_function() + return screen_size + + +def get_screen_size(): + screen_size = get_screen_size_function() + return screen_size() + + def options_spec(): if not hasattr(options_spec, 'ans'): options_spec.ans = OPTIONS.format( @@ -122,7 +136,7 @@ def calculate_in_cell_x_offset(width, cell_width, align): def set_cursor(cmd, width, height, align): - ss = screen_size() + ss = get_screen_size() cw = int(ss.width / ss.cols) num_of_cells_needed = int(ceil(width / cw)) if num_of_cells_needed > ss.cols: @@ -143,7 +157,7 @@ def set_cursor(cmd, width, height, align): def set_cursor_for_place(place, cmd, width, height, align): x = place.left + 1 - ss = screen_size() + ss = get_screen_size() cw = int(ss.width / ss.cols) num_of_cells_needed = int(ceil(width / cw)) cmd['X'] = calculate_in_cell_x_offset(width, cw, align) @@ -189,7 +203,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N def process(path, args, is_tempfile): m = identify(path) - ss = screen_size() + ss = get_screen_size() available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height needs_scaling = m.width > available_width or m.height > available_height @@ -314,7 +328,6 @@ def process_single_item(item, args, url_pat=None, maybe_dir=True): def main(args=sys.argv): - global screen_size args, items = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname)) if args.print_window_size: @@ -334,7 +347,7 @@ def main(args=sys.argv): sys.stdin.close() sys.stdin = open(os.ctermid(), 'r') - screen_size = screen_size_function() + screen_size = get_screen_size_function() signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True)) if screen_size().width == 0: if args.detect_support: diff --git a/kittens/show_error/main.py b/kittens/show_error/main.py index b9848579..933386c0 100644 --- a/kittens/show_error/main.py +++ b/kittens/show_error/main.py @@ -4,6 +4,7 @@ import os import sys +from contextlib import suppress from kitty.cli import parse_args @@ -30,9 +31,8 @@ def real_main(args): def main(args): try: - real_main(args) - except KeyboardInterrupt: - pass + with suppress(KeyboardInterrupt): + real_main(args) except Exception: import traceback traceback.print_exc() diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 99c9f3c8..ee32065e 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -76,6 +76,9 @@ def parse_ssh_args(args): if arg in boolean_ssh_args: ssh_args.append(arg) continue + if arg.startswith('-p') and arg[2:].isdigit(): + ssh_args.append(arg) + continue if arg in other_ssh_args: if i != len(all_args) - 1: raise SystemExit('Option {} cannot occur in the middle'.format(arg)) diff --git a/kittens/unicode_input/main.py b/kittens/unicode_input/main.py index 47ccdfe0..2fcbd6b7 100644 --- a/kittens/unicode_input/main.py +++ b/kittens/unicode_input/main.py @@ -5,25 +5,27 @@ import os import string import subprocess +import sys +from contextlib import suppress from functools import lru_cache from gettext import gettext as _ -from contextlib import suppress +from kitty.cli import parse_args from kitty.config import cached_values_for from kitty.constants import config_dir -from kitty.utils import get_editor -from kitty.fast_data_types import wcswidth +from kitty.fast_data_types import wcswidth, is_emoji_presentation_base from kitty.key_encoding import ( DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP, enter_key ) +from kitty.utils import get_editor -from ..tui.line_edit import LineEdit from ..tui.handler import Handler +from ..tui.line_edit import LineEdit from ..tui.loop import Loop from ..tui.operations import ( - clear_screen, colored, cursor, faint, set_line_wrapping, - set_window_title, sgr, styled + clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title, + sgr, styled ) HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES' @@ -131,7 +133,8 @@ def decode_hint(x): class Table: - def __init__(self): + def __init__(self, emoji_variation): + self.emoji_variation = emoji_variation self.layout_dirty = True self.last_rows = self.last_cols = -1 self.codepoints = [] @@ -161,7 +164,10 @@ class Table: self.layout_dirty = False def safe_chr(codepoint): - return chr(codepoint).encode('utf-8', 'replace').decode('utf-8') + ans = chr(codepoint).encode('utf-8', 'replace').decode('utf-8') + if self.emoji_variation and is_emoji_presentation_base(codepoint): + ans += self.emoji_variation + return ans if self.mode is NAME: def as_parts(i, codepoint): @@ -248,8 +254,13 @@ def is_index(w): class UnicodeInput(Handler): - def __init__(self, cached_values): + def __init__(self, cached_values, emoji_variation='none'): self.cached_values = cached_values + self.emoji_variation = '' + if emoji_variation == 'text': + self.emoji_variation = '\ufe0e' + elif emoji_variation == 'graphic': + self.emoji_variation = '\ufe0f' self.line_edit = LineEdit() self.recent = list(self.cached_values.get('recent', DEFAULT_SET)) self.current_char = None @@ -257,9 +268,17 @@ class UnicodeInput(Handler): self.last_updated_code_point_at = None self.choice_line = '' self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX') - self.table = Table() + self.table = Table(self.emoji_variation) self.update_prompt() + @property + def resolved_current_char(self): + ans = self.current_char + if ans: + if self.emoji_variation and is_emoji_presentation_base(ord(ans[0])): + ans += self.emoji_variation + return ans + def update_codepoints(self): codepoints = None if self.mode is HEX: @@ -320,8 +339,10 @@ class UnicodeInput(Handler): self.choice_line = '' else: c, color = self.current_char, 'green' + if self.emoji_variation and is_emoji_presentation_base(ord(c[0])): + c += self.emoji_variation self.choice_line = _('Chosen:') + ' {} U+{} {}'.format( - colored(c, 'green'), hex(ord(c))[2:], faint(styled(name(c) or '', italic=True))) + colored(c, 'green'), hex(ord(c[0]))[2:], faint(styled(name(c) or '', italic=True))) self.prompt = self.prompt_template.format(colored(c, color)) def init_terminal_state(self): @@ -475,17 +496,43 @@ class UnicodeInput(Handler): self.refresh() +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. + + +'''.format + + +def parse_unicode_input_args(args): + return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input') + + def main(args): + try: + args, items = parse_unicode_input_args(args[1:]) + except SystemExit as e: + if e.code != 0: + print(e.args[0], file=sys.stderr) + input(_('Press Enter to quit')) + return + loop = Loop() with cached_values_for('unicode-input') as cached_values: - handler = UnicodeInput(cached_values) + handler = UnicodeInput(cached_values, args.emoji_variation) loop.loop(handler) if handler.current_char and loop.return_code == 0: with suppress(Exception): handler.recent.remove(ord(handler.current_char)) recent = [ord(handler.current_char)] + handler.recent cached_values['recent'] = recent[:len(DEFAULT_SET)] - return handler.current_char + return handler.resolved_current_char if loop.return_code != 0: raise SystemExit(loop.return_code) @@ -497,10 +544,10 @@ def handle_result(args, current_char, target_window_id, boss): if __name__ == '__main__': - import sys - if '-h' in sys.argv or '--help' in sys.argv: - print('Choose a unicode character to input into the terminal') - raise SystemExit(0) ans = main(sys.argv) if ans: print(ans) +elif __name__ == '__doc__': + sys.cli_docs['usage'] = usage + sys.cli_docs['options'] = OPTIONS + sys.cli_docs['help_text'] = help_text diff --git a/kittens/unicode_input/names.h b/kittens/unicode_input/names.h index aaa0fdf2..1dc0d9bf 100644 --- a/kittens/unicode_input/names.h +++ b/kittens/unicode_input/names.h @@ -1,4 +1,4 @@ -// unicode data, built from the unicode standard on: 2019-08-02 +// unicode data, built from the unicode standard on: 2019-10-01 // see gen-wcwidth.py #pragma once #include "data-types.h" diff --git a/kitty/boss.py b/kitty/boss.py index 76b4a682..6d8c3f72 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -23,10 +23,10 @@ from .constants import ( from .fast_data_types import ( ChildMonitor, background_opacity_of, change_background_opacity, change_os_window_state, create_os_window, current_os_window, - destroy_global_data, get_clipboard_string, global_font_size, - mark_os_window_for_close, os_window_font_size, patch_global_colors, - safe_pipe, set_clipboard_string, set_in_sequence_mode, thread_write, - toggle_fullscreen, toggle_maximized + destroy_global_data, focus_os_window, get_clipboard_string, + global_font_size, mark_os_window_for_close, os_window_font_size, + patch_global_colors, safe_pipe, set_clipboard_string, set_in_sequence_mode, + thread_write, toggle_fullscreen, toggle_maximized ) from .keys import get_shortcut, shortcut_matches from .layout import set_layout_options @@ -58,9 +58,9 @@ def data_for_at(w, arg, add_wrap_markers=False): if arg == '@selection': return w.text_for_selection() - if arg == '@ansi': + if arg in ('@ansi', '@ansi_screen_scrollback'): return as_text(as_ansi=True, add_history=True) - if arg == '@text': + if arg in ('@text', '@screen_scrollback'): return as_text(add_history=True) if arg == '@screen': return as_text() @@ -121,7 +121,7 @@ class Boss: talk_fd = getattr(single_instance, 'socket', None) talk_fd = -1 if talk_fd is None else talk_fd.fileno() listen_fd = -1 - if opts.allow_remote_control and args.listen_on: + if args.listen_on and (opts.allow_remote_control in ('y', 'socket-only')): listen_fd = listen_on(args.listen_on) self.child_monitor = ChildMonitor( self.on_child_death, @@ -146,9 +146,9 @@ class Boss: from .fast_data_types import cocoa_set_notification_activated_callback cocoa_set_notification_activated_callback(self.notification_activated) - def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None): + def add_os_window(self, startup_session=None, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None): if os_window_id is None: - opts_for_size = opts_for_size or startup_session.os_window_size or self.opts + opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts cls = wclass or self.args.cls or appname 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( @@ -235,7 +235,7 @@ class Boss: if tab: yield tab - def set_active_window(self, window): + def set_active_window(self, window, switch_os_window_if_needed=False): for os_window_id, tm in self.os_window_map.items(): for tab in tm: for w in tab: @@ -243,6 +243,8 @@ class Boss: if tab is not self.active_tab: tm.set_active_tab(tab) tab.set_active_window(w) + if switch_os_window_if_needed and current_os_window() != os_window_id: + focus_os_window(os_window_id, True) return os_window_id def _new_os_window(self, args, cwd_from=None): @@ -276,9 +278,9 @@ class Boss: 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, window=None): + def _handle_remote_command(self, cmd, window=None, from_peer=False): response = None - if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False): + if self.opts.allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False): try: response = handle_cmd(self, window, cmd) except Exception as err: @@ -287,7 +289,7 @@ class Boss: if not getattr(err, 'hide_traceback', False): response['tb'] = traceback.format_exc() else: - response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'} + response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'} return response def peer_message_received(self, msg): @@ -295,7 +297,7 @@ class Boss: cmd_prefix = '\x1bP@kitty-cmd' if msg.startswith(cmd_prefix): cmd = msg[len(cmd_prefix):-2] - response = self._handle_remote_command(cmd) + response = self._handle_remote_command(cmd, from_peer=True) if response is not None: response = (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8') return response @@ -321,6 +323,16 @@ class Boss: if window is not None: window.send_cmd_response(response) + def _cleanup_tab_after_window_removal(self, src_tab): + 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: + mark_os_window_for_close(src_tab.os_window_id) + def on_child_death(self, window_id): window = self.window_id_map.pop(window_id, None) if window is None: @@ -334,20 +346,22 @@ class Boss: os_window_id = window.os_window_id window.destroy() tm = self.os_window_map.get(os_window_id) - if tm is None: - return - for tab in tm: - if window in tab: - break - else: - return - tab.remove_window(window) - if len(tab) == 0: - tm.remove(tab) - tab.destroy() - if len(tm) == 0: - if not self.shutting_down: - mark_os_window_for_close(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) + if window.action_on_removal: + try: + window.action_on_removal(window) + except Exception: + import traceback + traceback.print_exc() + window.action_on_close = window.action_on_removal = None def close_window(self, window=None): if window is None: @@ -512,27 +526,27 @@ class Boss: if t is not None: return t.active_window - def dispatch_special_key(self, key, scancode, action, mods): + def dispatch_special_key(self, key, native_key, action, mods): # Handles shortcuts, return True if the key was consumed - key_action = get_shortcut(self.keymap, mods, key, scancode) + key_action = get_shortcut(self.keymap, mods, key, native_key) if key_action is None: - sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode) + sequences = get_shortcut(self.opts.sequence_map, mods, key, native_key) if sequences: self.pending_sequences = sequences set_in_sequence_mode(True) return True else: - self.current_key_press_info = key, scancode, action, mods + self.current_key_press_info = key, native_key, action, mods return self.dispatch_action(key_action) - def process_sequence(self, key, scancode, action, mods): + def process_sequence(self, key, native_key, action, mods): if not self.pending_sequences: set_in_sequence_mode(False) remaining = {} matched_action = None for seq, key_action in self.pending_sequences.items(): - if shortcut_matches(seq[0], mods, key, scancode): + if shortcut_matches(seq[0], mods, key, native_key): seq = seq[1:] if seq: remaining[seq] = key_action @@ -670,7 +684,7 @@ class Boss: output += str(s.linebuf.line(i)) return output - def _run_kitten(self, kitten, args=(), input_data=None, window=None): + def _run_kitten(self, kitten, args=(), input_data=None, window=None, custom_callback=None, action_on_removal=None): orig_args, args = list(args), list(args) from kittens.runner import create_kitten_handler end_kitten = create_kitten_handler(kitten, orig_args) @@ -719,7 +733,10 @@ class Boss: ), copy_colors_from=w ) - overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten) + wid = w.id + overlay_window.action_on_close = partial(self.on_kitten_finish, wid, custom_callback or end_kitten) + if action_on_removal is not None: + overlay_window.action_on_removal = lambda *a: action_on_removal(wid, self) return overlay_window def kitten(self, kitten, *args): @@ -881,6 +898,9 @@ class Boss: stdin = stdin.encode('utf-8') return env, stdin + def data_for_at(self, which, window=None, add_wrap_markers=False): + return data_for_at(window or self.active_window, which, add_wrap_markers=add_wrap_markers) + def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False): w = window or self.active_window env, stdin = self.process_stdin_source(w, stdin) @@ -894,6 +914,25 @@ class Boss: overlay_for = w.id if as_overlay and w.overlay_for is None else None return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env) + def run_background_process(self, cmd, cwd=None, env=None, stdin=None, cwd_from=None): + import subprocess + if cwd_from: + with suppress(Exception): + cwd = cwd_of_process(cwd_from) + + if stdin: + r, w = safe_pipe(False) + try: + subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd) + except Exception: + os.close(w) + else: + thread_write(w, stdin) + finally: + os.close(r) + else: + subprocess.Popen(cmd, env=env, cwd=cwd) + def pipe(self, source, dest, exe, *args): cmd = [exe] + list(args) window = self.active_window @@ -919,24 +958,8 @@ class Boss: func = set_clipboard_string if dest == 'clipboard' else set_primary_selection func(stdin) else: - import subprocess env, stdin = self.process_stdin_source(stdin=source, window=window) - cwd = None - if cwd_from: - with suppress(Exception): - cwd = cwd_of_process(cwd_from) - if stdin: - r, w = safe_pipe(False) - try: - subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd) - except Exception: - os.close(w) - else: - thread_write(w, stdin) - finally: - os.close(r) - else: - subprocess.Popen(cmd, env=env, cwd=cwd) + self.run_background_process(cmd, cwd_from=cwd_from, stdin=stdin, env=env) def args_to_special_window(self, args, cwd_from=None): args = list(args) @@ -991,14 +1014,20 @@ class Boss: def _new_window(self, args, cwd_from=None): tab = self.active_tab if tab is not None: + allow_remote_control = False location = None if args and args[0].startswith('!'): location = args[0][1:].lower() args = args[1:] + if args and args[0] == '@': + args = args[1:] + allow_remote_control = True if args: - return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from), location=location) + return tab.new_special_window( + self.args_to_special_window(args, cwd_from=cwd_from), + location=location, allow_remote_control=allow_remote_control) else: - return tab.new_window(cwd_from=cwd_from, location=location) + return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control) def new_window(self, *args): self._new_window(args) @@ -1010,6 +1039,11 @@ class Boss: cwd_from = w.child.pid_for_cwd if w is not None else None self._new_window(args, cwd_from=cwd_from) + def launch(self, *args): + from kitty.launch import parse_launch_args, launch + opts, args = parse_launch_args(args) + launch(self, opts, args) + def move_tab_forward(self): tm = self.active_tab_manager if tm is not None: @@ -1096,3 +1130,132 @@ class Boss: msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip() self.show_error(_('Errors in kitty.conf'), msg) + + def set_colors(self, *args): + from .cmds import parse_subcommand_cli, cmd_set_colors, set_colors + opts, items = parse_subcommand_cli(cmd_set_colors, ['set-colors'] + list(args)) + payload = cmd_set_colors(None, opts, items) + set_colors(self, self.active_window, payload) + + def _move_window_to(self, window=None, target_tab_id=None, target_os_window_id=None): + window = window or self.active_window + if not window: + return + src_tab = self.tab_for_window(window) + if src_tab is None: + return + 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 target_tab_id == 'new': + tm = self.os_window_map[target_os_window_id] + target_tab = 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 + + underlaid_window, overlaid_window = src_tab.detach_window(window) + if underlaid_window: + target_tab.attach_window(underlaid_window) + if overlaid_window: + target_tab.attach_window(overlaid_window) + self._cleanup_tab_after_window_removal(src_tab) + target_tab.make_active() + + def _move_tab_to(self, tab=None, target_os_window_id=None): + tab = tab or self.active_tab + if tab is None: + return + if target_os_window_id is None: + target_os_window_id = self.add_os_window() + tm = self.os_window_map[target_os_window_id] + target_tab = tm.new_tab(empty_tab=True) + target_tab.take_over_from(tab) + self._cleanup_tab_after_window_removal(tab) + target_tab.make_active() + + def detach_window(self, *args): + if not args or args[0] == 'new': + return self._move_window_to(target_os_window_id='new') + if args[0] == 'new-tab': + return self._move_window_to(target_tab_id='new') + lines = [ + 'Choose a tab to move the window to', + '' + ] + tab_id_map = {} + current_tab = self.active_tab + for i, tab in enumerate(self.all_tabs): + if tab is not current_tab: + tab_id_map[i + 1] = tab.id + lines.append('{} {}'.format(i + 1, tab.title)) + new_idx = len(tab_id_map) + 1 + tab_id_map[new_idx] = 'new' + lines.append('{} {}'.format(new_idx, 'New tab')) + new_idx = len(tab_id_map) + 1 + tab_id_map[new_idx] = None + lines.append('{} {}'.format(new_idx, 'New OS Window')) + + def done(data, target_window_id, self): + done.tab_id = tab_id_map[int(data['match'][0].partition(' ')[0])] + + def done2(target_window_id, self): + tab_id = done.tab_id + target_window = None + for w in self.all_windows: + if w.id == target_window_id: + target_window = w + break + if tab_id is None: + self._move_window_to(window=target_window, target_os_window_id='new') + else: + self._move_window_to(window=target_window, target_tab_id=tab_id) + + self._run_kitten( + 'hints', args=('--type=regex', r'--regex=(?m)^\d+ .+$',), + input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2) + + def detach_tab(self, *args): + if not args or args[0] == 'new': + return self._move_tab_to() + + lines = [ + 'Choose an OS window to move the tab to', + '' + ] + os_window_id_map = {} + current_os_window = getattr(self.active_tab, 'os_window_id', 0) + for i, osw in enumerate(self.os_window_map): + tm = self.os_window_map[osw] + if current_os_window != osw and tm.active_tab and tm.active_tab: + os_window_id_map[i + 1] = osw + lines.append('{} {}'.format(i + 1, tm.active_tab.title)) + new_idx = len(os_window_id_map) + 1 + os_window_id_map[new_idx] = None + lines.append('{} {}'.format(new_idx, 'New OS Window')) + + def done(data, target_window_id, self): + done.os_window_id = os_window_id_map[int(data['match'][0].partition(' ')[0])] + + def done2(target_window_id, self): + os_window_id = done.os_window_id + target_tab = self.active_tab + for w in self.all_windows: + if w.id == target_window_id: + target_tab = w.tabref() + break + if target_tab and target_tab.os_window_id == os_window_id: + return + self._move_tab_to(tab=target_tab, target_os_window_id=os_window_id) + + self._run_kitten( + 'hints', args=('--type=regex', r'--regex=(?m)^\d+ .+$',), + input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2) diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index aafc7627..a226dcf3 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -11,6 +11,7 @@ #include "screen.h" #include "fonts.h" #include "charsets.h" +#include "monotonic.h" #include <termios.h> #include <unistd.h> #include <float.h> @@ -34,7 +35,7 @@ extern PyTypeObject Screen_Type; #endif #define USE_RENDER_FRAMES (global_state.has_render_frames && OPT(sync_to_monitor)) -static void (*parse_func)(Screen*, PyObject*, double); +static void (*parse_func)(Screen*, PyObject*, monotonic_t); typedef struct { char *data; @@ -110,10 +111,10 @@ static size_t reaped_pids_count = 0; // The max time (in secs) to wait for events from the window system // before ticking over the main loop. Negative values mean wait forever. -static double maximum_wait = -1.0; +static monotonic_t maximum_wait = -1; static inline void -set_maximum_wait(double val) { +set_maximum_wait(monotonic_t val) { if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val; } @@ -296,11 +297,11 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) { } static inline bool -do_parse(ChildMonitor *self, Screen *screen, double now) { +do_parse(ChildMonitor *self, Screen *screen, monotonic_t now) { bool input_read = false; screen_mutex(lock, read); if (screen->read_buf_sz || screen->pending_mode.used) { - double time_since_new_input = now - screen->new_input_at; + monotonic_t time_since_new_input = now - screen->new_input_at; if (time_since_new_input >= OPT(input_delay)) { bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ; input_read = true; @@ -308,7 +309,7 @@ do_parse(ChildMonitor *self, Screen *screen, double now) { if (read_buf_full) wakeup_io_loop(self, false); // Ensure the read fd has POLLIN set screen->new_input_at = 0; if (screen->pending_mode.activated_at) { - double time_since_pending = MAX(0, now - screen->pending_mode.activated_at); + monotonic_t time_since_pending = MAX(0, now - screen->pending_mode.activated_at); set_maximum_wait(screen->pending_mode.wait_time - time_since_pending); } } else set_maximum_wait(OPT(input_delay) - time_since_new_input); @@ -322,7 +323,7 @@ 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; - double now = monotonic(); + monotonic_t now = monotonic(); PyObject *msg = NULL; children_mutex(lock); while (remove_queue_count) { @@ -485,22 +486,22 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) { extern void cocoa_update_menu_bar_title(PyObject*); static inline void -collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_window) { +collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) { ScreenRenderData *rd = &w->render_data; Cursor *cursor = rd->screen->cursor; ans->x = cursor->x; ans->y = cursor->y; ans->is_visible = false; if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return; - double time_since_start_blink = now - os_window->cursor_blink_zero_time; + monotonic_t time_since_start_blink = now - os_window->cursor_blink_zero_time; bool cursor_blinking = OPT(cursor_blink_interval) > 0 && os_window->is_focused && (OPT(cursor_stop_blinking_after) == 0 || time_since_start_blink <= OPT(cursor_stop_blinking_after)); bool do_draw_cursor = true; if (cursor_blinking) { - int t = (int)(time_since_start_blink * 1000); - int d = (int)(OPT(cursor_blink_interval) * 1000); + int t = monotonic_t_to_ms(time_since_start_blink); + int d = monotonic_t_to_ms(OPT(cursor_blink_interval)); int n = t / d; do_draw_cursor = n % 2 == 0 ? true : false; - double bucket = (n + 1) * d; - double delay = (bucket / 1000.0) - time_since_start_blink; + monotonic_t bucket = ms_to_monotonic_t((n + 1) * d); + monotonic_t delay = bucket - time_since_start_blink; set_maximum_wait(delay); } if (!do_draw_cursor) { ans->is_visible = false; return; } @@ -526,7 +527,7 @@ update_window_title(Window *w, OSWindow *os_window) { } static inline bool -prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) { +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) { #define TD os_window->tab_bar_render_data bool needs_render = os_window->needs_render; os_window->needs_render = false; @@ -555,10 +556,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ 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 >= 0.02) { + 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(0.02); + 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); @@ -579,7 +580,7 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ } static inline void -render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) { +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) { // ensure all pixels are cleared to background color at least once in every buffer if (os_window->clear_count++ < 3) blank_os_window(os_window); Tab *tab = os_window->tabs + os_window->active_tab; @@ -587,8 +588,8 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC; float x_ratio = 1, y_ratio = 1; if (static_live_resize_in_progress) { - x_ratio = os_window->viewport_width / (double) os_window->live_resize.width; - y_ratio = os_window->viewport_height / (double) os_window->live_resize.height; + x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width; + y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height; } if (!static_live_resize_in_progress) { draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); @@ -601,7 +602,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, bool is_active_window = i == tab->active_window; draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx * x_ratio, WD.dy * y_ratio, WD.screen, os_window, is_active_window, true); if (WD.screen->start_visual_bell_at != 0) { - double bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at); + monotonic_t bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at); set_maximum_wait(bell_left); } w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape; @@ -638,17 +639,17 @@ draw_resizing_text(OSWindow *w) { } static inline bool -no_render_frame_received_recently(OSWindow *w, double now, double max_wait) { +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) log_error("No render frame received in %f seconds, re-requesting at: %f", max_wait, now); + if (ans) log_error("No render frame received in %f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now)); return ans; } static inline void -render(double now, bool input_read) { +render(monotonic_t now, bool input_read) { EVDBG("input_read: %d", input_read); - static double last_render_at = -DBL_MAX; - double time_since_last_render = now - last_render_at; + static monotonic_t last_render_at = MONOTONIC_T_MIN; + monotonic_t time_since_last_render = now - last_render_at; if (!input_read && time_since_last_render < OPT(repaint_delay)) { set_maximum_wait(OPT(repaint_delay) - time_since_last_render); return; @@ -662,7 +663,7 @@ render(double now, bool input_read) { continue; } if (USE_RENDER_FRAMES && w->render_state != RENDER_FRAME_READY) { - if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, 0.25)) request_frame_render(w); + if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, ms_to_monotonic_t(250ll))) request_frame_render(w); continue; } make_os_window_context_current(w); @@ -806,7 +807,7 @@ add_python_timer(PyObject *self UNUSED, PyObject *args) { 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(interval, repeats ? true: false, python_timer_callback, callback, python_timer_cleanup); + 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); } @@ -821,7 +822,7 @@ remove_python_timer(PyObject *self UNUSED, PyObject *args) { static inline void -process_pending_resizes(double now) { +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; @@ -830,11 +831,11 @@ process_pending_resizes(double now) { if (w->live_resize.from_os_notification) { if (w->live_resize.os_says_resize_complete || (now - w->live_resize.last_resize_event_at) > 1) update_viewport = true; } else { - double debounce_time = OPT(resize_debounce_time); + monotonic_t debounce_time = OPT(resize_debounce_time); // 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 (w->live_resize.num_of_resize_events > 1 && OPT(resize_draw_strategy) == RESIZE_DRAW_SIZE) debounce_time = MAX(0.2, debounce_time); + if (w->live_resize.num_of_resize_events > 1 && OPT(resize_draw_strategy) == RESIZE_DRAW_SIZE) debounce_time = MAX(ms_to_monotonic_t(200ll), debounce_time); if (now - w->live_resize.last_resize_event_at >= debounce_time) update_viewport = true; else { global_state.has_pending_resizes = true; @@ -916,7 +917,7 @@ process_global_state(void *data) { maximum_wait = -1; bool state_check_timer_enabled = false; - double now = monotonic(); + monotonic_t now = monotonic(); if (global_state.has_pending_resizes) process_pending_resizes(now); bool input_read = parse_input(self); render(now, input_read); @@ -1167,7 +1168,7 @@ io_loop(void *data) { size_t i; int ret; bool has_more, data_received, has_pending_wakeups = false; - double last_main_loop_wakeup_at = -1, now = -1; + monotonic_t last_main_loop_wakeup_at = -1, now = -1; Screen *screen; ChildMonitor *self = (ChildMonitor*)data; set_thread_name("KittyChildMon"); @@ -1188,8 +1189,8 @@ io_loop(void *data) { } if (has_pending_wakeups) { now = monotonic(); - double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at); - if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta)); + monotonic_t time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at); + if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, monotonic_t_to_ms(time_delta)); else ret = 0; } else { ret = poll(fds, self->count + EXTRA_FDS, -1); diff --git a/kitty/child.py b/kitty/child.py index 83834052..032ab91a 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -4,9 +4,9 @@ import fcntl import os +import sys from collections import defaultdict -from contextlib import contextmanager -from contextlib import suppress +from contextlib import contextmanager, suppress import kitty.fast_data_types as fast_data_types @@ -125,15 +125,24 @@ def remove_blocking(fd): os.set_blocking(fd, False) +def process_env(): + ans = os.environ + ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None) + if ssl_env_var is not None: + ans = ans.copy() + ans.pop(ssl_env_var, None) + return ans + + def default_env(): try: return default_env.env except AttributeError: - return os.environ + return process_env() def set_default_env(val=None): - env = os.environ.copy() + env = process_env().copy() if val: env.update(val) default_env.env = env @@ -151,12 +160,8 @@ class Child: child_fd = pid = None forked = False - def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None): - self.allow_remote_control = False - if argv and argv[0] == '@': - self.allow_remote_control = True - if len(argv) > 1: - argv = argv[1:] + def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None, allow_remote_control=False): + self.allow_remote_control = allow_remote_control self.argv = argv if cwd_from is not None: try: @@ -263,6 +268,13 @@ class Child: return list(self.argv) @property + def foreground_cmdline(self): + try: + return cmdline_of_process(self.pid_for_cwd) or self.cmdline + except Exception: + return self.cmdline + + @property def environ(self): try: return environ_of_process(self.pid) @@ -287,3 +299,14 @@ class Child: def foreground_cwd(self): with suppress(Exception): return cwd_of_process(self.pid_for_cwd) or None + + @property + def foreground_environ(self): + try: + return environ_of_process(self.pid_for_cwd) + except Exception: + try: + return environ_of_process(self.pid) + except Exception: + pass + return {} diff --git a/kitty/cli.py b/kitty/cli.py index c2cc394f..00127552 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -225,7 +225,7 @@ Run the :italic:`{appname}` terminal emulator. You can also specify the :italic: to run inside :italic:`{appname}` as normal arguments following the :italic:`options`. For example: {appname} /bin/sh -For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty''').format(appname=appname) +For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname) def print_help_for_seq(seq, usage, message, appname): @@ -423,8 +423,8 @@ class Options: class Namespace: def __init__(self, kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) + for name, val in kwargs.items(): + setattr(self, name, val) def parse_cmdline(oc, disabled, args=None): diff --git a/kitty/cmds.py b/kitty/cmds.py index 68235ccf..0154993b 100644 --- a/kitty/cmds.py +++ b/kitty/cmds.py @@ -7,10 +7,16 @@ import os import sys from contextlib import suppress -from .cli import parse_args, parse_option_spec, get_defaults_from_seq +from .cli import ( + Namespace, get_defaults_from_seq, parse_args, parse_option_spec +) from .config import parse_config, parse_send_text_bytes from .constants import appname from .fast_data_types import focus_os_window +from .launch import ( + launch as do_launch, options_spec as launch_options_spec, + parse_launch_args +) from .tabs import SpecialWindow from .utils import natsort_ints @@ -375,6 +381,100 @@ def set_tab_title(boss, window, payload): # }}} +# detach_window {{{ +@cmd( + 'Detach a window and place it in a different/new tab', + 'Detach the specified window and either move it into a new tab, a new OS window' + ' or add it to the specified tab. Use the special value :code:`new` for --target-tab' + ' to move to a new tab. If no target tab is specified the window is moved to a new OS window.', + options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n +--self +type=bool-set +If specified detach the window this command is run in, rather than the active window. +''', + argspec='' +) +def cmd_detach_window(global_opts, opts, args): + ''' + match: Which window to detach + target: Which tab to move the detached window to + self: Boolean indicating whether to detach the window the command is run in + ''' + return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self} + + +def detach_window(boss, window, payload): + pg = cmd_detach_window.payload_get + match = pg(payload, 'match') + if match: + windows = tuple(boss.match_windows(match)) + if not windows: + raise MatchError(match) + else: + windows = [window if window and pg(payload, 'self') else boss.active_window] + match = pg(payload, 'target_tab') + kwargs = {} + if match: + if match == 'new': + kwargs['target_tab_id'] = 'new' + else: + tabs = tuple(boss.match_tabs(match)) + if not tabs: + raise MatchError(match, 'tabs') + kwargs['target_tab_id'] = tabs[0].id + if not kwargs: + kwargs['target_os_window_id'] = 'new' + + for window in windows: + boss._move_window_to(window=window, **kwargs) + +# }}} + + +# detach_tab {{{ +@cmd( + 'Detach a tab and place it in a different/new OS Window', + 'Detach the specified tab and either move it into a new OS window' + ' or add it to the OS Window containing the tab specified by --target-tab', + options_spec=MATCH_TAB_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n +--self +type=bool-set +If specified detach the tab this command is run in, rather than the active tab. +''', + argspec='' +) +def cmd_detach_tab(global_opts, opts, args): + ''' + match: Which tab to detach + target: Which OS Window to move the detached tab to + self: Boolean indicating whether to detach the tab the command is run in + ''' + return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self} + + +def detach_tab(boss, window, payload): + pg = cmd_detach_tab.payload_get + match = pg(payload, 'match') + if match: + tabs = tuple(boss.match_tabs(match)) + if not tabs: + raise MatchError(match) + else: + tabs = [window.tabref() if pg(payload, 'self') and window and window.tabref() else boss.active_tab] + match = pg(payload, 'target_tab') + kwargs = {} + if match: + targets = tuple(boss.match_tabs(match)) + if not targets: + raise MatchError(match, 'tabs') + kwargs['target_os_window_id'] = targets[0].os_window_id + + for tab in tabs: + boss._move_tab_to(tab=tab, **kwargs) + +# }}} + + # goto_layout {{{ @cmd( 'Set the window layout', @@ -675,6 +775,76 @@ def new_window(boss, window, payload): # }}} +# launch {{{ +@cmd( + 'Run an arbitrary process in a new window/tab', + ' 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:' + ' :italic:`kitty @ launch --title Email mutt`.', + options_spec=MATCH_TAB_OPTION + '\n\n' + '''\ +--no-response +type=bool-set +Do not print out the id of the newly created window. + + +--self +type=bool-set +If specified the tab containing the window this command is run in is used +instead of the active tab + ''' + '\n\n' + launch_options_spec().replace(':option:`launch', ':option:`kitty @ launch'), + argspec='[CMD ...]' +) +def cmd_launch(global_opts, opts, args): + ''' + args+: The command line to run in the new window, as a list, use an empty list to run the default shell + match: The tab to open the new window in + window_title: Title for the new window + cwd: Working directory for the new window + env: List of environment varibles of the form NAME=VALUE + tab_title: Title for the new tab + type: The type of window to open + keep_focus: Boolean indicating whether the current window should retain focus or not + copy_colors: Boolean indicating whether to copy the colors from the current window + copy_cmdline: Boolean indicating whether to copy the cmdline from the current window + copy_env: Boolean indicating whether to copy the environ from the current window + location: Where in the tab to open the new window + allow_remote_control: Boolean indicating whether to allow remote control from the new window + stdin_source: Where to get stdin for thew process from + stdin_add_formatting: Boolean indicating whether to add formatting codes to stdin + stdin_add_line_wrap_markers: Boolean indicating whether to add line wrap markers to stdin + no_response: Boolean indicating whether to send back the window id + ''' + if opts.no_response: + global_opts.no_command_response = True + ans = {'args': args or []} + for attr, val in opts.__dict__.items(): + ans[attr] = val + return ans + + +def launch(boss, window, payload): + pg = cmd_launch.payload_get + default_opts = parse_launch_args()[0] + opts = {} + for key, default_value in default_opts.__dict__.items(): + opts[key] = payload.get(key, default_value) + opts = Namespace(opts) + match = pg(payload, 'match') + if match: + tabs = tuple(boss.match_tabs(match)) + if not tabs: + raise MatchError(match, 'tabs') + else: + tabs = [boss.active_tab] + if pg(payload, 'self') and window and window.tabref(): + tabs = [window.tabref()] + tab = tabs[0] + w = do_launch(boss, opts, pg(payload, 'args') or None, target_tab=tab) + return None if pg(payload, 'no_response') else str(getattr(w, 'id', 0)) +# }}} + + # focus_window {{{ @cmd( 'Focus the specified window', @@ -882,7 +1052,7 @@ this option, any color arguments are ignored and --configured and --all are impl def cmd_set_colors(global_opts, opts, args): ''' colors+: An object mapping names to colors as 24-bit RGB integers - cursor_text_color: A 24-bit clor for text under the cursor + cursor_text_color: A 24-bit color for text under the cursor match_window: Window to change colors in match_tab: Tab to change colors in all: Boolean indicating change colors everywhere or not diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index f404173f..3cb6f414 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -7,6 +7,7 @@ #include "state.h" +#include "monotonic.h" #include <Cocoa/Cocoa.h> #include <AvailabilityMacros.h> @@ -231,8 +232,6 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) { // global menu {{{ void cocoa_create_global_menu(void) { - @autoreleasepool { - NSString* app_name = find_app_name(); NSMenu* bar = [[NSMenu alloc] init]; GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance]; @@ -324,13 +323,11 @@ cocoa_create_global_menu(void) { [NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]]; - - } // autoreleasepool } void cocoa_update_menu_bar_title(PyObject *pytitle) { - NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)]; + NSString *title = @(PyUnicode_AsUTF8(pytitle)); NSMenu *bar = [NSApp mainMenu]; if (title_menu != NULL) { [bar removeItem:title_menu]; @@ -339,7 +336,6 @@ cocoa_update_menu_bar_title(PyObject *pytitle) { NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]]; [title_menu setSubmenu:m]; [m release]; - [title release]; } // }}} bool @@ -411,7 +407,7 @@ cocoa_get_lang(PyObject UNUSED *self) { } // autoreleasepool } -double +monotonic_t cocoa_cursor_blink_interval(void) { @autoreleasepool { @@ -420,12 +416,12 @@ cocoa_cursor_blink_interval(void) { 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 || off_period_ms) { + if (on_period_ms != 0. || off_period_ms != 0.) { ans = on_period_ms + off_period_ms; - } else if (period_ms) { + } else if (period_ms != 0.) { ans = period_ms; } - return ans > max_value ? 0.0 : ans; + return ans > max_value ? 0ll : ms_double_to_monotonic_t(ans); } // autoreleasepool } diff --git a/kitty/complete.py b/kitty/complete.py index cdf1f1b3..2c05b3c0 100644 --- a/kitty/complete.py +++ b/kitty/complete.py @@ -16,7 +16,7 @@ from .shell import options_for_cmd To add completion for a new shell, you need to: 1) Add an entry to completion scripts for your shell, this is -a simple function that call's kitty's completion code and passes the +a simple function that calls kitty's completion code and passes the results to the shell's completion system. This can be output by `kitty +complete setup shell_name` and its output goes into your shell's rc file. diff --git a/kitty/config.py b/kitty/config.py index 9f5abfaf..fdac97d4 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -50,7 +50,8 @@ func_with_args, args_funcs = key_func() @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' + 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd', + 'launch' ) def shlex_parse(func, rest): return func, to_cmdline(rest) @@ -92,6 +93,20 @@ def goto_tab_parse(func, rest): return func, args +@func_with_args('detach_window') +def detach_window_parse(func, rest): + if rest not in ('new', 'new-tab', 'ask'): + rest = 'new' + return func, (rest,) + + +@func_with_args('detach_tab') +def detach_tab_parse(func, rest): + if rest not in ('new', 'ask'): + rest = 'new' + return func, (rest,) + + @func_with_args('set_background_opacity', 'goto_layout', 'kitty_shell') def simple_parse(func, rest): return func, [rest] @@ -193,6 +208,15 @@ def pipe(func, rest): return func, rest +@func_with_args('set_colors') +def set_colors(func, rest): + import shlex + rest = shlex.split(rest) + if len(rest) < 1: + log_error('Too few arguments to set_colors function') + return func, rest + + @func_with_args('nth_window') def nth_window(func, rest): try: diff --git a/kitty/config_data.py b/kitty/config_data.py index 39ecd0ba..31ab6b75 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -393,10 +393,13 @@ Use negative numbers to change scroll direction.''')) g('mouse') # {{{ -o('mouse_hide_wait', 3.0, option_type=float, long_text=_(''' +o('mouse_hide_wait', 0.0 if is_macos else 3.0, option_type=float, 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.''')) +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. +''')) o('url_color', '#0087bd', option_type=to_color, long_text=_(''' The color and style for highlighting URLs on mouse-over. @@ -679,10 +682,10 @@ Which edge to show the tab bar on, top or bottom''')) o('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)''')) -o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_(''' -The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style, -each tab's edges fade into the background color, in the separator style, tabs are -separated by a configurable separator. +o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'powerline', 'hidden'), long_text=_(''' +The tab bar style, can be one of: :code:`fade`, :code:`separator`, :code:`powerline`, or :code:`hidden`. +In the fade style, each tab's edges fade into the background color, in the separator style, tabs are +separated by a configurable separator, and the powerline shows the tabs as a continuous line. ''')) o('tab_bar_min_tabs', 2, option_type=lambda x: max(1, positive_int(x)), long_text=_(''' @@ -812,9 +815,9 @@ ensure that the shell starts in interactive mode and reads its startup rc files. o('editor', '.', long_text=_(''' The console editor to use when editing the kitty config file or similar tasks. -A value of . means to use the environment variable EDITOR. Note that this -environment variable has to be set not just in your shell startup scripts but -system-wide, otherwise kitty will not see it. +A value of . means to use the environment variables VISUAL and EDITOR in that +order. Note that this environment variable has to be set not just in your shell +startup scripts but system-wide, otherwise kitty will not see it. ''')) o('close_on_child_death', False, long_text=_(''' @@ -826,11 +829,23 @@ Note that setting it to yes means that any background processes still using the terminal can fail silently because their stdout/stderr/stdin no longer work. ''')) -o('allow_remote_control', False, long_text=_(''' + +def allow_remote_control(x): + if x != 'socket-only': + x = 'y' if to_bool(x) else 'n' + return x + + +o('allow_remote_control', 'no', option_type=allow_remote_control, 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. +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. You can chose to either allow any program +running within kitty to control it, with :code:`yes` or only programs that +connect to the socket specified with the :option:`kitty --listen-on` command +line option, if you use the value :code:`socket-only`. The latter is useful if +you want to prevent programs running on a remote computer over ssh from +controlling kitty. ''')) o( @@ -1061,13 +1076,13 @@ if is_macos: k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_(''' You can pipe the contents of the current screen + history buffer as -:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example, +:file:`STDIN` to an arbitrary program using the ``launch`` function. For example, the following opens the scrollback buffer in less in an overlay window:: - map f1 pipe @ansi overlay less +G -R + 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:`pipe`. +see :doc:`launch`. ''')) @@ -1077,26 +1092,27 @@ g('shortcuts.window') # {{{ k('new_window', 'kitty_mod+enter', 'new_window', _(''), long_text=_(''' You can open a new window running an arbitrary program, for example:: - map kitty_mod+y new_window mutt + 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 new_window_with_cwd + 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 by prefixing the command line with @. Any programs running in that window will be allowed to control kitty. For example:: - map ctrl+enter new_window @ some_program + 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 new_window !neighbor some_program - map ctrl+f new_window !first some_program + map ctrl+n launch --location=neighbor some_program + map ctrl+f launch --location=first some_program +For more details, see :doc:`launch`. ''')) if is_macos: k('new_window', 'cmd+enter', 'new_window', _('New window'), add_to_docs=False) diff --git a/kitty/constants.py b/kitty/constants.py index abdc3f9c..73b0ba46 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -9,7 +9,7 @@ from collections import namedtuple from contextlib import suppress appname = 'kitty' -version = (0, 14, 6) +version = (0, 15, 0) str_version = '.'.join(map(str, version)) _plat = sys.platform.lower() is_macos = 'darwin' in _plat @@ -25,7 +25,7 @@ def kitty_exe(): if ans is None: rpath = sys._xoptions.get('bundle_exe_dir') if not rpath: - items = os.environ['PATH'].split(os.pathsep) + items = filter(None, os.environ.get('PATH', '').split(os.pathsep)) seen = set() for candidate in items: if candidate not in seen: @@ -49,9 +49,8 @@ def _get_config_dir(): locations.append(os.path.expanduser('~/.config')) if is_macos: locations.append(os.path.expanduser('~/Library/Preferences')) - if 'XDG_CONFIG_DIRS' in os.environ: - for loc in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep): - locations.append(os.path.abspath(os.path.expanduser(loc))) + for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)): + locations.append(os.path.abspath(os.path.expanduser(loc))) for loc in locations: if loc: q = os.path.join(loc, appname) diff --git a/kitty/core_text.m b/kitty/core_text.m index d41cf8e5..73ad19f0 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -278,7 +278,7 @@ get_glyph_width(PyObject *s, glyph_index g) { CGGlyph gg = g; CGRect bounds; CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1); - return bounds.size.width; + return (int)ceil(bounds.size.width); } static inline float @@ -326,7 +326,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u } } *cell_width = MAX(1u, width); - *underline_position = floor(self->ascent - self->underline_position + 0.5); + *underline_position = (unsigned int)floor(self->ascent - self->underline_position + 0.5); *underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness)); *baseline = (unsigned int)self->ascent; // float line_height = MAX(1, floor(self->ascent + self->descent + MAX(0, self->leading) + 0.5)); @@ -457,7 +457,7 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { CTFontGetGlyphsForCharacters(font, chars, glyphs, num_chars); CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, advances, num_chars); CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boxes, num_chars); - StringCanvas ans = { .width = 0, .height = 2 * bounding_box.size.height }; + StringCanvas ans = { .width = 0, .height = (size_t)(2 * bounding_box.size.height) }; for (size_t i = 0, y = 0; i < num_chars; i++) { positions[i] = CGPointMake(ans.width, y); ans.width += advances[i].width; y += advances[i].height; diff --git a/kitty/data-types.c b/kitty/data-types.c index 26f7d704..a5f4731b 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -5,6 +5,7 @@ * Distributed under terms of the GPL3 license. */ +#define MONOTONIC_START_MODULE #ifdef __APPLE__ // Needed for _CS_DARWIN_USER_CACHE_DIR #define _DARWIN_C_SOURCE @@ -22,31 +23,10 @@ #ifdef WITH_PROFILER #include <gperftools/profiler.h> #endif - -/* To millisecond (10^-3) */ -#define SEC_TO_MS 1000 - -/* To microseconds (10^-6) */ -#define MS_TO_US 1000 -#define SEC_TO_US (SEC_TO_MS * MS_TO_US) - -/* To nanoseconds (10^-9) */ -#define US_TO_NS 1000 -#define MS_TO_NS (MS_TO_US * US_TO_NS) -#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS) - -/* Conversion from nanoseconds */ -#define NS_TO_MS (1000 * 1000) -#define NS_TO_US (1000) +#include "monotonic.h" #ifdef __APPLE__ #include <libproc.h> -#include <mach/mach_time.h> -static mach_timebase_info_data_t timebase = {0}; - -static inline double monotonic_(void) { - return ((double)(mach_absolute_time() * timebase.numer) / timebase.denom)/SEC_TO_NS; -} static PyObject* user_cache_dir() { @@ -73,26 +53,8 @@ process_group_map() { free(buf); return ans; } - -#else -#include <time.h> -static inline double 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 (((double)ts.tv_nsec) / SEC_TO_NS) + (double)ts.tv_sec; -} #endif -static double start_time = 0; - -double monotonic() { return monotonic_() - start_time; } - static PyObject* redirect_std_streams(PyObject UNUSED *self, PyObject *args) { char *devnull = NULL; @@ -148,7 +110,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) { if (!read_with_timeout) flags |= O_NONBLOCK; static char ctty[L_ctermid+1]; int fd = open(ctermid(ctty), flags); - if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } + 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; } @@ -253,10 +215,7 @@ PyInit_fast_data_types(void) { m = PyModule_Create(&module); if (m == NULL) return NULL; -#ifdef __APPLE__ - mach_timebase_info(&timebase); -#endif - start_time = monotonic_(); + init_monotonic(); if (m != NULL) { if (!init_logging(m)) return NULL; diff --git a/kitty/data-types.h b/kitty/data-types.h index 3bd35f49..1dc24980 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -13,6 +13,7 @@ #include <stdbool.h> #include <poll.h> #include <pthread.h> +#include "glfw-wrapper.h" // Required minimum OpenGL version #define OPENGL_REQUIRED_VERSION_MAJOR 3 #define OPENGL_REQUIRED_VERSION_MINOR 3 @@ -291,7 +292,6 @@ void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned i const char* cell_as_sgr(const GPUCell *, const GPUCell *); const char* cursor_as_sgr(const Cursor *); -double monotonic(void); PyObject* cm_thread_write(PyObject *self, PyObject *args); bool schedule_write_to_child(unsigned long id, unsigned int num, ...); bool set_iutf8(int, bool); @@ -309,7 +309,7 @@ void focus_in_event(void); void scroll_event(double, double, int); void fake_scroll(int, bool); void set_special_key_combo(int glfw_key, int mods, bool is_native); -void on_key_input(int key, int scancode, int action, int mods, const char*, int); +void on_key_input(GLFWkeyevent *ev); void request_window_attention(id_type, bool); #ifndef __APPLE__ void play_canberra_sound(const char *which_sound, const char *event_id); diff --git a/kitty/desktop.c b/kitty/desktop.c index 3348aab4..5084ad27 100644 --- a/kitty/desktop.c +++ b/kitty/desktop.c @@ -7,7 +7,6 @@ #include "data-types.h" #include <dlfcn.h> -#include <canberra.h> #define FUNC(name, restype, ...) typedef restype (*name##_func)(__VA_ARGS__); static name##_func name = NULL #define LOAD_FUNC(handle, name) {\ @@ -90,15 +89,56 @@ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; -static ca_context *canberra_ctx = NULL; +static void* libcanberra_handle = NULL; +static void *canberra_ctx = NULL; +FUNC(ca_context_create, int, void**); +FUNC(ca_context_destroy, int, void*); +typedef int (*ca_context_play_func)(void*, uint32_t, ...); static ca_context_play_func ca_context_play = 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_destroy); + return NULL; +} + +static void +load_libcanberra(void) { + static const char* libname = "libcanberra.so"; + // some installs are missing the .so symlink, so try the full name + static const char* libname2 = "libcanberra.so.0"; + static const char* libname3 = "libcanberra.so.0.2.5"; + static bool done = false; + if (done) return; + done = true; + libcanberra_handle = dlopen(libname, RTLD_LAZY); + if (libcanberra_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY); + if (libcanberra_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY); + if (libcanberra_handle == NULL) { + fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libname, dlerror()); + return; + } + load_libcanberra_functions(); + if (PyErr_Occurred()) { + PyErr_Print(); + dlclose(libcanberra_handle); libcanberra_handle = NULL; + } + if (ca_context_create(&canberra_ctx) != 0) { + fprintf(stderr, "Failed to create libcanberra context, cannot play beep sound\n"); + ca_context_destroy(canberra_ctx); canberra_ctx = NULL; + dlclose(libcanberra_handle); libcanberra_handle = NULL; + } +} void play_canberra_sound(const char *which_sound, const char *event_id) { - if (canberra_ctx == NULL) ca_context_create(&canberra_ctx); + load_libcanberra(); + if (libcanberra_handle == NULL || canberra_ctx == NULL) return; ca_context_play( canberra_ctx, 0, - CA_PROP_EVENT_ID, which_sound, - CA_PROP_EVENT_DESCRIPTION, event_id, + "event.id", which_sound, + "event.description", event_id, NULL ); } @@ -109,6 +149,7 @@ finalize(void) { libsn_handle = NULL; if (canberra_ctx) ca_context_destroy(canberra_ctx); canberra_ctx = NULL; + if (libcanberra_handle) dlclose(libcanberra_handle); } bool diff --git a/kitty/emoji.h b/kitty/emoji.h index 91aed7a1..1a16de21 100644 --- a/kitty/emoji.h +++ b/kitty/emoji.h @@ -1,4 +1,4 @@ -// unicode data, built from the unicode standard on: 2019-08-02 +// unicode data, built from the unicode standard on: 2019-10-01 // see gen-wcwidth.py #pragma once #include "data-types.h" @@ -187,5 +187,464 @@ is_emoji_modifier(char_type code) { } return false; } +static inline bool +is_symbol(char_type code) { + switch(code) { + case 0x24: + return true; + case 0x2b: + return true; + case 0x3c ... 0x3e: + return true; + case 0x5e: + return true; + case 0x60: + return true; + case 0x7c: + return true; + case 0x7e: + return true; + case 0xa2 ... 0xa6: + return true; + case 0xa8 ... 0xa9: + return true; + case 0xac: + return true; + case 0xae ... 0xb1: + return true; + case 0xb4: + return true; + case 0xb8: + return true; + case 0xd7: + return true; + case 0xf7: + return true; + case 0x2c2 ... 0x2c5: + return true; + case 0x2d2 ... 0x2df: + return true; + case 0x2e5 ... 0x2eb: + return true; + case 0x2ed: + return true; + case 0x2ef ... 0x2ff: + return true; + case 0x375: + return true; + case 0x384 ... 0x385: + return true; + case 0x3f6: + return true; + case 0x482: + return true; + case 0x58d ... 0x58f: + return true; + case 0x606 ... 0x608: + return true; + case 0x60b: + return true; + case 0x60e ... 0x60f: + return true; + case 0x6de: + return true; + case 0x6e9: + return true; + case 0x6fd ... 0x6fe: + return true; + case 0x7f6: + return true; + case 0x7fe ... 0x7ff: + return true; + case 0x9f2 ... 0x9f3: + return true; + case 0x9fa ... 0x9fb: + return true; + case 0xaf1: + return true; + case 0xb70: + return true; + case 0xbf3 ... 0xbfa: + return true; + case 0xc7f: + return true; + case 0xd4f: + return true; + case 0xd79: + return true; + case 0xe3f: + return true; + case 0xf01 ... 0xf03: + return true; + case 0xf13: + return true; + case 0xf15 ... 0xf17: + return true; + case 0xf1a ... 0xf1f: + return true; + case 0xf34: + return true; + case 0xf36: + return true; + case 0xf38: + return true; + case 0xfbe ... 0xfc5: + return true; + case 0xfc7 ... 0xfcc: + return true; + case 0xfce ... 0xfcf: + return true; + case 0xfd5 ... 0xfd8: + return true; + case 0x109e ... 0x109f: + return true; + case 0x1390 ... 0x1399: + return true; + case 0x166d: + return true; + case 0x17db: + return true; + case 0x1940: + return true; + case 0x19de ... 0x19ff: + return true; + case 0x1b61 ... 0x1b6a: + return true; + case 0x1b74 ... 0x1b7c: + return true; + case 0x1fbd: + return true; + case 0x1fbf ... 0x1fc1: + return true; + case 0x1fcd ... 0x1fcf: + return true; + case 0x1fdd ... 0x1fdf: + return true; + case 0x1fed ... 0x1fef: + return true; + case 0x1ffd ... 0x1ffe: + return true; + case 0x2044: + return true; + case 0x2052: + return true; + case 0x207a ... 0x207c: + return true; + case 0x208a ... 0x208c: + return true; + case 0x20a0 ... 0x20bf: + return true; + case 0x2100 ... 0x2101: + return true; + case 0x2103 ... 0x2106: + return true; + case 0x2108 ... 0x2109: + return true; + case 0x2114: + return true; + case 0x2116 ... 0x2118: + return true; + case 0x211e ... 0x2123: + return true; + case 0x2125: + return true; + case 0x2127: + return true; + case 0x2129: + return true; + case 0x212e: + return true; + case 0x213a ... 0x213b: + return true; + case 0x2140 ... 0x2144: + return true; + case 0x214a ... 0x214d: + return true; + case 0x214f: + return true; + case 0x218a ... 0x218b: + return true; + case 0x2190 ... 0x2307: + return true; + case 0x230c ... 0x2328: + return true; + case 0x232b ... 0x2426: + return true; + case 0x2440 ... 0x244a: + return true; + case 0x249c ... 0x24e9: + return true; + case 0x2500 ... 0x2767: + return true; + case 0x2794 ... 0x27c4: + return true; + case 0x27c7 ... 0x27e5: + return true; + case 0x27f0 ... 0x2982: + return true; + case 0x2999 ... 0x29d7: + return true; + case 0x29dc ... 0x29fb: + return true; + case 0x29fe ... 0x2b73: + return true; + case 0x2b76 ... 0x2b95: + return true; + case 0x2b98 ... 0x2bff: + return true; + case 0x2ce5 ... 0x2cea: + return true; + case 0x2e80 ... 0x2e99: + return true; + case 0x2e9b ... 0x2ef3: + return true; + case 0x2f00 ... 0x2fd5: + return true; + case 0x2ff0 ... 0x2ffb: + return true; + case 0x3004: + return true; + case 0x3012 ... 0x3013: + return true; + case 0x3020: + return true; + case 0x3036 ... 0x3037: + return true; + case 0x303e ... 0x303f: + return true; + case 0x309b ... 0x309c: + return true; + case 0x3190 ... 0x3191: + return true; + case 0x3196 ... 0x319f: + return true; + case 0x31c0 ... 0x31e3: + return true; + case 0x3200 ... 0x321e: + return true; + case 0x322a ... 0x3247: + return true; + case 0x3250: + return true; + case 0x3260 ... 0x327f: + return true; + case 0x328a ... 0x32b0: + return true; + case 0x32c0 ... 0x33ff: + return true; + case 0x4dc0 ... 0x4dff: + return true; + case 0xa490 ... 0xa4c6: + return true; + case 0xa700 ... 0xa716: + return true; + case 0xa720 ... 0xa721: + return true; + case 0xa789 ... 0xa78a: + return true; + case 0xa828 ... 0xa82b: + return true; + case 0xa836 ... 0xa839: + return true; + case 0xaa77 ... 0xaa79: + return true; + case 0xab5b: + return true; + case 0xfb29: + return true; + case 0xfbb2 ... 0xfbc1: + return true; + case 0xfdfc ... 0xfdfd: + return true; + case 0xfe62: + return true; + case 0xfe64 ... 0xfe66: + return true; + case 0xfe69: + return true; + case 0xff04: + return true; + case 0xff0b: + return true; + case 0xff1c ... 0xff1e: + return true; + case 0xff3e: + return true; + case 0xff40: + return true; + case 0xff5c: + return true; + case 0xff5e: + return true; + case 0xffe0 ... 0xffe6: + return true; + case 0xffe8 ... 0xffee: + return true; + case 0xfffc ... 0xfffd: + return true; + case 0x10137 ... 0x1013f: + return true; + case 0x10179 ... 0x10189: + return true; + case 0x1018c ... 0x1018e: + return true; + case 0x10190 ... 0x1019b: + return true; + case 0x101a0: + return true; + case 0x101d0 ... 0x101fc: + return true; + case 0x10877 ... 0x10878: + return true; + case 0x10ac8: + return true; + case 0x1173f: + return true; + case 0x11fd5 ... 0x11ff1: + return true; + case 0x16b3c ... 0x16b3f: + return true; + case 0x16b45: + return true; + case 0x1bc9c: + return true; + case 0x1d000 ... 0x1d0f5: + return true; + case 0x1d100 ... 0x1d126: + return true; + case 0x1d129 ... 0x1d164: + return true; + case 0x1d16a ... 0x1d16c: + return true; + case 0x1d183 ... 0x1d184: + return true; + case 0x1d18c ... 0x1d1a9: + return true; + case 0x1d1ae ... 0x1d1e8: + return true; + case 0x1d200 ... 0x1d241: + return true; + case 0x1d245: + return true; + case 0x1d300 ... 0x1d356: + return true; + case 0x1d6c1: + return true; + case 0x1d6db: + return true; + case 0x1d6fb: + return true; + case 0x1d715: + return true; + case 0x1d735: + return true; + case 0x1d74f: + return true; + case 0x1d76f: + return true; + case 0x1d789: + return true; + case 0x1d7a9: + return true; + case 0x1d7c3: + return true; + case 0x1d800 ... 0x1d9ff: + return true; + case 0x1da37 ... 0x1da3a: + return true; + case 0x1da6d ... 0x1da74: + return true; + case 0x1da76 ... 0x1da83: + return true; + case 0x1da85 ... 0x1da86: + return true; + case 0x1e14f: + return true; + case 0x1e2ff: + return true; + case 0x1ecac: + return true; + case 0x1ecb0: + return true; + case 0x1ed2e: + return true; + case 0x1eef0 ... 0x1eef1: + return true; + case 0x1f000 ... 0x1f02b: + return true; + case 0x1f030 ... 0x1f093: + return true; + case 0x1f0a0 ... 0x1f0ae: + return true; + case 0x1f0b1 ... 0x1f0bf: + return true; + case 0x1f0c1 ... 0x1f0cf: + return true; + case 0x1f0d1 ... 0x1f0f5: + return true; + case 0x1f110 ... 0x1f16c: + return true; + case 0x1f170 ... 0x1f1ac: + return true; + case 0x1f1e6 ... 0x1f202: + return true; + case 0x1f210 ... 0x1f23b: + return true; + case 0x1f240 ... 0x1f248: + return true; + case 0x1f250 ... 0x1f251: + return true; + case 0x1f260 ... 0x1f265: + return true; + case 0x1f300 ... 0x1f6d5: + return true; + case 0x1f6e0 ... 0x1f6ec: + return true; + case 0x1f6f0 ... 0x1f6fa: + return true; + case 0x1f700 ... 0x1f773: + return true; + case 0x1f780 ... 0x1f7d8: + return true; + case 0x1f7e0 ... 0x1f7eb: + return true; + case 0x1f800 ... 0x1f80b: + return true; + case 0x1f810 ... 0x1f847: + return true; + case 0x1f850 ... 0x1f859: + return true; + case 0x1f860 ... 0x1f887: + return true; + case 0x1f890 ... 0x1f8ad: + return true; + case 0x1f900 ... 0x1f90b: + return true; + case 0x1f90d ... 0x1f971: + return true; + case 0x1f973 ... 0x1f976: + return true; + case 0x1f97a ... 0x1f9a2: + return true; + case 0x1f9a5 ... 0x1f9aa: + return true; + case 0x1f9ae ... 0x1f9ca: + return true; + case 0x1f9cd ... 0x1fa53: + return true; + case 0x1fa60 ... 0x1fa6d: + return true; + case 0x1fa70 ... 0x1fa73: + return true; + case 0x1fa78 ... 0x1fa7a: + return true; + case 0x1fa80 ... 0x1fa82: + return true; + case 0x1fa90 ... 0x1fa95: + return true; + default: return false; + } + return false; +} END_ALLOW_CASE_RANGE diff --git a/kitty/fonts.c b/kitty/fonts.c index f2530c99..bb2f01f4 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -409,9 +409,9 @@ calc_cell_metrics(FontGroup *fg) { unsigned int before_cell_height = cell_height; int cw = cell_width, ch = cell_height; if (OPT(adjust_line_height_px) != 0) ch += OPT(adjust_line_height_px); - if (OPT(adjust_line_height_frac) != 0.f) ch *= OPT(adjust_line_height_frac); + if (OPT(adjust_line_height_frac) != 0.f) ch = (int)(ch * OPT(adjust_line_height_frac)); if (OPT(adjust_column_width_px != 0)) cw += OPT(adjust_column_width_px); - if (OPT(adjust_column_width_frac) != 0.f) cw *= OPT(adjust_column_width_frac); + if (OPT(adjust_column_width_frac) != 0.f) cw = (int)(cw * OPT(adjust_column_width_frac)); #define MAX_DIM 1000 #define MIN_WIDTH 2 #define MIN_HEIGHT 4 @@ -543,8 +543,18 @@ in_symbol_maps(FontGroup *fg, char_type ch) { } -static ssize_t -font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { +// 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 inline ssize_t +font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_fallback_font, bool *is_emoji_presentation) { + *is_fallback_font = false; + *is_emoji_presentation = false; START_ALLOW_CASE_RANGE ssize_t ans; switch(cpu_cell->ch) { @@ -552,12 +562,14 @@ START_ALLOW_CASE_RANGE case ' ': case '\t': return BLANK_FONT; - case 0x2500 ... 0x2570: + case 0x2500 ... 0x2573: case 0x2574 ... 0x259f: - case 0xe0b0: - case 0xe0b2: - case 0xe0b4: + case 0xe0b0 ... 0xe0b4: case 0xe0b6: + case 0xe0b8: // + case 0xe0ba: // + case 0xe0bc: // + case 0xe0be: // return BOX_FONT; default: ans = in_symbol_maps(fg, cpu_cell->ch); @@ -573,7 +585,9 @@ START_ALLOW_CASE_RANGE ans = fg->bi_font_idx; break; } if (ans < 0) ans = fg->medium_font_idx; - if (!has_emoji_presentation(cpu_cell, gpu_cell) && has_cell_text(fg->fonts + ans, cpu_cell)) return ans; + *is_emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell); + if (!*is_emoji_presentation && has_cell_text(fg->fonts + ans, cpu_cell)) return ans; + *is_fallback_font = true; return fallback_font(fg, cpu_cell, gpu_cell); } END_ALLOW_CASE_RANGE @@ -584,20 +598,15 @@ set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) { cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z; } +// Gives a unique (arbitrary) id to a box glyph static inline glyph_index box_glyph_id(char_type ch) { START_ALLOW_CASE_RANGE switch(ch) { case 0x2500 ... 0x259f: - return ch - 0x2500; - case 0xe0b0: - return 0xfa; - case 0xe0b2: - return 0xfb; - case 0xe0b4: - return 0xfc; - case 0xe0b6: - return 0xfd; + return ch - 0x2500; // IDs from 0x00 to 0x9f + case 0xe0b0 ... 0xe0d4: + return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4 default: return 0xff; } @@ -682,7 +691,7 @@ static inline void render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, bool center_glyph) { static SpritePosition* sprite_position[16]; int error = 0; - num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells); + num_cells = MIN(arraysz(sprite_position), num_cells); for (unsigned int i = 0; i < num_cells; i++) { sprite_position[i] = sprite_position_for(fg, font, glyph, extra_glyphs, (uint8_t)i, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; } @@ -717,7 +726,7 @@ typedef struct { typedef struct { unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells; - bool has_special_glyph; + bool has_special_glyph, is_space_ligature; } Group; typedef struct { @@ -955,13 +964,11 @@ merge_groups_for_pua_space_ligature(void) { while (G(group_idx) > 0) { Group *g = G(groups), *g1 = G(groups) + 1; g->num_cells += g1->num_cells; - // We dont want the space glyphs rendered because some stupid - // fonts like PowerLine dont have a space glyph - // https://github.com/kovidgoyal/kitty/issues/1225 - /* g->num_glyphs += g1->num_glyphs; */ - /* g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1); */ + g->num_glyphs += g1->num_glyphs; + g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1); G(group_idx)--; } + G(groups)->is_space_ligature = true; } static inline void @@ -995,7 +1002,11 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) { int last = -1; for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; } if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0; - 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, font, primary, &ed, center_glyph); + // 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 + unsigned int num_glyphs = group->is_space_ligature ? 1 : group->num_glyphs; + render_group(fg, group->num_cells, 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, font, primary, &ed, center_glyph); idx++; } } @@ -1096,18 +1107,20 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, if (prev_width == 2) { prev_width = 0; continue; } CPUCell *cpu_cell = line->cpu_cells + i; GPUCell *gpu_cell = line->gpu_cells + i; - ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell); + bool is_fallback_font, is_emoji_presentation; + ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_fallback_font, &is_emoji_presentation); - if (is_private_use(cpu_cell->ch) - && cell_font_idx != BOX_FONT - && cell_font_idx != MISSING_FONT) { + if ( + cell_font_idx != MISSING_FONT && + ((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && is_private_use(cpu_cell->ch))) + ) { unsigned int desired_cells = 1; if (cell_font_idx > 0) { Font *font = (fg->fonts + cell_font_idx); glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch); int width = get_glyph_width(font->face, glyph_id); - desired_cells = ceilf((float)width / fg->cell_width); + desired_cells = (unsigned int)ceilf((float)width / fg->cell_width); } unsigned int num_spaces = 0; diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 7cd3e33c..d03f5bec 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -2,6 +2,11 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> +# +# NOTE: to add a new glyph, add an entry to the `box_chars` dict, then update +# the functions `font_for_cell` and `box_glyph_id` in `kitty/fonts.c`. +# + import math from functools import partial as p from itertools import repeat @@ -172,6 +177,117 @@ def triangle(buf, width, height, left=True): fill_region(buf, width, height, xlimits) +def corner_triangle(buf, width, height, corner): + if corner == 'top-right' or corner == 'bottom-left': + diagonal_y = line_equation(0, 0, width - 1, height - 1) + if corner == 'top-right': + xlimits = [(0, diagonal_y(x)) for x in range(width)] + elif corner == 'bottom-left': + xlimits = [(diagonal_y(x), height - 1) for x in range(width)] + else: + diagonal_y = line_equation(width - 1, 0, 0, height - 1) + if corner == 'top-left': + xlimits = [(0, diagonal_y(x)) for x in range(width)] + elif corner == 'bottom-right': + xlimits = [(diagonal_y(x), height - 1) for x in range(width)] + fill_region(buf, width, height, xlimits) + + +def antialiased_1px_line(buf, width, height, p1, p2): + # Draw an antialiased line using the Wu algorithm + x1, y1 = p1 + x2, y2 = p2 + dx, dy = x2 - x1, y2 - y1 + off_limit = height * width + steep = abs(dx) < abs(dy) + + if steep: + x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx + + def p(x, y): + return y, x + else: + def p(x, y): + return x, y + + if x2 < x1: + x1, x2, y1, y2 = x2, x1, y2, y1 + + def fpart(x): + return x - int(x) + + def rfpart(x): + return 1 - fpart(x) + + def putpixel(p, alpha): + x, y = p + off = int(x + y * width) + if 0 <= off < off_limit: + buf[off] = int(min(buf[off] + (alpha * 255), 255)) + + def draw_endpoint(pt): + x, y = pt + xend = round(x) + yend = y + grad * (xend - x) + xgap = rfpart(x + 0.5) + px, py = int(xend), int(yend) + putpixel(p(px, py), rfpart(yend) * xgap) + putpixel(p(px, py+1), fpart(yend) * xgap) + return px + + grad = dy/dx + intery = y1 + rfpart(x1) * grad + + xstart = draw_endpoint(p(*p1)) + xend = draw_endpoint(p(*p2)) + + if xstart > xend: + xstart, xend = xend, xstart + xstart += 1 + + for x in range(xstart, xend): + y = int(intery) + putpixel(p(x, y), rfpart(intery)) + putpixel(p(x, y+1), fpart(intery)) + intery += grad + + +def antialiased_line(buf, width, height, p1, p2, level=1): + th = thickness(level) + if th < 2: + return antialiased_1px_line(buf, width, height, p1, p2) + (x1, y1), (x2, y2) = p1, p2 + dh = th // 2 + items = range(-dh, dh + (th % 2)) + for delta in items: + antialiased_1px_line(buf, width, height, (x1, y1 + delta), (x2, y2 + delta)) + + +def cross_line(buf, width, height, left=True, level=1): + if left: + p1, p2 = (0, 0), (width - 1, height - 1) + else: + p1, p2 = (width - 1, 0), (0, height - 1) + antialiased_line(buf, width, height, p1, p2, level=level) + + +def half_cross_line(buf, width, height, which='tl', level=1): + my = (height - 1) // 2 + if which == 'tl': + p1 = 0, 0 + p2 = width - 1, my + elif which == 'bl': + p2 = 0, height - 1 + p1 = width - 1, my + elif which == 'tr': + p1 = width - 1, 0 + p2 = 0, my + else: + p2 = width - 1, height - 1 + p1 = 0, my + antialiased_line(buf, width, height, p1, p2, level=level) + + def cubic_bezier(start, end, c1, c2): def bezier_eq(p0, p1, p2, p3): @@ -459,6 +575,12 @@ box_chars = { '': [p(triangle, left=False)], '': [D], '': [p(D, left=False)], + '': [p(half_cross_line, which='tl'), p(half_cross_line, which='bl')], + '': [p(half_cross_line, which='tr'), p(half_cross_line, which='br')], + '': [p(corner_triangle, corner='bottom-left')], + '': [p(corner_triangle, corner='bottom-right')], + '': [p(corner_triangle, corner='top-left')], + '': [p(corner_triangle, corner='top-right')], '═': [dhline], '║': [dvline], '╞': [vline, p(half_dhline, which='right')], @@ -472,6 +594,9 @@ box_chars = { '╣': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')], '╦': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')], '╩': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')], + '╱': [p(cross_line, left=False)], + '╲': [cross_line], + '╳': [cross_line, p(cross_line, left=False)], '▀': [p(vblock, frac=1/2)], '▁': [p(vblock, frac=1/8, gravity='bottom')], '▂': [p(vblock, frac=1/4, gravity='bottom')], @@ -551,60 +676,60 @@ def render_missing_glyph(buf, width, height): def test_char(ch, sz=48): - # kitty +runpy "from kitty.fonts.box_drawing import test_char; import sys; test_char('XXX')" + # kitty +runpy "from kitty.fonts.box_drawing import test_char; test_char('XXX')" from .render import display_bitmap, setup_for_testing from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu - width, height = setup_for_testing('monospace', sz)[1:] - buf = bytearray(width * height) - try: - render_box_char(ch, buf, width, height) + with setup_for_testing('monospace', sz) as (_, width, height): + buf = bytearray(width * height) + try: + render_box_char(ch, buf, width, height) - def join_cells(*cells): - cells = tuple(bytes(x) for x in cells) - return concat_cells(width, height, False, cells) + def join_cells(*cells): + cells = tuple(bytes(x) for x in cells) + return concat_cells(width, height, False, cells) - rgb_data = join_cells(buf) - display_bitmap(rgb_data, width, height) - print() - finally: - set_send_sprite_to_gpu(None) + rgb_data = join_cells(buf) + display_bitmap(rgb_data, width, height) + print() + finally: + set_send_sprite_to_gpu(None) def test_drawing(sz=48, family='monospace'): from .render import display_bitmap, setup_for_testing from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu - width, height = setup_for_testing(family, sz)[1:] - space = bytearray(width * height) - - def join_cells(cells): - cells = tuple(bytes(x) for x in cells) - return concat_cells(width, height, False, cells) - - def render_chr(ch): - if ch in box_chars: - cell = bytearray(len(space)) - render_box_char(ch, cell, width, height) - return cell - return space - - pos = 0x2500 - rows = [] - space_row = join_cells(repeat(space, 32)) - - try: - for r in range(10): - row = [] - for i in range(16): - row.append(render_chr(chr(pos))) - row.append(space) - pos += 1 - rows.append(join_cells(row)) - rows.append(space_row) - rgb_data = b''.join(rows) - width *= 32 - height *= len(rows) - assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4) - display_bitmap(rgb_data, width, height) - finally: - set_send_sprite_to_gpu(None) + with setup_for_testing(family, sz) as (_, width, height): + space = bytearray(width * height) + + def join_cells(cells): + cells = tuple(bytes(x) for x in cells) + return concat_cells(width, height, False, cells) + + def render_chr(ch): + if ch in box_chars: + cell = bytearray(len(space)) + render_box_char(ch, cell, width, height) + return cell + return space + + pos = 0x2500 + rows = [] + space_row = join_cells(repeat(space, 32)) + + try: + for r in range(10): + row = [] + for i in range(16): + row.append(render_chr(chr(pos))) + row.append(space) + pos += 1 + rows.append(join_cells(row)) + rows.append(space_row) + rgb_data = b''.join(rows) + width *= 32 + height *= len(rows) + assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4) + display_bitmap(rgb_data, width, height) + finally: + set_send_sprite_to_gpu(None) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 245fa669..d8f46367 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -25,8 +25,29 @@ else: current_faces = None +def coalesce_symbol_maps(maps): + if not maps: + return maps + items = tuple((k, maps[k]) for k in sorted(maps)) + ans = [items[0]] + + def merge(prev_item, item): + s, e = item[0] + pe = prev_item[0][1] + ans[-1] = ((prev_item[0][0], max(pe, e)), prev_item[1]) + + for item in items[1:]: + current_item = ans[-1] + if current_item[1] != item[1] or item[0][0] > current_item[0][1] + 1: + ans.append(item) + else: + merge(current_item, item) + + return dict(ans) + + def create_symbol_map(opts): - val = opts.symbol_map + val = coalesce_symbol_maps(opts.symbol_map) family_map = {} count = 0 for family in val.values(): diff --git a/kitty/freetype.c b/kitty/freetype.c index e9ee22b5..90ee84a9 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -77,12 +77,12 @@ static FT_Library library; static inline int font_units_to_pixels_y(Face *self, int x) { - return ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0); + return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0); } static inline int font_units_to_pixels_x(Face *self, int x) { - return ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0); + return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0); } @@ -140,7 +140,7 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt if (!error) { unsigned int ch = calc_cell_height(self, false); if (desired_height && ch != desired_height) { - FT_F26Dot6 h = floor((double)char_height * (double)desired_height / (double) ch); + FT_F26Dot6 h = (FT_F26Dot6)floor((double)char_height * (double)desired_height / (double) ch); return set_font_size(self, 0, h, xdpi, ydpi, 0, cell_height); } self->char_width = char_width; self->char_height = char_height; self->xdpi = xdpi; self->ydpi = ydpi; @@ -160,8 +160,8 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt int32_t min_diff = INT32_MAX; if (desired_height == 0) desired_height = cell_height; if (desired_height == 0) { - desired_height = ceil(((double)char_height / 64.) * (double)ydpi / 72.); - desired_height += ceil(0.2 * desired_height); + 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++) { @@ -190,7 +190,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA 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; - ((Face*)self)->size_in_pts = fg->font_sz_in_pts; + ((Face*)self)->size_in_pts = (float)fg->font_sz_in_pts; return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height); } @@ -438,7 +438,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei // better with bi-cubic or lanczos, but at these small sizes I don't think // it matters float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height); - int factor = ceilf(ratio); + int factor = (int)ceilf(ratio); uint8_t *dest = calloc(4, width * cell_height); if (dest == NULL) fatal("Out of memory"); uint8_t *d = dest; @@ -499,8 +499,8 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int ans->rows = bitmap->rows; ans->pixel_mode = bitmap->pixel_mode; if (ans->width > num_cells * cell_width + 2) downsample_bitmap(ans, num_cells * cell_width, cell_height); - ans->bitmap_top = (float)self->face->glyph->bitmap_top / ans->factor; - ans->bitmap_left = (float)self->face->glyph->bitmap_left / ans->factor; + ans->bitmap_top = (int)((float)self->face->glyph->bitmap_top / ans->factor); + ans->bitmap_left = (int)((float)self->face->glyph->bitmap_left / ans->factor); detect_right_edge(ans); return true; } diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 98cfba9e..b9682397 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -245,8 +245,8 @@ load_glfw(const char* path) { *(void **) (&glfwGetKeyName_impl) = dlsym(handle, "glfwGetKeyName"); if (glfwGetKeyName_impl == NULL) fail("Failed to load glfw function glfwGetKeyName with error: %s", dlerror()); - *(void **) (&glfwGetKeyScancode_impl) = dlsym(handle, "glfwGetKeyScancode"); - if (glfwGetKeyScancode_impl == NULL) fail("Failed to load glfw function glfwGetKeyScancode 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()); @@ -389,6 +389,8 @@ load_glfw(const char* path) { *(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen"); + *(void **) (&glfwSetApplicationWillFinishLaunching_impl) = dlsym(handle, "glfwSetApplicationWillFinishLaunching"); + *(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent"); *(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame"); @@ -401,7 +403,7 @@ load_glfw(const char* path) { *(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString"); - *(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode"); + *(void **) (&glfwGetNativeKeyForName_impl) = dlsym(handle, "glfwGetNativeKeyForName"); *(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent"); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 0127da2d..67876d0a 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1,6 +1,13 @@ +// +// THIS FILE IS GENERATED BY glfw.py +// +// SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT +// + #pragma once #include <stddef.h> #include <stdint.h> +#include "monotonic.h" @@ -18,7 +25,7 @@ * backward-compatible. * @ingroup init */ -#define GLFW_VERSION_MINOR 3 +#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 @@ -54,6 +61,7 @@ /*! @} */ /*! @defgroup hat_state Joystick hat states + * @brief Joystick hat states. * * See [joystick hat input](@ref joystick_hat) for how these are used. * @@ -725,74 +733,87 @@ #define GLFW_CLIENT_API 0x00022001 /*! @brief Context client API major version hint and attribute. * - * Context client API major version [hint](@ref GLFW_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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 [hint](@ref GLFW_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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 OpenGL debug context hint and attribute. * - * OpenGL debug context [hint](@ref GLFW_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and + * [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib). */ #define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007 /*! @brief OpenGL profile hint and attribute. * - * OpenGL profile [hint](@ref GLFW_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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_CLIENT_API_hint) and - * [attribute](@ref GLFW_CLIENT_API_attrib). + * 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 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 @@ -855,11 +876,22 @@ typedef enum { /*! @addtogroup init * @{ */ +/*! @brief Joystick hat buttons init hint. + * + * Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS). + */ #define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001 #define GLFW_DEBUG_KEYBOARD 0x00050002 #define GLFW_ENABLE_JOYSTICKS 0x00050003 - +/*! @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 /*! @} */ @@ -930,17 +962,48 @@ typedef struct GLFWwindow GLFWwindow; * * @since Added in version 3.1. * - * @ingroup cursor + * @ingroup input */ typedef struct GLFWcursor GLFWcursor; -/*! @brief The function signature for error callbacks. +typedef struct GLFWkeyevent +{ + // The [keyboard key](@ref keys) that was pressed or released. + int key; + + // The platform-specific identifier of the key. + int native_key; + + // The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. + int 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 1 means the pre-edit text for the input event has been changed. + // A value of 2 means the text should be committed. + int ime_state; +} GLFWkeyevent; + +/*! @brief The function pointer type for error callbacks. * - * This is the function signature for error callback functions. + * 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 An [error code](@ref errors). + * @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 * @@ -950,9 +1013,13 @@ typedef struct GLFWcursor GLFWcursor; */ typedef void (* GLFWerrorfun)(int,const char*); -/*! @brief The function signature for window position callbacks. +/*! @brief The function pointer type for window position callbacks. * - * This is the function signature for window position callback functions. + * 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 @@ -969,9 +1036,13 @@ typedef void (* GLFWerrorfun)(int,const char*); */ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); -/*! @brief The function signature for window resize callbacks. +/*! @brief The function pointer type for window size callbacks. * - * This is the function signature for window size callback functions. + * 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. @@ -987,9 +1058,13 @@ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); -/*! @brief The function signature for window close callbacks. +/*! @brief The function pointer type for window close callbacks. * - * This is the function signature for window close callback functions. + * 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. * @@ -1003,9 +1078,13 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowclosefun)(GLFWwindow*); -/*! @brief The function signature for window content refresh callbacks. +/*! @brief The function pointer type for window content refresh callbacks. * - * This is the function signature for window refresh callback functions. + * 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. * @@ -1019,9 +1098,13 @@ typedef void (* GLFWwindowclosefun)(GLFWwindow*); */ typedef void (* GLFWwindowrefreshfun)(GLFWwindow*); -/*! @brief The function signature for window focus/defocus callbacks. +/*! @brief The function pointer type for window focus callbacks. * - * This is the function signature for window focus callback functions. + * 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 @@ -1054,10 +1137,13 @@ typedef void (* GLFWwindowfocusfun)(GLFWwindow*,int); typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); -/*! @brief The function signature for window iconify/restore callbacks. +/*! @brief The function pointer type for window iconify callbacks. * - * This is the function signature for window iconify/restore callback - * functions. + * 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 @@ -1072,10 +1158,13 @@ typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); */ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); -/*! @brief The function signature for window maximize/restore callbacks. +/*! @brief The function pointer type for window maximize callbacks. * - * This is the function signature for window maximize/restore callback - * functions. + * 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] iconified `true` if the window was maximized, or @@ -1090,10 +1179,13 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); */ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); -/*! @brief The function signature for framebuffer resize callbacks. +/*! @brief The function pointer type for framebuffer size callbacks. * - * This is the function signature for framebuffer resize callback - * functions. + * 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. @@ -1108,10 +1200,13 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); */ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); -/*! @brief The function signature for window content scale callbacks. +/*! @brief The function pointer type for window content scale callbacks. * - * This is the function signature for window content scale callback - * functions. + * 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. @@ -1126,14 +1221,19 @@ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); */ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); -/*! @brief The function signature for mouse button callbacks. +/*! @brief The function pointer type for mouse button callbacks. * - * This is the function signature for mouse button callback functions. + * 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`. + * @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. * @@ -1147,9 +1247,13 @@ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); */ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); -/*! @brief The function signature for cursor position callbacks. +/*! @brief The function pointer type for cursor position callbacks. * - * This is the function signature for cursor position callback functions. + * 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 @@ -1166,12 +1270,16 @@ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); */ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); -/*! @brief The function signature for cursor enter/leave callbacks. +/*! @brief The function pointer type for cursor enter/leave callbacks. * - * This is the function signature for cursor enter/leave callback functions. + * 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 client + * @param[in] entered `true` if the cursor entered the window's content * area, or `false` if it left it. * * @sa @ref cursor_enter @@ -1183,16 +1291,20 @@ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); */ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); -/*! @brief The function signature for scroll callbacks. +/*! @brief The function pointer type for scroll callbacks. * - * This is the function signature for scroll callback functions. + * 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 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 @@ -1208,9 +1320,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); */ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); -/*! @brief The function signature for key callbacks. +/*! @brief The function pointer type for key callbacks. * - * This is the function signature for key callback functions. + * This is the function pointer type for key callbacks. A keyboard + * key callback function has the following signature: + * @code + * void function_name(GLFWwindow* window, int 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 @@ -1219,15 +1335,8 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); * the "s" key will generate text "o" and GLFW_KEY_O. * * @param[in] window The window that received the event. - * @param[in] key The [keyboard key](@ref keys) that was pressed or released. - * @param[in] scancode The system-specific scancode of the key. - * @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. - * @param[in] mods Bit field describing which [modifier keys](@ref mods) were - * held down. - * @param[in] text UTF-8 encoded text generated by this key event or empty string. - * @param[in] Used for Input Method events. Zero for normal key events. - * A value of 1 means the pre-edit text for the input event has been changed. - * A value of 2 means the text should be committed. + * @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 @@ -1241,16 +1350,23 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); * * @ingroup input */ -typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int); +typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief The function signature for file drop callbacks. +/*! @brief The function pointer type for path drop callbacks. * - * This is the function signature for file drop callbacks. + * This is the function pointer type for path drop callbacks. A path drop + * callback function has the following signature: + * @code + * void function_name(GLFWwindow* window, int path_count, const char* paths[]) + * @endcode * * @param[in] window The window that received the event. - * @param[in] count The number of dropped files. + * @param[in] path_count The number of dropped paths. * @param[in] paths The UTF-8 encoded file and/or directory path names. * + * @pointer_lifetime The path array and its strings are valid until the + * callback function returns. + * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * @@ -1258,17 +1374,21 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, i * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**); +typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); -/*! @brief The function signature for monitor configuration callbacks. +/*! @brief The function pointer type for monitor configuration callbacks. * - * This is the function signature for monitor configuration callback functions. + * 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`. Remaining - * values reserved for future use. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future + * releases may add more events. * * @sa @ref monitor_event * @sa @ref glfwSetMonitorCallback @@ -1279,14 +1399,17 @@ typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); */ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); -/*! @brief The function signature for joystick configuration callbacks. +/*! @brief The function pointer type for joystick configuration callbacks. * - * This is the function signature for joystick configuration callback - * functions. + * 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`. Remaining - * values reserved for future use. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future + * releases may add more events. * * @sa @ref joystick_event * @sa @ref glfwSetJoystickCallback @@ -1373,6 +1496,8 @@ typedef struct GLFWgammaramp * * @since Added in version 2.1. * @glfw3 Removed format and bytes-per-pixel members. + * + * @ingroup window */ typedef struct GLFWimage { @@ -1395,6 +1520,8 @@ typedef struct GLFWimage * @sa @ref glfwGetGamepadState * * @since Added in version 3.3. + * + * @ingroup input */ typedef struct GLFWgamepadstate { @@ -1448,13 +1575,14 @@ typedef struct GLFWgamepadstate typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); -typedef int (* GLFWapplicationshouldhandlereopenfun)(int); -typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); +typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); +typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*); -typedef int (*glfwInit_func)(void); +typedef int (*glfwInit_func)(monotonic_t); glfwInit_func glfwInit_impl; #define glfwInit glfwInit_impl @@ -1466,11 +1594,11 @@ typedef void (*glfwStopMainLoop_func)(void); glfwStopMainLoop_func glfwStopMainLoop_impl; #define glfwStopMainLoop glfwStopMainLoop_impl -typedef unsigned long long (*glfwAddTimer_func)(double, bool, GLFWuserdatafun, void *, GLFWuserdatafun); +typedef unsigned long long (*glfwAddTimer_func)(monotonic_t, bool, GLFWuserdatafun, void *, GLFWuserdatafun); glfwAddTimer_func glfwAddTimer_impl; #define glfwAddTimer glfwAddTimer_impl -typedef void (*glfwUpdateTimer_func)(unsigned long long, double, bool); +typedef void (*glfwUpdateTimer_func)(unsigned long long, monotonic_t, bool); glfwUpdateTimer_func glfwUpdateTimer_impl; #define glfwUpdateTimer glfwUpdateTimer_impl @@ -1638,7 +1766,7 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*); glfwGetWindowContentScale_func glfwGetWindowContentScale_impl; #define glfwGetWindowContentScale glfwGetWindowContentScale_impl -typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*); +typedef monotonic_t (*glfwGetDoubleClickInterval_func)(GLFWwindow*); glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl; #define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl @@ -1762,9 +1890,9 @@ typedef const char* (*glfwGetKeyName_func)(int, int); glfwGetKeyName_func glfwGetKeyName_impl; #define glfwGetKeyName glfwGetKeyName_impl -typedef int (*glfwGetKeyScancode_func)(int); -glfwGetKeyScancode_func glfwGetKeyScancode_impl; -#define glfwGetKeyScancode glfwGetKeyScancode_impl +typedef int (*glfwGetNativeKeyForKey_func)(int); +glfwGetNativeKeyForKey_func glfwGetNativeKeyForKey_impl; +#define glfwGetNativeKeyForKey glfwGetNativeKeyForKey_impl typedef int (*glfwGetKey_func)(GLFWwindow*, int); glfwGetKey_func glfwGetKey_impl; @@ -1890,11 +2018,11 @@ typedef const char* (*glfwGetClipboardString_func)(GLFWwindow*); glfwGetClipboardString_func glfwGetClipboardString_impl; #define glfwGetClipboardString glfwGetClipboardString_impl -typedef double (*glfwGetTime_func)(void); +typedef monotonic_t (*glfwGetTime_func)(void); glfwGetTime_func glfwGetTime_impl; #define glfwGetTime glfwGetTime_impl -typedef void (*glfwSetTime_func)(double); +typedef void (*glfwSetTime_func)(monotonic_t); glfwSetTime_func glfwSetTime_impl; #define glfwSetTime glfwSetTime_impl @@ -1962,6 +2090,10 @@ typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReo glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl; #define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl +typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaunching_func)(GLFWapplicationwillfinishlaunchingfun); +glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl; +#define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl + typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*); glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl; #define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl @@ -1986,9 +2118,9 @@ typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*); glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl; #define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl -typedef int (*glfwGetXKBScancode_func)(const char*, int); -glfwGetXKBScancode_func glfwGetXKBScancode_impl; -#define glfwGetXKBScancode glfwGetXKBScancode_impl +typedef int (*glfwGetNativeKeyForName_func)(const char*, int); +glfwGetNativeKeyForName_func glfwGetNativeKeyForName_impl; +#define glfwGetNativeKeyForName glfwGetNativeKeyForName_impl typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc); glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl; diff --git a/kitty/glfw.c b/kitty/glfw.c index 4db7d296..b0e8fb70 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -7,6 +7,7 @@ #include "state.h" #include "glfw_tests.h" #include "fonts.h" +#include "monotonic.h" #include <structmember.h> #include "glfw-wrapper.h" extern bool cocoa_make_window_resizable(void *w, bool); @@ -17,7 +18,7 @@ extern void cocoa_set_activation_policy(bool); extern void cocoa_set_titlebar_color(void *w, color_type color); extern bool cocoa_alt_option_key_pressed(unsigned long); extern size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz); -extern double cocoa_cursor_blink_interval(void); +extern monotonic_t cocoa_cursor_blink_interval(void); #if GLFW_KEY_LAST >= MAX_KEY_COUNT @@ -83,7 +84,7 @@ log_event(const char *format, ...) { { va_list vl; - fprintf(stderr, "[%.4f] ", glfwGetTime()); + fprintf(stderr, "[%.4f] ", monotonic_t_to_s_double(glfwGetTime())); va_start(vl, format); vfprintf(stderr, format, vl); va_end(vl); @@ -231,14 +232,14 @@ refresh_callback(GLFWwindow *w) { static int mods_at_last_key_or_button_event = 0; static void -key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const char* text, int state) { +key_callback(GLFWwindow *w, GLFWkeyevent *ev) { if (!set_callback_window(w)) return; - mods_at_last_key_or_button_event = mods; + mods_at_last_key_or_button_event = ev->mods; global_state.callback_os_window->cursor_blink_zero_time = monotonic(); - if (key >= 0 && key <= GLFW_KEY_LAST) { - global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true; + if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST) { + global_state.callback_os_window->is_key_pressed[ev->key] = ev->action == GLFW_RELEASE ? false : true; } - if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods, text, state); + if (is_window_ready_for_callbacks()) on_key_input(ev); global_state.callback_os_window = NULL; request_tick_callback(); } @@ -248,7 +249,7 @@ cursor_enter_callback(GLFWwindow *w, int entered) { if (!set_callback_window(w)) return; if (entered) { show_mouse_cursor(w); - double now = monotonic(); + monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; if (is_window_ready_for_callbacks()) enter_event(); request_tick_callback(); @@ -261,7 +262,7 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { if (!set_callback_window(w)) return; show_mouse_cursor(w); mods_at_last_key_or_button_event = mods; - double now = monotonic(); + monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) { global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false; @@ -275,7 +276,7 @@ static void cursor_pos_callback(GLFWwindow *w, double x, double y) { if (!set_callback_window(w)) return; show_mouse_cursor(w); - double now = monotonic(); + monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->cursor_blink_zero_time = now; global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio; @@ -289,7 +290,7 @@ static void scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) { if (!set_callback_window(w)) return; show_mouse_cursor(w); - double now = monotonic(); + monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags); request_tick_callback(); @@ -308,7 +309,7 @@ window_focus_callback(GLFWwindow *w, int focused) { focus_in_event(); global_state.callback_os_window->last_focused_counter = ++focus_counter; } - double now = monotonic(); + monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->cursor_blink_zero_time = now; if (is_window_ready_for_callbacks()) { @@ -450,7 +451,7 @@ toggle_maximized_for_os_window(OSWindow *w) { #ifdef __APPLE__ static int -filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED, unsigned long flags) { +filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned long flags) { 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; @@ -460,20 +461,19 @@ filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED, unsigned l static GLFWwindow *application_quit_canary = NULL; -static int +static bool on_application_reopen(int has_visible_windows) { if (has_visible_windows) return true; set_cocoa_pending_action(NEW_OS_WINDOW, NULL); - // Without unjam wait_for_events() blocks until the next event return false; } -static int +static bool intercept_cocoa_fullscreen(GLFWwindow *w) { - if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return 0; + if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return false; toggle_fullscreen_for_os_window(global_state.callback_os_window); global_state.callback_os_window = NULL; - return 1; + return true; } #endif @@ -517,6 +517,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { cocoa_set_activation_policy(OPT(macos_hide_from_tasks)); glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true); glfwSetApplicationShouldHandleReopen(on_application_reopen); + glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu); if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false); #endif @@ -548,8 +549,13 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { if (!common_context) common_context = application_quit_canary; #endif - GLFWwindow *temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context); - if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); } + GLFWwindow *temp_window = NULL; + if (!global_state.is_wayland) { + // On Wayland windows dont get a content scale until they receive an enterEvent anyway + // which wont happen until the event loop ticks, so using a temp window is useless. + temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context); + if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); } + } float xscale, yscale; double xdpi, ydpi; get_window_content_scale(temp_window, &xscale, &yscale, &xdpi, &ydpi); @@ -564,8 +570,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { // is no startup notification in Wayland anyway. It amazes me that anyone // uses Wayland as anything other than a butt for jokes. if (global_state.is_wayland) glfwWindowHint(GLFW_VISIBLE, true); - GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window); - glfwDestroyWindow(temp_window); temp_window = NULL; + GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window ? temp_window : common_context); + if (temp_window) { glfwDestroyWindow(temp_window); temp_window = NULL; } if (glfw_window == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; } glfwMakeContextCurrent(glfw_window); if (is_first_window) { @@ -590,9 +596,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False); if (ret == NULL) return NULL; Py_DECREF(ret); -#ifdef __APPLE__ - cocoa_create_global_menu(); -#endif #define CC(dest, shape) {\ if (!dest##_cursor) { \ dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \ @@ -602,10 +605,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { #undef CC if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window); if (OPT(cursor_blink_interval) < 0) { - OPT(cursor_blink_interval) = 0.5; + OPT(cursor_blink_interval) = ms_to_monotonic_t(500ll); #ifdef __APPLE__ - double cbi = cocoa_cursor_blink_interval(); - if (cbi >= 0) OPT(cursor_blink_interval) = cbi / 2000.0; + monotonic_t cbi = cocoa_cursor_blink_interval(); + if (cbi >= 0) OPT(cursor_blink_interval) = cbi / 2; #endif } is_first_window = false; @@ -655,7 +658,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable)); } else log_error("Failed to load glfwGetCocoaWindow"); #endif - double now = monotonic(); + monotonic_t now = monotonic(); w->is_focused = true; w->cursor_blink_zero_time = now; w->last_mouse_activity_at = now; @@ -791,7 +794,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) { glfwDBusSetUserNotificationHandler(dbus_user_notification_activated); } #endif - PyObject *ans = glfwInit() ? Py_True: Py_False; + PyObject *ans = glfwInit(monotonic_start_time) ? Py_True: Py_False; if (ans == Py_True) { OSWindow w = {0}; set_os_window_dpi(&w); @@ -815,8 +818,8 @@ get_physical_dpi(GLFWmonitor *m) { 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 = vm->width / (width / 25.4); - float dpiy = vm->height / (height / 25.4); + float dpix = (float)(vm->width / (width / 25.4)); + float dpiy = (float)(vm->height / (height / 25.4)); return Py_BuildValue("ff", dpix, dpiy); } @@ -829,9 +832,9 @@ glfw_get_physical_dpi(PYNOARG) { static PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args) { - int key, scancode; - if (!PyArg_ParseTuple(args, "ii", &key, &scancode)) return NULL; - return Py_BuildValue("s", glfwGetKeyName(key, scancode)); + int key, native_key; + if (!PyArg_ParseTuple(args, "ii", &key, &native_key)) return NULL; + return Py_BuildValue("s", glfwGetKeyName(key, native_key)); } static PyObject* @@ -854,9 +857,9 @@ get_clipboard_string(PYNOARG) { void ring_audio_bell(OSWindow *w UNUSED) { - static double last_bell_at = -1; - double now = monotonic(); - if (now - last_bell_at <= 0.1) return; + static monotonic_t last_bell_at = -1; + monotonic_t now = monotonic(); + if (now - last_bell_at <= ms_to_monotonic_t(100ll)) return; last_bell_at = now; #ifdef __APPLE__ if (w->handle) { @@ -1139,12 +1142,12 @@ dbus_send_notification(PyObject *self UNUSED, PyObject *args) { #endif id_type -add_main_loop_timer(double interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback) { +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, double interval, bool enabled) { +update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled) { glfwUpdateTimer(timer_id, interval, enabled); } diff --git a/kitty/glfw_tests.c b/kitty/glfw_tests.c index 399f594d..2823f046 100644 --- a/kitty/glfw_tests.c +++ b/kitty/glfw_tests.c @@ -31,9 +31,9 @@ static void* empty_thread_main(void* data UNUSED) return 0; } -static void key_callback(GLFWwindow *w UNUSED, int key, int scancode UNUSED, int action, int mods UNUSED, const char* text UNUSED, int state UNUSED) +static void key_callback(GLFWwindow *w UNUSED, GLFWkeyevent *ev) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + if (ev->key == GLFW_KEY_ESCAPE && ev->action == GLFW_PRESS) { glfwSetWindowShouldClose(w, true); wakeup_main_loop(); } diff --git a/kitty/graphics.c b/kitty/graphics.c index e70fdee8..f306b8ef 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -121,7 +121,7 @@ trim_predicate(Image *img) { static int oldest_last(const void* a, const void *b) { - double ans = ((Image*)(b))->atime - ((Image*)(a))->atime; + monotonic_t ans = ((Image*)(b))->atime - ((Image*)(a))->atime; return ans < 0 ? -1 : (ans == 0 ? 0 : 1); } diff --git a/kitty/graphics.h b/kitty/graphics.h index 4f9e5a56..3a85dbc6 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -6,6 +6,7 @@ #pragma once #include "data-types.h" +#include "monotonic.h" typedef struct { unsigned char action, transmission_type, compressed, delete_action; @@ -50,7 +51,7 @@ typedef struct { ImageRef *refs; size_t refcnt, refcap; - double atime; + monotonic_t atime; size_t used_storage; } Image; diff --git a/kitty/keys.c b/kitty/keys.c index 16b60301..a4a495c4 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -26,7 +26,7 @@ key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) { #define SPECIAL_INDEX(key) ((key & 0x7f) | ( (mods & 0xF) << 7)) #define IS_ALT_MODS(mods) (mods == GLFW_MOD_ALT || mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT)) -typedef struct { int mods, scancode; } NativeKey; +typedef struct { int mods, native_key; } NativeKey; static NativeKey *native_special_keys = NULL; static size_t native_special_keys_capacity = 0, native_special_keys_count = 0; @@ -39,7 +39,7 @@ set_special_key_combo(int glfw_key, int mods, bool is_native) { if (native_special_keys == NULL) fatal("Out of memory"); } native_special_keys[native_special_keys_count].mods = mods; - native_special_keys[native_special_keys_count++].scancode = glfw_key; + native_special_keys[native_special_keys_count++].native_key = glfw_key; } else { uint16_t key = key_map[glfw_key]; if (key != UINT8_MAX) { @@ -68,6 +68,7 @@ is_modifier_key(int key) { case GLFW_KEY_RIGHT_CONTROL: case GLFW_KEY_LEFT_SUPER: case GLFW_KEY_RIGHT_SUPER: + case GLFW_KEY_CAPS_LOCK: return true; default: return false; @@ -96,7 +97,7 @@ is_ascii_control_char(char c) { } static inline bool -check_if_special(int key, int mods, int scancode) { +check_if_special(int key, int mods, int native_key) { uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX; bool special = false; if (qkey != UINT8_MAX) { @@ -104,7 +105,8 @@ check_if_special(int key, int mods, int scancode) { special = needs_special_handling[qkey]; } for (size_t i = 0; !special && i < native_special_keys_count; i++) { - if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true; + if (native_key == native_special_keys[i].native_key && mods == native_special_keys[i].mods) + special = true; } return special; } @@ -121,23 +123,26 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) { #define debug(...) if (OPT(debug_keyboard)) printf(__VA_ARGS__); void -on_key_input(int key, int scancode, int action, int mods, const char* text, int state) { +on_key_input(GLFWkeyevent *ev) { Window *w = active_window(); + int action = ev->action, native_key = ev->native_key, key = ev->key, mods = ev->mods; + const char *text = ev->text ? ev->text : ""; + debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ", - key, scancode, + key, native_key, (action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")), - mods, text, state); + mods, text, ev->ime_state); if (!w) { debug("no active window, ignoring\n"); return; } if (OPT(mouse_hide_wait) < 0 && !is_modifier_key(key)) hide_mouse(global_state.callback_os_window); Screen *screen = w->render_data.screen; - switch(state) { + switch(ev->ime_state) { case 1: // update pre-edit text update_ime_position(global_state.callback_os_window, w, screen); screen_draw_overlay_text(screen, text); debug("updated pre-edit text: '%s'\n", text); return; case 2: // commit text - if (text && *text) { + if (*text) { schedule_write_to_child(w->id, 1, text, strlen(text)); debug("committed pre-edit text: %s\n", text); } else debug("committed pre-edit text: (null)\n"); @@ -158,13 +163,13 @@ on_key_input(int key, int scancode, int action, int mods, const char* text, int if ( action != GLFW_RELEASE && key != GLFW_KEY_LEFT_SHIFT && key != GLFW_KEY_RIGHT_SHIFT && key != GLFW_KEY_LEFT_ALT && key != GLFW_KEY_RIGHT_ALT && key != GLFW_KEY_LEFT_CONTROL && key != GLFW_KEY_RIGHT_CONTROL - ) call_boss(process_sequence, "iiii", key, scancode, action, mods); + ) call_boss(process_sequence, "iiii", key, native_key, action, mods); return; } - bool has_text = text && !is_ascii_control_char(text[0]); + bool has_text = text[0] && !is_ascii_control_char(text[0]); if (action == GLFW_PRESS || action == GLFW_REPEAT) { - if (check_if_special(key, mods, scancode)) { - PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, scancode, action, mods); + if (check_if_special(key, mods, native_key)) { + PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, native_key, action, mods); if (ret == NULL) { PyErr_Print(); } else { bool consumed = ret == Py_True; @@ -225,9 +230,9 @@ PYWRAP1(key_for_native_key_name) { int case_sensitive = 0; PA("s|p", &name, &case_sensitive); #ifndef __APPLE__ - if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL - int scancode = glfwGetXKBScancode(name, case_sensitive); - if (scancode) return Py_BuildValue("i", scancode); + 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; diff --git a/kitty/keys.py b/kitty/keys.py index 49a36acc..88fae002 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -264,7 +264,7 @@ def key_to_bytes(key, smkx, extended, mods, action): return bytes(data) -def interpret_key_event(key, scancode, mods, window, action): +def interpret_key_event(key, native_key, mods, window, action): screen = window.screen if ( action == defines.GLFW_PRESS or @@ -275,17 +275,17 @@ def interpret_key_event(key, scancode, mods, window, action): return b'' -def get_shortcut(keymap, mods, key, scancode): +def get_shortcut(keymap, mods, key, native_key): mods &= 0b1111 ans = keymap.get((mods, False, key)) if ans is None: - ans = keymap.get((mods, True, scancode)) + ans = keymap.get((mods, True, native_key)) return ans -def shortcut_matches(s, mods, key, scancode): +def shortcut_matches(s, mods, key, native_key): mods &= 0b1111 - q = scancode if s[1] else key + q = native_key if s[1] else key return s[0] & 0b1111 == mods & 0b1111 and s[2] == q diff --git a/kitty/kittens.c b/kitty/kittens.c index 28c0e74e..0596d13d 100644 --- a/kitty/kittens.c +++ b/kitty/kittens.c @@ -6,6 +6,7 @@ */ #include "data-types.h" +#include "monotonic.h" #define CMD_BUF_SZ 2048 @@ -34,13 +35,13 @@ add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) { } static inline bool -read_response(int fd, double timeout, PyObject *ans) { +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; - double end_time = monotonic() + timeout; + monotonic_t end_time = monotonic() + timeout; while(monotonic() <= end_time) { ssize_t len = read(fd, &ch, 1); if (len == 0) continue; @@ -94,7 +95,7 @@ read_command_response(PyObject *self UNUSED, PyObject *args) { int fd; PyObject *ans; if (!PyArg_ParseTuple(args, "idO!", &fd, &timeout, &PyList_Type, &ans)) return NULL; - if (!read_response(fd, timeout, ans)) return NULL; + if (!read_response(fd, s_double_to_monotonic_t(timeout), ans)) return NULL; Py_RETURN_NONE; } diff --git a/kitty/launch.py b/kitty/launch.py new file mode 100644 index 00000000..24c20d7e --- /dev/null +++ b/kitty/launch.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net> + + +from kitty.cli import parse_args +from kitty.fast_data_types import set_clipboard_string +from kitty.utils import set_primary_selection + + +def options_spec(): + if not hasattr(options_spec, 'ans'): + OPTIONS = ''' +--window-title --title +The title to set for the new window. By default, title is controlled by the +child process. + + +--tab-title +The title for the new tab if launching in a new tab. By default, the title +of the actie window in the tab is used as the tab title. + + +--type +type=choices +default=window +choices=window,tab,os-window,overlay,background,clipboard,primary +Where to launch the child process, in a new kitty window in the current tab, +a new tab, or a new OS window or an overlay over the current window. +Note that if the current window already has an overlay, then it will +open a new window. The value of none means the process will be +run in the background. The values clipboard and primary are meant +to work with :option:`launch --stdin-source` to copy data to the system +clipboard or primary selection. + + +--keep-focus +type=bool-set +Keep the focus on the currently active window instead of switching +to the newly opened window. + + +--cwd +The working directory for the newly launched child. Use the special value +:code:`current` to use the working directory of the currently active window. + + +--env +type=list +Environment variables to set in the child process. Can be specified multiple +times to set different environment variables. +Syntax: :italic:`name=value`. + + +--copy-colors +type=bool-set +Set the colors of the newly created window to be the same as the colors in the +currently active window. + + +--copy-cmdline +type=bool-set +Ignore any specified command line and instead use the command line from the +currently active window. + + +--copy-env +type=bool-set +Copy the environment variables from the currently active window into the +newly launched child process. + + +--location +type=choices +default=last +choices=first,neighbor,last +Where to place the newly created window when it is added to a tab which +already has existing windows in it. Also applies to creating a new tab, +where the value of neighbor will cause the new tab to be placed next to +the current tab instead of at the end. + + +--allow-remote-control +type=bool-set +Programs running in this window can control kitty (if remote control is +enabled). 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. + + +--stdin-source +type=choices +default=none +choices=none,@selection,@screen,@screen_scrollback,@alternate,@alternate_scrollback +Pass the screen contents as :code:`STDIN` to the child process. @selection is +the currently selected text. @screen is the contents of the currently active +window. @screen_scrollback is the same as @screen, but includes the scrollback +buffer as well. @alternate is the secondary screen of the current active +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. + + +--stdin-add-formatting +type=bool-set +When using :option:`launch --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:`launch --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. + + +''' + options_spec.ans = OPTIONS + return options_spec.ans + + +def parse_launch_args(args=None): + args = list(args or ()) + try: + opts, args = parse_args(args=args, ospec=options_spec) + except SystemExit as e: + raise ValueError from e + return opts, args + + +def get_env(opts, active_child): + env = {} + if opts.copy_env and active_child: + env.update(active_child.foreground_environ) + for x in opts.env: + parts = x.split('=', 1) + if len(parts) == 2: + env[parts[0]] = parts[1] + return env + + +def tab_for_window(boss, opts, target_tab=None): + if opts.type == 'tab': + tm = boss.active_tab_manager + tab = tm.new_tab(empty_tab=True, as_neighbor=opts.location == 'neighbor') + if opts.tab_title: + tab.set_title(opts.tab_title) + elif opts.type == 'os-window': + oswid = boss.add_os_window() + tm = boss.os_window_map[oswid] + tab = tm.new_tab(empty_tab=True) + if opts.tab_title: + tab.set_title(opts.tab_title) + else: + tab = target_tab or boss.active_tab + + return tab + + +def launch(boss, opts, args, target_tab=None): + active = boss.active_window_for_cwd + active_child = getattr(active, 'child', None) + env = get_env(opts, active_child) + kw = { + 'allow_remote_control': opts.allow_remote_control + } + if opts.cwd: + if opts.cwd == 'current': + if active_child: + kw['cwd_from'] = active_child.pid_for_cwd + else: + kw['cwd'] = opts.cwd + if opts.location != 'last': + kw['location'] = opts.location + if opts.window_title: + kw['override_title'] = opts.window_title + if opts.copy_colors and active: + kw['copy_colors_from'] = active + cmd = args or None + if opts.copy_cmdline and active_child: + cmd = active_child.foreground_cmdline + if cmd: + final_cmd = [] + for x in cmd: + if x == '@selection' and active and not opts.copy_cmdline: + s = boss.data_for_at(active, x) + if s: + x = s + final_cmd.append(x) + kw['cmd'] = final_cmd + if opts.type == 'overlay' and active and not active.overlay_window_id: + kw['overlay_for'] = active.id + if opts.stdin_source and opts.stdin_source != 'none': + q = opts.stdin_source + if opts.stdin_add_line_wrap_markers: + q += '_wrap' + penv, stdin = boss.process_stdin_source(window=active, stdin=q) + if stdin: + kw['stdin'] = stdin + if penv: + env.update(penv) + + if opts.type == 'background': + boss.run_background_process(kw['cmd'], cwd=kw.get('cwd'), cwd_from=kw.get('cwd_from'), env=env or None, stdin=kw.get('stdin')) + elif opts.type in ('clipboard', 'primary'): + if 'stdin' in kw: + func = set_clipboard_string if opts.type == 'clipboard' else set_primary_selection + func(kw['stdin']) + else: + tab = tab_for_window(boss, opts, target_tab) + new_window = tab.new_window(env=env or None, **kw) + if opts.keep_focus and active: + boss.set_active_window(active, switch_os_window_if_needed=True) + return new_window diff --git a/kitty/layout.py b/kitty/layout.py index 99c20148..e3fe2e8a 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -279,8 +279,8 @@ class Layout: # {{{ all_windows[i] = window active_window_idx = i elif location is not None: - if location == 'neighbor' and current_active_window_idx is not None: - active_window_idx = current_active_window_idx + 1 + if location == 'neighbor' and current_active_window_idx is not None and len(all_windows) > 1: + active_window_idx = min(current_active_window_idx + 1, len(all_windows)) elif location == 'first': active_window_idx = 0 if active_window_idx is not None: diff --git a/kitty/main.py b/kitty/main.py index faf65952..ff868301 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -214,7 +214,9 @@ def read_shell_environment(opts=None): with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'): raw = b'' from subprocess import TimeoutExpired - while True: + from time import monotonic + start_time = monotonic() + while monotonic() - start_time < 1.5: try: ret = p.wait(0.01) except TimeoutExpired: @@ -223,7 +225,10 @@ def read_shell_environment(opts=None): raw += stdout.read() if ret is not None: break - if p.returncode == 0: + if p.returncode is None: + log_error('Timed out waiting for shell to quit while reading shell environment') + p.kill() + elif p.returncode == 0: while True: try: x = stdout.read() @@ -244,24 +249,32 @@ def read_shell_environment(opts=None): return read_shell_environment.ans +def get_editor_from_env(shell_env): + for var in ('VISUAL', 'EDITOR'): + editor = shell_env.get(var) + if editor: + if 'PATH' in shell_env: + import shlex + editor_cmd = shlex.split(editor) + if not os.path.isabs(editor_cmd[0]): + editor_cmd[0] = shutil.which(editor_cmd[0], path=shell_env['PATH']) + if editor_cmd[0]: + editor = ' '.join(map(shlex.quote, editor_cmd)) + else: + editor = None + if editor: + return editor + + def setup_environment(opts, args): extra_env = opts.env.copy() if opts.editor == '.': - if 'EDITOR' not in os.environ: + editor = get_editor_from_env(os.environ) + if not editor: shell_env = read_shell_environment(opts) - if 'EDITOR' in shell_env: - editor = shell_env['EDITOR'] - if 'PATH' in shell_env: - import shlex - editor_cmd = shlex.split(editor) - if not os.path.isabs(editor_cmd[0]): - editor_cmd[0] = shutil.which(editor_cmd[0], path=shell_env['PATH']) - if editor_cmd[0]: - editor = ' '.join(map(shlex.quote, editor_cmd)) - else: - editor = None - if editor: - os.environ['EDITOR'] = editor + editor = get_editor_from_env(shell_env) + if editor: + os.environ['EDITOR'] = editor else: os.environ['EDITOR'] = opts.editor if args.listen_on: diff --git a/kitty/monotonic.h b/kitty/monotonic.h new file mode 100644 index 00000000..7be3c11a --- /dev/null +++ b/kitty/monotonic.h @@ -0,0 +1,89 @@ +/* + * monotonic.h + * Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net> + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + + +#include <stdint.h> +#include <time.h> + +#define MONOTONIC_T_MAX INT64_MAX +#define MONOTONIC_T_MIN INT64_MIN + +typedef int64_t monotonic_t; + +static inline monotonic_t calc_nano_time(struct timespec time) { + int64_t result = (monotonic_t)time.tv_sec; + result *= 1000LL; + result *= 1000LL; + result *= 1000LL; + result += (monotonic_t)time.tv_nsec; + return result; +} + +static inline 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; +} + +static inline monotonic_t s_double_to_monotonic_t(double time) { + time *= 1000.0; + time *= 1000.0; + time *= 1000.0; + return (monotonic_t)time; +} + +static inline monotonic_t ms_double_to_monotonic_t(double time) { + time *= 1000.0; + time *= 1000.0; + return (monotonic_t)time; +} + +static inline monotonic_t s_to_monotonic_t(monotonic_t time) { + return time * 1000ll * 1000ll * 1000ll; +} + +static inline monotonic_t ms_to_monotonic_t(monotonic_t time) { + return time * 1000ll * 1000ll; +} + +static inline int monotonic_t_to_ms(monotonic_t time) { + return (int)(time / 1000ll / 1000ll); +} + +static inline double monotonic_t_to_s_double(monotonic_t time) { + return (double)time / 1000.0 / 1000.0 / 1000.0; +} + +#ifdef MONOTONIC_START_MODULE +monotonic_t monotonic_start_time = 0; +#else +extern monotonic_t monotonic_start_time; +#endif + + +static inline 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); +} + +static inline monotonic_t monotonic(void) { + return monotonic_() - monotonic_start_time; +} + +static inline void init_monotonic(void) { + monotonic_start_time = monotonic_(); +} diff --git a/kitty/mouse.c b/kitty/mouse.c index 341ac18b..d40b772e 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -13,6 +13,7 @@ #include <math.h> #include "glfw-wrapper.h" #include "control-codes.h" +#include "monotonic.h" static MouseShape mouse_cursor_shape = BEAM; typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction; @@ -292,8 +293,8 @@ HANDLER(handle_move_event) { bool handle_in_kitty = !in_tracking_mode || has_terminal_select_modifiers; if (handle_in_kitty) { if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) { - double now = monotonic(); - if ((now - w->last_drag_scroll_at) >= 0.02 || mouse_cell_changed) { + monotonic_t now = monotonic(); + if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { update_drag(false, w, false, 0); w->last_drag_scroll_at = now; } @@ -338,7 +339,7 @@ distance(double x1, double y1, double x2, double y2) { HANDLER(add_click) { ClickQueue *q = &w->click_queue; if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; } - double now = monotonic(); + monotonic_t now = monotonic(); #define N(n) (q->clicks[q->length - n]) N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; N(0).x = w->mouse_pos.x; N(0).y = w->mouse_pos.y; q->length++; @@ -608,16 +609,16 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) { if (is_high_resolution) { yoffset *= OPT(touch_scroll_multiplier); - if (yoffset * global_state.callback_os_window->pending_scroll_pixels < 0) { - global_state.callback_os_window->pending_scroll_pixels = 0; // change of direction + if (yoffset * screen->pending_scroll_pixels < 0) { + screen->pending_scroll_pixels = 0; // change of direction } - double pixels = global_state.callback_os_window->pending_scroll_pixels + yoffset; + double pixels = screen->pending_scroll_pixels + yoffset; if (fabs(pixels) < global_state.callback_os_window->fonts_data->cell_height) { - global_state.callback_os_window->pending_scroll_pixels = pixels; + screen->pending_scroll_pixels = pixels; return; } s = (int)round(pixels) / (int)global_state.callback_os_window->fonts_data->cell_height; - global_state.callback_os_window->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height; + screen->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height; } else { if (screen->linebuf == screen->main_linebuf || !screen->modes.mouse_tracking_mode) { // Only use wheel_scroll_multiplier if we are scrolling kitty scrollback or in mouse @@ -632,7 +633,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) { // apparently on cocoa some mice generate really small yoffset values // when scrolling slowly https://github.com/kovidgoyal/kitty/issues/1238 if (s == 0 && yoffset != 0) s = yoffset > 0 ? 1 : -1; - global_state.callback_os_window->pending_scroll_pixels = 0; + screen->pending_scroll_pixels = 0; } if (s == 0) return; bool upwards = s > 0; diff --git a/kitty/parser.c b/kitty/parser.c index 129d75b3..c433bfc4 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -9,6 +9,7 @@ #include "screen.h" #include "graphics.h" #include "charsets.h" +#include "monotonic.h" #include <time.h> extern PyTypeObject Screen_Type; @@ -1196,7 +1197,7 @@ end: } static inline void -do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, double now, PyObject *dump_callback DUMP_UNUSED) { +do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) { enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING}; enum STATE state = START; size_t read_buf_pos = 0; @@ -1272,7 +1273,7 @@ FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) { void -FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, double now) { +FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, monotonic_t now) { #ifdef DUMP_COMMANDS if (screen->read_buf_sz) { Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear(); diff --git a/kitty/screen.c b/kitty/screen.c index 8d70745d..e38fedc0 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1538,7 +1538,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat static inline bool is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) { - return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false; + return start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y); } static inline void @@ -1730,7 +1730,7 @@ static PyObject* set_pending_timeout(Screen *self, PyObject *val) { if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; } PyObject *ans = PyFloat_FromDouble(self->pending_mode.wait_time); - self->pending_mode.wait_time = PyFloat_AS_DOUBLE(val); + self->pending_mode.wait_time = s_double_to_monotonic_t(PyFloat_AS_DOUBLE(val)); return ans; } @@ -2267,6 +2267,13 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) { return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr))); } +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; +} + #define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) @@ -2380,6 +2387,7 @@ PyTypeObject Screen_Type = { static PyMethodDef module_methods[] = { {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""}, + {"is_emoji_presentation_base", (PyCFunction)screen_is_emoji_presentation_base, METH_O, ""}, {"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty/screen.h b/kitty/screen.h index 4eff12ee..e448c005 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -7,6 +7,7 @@ #pragma once #include "graphics.h" +#include "monotonic.h" typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType; @@ -66,6 +67,7 @@ typedef struct { unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by, last_selection_scrolled_by; unsigned int last_rendered_cursor_x, last_rendered_cursor_y; + double pending_scroll_pixels; CellPixelSize cell_size; OverlayLine overlay_line; id_type window_id; @@ -86,13 +88,13 @@ typedef struct { bool *tabstops, *main_tabstops, *alt_tabstops; ScreenModes modes; ColorProfile *color_profile; - double start_visual_bell_at; + monotonic_t start_visual_bell_at; uint32_t parser_buf[PARSER_BUF_SZ]; unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; uint8_t read_buf[READ_BUF_SZ], *write_buf; - double new_input_at; + monotonic_t new_input_at; size_t read_buf_sz, write_buf_sz, write_buf_used; pthread_mutex_t read_buf_lock, write_buf_lock; @@ -101,7 +103,7 @@ typedef struct { struct { size_t capacity, used, stop_buf_pos; uint8_t *buf; - double activated_at, wait_time; + monotonic_t activated_at, wait_time; int state; uint8_t stop_buf[32]; } pending_mode; @@ -110,8 +112,8 @@ typedef struct { } Screen; -void parse_worker(Screen *screen, PyObject *dump_callback, double now); -void parse_worker_dump(Screen *screen, PyObject *dump_callback, double now); +void parse_worker(Screen *screen, PyObject *dump_callback, monotonic_t now); +void parse_worker_dump(Screen *screen, PyObject *dump_callback, monotonic_t now); void screen_align(Screen*); void screen_restore_cursor(Screen *); void screen_save_cursor(Screen *); diff --git a/kitty/shaders.c b/kitty/shaders.c index 28c312c0..ae38553a 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -502,7 +502,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index); bind_vertex_array(vao_idx); - float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0 : OPT(inactive_text_alpha); + float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0f : (float)OPT(inactive_text_alpha); set_cell_uniforms(current_inactive_text_alpha); GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy; // The scissor limits below are calculated to ensure that they do not diff --git a/kitty/state.c b/kitty/state.c index f4920e85..251dd7c8 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -99,6 +99,19 @@ add_tab(id_type os_window_id) { return 0; } +static inline void +create_gpu_resources_for_window(Window *w) { + w->render_data.vao_idx = create_cell_vao(); + w->render_data.gvao_idx = create_graphics_vao(); +} + +static inline void +release_gpu_resources_for_window(Window *w) { + remove_vao(w->render_data.vao_idx); remove_vao(w->render_data.gvao_idx); + w->render_data.vao_idx = 0; w->render_data.gvao_idx = 0; +} + + static inline id_type add_window(id_type os_window_id, id_type tab_id, PyObject *title) { WITH_TAB(os_window_id, tab_id); @@ -108,8 +121,7 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) { tab->windows[tab->num_windows].id = ++global_state.window_id_counter; tab->windows[tab->num_windows].visible = true; tab->windows[tab->num_windows].title = title; - tab->windows[tab->num_windows].render_data.vao_idx = create_cell_vao(); - tab->windows[tab->num_windows].render_data.gvao_idx = create_graphics_vao(); + create_gpu_resources_for_window(&tab->windows[tab->num_windows]); Py_INCREF(tab->windows[tab->num_windows].title); return tab->windows[tab->num_windows++].id; END_WITH_TAB; @@ -133,7 +145,7 @@ update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyO static inline void destroy_window(Window *w) { Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title); - remove_vao(w->render_data.vao_idx); remove_vao(w->render_data.gvao_idx); + release_gpu_resources_for_window(w); } static inline void @@ -149,6 +161,70 @@ remove_window(id_type os_window_id, id_type tab_id, id_type 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 inline void +detach_window(id_type os_window_id, id_type tab_id, id_type id) { + WITH_TAB(os_window_id, tab_id); + for (size_t i = 0; i < tab->num_windows; i++) { + if (tab->windows[i].id == id) { + make_os_window_context_current(osw); + release_gpu_resources_for_window(&tab->windows[i]); + add_detached_window(tab->windows + i); + zero_at_i(tab->windows, i); + remove_i_from_array(tab->windows, i, tab->num_windows); + break; + } + } + END_WITH_TAB; +} + + +static inline void +resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) { + if (screen) { + screen->cell_size.width = os_window->fonts_data->cell_width; + screen->cell_size.height = os_window->fonts_data->cell_height; + screen_dirty_sprite_positions(screen); + if (has_graphics) screen_rescale_images(screen); + } +} + +static inline 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->cell_width || + w->render_data.screen->cell_size.height != osw->fonts_data->cell_height + ) resize_screen(osw, w->render_data.screen, true); + else screen_dirty_sprite_positions(w->render_data.screen); + break; + } + } + END_WITH_TAB; +} + static inline void destroy_tab(Tab *tab) { for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id); @@ -300,9 +376,14 @@ color_as_int(PyObject *color) { #undef I } -static inline double -repaint_delay(PyObject *val) { - return (double)(PyLong_AsUnsignedLong(val)) / 1000.0; +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 int kitty_mod = 0; @@ -360,6 +441,11 @@ set_special_keys(PyObject *dict) { }} } +static inline float +PyFloat_AsFloat(PyObject *o) { + return (float)PyFloat_AsDouble(o); +} + PYWRAP0(next_window_id) { return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1); } @@ -390,28 +476,28 @@ PYWRAP1(set_options) { #define S(name, convert) SS(name, OPT(name), convert) SS(kitty_mod, kitty_mod, PyLong_AsLong); S(hide_window_decorations, PyObject_IsTrue); - S(visual_bell_duration, PyFloat_AsDouble); + S(visual_bell_duration, parse_s_double_to_monotonic_t); S(enable_audio_bell, PyObject_IsTrue); S(focus_follows_mouse, PyObject_IsTrue); - S(cursor_blink_interval, PyFloat_AsDouble); - S(cursor_stop_blinking_after, PyFloat_AsDouble); - S(background_opacity, PyFloat_AsDouble); - S(dim_opacity, PyFloat_AsDouble); + S(cursor_blink_interval, parse_s_double_to_monotonic_t); + S(cursor_stop_blinking_after, parse_s_double_to_monotonic_t); + S(background_opacity, PyFloat_AsFloat); + S(dim_opacity, PyFloat_AsFloat); S(dynamic_background_opacity, PyObject_IsTrue); - S(inactive_text_alpha, PyFloat_AsDouble); - S(window_padding_width, PyFloat_AsDouble); + S(inactive_text_alpha, PyFloat_AsFloat); + S(window_padding_width, PyFloat_AsFloat); S(scrollback_pager_history_size, PyLong_AsUnsignedLong); S(cursor_shape, PyLong_AsLong); S(url_style, PyLong_AsUnsignedLong); S(tab_bar_edge, PyLong_AsLong); - S(mouse_hide_wait, PyFloat_AsDouble); + S(mouse_hide_wait, parse_s_double_to_monotonic_t); S(wheel_scroll_multiplier, PyFloat_AsDouble); S(touch_scroll_multiplier, PyFloat_AsDouble); S(open_url_modifiers, convert_mods); S(rectangle_select_modifiers, convert_mods); S(terminal_select_modifiers, convert_mods); - S(click_interval, PyFloat_AsDouble); - S(resize_debounce_time, PyFloat_AsDouble); + S(click_interval, parse_s_double_to_monotonic_t); + S(resize_debounce_time, parse_s_double_to_monotonic_t); S(url_color, color_as_int); S(background, color_as_int); S(foreground, color_as_int); @@ -420,8 +506,8 @@ PYWRAP1(set_options) { default_color = 0; S(inactive_border_color, color_as_int); S(bell_border_color, color_as_int); - S(repaint_delay, repaint_delay); - S(input_delay, repaint_delay); + S(repaint_delay, parse_ms_long_to_monotonic_t); + S(input_delay, parse_ms_long_to_monotonic_t); S(sync_to_monitor, PyObject_IsTrue); S(close_on_child_death, PyObject_IsTrue); S(window_alert_on_bell, PyObject_IsTrue); @@ -431,7 +517,7 @@ PYWRAP1(set_options) { S(macos_show_window_title_in, window_title_in); S(macos_window_resizable, PyObject_IsTrue); S(macos_hide_from_tasks, PyObject_IsTrue); - S(macos_thicken_font, PyFloat_AsDouble); + S(macos_thicken_font, PyFloat_AsFloat); S(tab_bar_min_tabs, PyLong_AsUnsignedLong); S(disable_ligatures, PyLong_AsLong); S(resize_draw_strategy, PyLong_AsLong); @@ -675,16 +761,6 @@ PYWRAP1(global_font_size) { return Py_BuildValue("d", global_state.font_sz_in_pts); } -static inline void -resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) { - if (screen) { - screen->cell_size.width = os_window->fonts_data->cell_width; - screen->cell_size.height = os_window->fonts_data->cell_height; - screen_dirty_sprite_positions(screen); - if (has_graphics) screen_rescale_images(screen); - } -} - PYWRAP1(os_window_font_size) { id_type os_window_id; int force = 0; @@ -743,6 +819,8 @@ PYWRAP0(destroy_global_data) { THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) +THREE_ID(detach_window) +THREE_ID(attach_window) PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); } 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)); } @@ -770,6 +848,8 @@ static PyMethodDef module_methods[] = { 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(set_active_window, METH_VARARGS), MW(swap_tabs, METH_VARARGS), @@ -795,6 +875,15 @@ static PyMethodDef module_methods[] = { {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; +} + bool init_state(PyObject *module) { global_state.font_sz_in_pts = 11.0; @@ -808,6 +897,10 @@ init_state(PyObject *module) { if (PyStructSequence_InitType2(&RegionType, ®ion_desc) != 0) return false; Py_INCREF((PyObject *) &RegionType); PyModule_AddObject(module, "Region", (PyObject *) &RegionType); + if (Py_AtExit(finalize) != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to register the state at exit handler"); + return false; + } return true; } // }}} diff --git a/kitty/state.h b/kitty/state.h index cd21e302..cfec6931 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -7,6 +7,7 @@ #pragma once #include "data-types.h" #include "screen.h" +#include "monotonic.h" #define OPT(name) global_state.opts.name @@ -14,7 +15,8 @@ typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge; typedef enum { RESIZE_DRAW_STATIC, RESIZE_DRAW_SCALED, RESIZE_DRAW_BLANK, RESIZE_DRAW_SIZE } ResizeDrawStrategy; typedef struct { - double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier, touch_scroll_multiplier; + monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval; + double wheel_scroll_multiplier, touch_scroll_multiplier; bool enable_audio_bell; CursorShape cursor_shape; unsigned int open_url_modifiers; @@ -24,7 +26,7 @@ typedef struct { unsigned int scrollback_pager_history_size; char_type select_by_word_characters[256]; size_t select_by_word_characters_count; color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color; - double repaint_delay, input_delay; + monotonic_t repaint_delay, input_delay; bool focus_follows_mouse, 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; @@ -44,7 +46,7 @@ typedef struct { bool close_on_child_death; bool window_alert_on_bell; bool debug_keyboard; - double resize_debounce_time; + monotonic_t resize_debounce_time; MouseShape pointer_shape_when_grabbed; } Options; @@ -59,7 +61,7 @@ typedef struct { } WindowGeometry; typedef struct { - double at; + monotonic_t at; int button, modifiers; double x, y; } Click; @@ -83,7 +85,7 @@ typedef struct { } mouse_pos; WindowGeometry geometry; ClickQueue click_queue; - double last_drag_scroll_at; + monotonic_t last_drag_scroll_at; } Window; typedef struct { @@ -114,7 +116,7 @@ typedef struct { enum RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY }; typedef struct { - double last_resize_event_at; + monotonic_t last_resize_event_at; bool in_progress; bool from_os_notification; bool os_says_resize_complete; @@ -134,7 +136,7 @@ typedef struct { ScreenRenderData tab_bar_render_data; bool tab_bar_data_updated; bool is_focused; - double cursor_blink_zero_time, last_mouse_activity_at; + monotonic_t cursor_blink_zero_time, last_mouse_activity_at; double mouse_x, mouse_y; double logical_dpi_x, logical_dpi_y, font_sz_in_pts; bool mouse_button_pressed[20]; @@ -149,9 +151,8 @@ typedef struct { float background_opacity; FONTS_DATA_HANDLE fonts_data; id_type temp_font_group_id; - double pending_scroll_pixels; enum RENDER_STATE render_state; - double last_render_frame_received_at; + monotonic_t last_render_frame_received_at; id_type last_focused_counter; ssize_t gvao_idx; } OSWindow; @@ -239,8 +240,8 @@ 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(double interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback); +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, double interval, bool enabled); +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); diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index 9db6a578..da5c3f75 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -18,7 +18,7 @@ from .rgb import alpha_blend, color_from_int TabBarData = namedtuple('TabBarData', 'title is_active needs_attention') DrawData = namedtuple( 'DrawData', 'leading_spaces sep trailing_spaces bell_on_tab' - ' bell_fg alpha active_bg inactive_bg default_bg title_template') + ' bell_fg alpha active_fg active_bg inactive_fg inactive_bg default_bg title_template') def as_rgb(x): @@ -41,7 +41,7 @@ def draw_title(draw_data, screen, tab, index): screen.draw(title) -def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, index): +def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, index, is_last): if draw_data.leading_spaces: screen.draw(' ' * draw_data.leading_spaces) draw_title(draw_data, screen, tab, index) @@ -60,7 +60,7 @@ def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, in return end -def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index): +def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index, is_last): tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg fade_colors = [as_rgb(color_as_int(alpha_blend(tab_bg, draw_data.default_bg, alpha))) for alpha in draw_data.alpha] for bg in fade_colors: @@ -87,6 +87,58 @@ def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index): return end +def draw_tab_with_powerline(draw_data, screen, tab, before, max_title_length, index, is_last): + tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg)) + tab_fg = as_rgb(color_as_int(draw_data.active_fg if tab.is_active else draw_data.inactive_fg)) + inactive_bg = as_rgb(color_as_int(draw_data.inactive_bg)) + default_bg = as_rgb(color_as_int(draw_data.default_bg)) + + min_title_length = 1 + 2 + + if screen.cursor.x + min_title_length >= screen.columns: + screen.cursor.x -= 2 + screen.cursor.bg = default_bg + screen.cursor.fg = inactive_bg + screen.draw(' ') + return screen.cursor.x + + start_draw = 2 + if tab.is_active and screen.cursor.x >= 2: + screen.cursor.x -= 2 + screen.cursor.fg = inactive_bg + screen.cursor.bg = tab_bg + screen.draw(' ') + screen.cursor.fg = tab_fg + elif screen.cursor.x == 0: + screen.draw(' ') + start_draw = 1 + + if min_title_length >= max_title_length: + screen.draw('…') + else: + draw_title(draw_data, screen, tab, index) + extra = screen.cursor.x + start_draw - before - max_title_length + if extra > 0 and extra + 1 < screen.cursor.x: + screen.cursor.x -= extra + 1 + screen.draw('…') + + if tab.is_active or is_last: + screen.draw(' ') + screen.cursor.fg = tab_bg + if is_last: + screen.cursor.bg = default_bg + else: + screen.cursor.bg = inactive_bg + screen.draw('') + else: + screen.draw(' ') + + end = screen.cursor.x + if end < screen.columns: + screen.draw(' ') + return end + + class TabBar: def __init__(self, os_window_id, opts): @@ -123,10 +175,16 @@ class TabBar: self.bell_fg = as_rgb(0xff0000) self.draw_data = DrawData( self.leading_spaces, self.sep, self.trailing_spaces, self.opts.bell_on_tab, self.bell_fg, - self.opts.tab_fade, self.opts.active_tab_background, self.opts.inactive_tab_background, + self.opts.tab_fade, self.opts.active_tab_foreground, self.opts.active_tab_background, + self.opts.inactive_tab_foreground, self.opts.inactive_tab_background, self.opts.background, self.opts.tab_title_template ) - self.draw_func = draw_tab_with_separator if self.opts.tab_bar_style == 'separator' else draw_tab_with_fade + if self.opts.tab_bar_style == 'separator': + self.draw_func = draw_tab_with_separator + elif self.opts.tab_bar_style == 'powerline': + self.draw_func = draw_tab_with_powerline + else: + self.draw_func = draw_tab_with_fade def patch_colors(self, spec): if 'active_tab_foreground' in spec: @@ -179,10 +237,13 @@ class TabBar: s.cursor.fg = self.active_fg if t.is_active else 0 s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style before = s.cursor.x - end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1) + end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1, t is last_tab) cr.append((before, end)) if s.cursor.x > s.columns - max_title_length and t is not last_tab: - s.draw('…') + s.cursor.x = s.columns - 2 + s.cursor.bg = as_rgb(color_as_int(self.draw_data.default_bg)) + s.cursor.fg = self.bell_fg + s.draw(' …') break s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.cell_ranges = cr diff --git a/kitty/tabs.py b/kitty/tabs.py index 65bbcc8f..074be4d6 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -4,14 +4,14 @@ import weakref from collections import deque, namedtuple -from functools import partial from contextlib import suppress +from functools import partial from .borders import Borders from .child import Child from .constants import appname, get_boss, is_macos, is_wayland from .fast_data_types import ( - add_tab, mark_tab_bar_dirty, next_window_id, + add_tab, attach_window, detach_window, mark_tab_bar_dirty, next_window_id, pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs, x11_window_id ) @@ -38,7 +38,7 @@ def add_active_id_to_history(items, item_id, maxlen=64): class Tab: # {{{ - def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None): + def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None, no_initial_window=False): self._active_window_idx = 0 self.tab_manager_ref = weakref.ref(tab_manager) self.os_window_id = tab_manager.os_window_id @@ -57,8 +57,10 @@ class Tab: # {{{ for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()): setattr(self, which + '_window', partial(self.nth_window, num=i)) self._last_used_layout = self._current_layout_name = None - if session_tab is None: - self.cwd = self.args.directory + self.cwd = self.args.directory + if no_initial_window: + self._set_current_layout(self.enabled_layouts[0]) + elif session_tab is None: sl = self.enabled_layouts[0] self._set_current_layout(sl) if special_window is None: @@ -66,11 +68,33 @@ class Tab: # {{{ else: self.new_special_window(special_window) else: - self.cwd = session_tab.cwd or self.args.directory + if session_tab.cwd: + self.cwd = session_tab.cwd l0 = session_tab.layout self._set_current_layout(l0) self.startup(session_tab) + def take_over_from(self, other_tab): + self.name, self.cwd = other_tab.name, other_tab.cwd + self.enabled_layouts = list(other_tab.enabled_layouts) + self._set_current_layout(other_tab._current_layout_name) + self._last_used_layout = other_tab._last_used_layout + + orig_windows = deque(other_tab.windows) + orig_history = deque(other_tab.active_window_history) + orig_active = other_tab._active_window_idx + for window in other_tab.windows: + detach_window(other_tab.os_window_id, other_tab.id, window.id) + other_tab.windows = deque() + other_tab._active_window_idx = 0 + self.active_window_history = orig_history + self.windows = orig_windows + self._active_window_idx = orig_active + for window in self.windows: + window.change_tab(self) + attach_window(self.os_window_id, self.id, window.id) + self.relayout() + def _set_current_layout(self, layout_name): self._last_used_layout = self._current_layout_name self.current_layout = self.create_layout_object(layout_name) @@ -212,7 +236,7 @@ class Tab: # {{{ if self.current_layout.remove_all_biases(): self.relayout() - def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None): + def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None, allow_remote_control=False): if cmd is None: if use_shell: cmd = resolved_shell(self.opts) @@ -228,16 +252,21 @@ class Tab: # {{{ except Exception: import traceback traceback.print_exc() - ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from) + ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from, allow_remote_control=allow_remote_control) ans.fork() return ans + def _add_window(self, window, location=None): + self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) + self.relayout_borders() + def new_window( self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None, location=None, - copy_colors_from=None + copy_colors_from=None, allow_remote_control=False ): - child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env) + child = self.launch_child( + use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control) window = Window(self, child, self.opts, self.args, override_title=override_title, copy_colors_from=copy_colors_from) if overlay_for is not None: overlaid = next(w for w in self.windows if w.id == overlay_for) @@ -245,12 +274,11 @@ class Tab: # {{{ overlaid.overlay_window_id = window.id # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) - self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) - self.relayout_borders() + self._add_window(window, location=location) return window - def new_special_window(self, special_window, location=None, copy_colors_from=None): - return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from) + def new_special_window(self, special_window, location=None, copy_colors_from=None, allow_remote_control=False): + return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from, allow_remote_control=allow_remote_control) def close_window(self): if self.windows: @@ -265,13 +293,16 @@ class Tab: # {{{ if w.id == old_window_id: return idx - def remove_window(self, window): + def remove_window(self, window, destroy=True): idx = self.previous_active_window_idx(1) next_window_id = None if idx is not None: next_window_id = self.windows[idx].id active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) - remove_window(self.os_window_id, self.id, window.id) + if destroy: + remove_window(self.os_window_id, self.id, window.id) + else: + detach_window(self.os_window_id, self.id, window.id) if window.overlay_for is not None: for idx, q in enumerate(self.windows): if q.id == window.overlay_for: @@ -294,6 +325,32 @@ class Tab: # {{{ if active_window: self.title_changed(active_window) + def detach_window(self, window): + underlaid_window = None + overlaid_window = window + if window.overlay_for: + for x in self.windows: + if x.id == window.overlay_for: + underlaid_window = x + break + elif window.overlay_window_id: + underlaid_window = window + overlaid_window = None + for x in self.windows: + if x.id == window.overlay_window_id: + overlaid_window = x + break + if overlaid_window is not None: + self.remove_window(overlaid_window, destroy=False) + if underlaid_window is not None: + self.remove_window(underlaid_window, destroy=False) + return underlaid_window, overlaid_window + + def attach_window(self, window): + window.change_tab(self) + attach_window(self.os_window_id, self.id, window.id) + self._add_window(window) + def set_active_window_idx(self, idx): if idx != self.active_window_idx: self.active_window_idx = self.current_layout.set_active_window(self.windows, idx) @@ -382,12 +439,17 @@ class Tab: # {{{ def __repr__(self): return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self))) + + def make_active(self): + tm = self.tab_manager_ref() + if tm is not None: + tm.set_active_tab(self) # }}} class TabManager: # {{{ - def __init__(self, os_window_id, opts, args, startup_session): + def __init__(self, os_window_id, opts, args, startup_session=None): self.os_window_id = os_window_id self.last_active_tab_id = None self.opts, self.args = opts, args @@ -397,9 +459,10 @@ class TabManager: # {{{ self.tab_bar = TabBar(self.os_window_id, opts) self._active_tab_idx = 0 - for t in startup_session.tabs: - self._add_tab(Tab(self, session_tab=t)) - self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1))) + if startup_session is not None: + for t in startup_session.tabs: + self._add_tab(Tab(self, session_tab=t)) + self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1))) @property def active_tab_idx(self): @@ -545,10 +608,10 @@ class TabManager: # {{{ self._set_active_tab(nidx) self.mark_tab_bar_dirty() - def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False): + def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False): nidx = self.active_tab_idx + 1 idx = len(self.tabs) - self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from)) + self._add_tab(Tab(self, no_initial_window=True) if empty_tab else Tab(self, special_window=special_window, cwd_from=cwd_from)) self._set_active_tab(idx) if len(self.tabs) > 2 and as_neighbor and idx != nidx: for i in range(idx, nidx, -1): diff --git a/kitty/unicode-data.c b/kitty/unicode-data.c index 4d148d7a..aa4a142a 100644 --- a/kitty/unicode-data.c +++ b/kitty/unicode-data.c @@ -1,4 +1,4 @@ -// unicode data, built from the unicode standard on: 2019-08-02 +// unicode data, built from the unicode standard on: 2019-10-01 // see gen-wcwidth.py #include "data-types.h" diff --git a/kitty/utils.py b/kitty/utils.py index 06c2c30f..e0f1be9f 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -164,7 +164,10 @@ def open_cmd(cmd, arg=None, cwd=None): import subprocess if arg is not None: cmd = list(cmd) - cmd.append(arg) + if isinstance(arg, (list, tuple)): + cmd.extend(arg) + else: + cmd.append(arg) return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None) @@ -415,15 +418,12 @@ def get_editor(): ans = getattr(get_editor, 'ans', False) if ans is False: import shlex - ans = os.environ.get('EDITOR') - if not ans or not exe_exists(shlex.split(ans)[0]): - for q in ('vim', 'nvim', 'vi', 'emacs', 'kak', 'micro', 'nano', 'vis'): - r = exe_exists(q) - if r: - ans = r - break - else: - ans = 'vim' + for ans in (os.environ.get('VISUAL'), os.environ.get('EDITOR'), 'vim', + 'nvim', 'vi', 'emacs', 'kak', 'micro', 'nano', 'vis'): + if ans and exe_exists(shlex.split(ans)[0]): + break + else: + ans = 'vim' ans = shlex.split(ans) get_editor.ans = ans return ans diff --git a/kitty/wcwidth-std.h b/kitty/wcwidth-std.h index 303ee6a9..c876c08d 100644 --- a/kitty/wcwidth-std.h +++ b/kitty/wcwidth-std.h @@ -1,4 +1,4 @@ -// unicode data, built from the unicode standard on: 2019-08-02 +// unicode data, built from the unicode standard on: 2019-10-01 // see gen-wcwidth.py #pragma once #include "data-types.h" diff --git a/kitty/window.py b/kitty/window.py index c3b0b02a..b9e2c6d8 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -132,7 +132,7 @@ def text_sanitizer(as_ansi, add_wrap_markers): class Window: def __init__(self, tab, child, opts, args, override_title=None, copy_colors_from=None): - self.action_on_close = None + self.action_on_close = self.action_on_removal = None self.layout_data = None self.pty_resized_once = False self.needs_attention = False @@ -163,6 +163,11 @@ class Window: else: setup_colors(self.screen, opts) + def change_tab(self, tab): + self.tab_id = tab.id + self.os_window_id = tab.os_window_id + self.tabref = weakref.ref(tab) + @property def title(self): return self.override_title or self.child_title diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index f5b59a61..cda77d64 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -11,7 +11,9 @@ from kitty.fast_data_types import ( test_render_line, test_sprite_position_for, wcwidth ) from kitty.fonts.box_drawing import box_chars -from kitty.fonts.render import render_string, setup_for_testing, shape_string +from kitty.fonts.render import ( + coalesce_symbol_maps, render_string, setup_for_testing, shape_string +) from . import BaseTest @@ -122,3 +124,7 @@ class Rendering(BaseTest): finally: sys.stderr = orig self.assertIn('LastResort', buf.getvalue()) + + def test_coalesce_symbol_maps(self): + q = {(2, 3): 'a', (4, 6): 'b', (5, 5): 'b', (7, 7): 'b', (9, 9): 'b', (1, 1): 'a'} + self.ae(coalesce_symbol_maps(q), {(1, 3): 'a', (4, 7): 'b', (9, 9): 'b'}) @@ -200,8 +200,10 @@ def init_env( if ccver < (5, 2) and cc == 'gcc': missing_braces = '-Wno-missing-braces' df = '-g3' + float_conversion = '' if ccver >= (5, 0): df += ' -Og' + float_conversion = '-Wfloat-conversion' optimize = df if debug or sanitize else '-O3' sanitize_args = get_sanitize_args(cc, ccver) if sanitize else set() cppflags = os.environ.get( @@ -210,11 +212,14 @@ def init_env( cppflags = shlex.split(cppflags) for el in extra_logging: cppflags.append('-DDEBUG_{}'.format(el.upper().replace('-', '_'))) + # gnu11 is needed to get monotonic.h to build on older Linux distros + std = 'c' if is_macos or ccver[0] >= 5 else 'gnu' cflags = os.environ.get( 'OVERRIDE_CFLAGS', ( - '-Wextra -Wno-missing-field-initializers -Wall -Wstrict-prototypes -std=c11' + '-Wextra {} -Wno-missing-field-initializers -Wall -Wstrict-prototypes -std={}11' ' -pedantic-errors -Werror {} {} -fwrapv {} {} -pipe {} -fvisibility=hidden' ).format( + float_conversion, std, optimize, ' '.join(sanitize_args), stack_protector, @@ -270,9 +275,6 @@ def kitty_env(): gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs') libpng = pkg_config('libpng', '--libs') ans.ldpaths += pylib + font_libs + gl_libs + libpng - if not is_macos: - cflags.extend(pkg_config('libcanberra', '--cflags-only-I')) - ans.ldpaths += pkg_config('libcanberra', '--libs') if is_macos: ans.ldpaths.extend('-framework Cocoa'.split()) else: @@ -382,8 +384,9 @@ def parallel_run(items): return pid, s = os.wait() compile_cmd, w = workers.pop(pid, (None, None)) - if compile_cmd is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0) and failed is None: - failed = compile_cmd + if compile_cmd is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0): + if failed is None: + failed = compile_cmd elif compile_cmd.on_success is not None: compile_cmd.on_success() @@ -759,7 +762,7 @@ def macos_info_plist(): NSRequiresAquaSystemAppearance='NO', NSHumanReadableCopyright=time.strftime( 'Copyright %Y, Kovid Goyal'), - CFBundleGetInfoString='kitty, an OpenGL based terminal emulator https://sw.kovidgoyal.net/kitty', + CFBundleGetInfoString='kitty, an OpenGL based terminal emulator https://sw.kovidgoyal.net/kitty/', CFBundleIconFile=appname + '.icns', NSHighResolutionCapable=True, NSSupportsAutomaticGraphicsSwitching=True, |