summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames McCoy <jamessan@debian.org>2019-12-15 21:47:10 -0500
committerJames McCoy <jamessan@debian.org>2019-12-15 21:47:10 -0500
commit0bcfee27861d8e0144fb261c4f0d87a7b77d68ff (patch)
tree912eed4e954fab8b668f8ab291d8fcefeea8f6a2
parent0e4dcf278892f0ee7580e2391678abc9f317ef3b (diff)
parent804f72b31a7fabb9f8a484e2ada296c2a75628e0 (diff)
Merge tag 'v0.15.0' into debian/sid
version-0.15.0 Signed-off-by: James McCoy <jamessan@debian.org>
-rw-r--r--.gitattributes4
-rw-r--r--README.asciidoc2
-rw-r--r--__main__.py2
-rwxr-xr-xcount-lines-of-code29
-rw-r--r--docs/_static/custom.css2
-rw-r--r--docs/build.rst2
-rw-r--r--docs/changelog.rst50
-rw-r--r--docs/conf.py12
-rw-r--r--docs/faq.rst14
-rw-r--r--docs/index.rst20
-rw-r--r--docs/kittens/custom.rst22
-rw-r--r--docs/kittens/hints.rst50
-rw-r--r--docs/kittens/unicode-input.rst5
-rw-r--r--docs/launch.rst56
-rw-r--r--docs/pipe.rst6
-rw-r--r--docs/remote-control.rst2
-rwxr-xr-xgen-wcwidth.py13
-rw-r--r--glfw/backend_utils.c45
-rw-r--r--glfw/backend_utils.h13
-rw-r--r--glfw/cocoa_init.m264
-rw-r--r--glfw/cocoa_monitor.m14
-rw-r--r--glfw/cocoa_platform.h14
-rw-r--r--glfw/cocoa_time.c3
-rw-r--r--glfw/cocoa_window.m367
-rw-r--r--glfw/dbus_glfw.c3
-rwxr-xr-xglfw/glfw.py15
-rw-r--r--glfw/glfw3.h646
-rw-r--r--glfw/ibus_glfw.c29
-rw-r--r--glfw/ibus_glfw.h15
-rw-r--r--glfw/init.c10
-rw-r--r--glfw/input.c58
-rw-r--r--glfw/internal.h17
-rw-r--r--glfw/linux_joystick.c3
-rw-r--r--glfw/main_loop.h5
-rw-r--r--glfw/nsgl_context.h4
-rw-r--r--glfw/nsgl_context.m4
-rw-r--r--glfw/null_window.c11
-rw-r--r--glfw/window.c11
-rw-r--r--glfw/wl_init.c55
-rw-r--r--glfw/wl_platform.h4
-rw-r--r--glfw/wl_window.c52
-rw-r--r--glfw/x11_init.c12
-rw-r--r--glfw/x11_monitor.c10
-rw-r--r--glfw/x11_window.c90
-rw-r--r--glfw/xkb_glfw.c119
-rw-r--r--glfw/xkb_glfw.h6
-rw-r--r--kittens/diff/main.py22
-rw-r--r--kittens/hints/main.py94
-rwxr-xr-xkittens/icat/main.py25
-rw-r--r--kittens/show_error/main.py6
-rw-r--r--kittens/ssh/main.py3
-rw-r--r--kittens/unicode_input/main.py81
-rw-r--r--kittens/unicode_input/names.h2
-rw-r--r--kitty/boss.py273
-rw-r--r--kitty/child-monitor.c69
-rw-r--r--kitty/child.py43
-rw-r--r--kitty/cli.py6
-rw-r--r--kitty/cmds.py174
-rw-r--r--kitty/cocoa_window.m16
-rw-r--r--kitty/complete.py2
-rw-r--r--kitty/config.py26
-rw-r--r--kitty/config_data.py58
-rw-r--r--kitty/constants.py9
-rw-r--r--kitty/core_text.m6
-rw-r--r--kitty/data-types.c49
-rw-r--r--kitty/data-types.h4
-rw-r--r--kitty/desktop.c51
-rw-r--r--kitty/emoji.h461
-rw-r--r--kitty/fonts.c75
-rw-r--r--kitty/fonts/box_drawing.py219
-rw-r--r--kitty/fonts/render.py23
-rw-r--r--kitty/freetype.c18
-rw-r--r--kitty/glfw-wrapper.c8
-rw-r--r--kitty/glfw-wrapper.h334
-rw-r--r--kitty/glfw.c83
-rw-r--r--kitty/glfw_tests.c4
-rw-r--r--kitty/graphics.c2
-rw-r--r--kitty/graphics.h3
-rw-r--r--kitty/keys.c37
-rw-r--r--kitty/keys.py10
-rw-r--r--kitty/kittens.c7
-rw-r--r--kitty/launch.py216
-rw-r--r--kitty/layout.py4
-rw-r--r--kitty/main.py45
-rw-r--r--kitty/monotonic.h89
-rw-r--r--kitty/mouse.c19
-rw-r--r--kitty/parser.c5
-rw-r--r--kitty/screen.c12
-rw-r--r--kitty/screen.h12
-rw-r--r--kitty/shaders.c2
-rw-r--r--kitty/state.c151
-rw-r--r--kitty/state.h23
-rw-r--r--kitty/tab_bar.py75
-rw-r--r--kitty/tabs.py107
-rw-r--r--kitty/unicode-data.c2
-rw-r--r--kitty/utils.py20
-rw-r--r--kitty/wcwidth-std.h2
-rw-r--r--kitty/window.py7
-rw-r--r--kitty_tests/fonts.py8
-rwxr-xr-xsetup.py17
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, &current);
+
+ const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
if (_glfwCompareVideoModes(&current, 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, &registryListener, 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,
&notification))
{
- 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) &notification))
{
- 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, &region_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'})
diff --git a/setup.py b/setup.py
index 68e47411..e3109bfc 100755
--- a/setup.py
+++ b/setup.py
@@ -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,