Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f05024550 | ||
|
|
4d2d9d5574 | ||
|
|
14f44e79f7 | ||
|
|
ab960ea12d | ||
|
|
d276317e3a | ||
|
|
8b3b0064a9 | ||
|
|
66b890bfd3 | ||
|
|
2454ecd586 | ||
|
|
b891f4f874 | ||
|
|
92d06b3088 | ||
|
|
3067103b18 | ||
|
|
0e248b3faa | ||
|
|
d1a76bbcd9 | ||
|
|
1d91edfb00 | ||
|
|
cf7ecd5e66 | ||
|
|
63a2ecb89c | ||
|
|
8e6b58b1db | ||
|
|
0f594ef950 | ||
|
|
0c1b76c17a | ||
|
|
1c11ba8dbd | ||
|
|
529d5b2226 | ||
|
|
9fe16867c6 | ||
|
|
1d275c6b35 | ||
|
|
d19b29ced0 | ||
|
|
bbefcb2cd2 | ||
|
|
c864cba4a2 | ||
|
|
42546f4371 | ||
|
|
ff09bdcbf1 | ||
|
|
f36a9fffa3 | ||
|
|
933cced76c | ||
|
|
c51e7d3bde | ||
|
|
d3b31224cd | ||
|
|
ec9bb2302c | ||
|
|
009ef54de7 | ||
|
|
0e0e25f986 | ||
|
|
8b02b89bd6 | ||
|
|
8ba3c4ba50 | ||
|
|
16764c79b2 | ||
|
|
96c444d041 | ||
|
|
ec1c72e9a3 | ||
|
|
f9b1f6be5c | ||
|
|
9201d27875 | ||
|
|
ecbe79e04f | ||
|
|
6e34a7559b | ||
|
|
ea8b17565d | ||
|
|
a143faaf05 | ||
|
|
acdb56ad5d | ||
|
|
1f90238b18 | ||
|
|
89b636376e | ||
|
|
59f39ae3ab | ||
|
|
a92b20f85a | ||
|
|
e71c432b7c | ||
|
|
b6a5d09d4f | ||
|
|
9235d2b8e4 | ||
|
|
605847fd75 | ||
|
|
6222273adc | ||
|
|
6a759ea7d5 | ||
|
|
d8a7114dba | ||
|
|
ab818ed89f | ||
|
|
1bb6540b64 | ||
|
|
ddd8209c45 | ||
|
|
1dc3dfe58f | ||
|
|
634d262b64 | ||
|
|
d964146f8c | ||
|
|
05b817f5f5 | ||
|
|
eb2ec1833c | ||
|
|
094ddd9333 | ||
|
|
e05d48a574 | ||
|
|
927bfcd37a | ||
|
|
42cabace47 | ||
|
|
9a1f14d05c | ||
|
|
4189aa8f14 | ||
|
|
5fa5bf8142 | ||
|
|
000c1cf306 | ||
|
|
28447d3389 | ||
|
|
ab7f3310c4 | ||
|
|
e5a720c6fa | ||
|
|
8d20dbe81d | ||
|
|
1fc666d82c | ||
|
|
1b2a1a1e77 | ||
|
|
9af3081f45 | ||
|
|
78def6e6bf | ||
|
|
b4269199b4 | ||
|
|
cb289125ab | ||
|
|
a4e3ce31dd | ||
|
|
8a725d3d3e | ||
|
|
c48fa0f078 | ||
|
|
31aa782362 | ||
|
|
892637683c | ||
|
|
1cf6f28fe8 | ||
|
|
29f9394257 | ||
|
|
8fd92df7ae | ||
|
|
374f7abb5d | ||
|
|
3d1e604a6c | ||
|
|
1ca49b9380 | ||
|
|
9bd9cf8052 | ||
|
|
ed934a0206 | ||
|
|
dc891760ec | ||
|
|
97401f843a | ||
|
|
833865cb28 | ||
|
|
02635d506e | ||
|
|
f018e1a075 | ||
|
|
c79cde43ae | ||
|
|
8a812c9d02 | ||
|
|
3df78de3f8 | ||
|
|
bd4831e2ee | ||
|
|
341a34ea9e | ||
|
|
011866a101 | ||
|
|
1486af8770 | ||
|
|
f06e04ffc5 | ||
|
|
d72098bdf8 | ||
|
|
57871e1983 | ||
|
|
21dbb0e820 | ||
|
|
007f377f16 | ||
|
|
da15477aaf | ||
|
|
4f34ad0a8d | ||
|
|
6ed9dccde2 | ||
|
|
ce2b3265be | ||
|
|
14ea69c9c7 | ||
|
|
72e2307c16 | ||
|
|
0dd3334811 | ||
|
|
5485436ea1 | ||
|
|
9e044750ce | ||
|
|
f84cb2f07a | ||
|
|
e0ff6bcc5d | ||
|
|
1695f6800c | ||
|
|
810f149501 | ||
|
|
05d51d8693 | ||
|
|
ac4c819f57 | ||
|
|
e7cc654bb4 | ||
|
|
bf37e3f77f | ||
|
|
af9f7bb0d2 | ||
|
|
0b7abf5690 | ||
|
|
490ae248f8 | ||
|
|
e552bcb0c6 | ||
|
|
2c27316c4a | ||
|
|
c49dd12855 | ||
|
|
544f97a3fe | ||
|
|
6a909e0700 | ||
|
|
40a6ad9a1f | ||
|
|
039f2b49e8 | ||
|
|
5ae2406a03 | ||
|
|
2c3235d452 | ||
|
|
d2ea71a403 | ||
|
|
feb97b2313 | ||
|
|
0b662ecb9a | ||
|
|
3d0da77c80 | ||
|
|
9918397190 | ||
|
|
b3e2562c00 | ||
|
|
255cacf912 | ||
|
|
8268f9f612 | ||
|
|
92b8798cad | ||
|
|
fbb4db8a83 | ||
|
|
19a672caf2 | ||
|
|
5a85c890ba | ||
|
|
55556aaac4 | ||
|
|
31853ee20c | ||
|
|
037bfeff82 | ||
|
|
bf1c919547 | ||
|
|
202ec17a08 | ||
|
|
98ede457a0 | ||
|
|
3c772c3576 | ||
|
|
5b1eaa741c | ||
|
|
1a1aa2d6aa | ||
|
|
6e9aecdff3 | ||
|
|
95187632bf | ||
|
|
e3e28a023c | ||
|
|
65bb71779a | ||
|
|
29dd766a67 | ||
|
|
851145e69e | ||
|
|
b2e300c440 | ||
|
|
38998a4869 | ||
|
|
21f1fd55f8 | ||
|
|
c6c6a18375 | ||
|
|
56063b283e | ||
|
|
d27e7ead49 | ||
|
|
342164ede1 | ||
|
|
1061e40b98 | ||
|
|
377115a594 | ||
|
|
e405b68da1 | ||
|
|
d875d7baed | ||
|
|
9c48e4965c | ||
|
|
8675659fcb | ||
|
|
3b226618ad | ||
|
|
6879a492dc | ||
|
|
4070255dde | ||
|
|
dd9e484336 | ||
|
|
78cda7aa74 | ||
|
|
9ced447fa3 | ||
|
|
8e665ffedc | ||
|
|
99ea6c08a7 | ||
|
|
b0bfa2b2fc | ||
|
|
3797294838 | ||
|
|
3d6995d1e6 | ||
|
|
e91eb27e56 | ||
|
|
3a4b614ae0 | ||
|
|
26a6d6bef9 | ||
|
|
dca81589a7 | ||
|
|
5b7c697dfe | ||
|
|
18d6b8a0ad | ||
|
|
cdee601137 | ||
|
|
e74c08ff39 | ||
|
|
5bfe2fb8d3 | ||
|
|
55fc5c4358 | ||
|
|
d6a603d536 | ||
|
|
5c85d915cc |
@@ -17,6 +17,7 @@ matrix:
|
||||
- libxinerama-dev
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libdbus-1-dev
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
@@ -37,6 +38,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
|
||||
- os: linux
|
||||
@@ -58,6 +60,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
|
||||
- os: linux
|
||||
@@ -79,6 +82,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
- clang
|
||||
- os: osx
|
||||
|
||||
@@ -33,6 +33,11 @@ def hold(args):
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def complete(args):
|
||||
from kitty.complete import main
|
||||
main(args[1:], entry_points, namespaced_entry_points)
|
||||
|
||||
|
||||
def launch(args):
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
@@ -69,6 +74,7 @@ entry_points = {
|
||||
}
|
||||
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
|
||||
namespaced_entry_points['hold'] = hold
|
||||
namespaced_entry_points['complete'] = complete
|
||||
|
||||
|
||||
def setup_openssl_environment():
|
||||
|
||||
@@ -28,7 +28,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:
|
||||
``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev`` and ``libxkbcommon-x11-dev``,
|
||||
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev`` and ``libxkbcommon-x11-dev``,
|
||||
if they are not already installed by your distro.
|
||||
|
||||
Install and run from source
|
||||
@@ -85,7 +85,7 @@ without needing to install all of |kitty|.
|
||||
.. note::
|
||||
You need a couple of extra dependencies to build linux-package.
|
||||
:file:`tic` to compile terminfo files, usually found in the
|
||||
development package of :file:`ncurses`. Also, if you are building form
|
||||
development package of :file:`ncurses`. Also, if you are building from
|
||||
a git checkout instead of the released source code tarball, you will
|
||||
need :file:`sphinx-build` from the `Sphinx documentation generator
|
||||
<http://www.sphinx-doc.org/>`_.
|
||||
|
||||
@@ -3,6 +3,78 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
0.12.0 [2018-09-01]
|
||||
------------------------------
|
||||
|
||||
- Preserve the mouse selection even when the contents of the screen are
|
||||
scrolled or overwritten provided the new text does not intersect the
|
||||
selected lines.
|
||||
|
||||
- Linux: Implement support for Input Method Extensions (multilingual input
|
||||
using standard keyboards) via `IBus
|
||||
<https://github.com/ibus/ibus/wiki/ReadMe>`_ (:iss:`469`)
|
||||
|
||||
- Implement completion for the kitty command in bash and zsh. See
|
||||
:ref:`completion`.
|
||||
|
||||
- Render the text under the cursor in a fixed color, configurable via
|
||||
the option :opt:`cursor_text_color` (:iss:`126`)
|
||||
|
||||
- Add an option :opt:`env` to set environment variables in child processes
|
||||
from kitty.conf
|
||||
|
||||
- Implement high precision scrolling with the trackpad on platforms such as
|
||||
macOS and Wayland that implement it. (:pull:`819`)
|
||||
|
||||
- macOS: Allow scrolling window contents using mouse wheel/trackpad even when the
|
||||
window is not the active window (:iss:`729`)
|
||||
|
||||
- Remote control: Allow changing the current window layout with a new
|
||||
:ref:`at_goto-layout` command (:iss:`845`)
|
||||
|
||||
- Remote control: Allow matching windows by the environment variables of their
|
||||
child process as well
|
||||
|
||||
- Allow running kittens via the remote control system (:iss:`738`)
|
||||
|
||||
- Allow enabling remote control in only some kitty windows
|
||||
|
||||
- Add a keyboard shortcut to reset the terminal (:sc:`reset_terminal`). It
|
||||
takes parameters so you can define your own shortcuts to clear the
|
||||
screen/scrollback also (:iss:`747`)
|
||||
|
||||
- Fix one-pixel line appearing at window edges at some window sizes when
|
||||
displaying images with background opacity enabled (:iss:`741`)
|
||||
|
||||
- diff kitten: Fix error when right hand side file is binary and left hand side
|
||||
file is text (:pull:`752`)
|
||||
|
||||
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
|
||||
to create top-level OS windows (:iss:`770`)
|
||||
|
||||
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
|
||||
(:iss:`754`)
|
||||
|
||||
- Fix detection of URLs in HTML source code (URLs inside quotes) (:iss:`785`)
|
||||
|
||||
- Implement support for emoji skin tone modifiers (:iss:`787`)
|
||||
|
||||
- Round-trip the zwj unicode character. Rendering of sequences containing zwj
|
||||
is still not implemented, since it can cause the collapse of an unbounded
|
||||
number of characters into a single cell. However, kitty at least preserves
|
||||
the zwj by storing it as a combining character.
|
||||
|
||||
- macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual
|
||||
GPU machines. I give up, Apple users will just have to live with the
|
||||
limitations of their choice of OS. (:iss:`794`)
|
||||
|
||||
- macOS: Fix control+tab key combination not working (:iss:`801`)
|
||||
|
||||
- Linux: Fix slow startup on some systems caused by GLFW searching for
|
||||
joysticks. Since kitty does not use joysticks, disable joystick support.
|
||||
(:iss:`830`)
|
||||
|
||||
|
||||
0.11.3 [2018-07-10]
|
||||
------------------------------
|
||||
|
||||
@@ -10,7 +82,7 @@ Changelog
|
||||
that separate the inactive window from a neighbor. Note that setting
|
||||
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||
The old behavior of drawing all borders can be restored via the
|
||||
:opt:`draw_minimal_borders` setting in kitty.conf.
|
||||
:opt:`draw_minimal_borders` setting in kitty.conf. (:iss:`699`)
|
||||
|
||||
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
|
||||
top-level windows are resizable using the mouse or not (:iss:`698`)
|
||||
@@ -295,7 +367,7 @@ Changelog
|
||||
|
||||
- When triple-clicking select all wrapped lines belonging to a single logical line.
|
||||
|
||||
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
|
||||
- hints kitten: Detect bracketed URLs and don't include the closing bracket in the URL.
|
||||
|
||||
- When calling pass_selection_to_program use the current directory of the child
|
||||
process as the cwd of the program.
|
||||
|
||||
@@ -372,9 +372,9 @@ Key Value Default Description
|
||||
**Keys for deleting images**
|
||||
-----------------------------------------------------------
|
||||
``d`` Single character. ``a`` What to delete.
|
||||
``(a, A, c, C, p,
|
||||
P, q, Q, x, X, y,
|
||||
Y, z, Z)``.
|
||||
``(a, A, c, C, i,
|
||||
I, p, P, q, Q, x, X,
|
||||
y, Y, z, Z)``.
|
||||
======= ==================== ========= =================
|
||||
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ Action Shortcut
|
||||
======================== =======================
|
||||
New tab :sc:`new_tab`
|
||||
Close tab :sc:`close_tab`
|
||||
Next tab :sc:`next_tab`
|
||||
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||
Previous tab :sc:`previous_tab`
|
||||
Next layout :sc:`next_layout`
|
||||
Move tab forward :sc:`move_tab_forward`
|
||||
@@ -165,6 +165,7 @@ Restore font size :sc:`reset_font_size`
|
||||
Toggle fullscreen :sc:`toggle_fullscreen`
|
||||
Input unicode character :sc:`input_unicode_character`
|
||||
Click URL using the keyboard :sc:`open_url`
|
||||
Reset the terminal :sc:`reset_terminal`
|
||||
Pass current selection to program :sc:`pass_selection_to_program`
|
||||
Edit |kitty| config file :sc:`edit_config_file`
|
||||
Open a |kitty| shell :sc:`kitty_shell`
|
||||
@@ -259,6 +260,15 @@ see the :doc:`configuration docs <conf>`.
|
||||
|
||||
.. _sessions:
|
||||
|
||||
Remote control
|
||||
------------------
|
||||
|
||||
|kitty| has a very powerful system that allows you to control it from the
|
||||
:doc:`shell prompt, even over SSH <remote-control>`. You can change colors,
|
||||
fonts, open new windows, tabs, set their titles, change window layout, get text
|
||||
from one window and send text to another, etc, etc. The possibilities are
|
||||
endless. See the :doc:`tutorial <remote-control>` to get started.
|
||||
|
||||
Startup Sessions
|
||||
------------------
|
||||
|
||||
@@ -303,6 +313,8 @@ Mouse features
|
||||
* You can also hold down :kbd:`ctrl+shift` and click on a URL to open it in a browser.
|
||||
* You can double click to select a word and triple click to select a line.
|
||||
* You can right click to extend a previous selection
|
||||
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
|
||||
columns
|
||||
|
||||
|
||||
Font control
|
||||
@@ -338,6 +350,39 @@ Frequently Asked Questions
|
||||
|
||||
The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
|
||||
|
||||
.. _completion:
|
||||
|
||||
Completion for kitty
|
||||
---------------------------------
|
||||
|
||||
|kitty| comes with completion for the ``kitty`` command for popular shells.
|
||||
|
||||
|
||||
bash
|
||||
~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.bashrc`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
source <(kitty + complete setup bash)
|
||||
|
||||
|
||||
zsh
|
||||
~~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.zshrc`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
# Completion for kitty
|
||||
kitty + complete setup zsh | source /dev/stdin
|
||||
|
||||
The important thing above is to make sure the call to |kitty| to load the zsh
|
||||
completions happens after the call to :file:`compinit`.
|
||||
|
||||
|
||||
Changelog
|
||||
------------------
|
||||
|
||||
@@ -18,5 +18,5 @@ typing a leading period and then the two character index and pressing Enter.
|
||||
|
||||
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
|
||||
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 dont like to use arrow
|
||||
a leading period and the index for the match if you don't like to use arrow
|
||||
keys.
|
||||
|
||||
@@ -184,3 +184,25 @@ changes even after it exits. To avoid this, kitty introduces a new pair of
|
||||
These escape codes save/restore the so called *dynamic colors*, default
|
||||
background, default foreground, selection background, selection foreground and
|
||||
cursor color.
|
||||
|
||||
|
||||
Pasting to clipboard
|
||||
----------------------
|
||||
|
||||
|kitty| implements the OSC 52 escape code protocol to get/set the clipboard
|
||||
contents (controlled via the :opt:`clipboard_control` setting). There is one
|
||||
difference in kitty's implementation compared to some other terminal emulators.
|
||||
|kitty| allows sending arbitrary amounts of text to the clipboard. It does so
|
||||
by modifying the protocol slightly. Successive OSC 52 escape codes to set the
|
||||
clipboard will concatenate, so::
|
||||
|
||||
<ESC>]52;c;<payload1><ESC>\
|
||||
<ESC>]52;c;<payload2><ESC>\
|
||||
|
||||
will result in the clipboard having the contents ``payload1 + payload2``. To
|
||||
send a new string to the clipboard send an OSC 52 sequence with an invalid payload
|
||||
first, for example::
|
||||
|
||||
<ESC>]52;c;!<ESC>\
|
||||
|
||||
Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
||||
|
||||
@@ -103,7 +103,7 @@ instance of |kitty| using the :option:`kitty @ --to` command line argument to ``
|
||||
kitty @ --to unix:/tmp/mykitty ls
|
||||
|
||||
|
||||
Note that is all you want to do is run a single |kitty| "daemon" and have subsequent
|
||||
Note that if all you want to do is run a single |kitty| "daemon" and have subsequent
|
||||
|kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance`
|
||||
option, see ``kitty --help`` for that.
|
||||
|
||||
@@ -116,7 +116,23 @@ shell with completion for |kitty| command names and options.
|
||||
|
||||
You can even open the |kitty| shell inside a running |kitty| using a simple
|
||||
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
|
||||
advantage that you dont need to use ``allow_remote_control`` to make it work.
|
||||
advantage that you don't need to use ``allow_remote_control`` to make it work.
|
||||
|
||||
|
||||
Allowing only some windows to control kitty
|
||||
----------------------------------------------
|
||||
|
||||
If you do not want to allow all programs running in |kitty| to control it, you can selectively
|
||||
enable remote control for only some |kitty| windows. Simply create a shortcut
|
||||
such as::
|
||||
|
||||
map ctrl+k new_window @ 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
|
||||
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.
|
||||
|
||||
|
||||
.. include:: generated/cli-kitty-at.rst
|
||||
|
||||
264
gen-apc-parsers.py
Executable file
264
gen-apc-parsers.py
Executable file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def resolve_keys(keymap):
|
||||
ans = defaultdict(list)
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
if atype not in ('int', 'uint'):
|
||||
atype = 'flag'
|
||||
ans[atype].append(ch)
|
||||
return ans
|
||||
|
||||
|
||||
def enum(keymap):
|
||||
lines = []
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
lines.append(f"{attr}='{ch}'")
|
||||
return '''
|
||||
enum KEYS {{
|
||||
{}
|
||||
}};
|
||||
'''.format(',\n'.join(lines))
|
||||
|
||||
|
||||
def parse_key(keymap):
|
||||
lines = []
|
||||
for attr, atype in keymap.values():
|
||||
vs = atype.upper() if atype in ('uint', 'int') else 'FLAG'
|
||||
lines.append(f'case {attr}: value_state = {vs}; break;')
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_flag(keymap, type_map, command_class):
|
||||
lines = []
|
||||
for ch in type_map['flag']:
|
||||
attr, allowed_values = keymap[ch]
|
||||
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
|
||||
lines.append(f'''
|
||||
case {attr}: {{
|
||||
g.{attr} = screen->parser_buf[pos++] & 0xff;
|
||||
if ({q}) {{
|
||||
REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
|
||||
return;
|
||||
}};
|
||||
}}
|
||||
break;
|
||||
''')
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_number(keymap):
|
||||
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
|
||||
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
|
||||
return '; '.join(int_keys), '; '.join(uint_keys)
|
||||
|
||||
|
||||
def cmd_for_report(report_name, keymap, type_map, payload_allowed):
|
||||
def group(atype, conv):
|
||||
flag_fmt, flag_attrs = [], []
|
||||
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
||||
for ch in type_map[atype]:
|
||||
flag_fmt.append('s' + cv)
|
||||
attr = keymap[ch][0]
|
||||
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
|
||||
return ' '.join(flag_fmt), ', '.join(flag_attrs)
|
||||
|
||||
flag_fmt, flag_attrs = group('flag', '')
|
||||
int_fmt, int_attrs = group('int', '(int)')
|
||||
uint_fmt, uint_attrs = group('uint', '(unsigned int)')
|
||||
|
||||
fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
|
||||
if payload_allowed:
|
||||
ans = [f'REPORT_VA_COMMAND("s {{{fmt} sI}} y#", "{report_name}",']
|
||||
else:
|
||||
ans = [f'REPORT_VA_COMMAND("s {{{fmt}}}", "{report_name}",']
|
||||
ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
|
||||
if payload_allowed:
|
||||
ans.append(', "payload_sz", g.payload_sz, payload, g.payload_sz')
|
||||
ans.append(');')
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True):
|
||||
type_map = resolve_keys(keymap)
|
||||
keys_enum = enum(keymap)
|
||||
handle_key = parse_key(keymap)
|
||||
flag_keys = parse_flag(keymap, type_map, command_class)
|
||||
int_keys, uint_keys = parse_number(keymap)
|
||||
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
|
||||
if payload_allowed:
|
||||
payload_after_value = "case ';': state = PAYLOAD; break;"
|
||||
payload = payload = ', PAYLOAD'
|
||||
parr = 'static uint8_t payload[4096];'
|
||||
payload_case = f'''
|
||||
case PAYLOAD: {{
|
||||
sz = screen->parser_buf_pos - pos;
|
||||
const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz);
|
||||
if (err != NULL) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", err); return; }}
|
||||
pos = screen->parser_buf_pos;
|
||||
}}
|
||||
break;
|
||||
'''
|
||||
callback = f'{callback_name}(screen, &g, payload)'
|
||||
else:
|
||||
payload_after_value = payload = parr = payload_case = ''
|
||||
callback = f'{callback_name}(screen, &g)'
|
||||
|
||||
return f'''
|
||||
static inline void
|
||||
{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{
|
||||
unsigned int pos = 1;
|
||||
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
|
||||
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||
static {command_class} g;
|
||||
unsigned int i, code;
|
||||
uint64_t lcode;
|
||||
bool is_negative;
|
||||
memset(&g, 0, sizeof(g));
|
||||
size_t sz;
|
||||
{parr}
|
||||
{keys_enum}
|
||||
enum KEYS key = '{initial_key}';
|
||||
|
||||
while (pos < screen->parser_buf_pos) {{
|
||||
switch(state) {{
|
||||
case KEY:
|
||||
key = screen->parser_buf[pos++];
|
||||
state = EQUAL;
|
||||
switch(key) {{
|
||||
{handle_key}
|
||||
default:
|
||||
REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
|
||||
return;
|
||||
}}
|
||||
break;
|
||||
|
||||
case EQUAL:
|
||||
if (screen->parser_buf[pos++] != '=') {{
|
||||
REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]);
|
||||
return;
|
||||
}}
|
||||
state = value_state;
|
||||
break;
|
||||
|
||||
case FLAG:
|
||||
switch(key) {{
|
||||
{flag_keys}
|
||||
default:
|
||||
break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
|
||||
case INT:
|
||||
#define READ_UINT \\
|
||||
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) {{ \\
|
||||
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \\
|
||||
}} \\
|
||||
if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
|
||||
lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \\
|
||||
if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
|
||||
code = lcode;
|
||||
|
||||
is_negative = false;
|
||||
if(screen->parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
|
||||
#define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
|
||||
READ_UINT;
|
||||
switch(key) {{
|
||||
{int_keys};
|
||||
default: break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef I
|
||||
case UINT:
|
||||
READ_UINT;
|
||||
#define U(x) case x: g.x = code; break
|
||||
switch(key) {{
|
||||
{uint_keys};
|
||||
default: break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef U
|
||||
#undef READ_UINT
|
||||
|
||||
case AFTER_VALUE:
|
||||
switch (screen->parser_buf[pos++]) {{
|
||||
default:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
|
||||
screen->parser_buf[pos - 1]);
|
||||
return;
|
||||
case ',':
|
||||
state = KEY;
|
||||
break;
|
||||
{payload_after_value}
|
||||
}}
|
||||
break;
|
||||
|
||||
{payload_case}
|
||||
|
||||
}} // end switch
|
||||
}} // end while
|
||||
|
||||
switch(state) {{
|
||||
case EQUAL:
|
||||
REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
|
||||
case INT:
|
||||
case UINT:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
|
||||
case FLAG:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
|
||||
default:
|
||||
break;
|
||||
}}
|
||||
|
||||
{report_cmd}
|
||||
|
||||
{callback};
|
||||
}}
|
||||
'''
|
||||
|
||||
|
||||
def write_header(text, path):
|
||||
with open(path, 'w') as f:
|
||||
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
|
||||
print('#pragma once', file=f)
|
||||
print(text, file=f)
|
||||
subprocess.check_call(['clang-format', '-i', path])
|
||||
|
||||
|
||||
def graphics_parser():
|
||||
flag = frozenset
|
||||
keymap = {
|
||||
'a': ('action', flag('tTqpd')),
|
||||
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
||||
't': ('transmission_type', flag('dfts')),
|
||||
'o': ('compressed', flag('z')),
|
||||
'f': ('format', 'uint'),
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('id', 'uint'),
|
||||
'w': ('width', 'uint'),
|
||||
'h': ('height', 'uint'),
|
||||
'x': ('x_offset', 'uint'),
|
||||
'y': ('y_offset', 'uint'),
|
||||
'v': ('data_height', 'uint'),
|
||||
's': ('data_width', 'uint'),
|
||||
'S': ('data_sz', 'uint'),
|
||||
'O': ('data_offset', 'uint'),
|
||||
'c': ('num_cells', 'uint'),
|
||||
'r': ('num_lines', 'uint'),
|
||||
'X': ('cell_x_offset', 'uint'),
|
||||
'Y': ('cell_y_offset', 'uint'),
|
||||
'z': ('z_index', 'int'),
|
||||
}
|
||||
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
||||
write_header(text, 'kitty/parse-graphics-command.h')
|
||||
|
||||
|
||||
graphics_parser()
|
||||
@@ -20,6 +20,7 @@ non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
|
||||
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
|
||||
if len(non_characters) != 66:
|
||||
raise SystemExit('non_characters table incorrect')
|
||||
emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1))
|
||||
|
||||
|
||||
def get_data(fname, folder='UCD'):
|
||||
@@ -41,7 +42,8 @@ def get_data(fname, folder='UCD'):
|
||||
class_maps = {}
|
||||
name_map = {}
|
||||
word_search_map = defaultdict(set)
|
||||
marks = set()
|
||||
zwj = 0x200d
|
||||
marks = set(emoji_skin_tone_modifiers) | {zwj}
|
||||
not_assigned = set(range(0, sys.maxunicode))
|
||||
|
||||
|
||||
@@ -195,12 +197,13 @@ def gen_emoji():
|
||||
p('\treturn false;\n}')
|
||||
|
||||
|
||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset()):
|
||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
|
||||
static = 'static inline ' if static else ''
|
||||
chars = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
chars |= extra_chars
|
||||
chars -= exclude
|
||||
p(f'{static}bool\n{name}(char_type code) {{')
|
||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
p('\tswitch(code) {')
|
||||
@@ -251,8 +254,16 @@ def gen_ucd():
|
||||
cz = {c for c in class_maps if c[0] in 'CZ'}
|
||||
with create_header('kitty/unicode-data.c') as p:
|
||||
p('#include "unicode-data.h"')
|
||||
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
|
||||
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters and non-characters', extra_chars=non_characters)
|
||||
category_test(
|
||||
'is_combining_char', p,
|
||||
{c for c in class_maps if c.startswith('M')},
|
||||
'M category (marks)',
|
||||
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||
extra_chars=emoji_skin_tone_modifiers | {zwj}
|
||||
)
|
||||
category_test(
|
||||
'is_ignored_char', p, 'Cc Cf Cs'.split(),
|
||||
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
|
||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||
@@ -265,7 +276,7 @@ def gen_ucd():
|
||||
p('combining_type mark_for_codepoint(char_type c) {')
|
||||
rmap = codepoint_to_mark_map(p, mark_map)
|
||||
p('}\n')
|
||||
if rmap[0xfe0e] != 1280:
|
||||
if rmap[0xfe0e] != 1281:
|
||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||
rmap[0xfe0e], rmap[0xfe0f]
|
||||
))
|
||||
|
||||
@@ -43,8 +43,6 @@ def export():
|
||||
switch = ['glad_debug_{0} = glad_{0}'.format(f) for f in functions]
|
||||
c = c.replace('<glad/glad.h>', '"gl-wrapper.h"', 1)
|
||||
c = '#pragma GCC diagnostic ignored "-Wpedantic"\n' + c
|
||||
# See https://github.com/kovidgoyal/kitty/issues/654
|
||||
c = c.replace('strncpy(local_str, gl_str_tmp, len+1)', 'memcpy(local_str, gl_str_tmp, len+1)', 1)
|
||||
c += '''
|
||||
int
|
||||
init_glad(GLADloadproc load, int debug) {
|
||||
|
||||
263
glfw/backend_utils.c
vendored
Normal file
263
glfw/backend_utils.c
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* backend_utils.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "backend_utils.h"
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <float.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define ppoll pollts
|
||||
#endif
|
||||
|
||||
static inline double
|
||||
monotonic() {
|
||||
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++) {
|
||||
Watch *w = eld->watches + i;
|
||||
eld->fds[i].fd = w->fd;
|
||||
eld->fds[i].events = w->enabled ? w->events : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static id_type watch_counter = 0;
|
||||
|
||||
id_type
|
||||
addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) {
|
||||
if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added");
|
||||
return 0;
|
||||
}
|
||||
Watch *w = eld->watches + eld->watches_count++;
|
||||
w->name = name;
|
||||
w->fd = fd; w->events = events; w->enabled = enabled;
|
||||
w->callback = cb;
|
||||
w->callback_data = cb_data;
|
||||
w->id = ++watch_counter;
|
||||
update_fds(eld);
|
||||
return w->id;
|
||||
}
|
||||
|
||||
#define removeX(which, item_id, update_func) {\
|
||||
for (nfds_t i = 0; i < eld->which##_count; i++) { \
|
||||
if (eld->which[i].id == item_id) { \
|
||||
eld->which##_count--; \
|
||||
if (i < eld->which##_count) { \
|
||||
memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \
|
||||
} \
|
||||
update_func(eld); break; \
|
||||
}}}
|
||||
|
||||
void
|
||||
removeWatch(EventLoopData *eld, id_type watch_id) {
|
||||
removeX(watches, watch_id, update_fds);
|
||||
}
|
||||
|
||||
void
|
||||
toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||
if (eld->watches[i].id == watch_id) {
|
||||
if (eld->watches[i].enabled != enabled) {
|
||||
eld->watches[i].enabled = enabled;
|
||||
update_fds(eld);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static id_type timer_counter = 0;
|
||||
|
||||
static int
|
||||
compare_timers(const void *a_, const void *b_) {
|
||||
const Timer *a = (const Timer*)a_, *b = (const Timer*)b_;
|
||||
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
update_timers(EventLoopData *eld) {
|
||||
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
|
||||
}
|
||||
|
||||
id_type
|
||||
addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data) {
|
||||
if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
|
||||
return 0;
|
||||
}
|
||||
Timer *t = eld->timers + eld->timers_count++;
|
||||
t->interval = interval;
|
||||
t->name = name;
|
||||
t->trigger_at = enabled ? monotonic() + interval : DBL_MAX;
|
||||
t->callback = cb;
|
||||
t->callback_data = cb_data;
|
||||
t->id = ++timer_counter;
|
||||
update_timers(eld);
|
||||
return t->id;
|
||||
}
|
||||
|
||||
void
|
||||
removeTimer(EventLoopData *eld, id_type timer_id) {
|
||||
removeX(timers, timer_id, update_timers);
|
||||
}
|
||||
|
||||
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;
|
||||
if (trigger_at != eld->timers[i].trigger_at) {
|
||||
eld->timers[i].trigger_at = trigger_at;
|
||||
update_timers(eld);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
|
||||
for (nfds_t i = 0; i < eld->timers_count; i++) {
|
||||
if (eld->timers[i].id == timer_id) {
|
||||
eld->timers[i].interval = interval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double
|
||||
prepareForPoll(EventLoopData *eld, double 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 (timeout < 0 || now + timeout > next_repeat_at) {
|
||||
timeout = next_repeat_at <= now ? 0 : next_repeat_at - now;
|
||||
}
|
||||
return 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 };
|
||||
return ppoll(fds, nfds, &tv, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchEvents(EventLoopData *eld) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||
Watch *ww = eld->watches + i;
|
||||
struct pollfd *pfd = eld->fds + i;
|
||||
if (pfd->revents & ww->events) {
|
||||
ww->ready = 1;
|
||||
if (ww->callback) ww->callback(ww->fd, pfd->revents, ww->callback_data);
|
||||
} else ww->ready = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned
|
||||
dispatchTimers(EventLoopData *eld) {
|
||||
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0;
|
||||
static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])];
|
||||
unsigned num_dispatches = 0;
|
||||
double now = monotonic();
|
||||
for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) {
|
||||
eld->timers[i].trigger_at = now + eld->timers[i].interval;
|
||||
dispatches[num_dispatches].func = eld->timers[i].callback;
|
||||
dispatches[num_dispatches].id = eld->timers[i].id;
|
||||
dispatches[num_dispatches].data = eld->timers[i].callback_data;
|
||||
num_dispatches++;
|
||||
}
|
||||
// we dispatch separately so that the callbacks can modify timers
|
||||
for (unsigned i = 0; i < num_dispatches; i++) {
|
||||
dispatches[i].func(dispatches[i].id, dispatches[i].data);
|
||||
}
|
||||
if (num_dispatches) update_timers(eld);
|
||||
return num_dispatches;
|
||||
}
|
||||
|
||||
static void
|
||||
drain_wakeup_fd(int fd, int events, void* data) {
|
||||
static char drain_buf[64];
|
||||
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
||||
}
|
||||
|
||||
void
|
||||
initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) {
|
||||
addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL);
|
||||
addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
pollForEvents(EventLoopData *eld, double timeout) {
|
||||
int read_ok = 0;
|
||||
timeout = prepareForPoll(eld, timeout);
|
||||
int result;
|
||||
double end_time = monotonic() + timeout;
|
||||
|
||||
while(1) {
|
||||
if (timeout >= 0) {
|
||||
errno = 0;
|
||||
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
|
||||
int saved_errno = errno;
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
read_ok = eld->watches[0].ready;
|
||||
break;
|
||||
}
|
||||
timeout = end_time - monotonic();
|
||||
if (timeout <= 0) break;
|
||||
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||
break;
|
||||
} else {
|
||||
errno = 0;
|
||||
result = poll(eld->fds, eld->watches_count, -1);
|
||||
int saved_errno = errno;
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
read_ok = eld->watches[0].ready;
|
||||
}
|
||||
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return read_ok;
|
||||
}
|
||||
|
||||
void
|
||||
closeFds(int *fds, size_t count) {
|
||||
while(count--) {
|
||||
if (*fds > 0) {
|
||||
close(*fds);
|
||||
*fds = -1;
|
||||
}
|
||||
fds++;
|
||||
}
|
||||
}
|
||||
71
glfw/backend_utils.h
vendored
71
glfw/backend_utils.h
vendored
@@ -25,42 +25,49 @@
|
||||
//========================================================================
|
||||
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define ppoll pollts
|
||||
#endif
|
||||
typedef unsigned long long id_type;
|
||||
typedef void(*watch_callback_func)(int, int, void*);
|
||||
typedef void(*timer_callback_func)(id_type, void*);
|
||||
|
||||
static inline void
|
||||
drainFd(int fd) {
|
||||
static char drain_buf[64];
|
||||
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
||||
}
|
||||
typedef struct {
|
||||
int fd, events, enabled, ready;
|
||||
watch_callback_func callback;
|
||||
void *callback_data;
|
||||
id_type id;
|
||||
const char *name;
|
||||
} Watch;
|
||||
|
||||
typedef struct {
|
||||
id_type id;
|
||||
double interval, trigger_at;
|
||||
timer_callback_func callback;
|
||||
void *callback_data;
|
||||
const char *name;
|
||||
} Timer;
|
||||
|
||||
|
||||
static inline 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 };
|
||||
return ppoll(fds, nfds, &tv, NULL);
|
||||
}
|
||||
typedef struct {
|
||||
struct pollfd fds[32];
|
||||
int wakeupFds[2];
|
||||
nfds_t watches_count, timers_count;
|
||||
Watch watches[32];
|
||||
Timer timers[128];
|
||||
} EventLoopData;
|
||||
|
||||
static inline void
|
||||
initPollData(struct pollfd *fds, int wakeup_fd, int display_fd) {
|
||||
fds[0].fd = wakeup_fd; fds[1].fd = display_fd;
|
||||
fds[0].events = POLLIN; fds[1].events = POLLIN;
|
||||
}
|
||||
|
||||
static inline void
|
||||
closeFds(int *fds, size_t count) {
|
||||
while(count--) {
|
||||
if (*fds > 0) {
|
||||
close(*fds);
|
||||
*fds = -1;
|
||||
}
|
||||
fds++;
|
||||
}
|
||||
}
|
||||
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, timer_callback_func cb, void *cb_data);
|
||||
void removeTimer(EventLoopData *eld, id_type timer_id);
|
||||
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);
|
||||
unsigned dispatchTimers(EventLoopData *eld);
|
||||
void closeFds(int *fds, size_t count);
|
||||
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
||||
|
||||
@@ -739,7 +739,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
}
|
||||
|
||||
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
|
||||
NSTrackingActiveInKeyWindow |
|
||||
NSTrackingActiveAlways |
|
||||
NSTrackingEnabledDuringMouseDrag |
|
||||
NSTrackingCursorUpdate |
|
||||
NSTrackingInVisibleRect |
|
||||
@@ -845,7 +845,7 @@ is_ascii_control_char(char x) {
|
||||
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
|
||||
scancode, safe_name_for_scancode(scancode), format_mods(mods),
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
@@ -884,15 +884,10 @@ is_ascii_control_char(char x) {
|
||||
|
||||
deltaX = [event scrollingDeltaX];
|
||||
deltaY = [event scrollingDeltaY];
|
||||
|
||||
if ([event hasPreciseScrollingDeltas])
|
||||
{
|
||||
deltaX *= 0.1;
|
||||
deltaY *= 0.1;
|
||||
}
|
||||
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
||||
|
||||
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
|
||||
_glfwInputScroll(window, deltaX, deltaY);
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
@@ -1061,18 +1056,39 @@ is_ascii_control_char(char x) {
|
||||
|
||||
@implementation GLFWApplication
|
||||
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
if ([event type] == NSEventTypeKeyUp &&
|
||||
([event modifierFlags] & NSEventModifierFlagCommand))
|
||||
{
|
||||
[[self keyWindow] sendEvent:event];
|
||||
NSEventType etype = [event type];
|
||||
NSEventModifierFlags flags;
|
||||
switch(etype) {
|
||||
case NSEventTypeKeyUp:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (flags & NSEventModifierFlagCommand) {
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
[[self keyWindow] sendEvent:event];
|
||||
return;
|
||||
}
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyUp:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NSEventTypeKeyDown:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyDown:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
else
|
||||
[super sendEvent:event];
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
|
||||
@@ -1083,7 +1099,7 @@ is_ascii_control_char(char x) {
|
||||
}
|
||||
|
||||
- (void)loadMainMenu
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
295
glfw/dbus_glfw.c
vendored
Normal file
295
glfw/dbus_glfw.c
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#include "internal.h"
|
||||
#include "dbus_glfw.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline void
|
||||
report_error(DBusError *err, const char *fmt, ...) {
|
||||
static char buf[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int n = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
|
||||
dbus_error_free(err);
|
||||
}
|
||||
|
||||
static _GLFWDBUSData *dbus_data = NULL;
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
|
||||
dbus->eld = eld;
|
||||
dbus_data = dbus;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_watch_ready(int fd, int events, void *data) {
|
||||
DBusWatch *watch = (DBusWatch*)data;
|
||||
unsigned int flags = 0;
|
||||
if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
|
||||
if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
|
||||
if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
|
||||
if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
|
||||
dbus_watch_handle(watch, flags);
|
||||
}
|
||||
|
||||
static inline int
|
||||
events_for_watch(DBusWatch *watch) {
|
||||
int events = 0;
|
||||
unsigned int flags = dbus_watch_get_flags(watch);
|
||||
if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
|
||||
if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
|
||||
return events;
|
||||
}
|
||||
|
||||
static dbus_bool_t
|
||||
add_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch);
|
||||
if (!watch_id) return FALSE;
|
||||
id_type *idp = malloc(sizeof(id_type));
|
||||
if (!idp) return FALSE;
|
||||
*idp = watch_id;
|
||||
dbus_watch_set_data(watch, idp, free);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type *idp = dbus_watch_get_data(watch);
|
||||
if (idp) removeWatch(dbus_data->eld, *idp);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type *idp = dbus_watch_get_data(watch);
|
||||
if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_timer_ready(id_type timer_id, void *data) {
|
||||
if (data) {
|
||||
DBusTimeout *t = (DBusTimeout*)data;
|
||||
dbus_timeout_handle(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static dbus_bool_t
|
||||
add_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
|
||||
double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.0;
|
||||
if (interval < 0) return FALSE;
|
||||
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, on_dbus_timer_ready, timeout);
|
||||
if (!timer_id) return FALSE;
|
||||
id_type *idp = malloc(sizeof(id_type));
|
||||
if (!idp) {
|
||||
removeTimer(dbus_data->eld, timer_id);
|
||||
return FALSE;
|
||||
}
|
||||
*idp = timer_id;
|
||||
dbus_timeout_set_data(timeout, idp, free);
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
remove_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
id_type *idp = dbus_timeout_get_data(timeout);
|
||||
if (idp) removeTimer(dbus_data->eld, *idp);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
id_type *idp = dbus_timeout_get_data(timeout);
|
||||
if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
|
||||
}
|
||||
|
||||
|
||||
DBusConnection*
|
||||
glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, GLFWbool register_on_bus) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
DBusConnection *ans = dbus_connection_open_private(path, &err);
|
||||
if (!ans) {
|
||||
report_error(&err, err_msg);
|
||||
return NULL;
|
||||
}
|
||||
dbus_connection_set_exit_on_disconnect(ans, FALSE);
|
||||
dbus_error_free(&err);
|
||||
if (register_on_bus) {
|
||||
if (!dbus_bus_register(ans, &err)) {
|
||||
report_error(&err, err_msg);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
|
||||
dbus_connection_close(ans);
|
||||
dbus_connection_unref(ans);
|
||||
return NULL;
|
||||
}
|
||||
if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
|
||||
dbus_connection_close(ans);
|
||||
dbus_connection_unref(ans);
|
||||
return NULL;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_dispatch(DBusConnection *conn) {
|
||||
while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_terminate(_GLFWDBUSData *dbus) {
|
||||
if (dbus_data) {
|
||||
dbus_data->eld = NULL;
|
||||
dbus_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_close_connection(DBusConnection *conn) {
|
||||
dbus_connection_close(conn);
|
||||
dbus_connection_unref(conn);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
va_list ap;
|
||||
va_start(ap, failmsg);
|
||||
int firstarg = va_arg(ap, int);
|
||||
GLFWbool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? GLFW_TRUE : GLFW_FALSE;
|
||||
va_end(ap);
|
||||
if (!ret) report_error(&err, failmsg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
dbus_pending_callback callback;
|
||||
void *user_data;
|
||||
} MethodResponse;
|
||||
|
||||
static const char*
|
||||
format_message_error(DBusError *err) {
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
method_reply_received(DBusPendingCall *pending, void *user_data) {
|
||||
MethodResponse *res = (MethodResponse*)user_data;
|
||||
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
|
||||
if (msg) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
|
||||
else res->callback(msg, NULL, res->user_data);
|
||||
dbus_message_unref(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static GLFWbool
|
||||
call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) {
|
||||
if (!conn) return GLFW_FALSE;
|
||||
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
|
||||
if (!msg) return GLFW_FALSE;
|
||||
GLFWbool retval = GLFW_FALSE;
|
||||
MethodResponse *res = malloc(sizeof(MethodResponse));
|
||||
if (!res) { dbus_message_unref(msg); return GLFW_FALSE; }
|
||||
res->callback = callback;
|
||||
res->user_data = user_data;
|
||||
|
||||
int firstarg = va_arg(ap, int);
|
||||
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
|
||||
if (callback) {
|
||||
DBusPendingCall *pending = NULL;
|
||||
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
|
||||
dbus_pending_call_set_notify(pending, method_reply_received, res, free);
|
||||
retval = GLFW_TRUE;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||
}
|
||||
} else {
|
||||
if (dbus_connection_send(conn, msg, NULL)) {
|
||||
retval = GLFW_TRUE;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
|
||||
}
|
||||
dbus_message_unref(msg);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
|
||||
GLFWbool retval;
|
||||
va_list ap;
|
||||
va_start(ap, user_data);
|
||||
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
|
||||
va_end(ap);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
|
||||
GLFWbool retval;
|
||||
va_list ap;
|
||||
va_start(ap, method);
|
||||
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
|
||||
va_end(ap);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, interface);
|
||||
int ans = -1, num = -1;
|
||||
while(1) {
|
||||
num++;
|
||||
const char *name = va_arg(ap, const char*);
|
||||
if (!name) break;
|
||||
if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
|
||||
}
|
||||
va_end(ap);
|
||||
return ans;
|
||||
}
|
||||
50
glfw/dbus_glfw.h
vendored
Normal file
50
glfw/dbus_glfw.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include "backend_utils.h"
|
||||
|
||||
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
|
||||
|
||||
typedef struct {
|
||||
EventLoopData* eld;
|
||||
} _GLFWDBUSData;
|
||||
|
||||
|
||||
GLFWbool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld);
|
||||
void glfw_dbus_terminate(_GLFWDBUSData *dbus);
|
||||
DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, GLFWbool register_on_bus);
|
||||
void glfw_dbus_close_connection(DBusConnection *conn);
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...);
|
||||
void glfw_dbus_dispatch(DBusConnection *);
|
||||
GLFWbool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);
|
||||
int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...);
|
||||
30
glfw/glfw.py
30
glfw/glfw.py
@@ -55,7 +55,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
at_least_version('xkbcommon', 0, 5)
|
||||
|
||||
if module == 'x11':
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
|
||||
@@ -73,7 +73,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
for p in ans.wayland_protocols:
|
||||
ans.sources.append(wayland_protocol_file_name(p))
|
||||
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon'.split():
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
|
||||
@@ -265,8 +265,8 @@ unload_glfw() {
|
||||
f.write(code)
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(sys.argv[-1])
|
||||
def from_glfw(glfw_dir):
|
||||
os.chdir(glfw_dir)
|
||||
sinfo = collect_source_information()
|
||||
files_to_copy = set()
|
||||
for x in sinfo.values():
|
||||
@@ -283,7 +283,6 @@ def main():
|
||||
for src in files_to_copy:
|
||||
shutil.copy2(src, '.')
|
||||
shutil.copy2(glfw_header, '.')
|
||||
patch_in_file('internal.h', lambda x: x.replace('../include/GLFW/', ''))
|
||||
patch_in_file('cocoa_window.m', lambda x: re.sub(
|
||||
r'[(]void[)]loadMainMenu.+?}', '(void)loadMainMenu\n{ // removed by Kovid as it generated compiler warnings \n}\n', x, flags=re.DOTALL))
|
||||
json.dump(
|
||||
@@ -296,5 +295,26 @@ def main():
|
||||
generate_wrappers(glfw_header, glfw_native_header)
|
||||
|
||||
|
||||
def to_glfw(glfw_dir):
|
||||
src = base
|
||||
for x in os.listdir(src):
|
||||
if x in ('glfw.py', 'glfw3.h', '__pycache__', 'source-info.json') or x.startswith('wayland-'):
|
||||
continue
|
||||
xp = os.path.join(src, x)
|
||||
shutil.copyfile(xp, os.path.join(glfw_dir, 'src', x))
|
||||
shutil.copyfile(os.path.join(src, 'glfw3.h'), os.path.join(glfw_dir, 'include/GLFW/glfw3.h'))
|
||||
|
||||
|
||||
def main():
|
||||
glfw_dir = os.path.abspath(os.path.join(base, '../../glfw'))
|
||||
q = sys.argv[1].lower().replace('_', '-')
|
||||
if q == 'from-glfw':
|
||||
from_glfw(glfw_dir)
|
||||
elif q == 'to-glfw':
|
||||
to_glfw(glfw_dir)
|
||||
else:
|
||||
raise SystemExit('First argument must be one of to-glfw or from-glfw')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
32
glfw/glfw3.h
vendored
32
glfw/glfw3.h
vendored
@@ -1058,6 +1058,7 @@ extern "C" {
|
||||
* @{ */
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
@@ -1372,15 +1373,17 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* @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.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
*
|
||||
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
||||
* @since Changed in version 4.0. Added `flags` parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
|
||||
/*! @brief The function signature for key callbacks.
|
||||
*
|
||||
@@ -1399,7 +1402,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
* @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] reserved Reserved for future use.
|
||||
* @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.
|
||||
*
|
||||
* @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
|
||||
@@ -4007,8 +4012,8 @@ GLFWAPI int glfwGetKey(GLFWwindow* window, int key);
|
||||
* `GLFW_RELEASE`.
|
||||
*
|
||||
* If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function
|
||||
* `GLFW_PRESS` the first time you call it for a mouse button that was pressed,
|
||||
* even if that mouse button has already been released.
|
||||
* returns `GLFW_PRESS` the first time you call it for a mouse button that was
|
||||
* pressed, even if that mouse button has already been released.
|
||||
*
|
||||
* @param[in] window The desired window.
|
||||
* @param[in] button The desired [mouse button](@ref buttons).
|
||||
@@ -4228,6 +4233,23 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
|
||||
*/
|
||||
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
|
||||
|
||||
/*! @brief Notifies the OS Input Method Event system of changes to application input state
|
||||
*
|
||||
* Used to notify the IME system of changes in state such as focus gained/lost
|
||||
* and text cursor position.
|
||||
*
|
||||
* @param which: What data to notify. 1 means focus and 2 means cursor position.
|
||||
* @param a, b, c, d: Interpreted based on the value of which. When which is 1
|
||||
* a is interpreted as a boolean indicating focus gained/lost. When which is 2
|
||||
* a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate
|
||||
* system).
|
||||
*
|
||||
* @ingroup input
|
||||
* @since Added in version 4.0
|
||||
*/
|
||||
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d);
|
||||
|
||||
|
||||
/*! @brief Sets the mouse button callback.
|
||||
*
|
||||
* This function sets the mouse button callback of the specified window, which
|
||||
@@ -4788,6 +4810,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref gamepad
|
||||
* @sa @ref glfwUpdateGamepadMappings
|
||||
* @sa @ref glfwJoystickIsGamepad
|
||||
|
||||
427
glfw/ibus_glfw.c
vendored
Normal file
427
glfw/ibus_glfw.c
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "ibus_glfw.h"
|
||||
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
|
||||
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
|
||||
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
|
||||
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
|
||||
enum Capabilities {
|
||||
IBUS_CAP_PREEDIT_TEXT = 1 << 0,
|
||||
IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
|
||||
IBUS_CAP_LOOKUP_TABLE = 1 << 2,
|
||||
IBUS_CAP_FOCUS = 1 << 3,
|
||||
IBUS_CAP_PROPERTY = 1 << 4,
|
||||
IBUS_CAP_SURROUNDING_TEXT = 1 << 5
|
||||
};
|
||||
|
||||
|
||||
static inline GLFWbool
|
||||
test_env_var(const char *name, const char *val) {
|
||||
const char *q = getenv(name);
|
||||
return (q && strcmp(q, val) == 0) ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
static inline GLFWbool
|
||||
MIN(size_t a, size_t b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
static const char*
|
||||
get_ibus_text_from_message(DBusMessage *msg) {
|
||||
/* The message structure is (from dbus-monitor)
|
||||
variant struct {
|
||||
string "IBusText"
|
||||
array [
|
||||
]
|
||||
string "ash "
|
||||
variant struct {
|
||||
string "IBusAttrList"
|
||||
array [
|
||||
]
|
||||
array [
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
const char *text = NULL;
|
||||
const char *struct_id = NULL;
|
||||
DBusMessageIter iter, sub1, sub2;
|
||||
dbus_message_iter_init(msg, &iter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return NULL;
|
||||
|
||||
dbus_message_iter_recurse(&iter, &sub1);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) return NULL;
|
||||
|
||||
dbus_message_iter_recurse(&sub1, &sub2);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||
|
||||
dbus_message_iter_get_basic(&sub2, &struct_id);
|
||||
if (!struct_id || strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) return NULL;
|
||||
|
||||
dbus_message_iter_next(&sub2);
|
||||
dbus_message_iter_next(&sub2);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||
|
||||
dbus_message_iter_get_basic(&sub2, &text);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static inline void
|
||||
send_text(const char *text, int state) {
|
||||
_GLFWwindow *w = _glfwFocusedWindow();
|
||||
if (w && w->callbacks.keyboard) {
|
||||
w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Connection handling {{{
|
||||
|
||||
static DBusHandlerResult
|
||||
message_handler(DBusConnection *conn, DBusMessage *msg, void *user_data) {
|
||||
// To monitor signals from IBUS, use
|
||||
// dbus-monitor --address `ibus address` "type='signal',interface='org.freedesktop.IBus.InputContext'"
|
||||
_GLFWIBUSData *ibus = (_GLFWIBUSData*)user_data;
|
||||
(void)ibus;
|
||||
const char *text;
|
||||
switch(glfw_dbus_match_signal(msg, IBUS_INPUT_INTERFACE, "CommitText", "UpdatePreeditText", "HidePreeditText", "ShowPreeditText", NULL)) {
|
||||
case 0:
|
||||
text = get_ibus_text_from_message(msg);
|
||||
debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)");
|
||||
send_text(text, 2);
|
||||
break;
|
||||
case 1:
|
||||
text = get_ibus_text_from_message(msg);
|
||||
send_text(text, 1);
|
||||
debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)");
|
||||
break;
|
||||
case 2:
|
||||
debug("IBUS: HidePreeditText\n");
|
||||
break;
|
||||
case 3:
|
||||
debug("IBUS: ShowPreeditText\n");
|
||||
break;
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
get_ibus_address_file_name(void) {
|
||||
const char *addr;
|
||||
static char ans[PATH_MAX];
|
||||
addr = getenv("IBUS_ADDRESS");
|
||||
int offset = 0;
|
||||
if (addr && addr[0]) {
|
||||
memcpy(ans, addr, MIN(strlen(addr), sizeof(ans)));
|
||||
return ans;
|
||||
}
|
||||
|
||||
const char *de = getenv("DISPLAY");
|
||||
if (!de || !de[0]) de = ":0.0";
|
||||
char *display = _glfw_strdup(de);
|
||||
const char *host = display;
|
||||
char *disp_num = strrchr(display, ':');
|
||||
char *screen_num = strrchr(display, '.');
|
||||
|
||||
if (!disp_num) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
|
||||
free(display);
|
||||
return NULL;
|
||||
}
|
||||
*disp_num = 0;
|
||||
disp_num++;
|
||||
if (screen_num) *screen_num = 0;
|
||||
if (!*host) host = "unix";
|
||||
|
||||
memset(ans, 0, sizeof(ans));
|
||||
const char *conf_env = getenv("XDG_CONFIG_HOME");
|
||||
if (conf_env && conf_env[0]) {
|
||||
offset = snprintf(ans, sizeof(ans), "%s", conf_env);
|
||||
} else {
|
||||
conf_env = getenv("HOME");
|
||||
if (!conf_env || !conf_env[0]) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
|
||||
free(display);
|
||||
return NULL;
|
||||
}
|
||||
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
|
||||
}
|
||||
char *key = dbus_get_local_machine_id();
|
||||
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
|
||||
dbus_free(key);
|
||||
free(display);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static inline GLFWbool
|
||||
read_ibus_address(_GLFWIBUSData *ibus) {
|
||||
static char buf[1024];
|
||||
struct stat s;
|
||||
FILE *addr_file = fopen(ibus->address_file_name, "r");
|
||||
if (!addr_file) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to open IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
int stat_result = fstat(fileno(addr_file), &s);
|
||||
GLFWbool found = GLFW_FALSE;
|
||||
while (fgets(buf, sizeof(buf), addr_file)) {
|
||||
if (strncmp(buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
|
||||
size_t sz = strlen(buf);
|
||||
if (buf[sz-1] == '\n') buf[sz-1] = 0;
|
||||
if (buf[sz-2] == '\r') buf[sz-2] = 0;
|
||||
found = GLFW_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(addr_file); addr_file = NULL;
|
||||
if (stat_result != 0) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to stat IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
ibus->address_file_mtime = s.st_mtime;
|
||||
if (found) {
|
||||
free((void*)ibus->address);
|
||||
ibus->address = _glfw_strdup(buf + sizeof("IBUS_ADDRESS=") - 1);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not find IBUS_ADDRESS in %s", ibus->address_file_name);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
if (errmsg) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
|
||||
return;
|
||||
}
|
||||
const char *path = NULL;
|
||||
if (!glfw_dbus_get_args(msg, "Failed to get IBUS context path from reply", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return;
|
||||
_GLFWIBUSData *ibus = (_GLFWIBUSData*)data;
|
||||
free((void*)ibus->input_ctx_path);
|
||||
ibus->input_ctx_path = _glfw_strdup(path);
|
||||
if (!ibus->input_ctx_path) return;
|
||||
dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
|
||||
DBusObjectPathVTable ibus_vtable = {.message_function = message_handler};
|
||||
dbus_connection_try_register_object_path(ibus->conn, ibus->input_ctx_path, &ibus_vtable, ibus, NULL);
|
||||
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
||||
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
||||
ibus->ok = GLFW_TRUE;
|
||||
glfw_ibus_set_focused(ibus, GLFW_FALSE);
|
||||
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
||||
debug("Connected to IBUS daemon for IME input management\n");
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
setup_connection(_GLFWIBUSData *ibus) {
|
||||
const char *client_name = "GLFW_Application";
|
||||
const char *address_file_name = get_ibus_address_file_name();
|
||||
ibus->ok = GLFW_FALSE;
|
||||
if (!address_file_name) return GLFW_FALSE;
|
||||
free((void*)ibus->address_file_name);
|
||||
ibus->address_file_name = _glfw_strdup(address_file_name);
|
||||
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||
if (ibus->conn) {
|
||||
glfw_dbus_close_connection(ibus->conn);
|
||||
ibus->conn = NULL;
|
||||
}
|
||||
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
|
||||
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", GLFW_FALSE);
|
||||
if (!ibus->conn) return GLFW_FALSE;
|
||||
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
|
||||
if (!glfw_dbus_call_method_with_reply(
|
||||
ibus->conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext", DBUS_TIMEOUT_USE_DEFAULT, input_context_created, ibus,
|
||||
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) {
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||
if (ibus->inited) return;
|
||||
if (!test_env_var("XMODIFIERS", "@im=ibus") && !test_env_var("GTK_IM_MODULE", "ibus") && !test_env_var("QT_IM_MODULE", "ibus") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
if (getenv("GLFW_IM_MODULE") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
ibus->inited = GLFW_TRUE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_terminate(_GLFWIBUSData *ibus) {
|
||||
if (ibus->conn) {
|
||||
glfw_dbus_close_connection(ibus->conn);
|
||||
ibus->conn = NULL;
|
||||
}
|
||||
#define F(x) if (ibus->x) { free((void*)ibus->x); ibus->x = NULL; }
|
||||
F(input_ctx_path);
|
||||
F(address);
|
||||
F(address_file_name);
|
||||
#undef F
|
||||
|
||||
ibus->ok = GLFW_FALSE;
|
||||
}
|
||||
|
||||
static GLFWbool
|
||||
check_connection(_GLFWIBUSData *ibus) {
|
||||
if (!ibus->inited) return GLFW_FALSE;
|
||||
if (ibus->conn && dbus_connection_get_is_connected(ibus->conn)) {
|
||||
return ibus->ok;
|
||||
}
|
||||
struct stat s;
|
||||
if (stat(ibus->address_file_name, &s) != 0 || s.st_mtime != ibus->address_file_mtime) {
|
||||
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
glfw_ibus_dispatch(_GLFWIBUSData *ibus) {
|
||||
if (ibus->conn) glfw_dbus_dispatch(ibus->conn);
|
||||
}
|
||||
// }}}
|
||||
|
||||
static void
|
||||
simple_message(_GLFWIBUSData *ibus, const char *method) {
|
||||
if (check_connection(ibus)) {
|
||||
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused) {
|
||||
simple_message(ibus, focused ? "FocusIn" : "FocusOut");
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
|
||||
if (check_connection(ibus)) {
|
||||
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
|
||||
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IBUS_SHIFT_MASK = 1 << 0,
|
||||
IBUS_LOCK_MASK = 1 << 1,
|
||||
IBUS_CONTROL_MASK = 1 << 2,
|
||||
IBUS_MOD1_MASK = 1 << 3,
|
||||
IBUS_MOD2_MASK = 1 << 4,
|
||||
IBUS_MOD3_MASK = 1 << 5,
|
||||
IBUS_MOD4_MASK = 1 << 6,
|
||||
IBUS_MOD5_MASK = 1 << 7,
|
||||
IBUS_BUTTON1_MASK = 1 << 8,
|
||||
IBUS_BUTTON2_MASK = 1 << 9,
|
||||
IBUS_BUTTON3_MASK = 1 << 10,
|
||||
IBUS_BUTTON4_MASK = 1 << 11,
|
||||
IBUS_BUTTON5_MASK = 1 << 12,
|
||||
|
||||
/* The next few modifiers are used by XKB, so we skip to the end.
|
||||
* Bits 15 - 23 are currently unused. Bit 29 is used internally.
|
||||
*/
|
||||
|
||||
/* ibus mask */
|
||||
IBUS_HANDLED_MASK = 1 << 24,
|
||||
IBUS_FORWARD_MASK = 1 << 25,
|
||||
IBUS_IGNORED_MASK = IBUS_FORWARD_MASK,
|
||||
|
||||
IBUS_SUPER_MASK = 1 << 26,
|
||||
IBUS_HYPER_MASK = 1 << 27,
|
||||
IBUS_META_MASK = 1 << 28,
|
||||
|
||||
IBUS_RELEASE_MASK = 1 << 30,
|
||||
|
||||
IBUS_MODIFIER_MASK = 0x5f001fff
|
||||
} IBusModifierType;
|
||||
|
||||
|
||||
static inline uint32_t
|
||||
ibus_key_state(unsigned int glfw_modifiers, int action) {
|
||||
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
|
||||
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i
|
||||
M(SHIFT, IBUS_SHIFT_MASK);
|
||||
M(CAPS_LOCK, IBUS_LOCK_MASK);
|
||||
M(CONTROL, IBUS_CONTROL_MASK);
|
||||
M(ALT, IBUS_MOD1_MASK);
|
||||
M(NUM_LOCK, IBUS_MOD2_MASK);
|
||||
M(SUPER, IBUS_MOD4_MASK);
|
||||
#undef M
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
uint32_t handled = 0;
|
||||
KeyEvent *ev = (KeyEvent*)data;
|
||||
GLFWbool is_release = ev->action == GLFW_RELEASE;
|
||||
GLFWbool failed = GLFW_FALSE;
|
||||
if (errmsg) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
|
||||
failed = GLFW_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);
|
||||
}
|
||||
glfw_xkb_key_from_ime(ev, handled ? GLFW_TRUE : GLFW_FALSE, failed);
|
||||
free(ev);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus) {
|
||||
if (!check_connection(ibus)) return GLFW_FALSE;
|
||||
KeyEvent *ev = malloc(sizeof(KeyEvent));
|
||||
if (!ev) return GLFW_FALSE;
|
||||
memcpy(ev, ev_, sizeof(KeyEvent));
|
||||
uint32_t state = ibus_key_state(ev->glfw_modifiers, 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,
|
||||
&state, DBUS_TYPE_INVALID)) {
|
||||
free(ev);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
55
glfw/ibus_glfw.h
vendored
Normal file
55
glfw/ibus_glfw.h
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dbus_glfw.h"
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
typedef struct {
|
||||
GLFWbool ok, inited;
|
||||
time_t address_file_mtime;
|
||||
DBusConnection *conn;
|
||||
const char *input_ctx_path, *address_file_name, *address;
|
||||
} _GLFWIBUSData;
|
||||
|
||||
typedef struct {
|
||||
xkb_keycode_t keycode, ibus_keycode;
|
||||
xkb_keysym_t keysym, ibus_sym;
|
||||
unsigned int glfw_modifiers;
|
||||
int action;
|
||||
GLFWid window_id;
|
||||
int glfw_keycode;
|
||||
char text[64];
|
||||
} KeyEvent;
|
||||
|
||||
void glfw_connect_to_ibus(_GLFWIBUSData *ibus);
|
||||
void glfw_ibus_terminate(_GLFWIBUSData *ibus);
|
||||
void glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused);
|
||||
void glfw_ibus_dispatch(_GLFWIBUSData *ibus);
|
||||
GLFWbool ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus);
|
||||
void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h);
|
||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -52,6 +52,7 @@ static _GLFWinitconfig _glfwInitHints =
|
||||
{
|
||||
GLFW_TRUE, // hat buttons
|
||||
GLFW_FALSE, // debug keyboard
|
||||
GLFW_TRUE, // enable joystick
|
||||
{
|
||||
GLFW_TRUE, // macOS menu bar
|
||||
GLFW_TRUE // macOS bundle chdir
|
||||
@@ -115,8 +116,9 @@ static void terminate(void)
|
||||
char* _glfw_strdup(const char* source)
|
||||
{
|
||||
const size_t length = strlen(source);
|
||||
char* result = calloc(length + 1, 1);
|
||||
strcpy(result, source);
|
||||
char* result = malloc(length + 1);
|
||||
memcpy(result, source, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -253,6 +255,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
||||
{
|
||||
switch (hint)
|
||||
{
|
||||
case GLFW_ENABLE_JOYSTICKS:
|
||||
_glfwInitHints.enableJoysticks = value;
|
||||
return;
|
||||
case GLFW_JOYSTICK_HAT_BUTTONS:
|
||||
_glfwInitHints.hatButtons = value;
|
||||
return;
|
||||
|
||||
16
glfw/input.c
vendored
16
glfw/input.c
vendored
@@ -286,10 +286,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action,
|
||||
|
||||
// Notifies shared code of a scroll event
|
||||
//
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
|
||||
{
|
||||
if (window->callbacks.scroll)
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
|
||||
}
|
||||
|
||||
// Notifies shared code of a mouse button click event
|
||||
@@ -909,6 +909,18 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
_glfwPlatformUpdateIMEState(window, which, a, b, c, d);
|
||||
#else
|
||||
(void)window; (void)which; (void)a; (void)b; (void)c; (void)d;
|
||||
#endif
|
||||
}
|
||||
|
||||
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
|
||||
GLFWmousebuttonfun cbfun)
|
||||
{
|
||||
|
||||
9
glfw/internal.h
vendored
9
glfw/internal.h
vendored
@@ -59,6 +59,7 @@
|
||||
#define _GLFW_MESSAGE_SIZE 1024
|
||||
|
||||
typedef int GLFWbool;
|
||||
typedef unsigned long long GLFWid;
|
||||
|
||||
typedef struct _GLFWerror _GLFWerror;
|
||||
typedef struct _GLFWinitconfig _GLFWinitconfig;
|
||||
@@ -243,6 +244,7 @@ struct _GLFWinitconfig
|
||||
{
|
||||
GLFWbool hatButtons;
|
||||
GLFWbool debugKeyboard;
|
||||
GLFWbool enableJoysticks;
|
||||
struct {
|
||||
GLFWbool menubar;
|
||||
GLFWbool chdir;
|
||||
@@ -377,6 +379,7 @@ struct _GLFWwindow
|
||||
GLFWbool focusOnShow;
|
||||
GLFWbool shouldClose;
|
||||
void* userPointer;
|
||||
GLFWid id;
|
||||
GLFWvidmode videoMode;
|
||||
_GLFWmonitor* monitor;
|
||||
_GLFWcursor* cursor;
|
||||
@@ -525,6 +528,7 @@ struct _GLFWlibrary
|
||||
_GLFWerror* errorListHead;
|
||||
_GLFWcursor* cursorListHead;
|
||||
_GLFWwindow* windowListHead;
|
||||
GLFWid focusedWindowId;
|
||||
|
||||
_GLFWmonitor** monitors;
|
||||
int monitorCount;
|
||||
@@ -670,6 +674,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
||||
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
||||
|
||||
void _glfwPlatformPollEvents(void);
|
||||
void _glfwPlatformWaitEvents(void);
|
||||
@@ -713,7 +718,7 @@ 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 _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
|
||||
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);
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
|
||||
@@ -766,5 +771,7 @@ const char* _glfwGetKeyName(int key);
|
||||
GLFWbool _glfwInitVulkan(int mode);
|
||||
void _glfwTerminateVulkan(void);
|
||||
const char* _glfwGetVulkanResultString(VkResult result);
|
||||
_GLFWwindow* _glfwFocusedWindow();
|
||||
_GLFWwindow* _glfwWindowForId(GLFWid id);
|
||||
|
||||
char* _glfw_strdup(const char* source);
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"xkb_glfw.h",
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
"backend_utils.h",
|
||||
"egl_context.h",
|
||||
"osmesa_context.h",
|
||||
@@ -78,8 +80,11 @@
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
"ibus_glfw.c",
|
||||
"egl_context.c",
|
||||
"osmesa_context.c",
|
||||
"backend_utils.c",
|
||||
"linux_joystick.c",
|
||||
"null_joystick.c"
|
||||
]
|
||||
@@ -112,6 +117,8 @@
|
||||
"headers": [
|
||||
"x11_platform.h",
|
||||
"xkb_glfw.h",
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
"backend_utils.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
@@ -126,11 +133,14 @@
|
||||
"x11_monitor.c",
|
||||
"x11_window.c",
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
"ibus_glfw.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"glx_context.c",
|
||||
"egl_context.c",
|
||||
"osmesa_context.c",
|
||||
"backend_utils.c",
|
||||
"linux_joystick.c",
|
||||
"null_joystick.c"
|
||||
]
|
||||
|
||||
4
glfw/win32_platform.h
vendored
4
glfw/win32_platform.h
vendored
@@ -142,6 +142,9 @@ typedef enum
|
||||
|
||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
|
||||
#define IsWindowsXPOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||
#define IsWindowsVistaOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||
@@ -300,6 +303,7 @@ typedef struct _GLFWlibraryWin32
|
||||
_GLFWwindow* disabledCursorWindow;
|
||||
RAWINPUT* rawInput;
|
||||
int rawInputSize;
|
||||
UINT mouseTrailSize;
|
||||
|
||||
struct {
|
||||
HINSTANCE instance;
|
||||
|
||||
17
glfw/win32_window.c
vendored
17
glfw/win32_window.c
vendored
@@ -529,7 +529,18 @@ static void fitToMonitor(_GLFWwindow* window)
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
if (!_glfw.win32.acquiredMonitorCount)
|
||||
{
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||
|
||||
// HACK: When mouse trails are enabled the cursor becomes invisible when
|
||||
// the OpenGL ICD switches to page flipping
|
||||
if (IsWindowsXPOrGreater())
|
||||
{
|
||||
SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0);
|
||||
SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window->monitor->window)
|
||||
_glfw.win32.acquiredMonitorCount++;
|
||||
|
||||
@@ -546,8 +557,14 @@ static void releaseMonitor(_GLFWwindow* window)
|
||||
|
||||
_glfw.win32.acquiredMonitorCount--;
|
||||
if (!_glfw.win32.acquiredMonitorCount)
|
||||
{
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
|
||||
// HACK: Restore mouse trail length saved in acquireMonitor
|
||||
if (IsWindowsXPOrGreater())
|
||||
SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0);
|
||||
}
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, NULL);
|
||||
_glfwRestoreVideoModeWin32(window->monitor);
|
||||
}
|
||||
|
||||
24
glfw/window.c
vendored
24
glfw/window.c
vendored
@@ -48,6 +48,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
if (!focused)
|
||||
{
|
||||
int key, button;
|
||||
_glfw.focusedWindowId = 0;
|
||||
|
||||
for (key = 0; key <= GLFW_KEY_LAST; key++)
|
||||
{
|
||||
@@ -63,7 +64,28 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
if (window->mouseButtons[button] == GLFW_PRESS)
|
||||
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);
|
||||
}
|
||||
} else
|
||||
_glfw.focusedWindowId = window->id;
|
||||
}
|
||||
|
||||
_GLFWwindow* _glfwFocusedWindow() {
|
||||
if (_glfw.focusedWindowId) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
if (w->id == _glfw.focusedWindowId) return w;
|
||||
w = w->next;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_GLFWwindow* _glfwWindowForId(GLFWid id) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
if (w->id == _glfw.focusedWindowId) return w;
|
||||
w = w->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has moved
|
||||
@@ -185,8 +207,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
if (!_glfwIsValidContextConfig(&ctxconfig))
|
||||
return NULL;
|
||||
|
||||
static GLFWid windowIdCounter = 0;
|
||||
window = calloc(1, sizeof(_GLFWwindow));
|
||||
window->next = _glfw.windowListHead;
|
||||
window->id = ++windowIdCounter;
|
||||
_glfw.windowListHead = window;
|
||||
|
||||
window->videoMode.width = width;
|
||||
|
||||
41
glfw/wl_init.c
vendored
41
glfw/wl_init.c
vendored
@@ -308,7 +308,7 @@ static void pointerHandleButton(void* data,
|
||||
state == WL_POINTER_BUTTON_STATE_PRESSED
|
||||
? GLFW_PRESS
|
||||
: GLFW_RELEASE,
|
||||
_glfw.wl.xkb.modifiers);
|
||||
_glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static void pointerHandleAxis(void* data,
|
||||
@@ -319,11 +319,6 @@ static void pointerHandleAxis(void* data,
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
double x = 0.0, y = 0.0;
|
||||
// Wayland scroll events are in pointer motion coordinate space (think two
|
||||
// finger scroll). The factor 10 is commonly used to convert to "scroll
|
||||
// step means 1.0.
|
||||
const double scrollFactor = 1.0 / 10.0;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
@@ -331,11 +326,11 @@ static void pointerHandleAxis(void* data,
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||
x = wl_fixed_to_double(value) * scrollFactor;
|
||||
x = wl_fixed_to_double(value) * -1;
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * scrollFactor;
|
||||
y = wl_fixed_to_double(value) * -1;
|
||||
|
||||
_glfwInputScroll(window, x, y);
|
||||
_glfwInputScroll(window, x, y, 1);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
@@ -407,6 +402,15 @@ static void keyboardHandleLeave(void* data,
|
||||
_glfwInputWindowFocus(window, GLFW_FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchPendingKeyRepeats(id_type timer_id, void *data) {
|
||||
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, ((double)_glfw.wl.keyboardRepeatRate) / 1000.0);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||
}
|
||||
|
||||
|
||||
static void keyboardHandleKey(void* data,
|
||||
struct wl_keyboard* keyboard,
|
||||
uint32_t serial,
|
||||
@@ -419,14 +423,18 @@ static void keyboardHandleKey(void* data,
|
||||
return;
|
||||
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
|
||||
GLFWbool repeatable = GLFW_FALSE;
|
||||
|
||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||
{
|
||||
_glfw.wl.keyRepeatInfo.key = key;
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
|
||||
repeatable = GLFW_TRUE;
|
||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||
}
|
||||
if (repeatable) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0);
|
||||
}
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
|
||||
}
|
||||
|
||||
static void keyboardHandleModifiers(void* data,
|
||||
@@ -664,7 +672,9 @@ int _glfwPlatformInit(void)
|
||||
"Wayland: Failed to connect to display");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
initPollData(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
||||
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
||||
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
|
||||
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-keyrepeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
|
||||
|
||||
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
||||
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
||||
@@ -678,8 +688,10 @@ int _glfwPlatformInit(void)
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
|
||||
#ifdef __linux__
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
@@ -713,6 +725,7 @@ void _glfwPlatformTerminate(void)
|
||||
}
|
||||
|
||||
glfw_xkb_release(&_glfw.wl.xkb);
|
||||
glfw_dbus_terminate(&_glfw.wl.dbus);
|
||||
|
||||
if (_glfw.wl.cursorTheme)
|
||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||
|
||||
9
glfw/wl_platform.h
vendored
9
glfw/wl_platform.h
vendored
@@ -49,6 +49,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#else
|
||||
#include "null_joystick.h"
|
||||
#endif
|
||||
#include "backend_utils.h"
|
||||
#include "xkb_glfw.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
@@ -204,10 +205,11 @@ typedef struct _GLFWlibraryWayland
|
||||
int32_t keyboardRepeatDelay;
|
||||
struct {
|
||||
uint32_t key;
|
||||
double nextRepeatAt;
|
||||
id_type keyRepeatTimer;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
} keyRepeatInfo;
|
||||
_GLFWXKBData xkb;
|
||||
_GLFWDBUSData dbus;
|
||||
|
||||
_GLFWwindow* pointerFocus;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
@@ -229,10 +231,7 @@ typedef struct _GLFWlibraryWayland
|
||||
PFN_wl_egl_window_resize window_resize;
|
||||
} egl;
|
||||
|
||||
struct {
|
||||
struct pollfd fds[2];
|
||||
int wakeupFds[2];
|
||||
} eventLoopData;
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
|
||||
60
glfw/wl_window.c
vendored
60
glfw/wl_window.c
vendored
@@ -688,34 +688,14 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchPendingKeyRepeats() {
|
||||
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
|
||||
double now = glfwGetTime();
|
||||
while (_glfw.wl.keyRepeatInfo.nextRepeatAt <= now) {
|
||||
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt += 1.0 / _glfw.wl.keyboardRepeatRate;
|
||||
now = glfwGetTime();
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
adjustTimeoutForKeyRepeat(double timeout) {
|
||||
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return timeout;
|
||||
double now = glfwGetTime();
|
||||
if (timeout < 0 || now + timeout > _glfw.wl.keyRepeatInfo.nextRepeatAt) {
|
||||
timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) + 0.001 );
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static void
|
||||
handleEvents(double timeout)
|
||||
{
|
||||
struct wl_display* display = _glfw.wl.display;
|
||||
|
||||
while (wl_display_prepare_read(display) != 0)
|
||||
while (wl_display_prepare_read(display) != 0) {
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
|
||||
// If an error different from EAGAIN happens, we have likely been
|
||||
// disconnected from the Wayland session, try to handle that the best we
|
||||
@@ -732,25 +712,8 @@ handleEvents(double timeout)
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPendingKeyRepeats();
|
||||
timeout = adjustTimeoutForKeyRepeat(timeout);
|
||||
GLFWbool read_ok = GLFW_FALSE;
|
||||
for (nfds_t i = 0; i < 2; i++) _glfw.wl.eventLoopData.fds[i].revents = 0;
|
||||
|
||||
if (timeout >= 0) {
|
||||
const int result = pollWithTimeout(_glfw.wl.eventLoopData.fds, 2, timeout);
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.wl.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.wl.eventLoopData.fds[0].fd);
|
||||
read_ok = _glfw.wl.eventLoopData.fds[1].revents & POLLIN;
|
||||
}
|
||||
} else {
|
||||
if (poll(_glfw.wl.eventLoopData.fds, 2, -1) > 0) {
|
||||
if (_glfw.wl.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.wl.eventLoopData.fds[0].fd);
|
||||
read_ok = _glfw.wl.eventLoopData.fds[1].revents & POLLIN;
|
||||
}
|
||||
}
|
||||
if (read_ok) {
|
||||
GLFWbool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout);
|
||||
if (display_read_ok) {
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
@@ -758,7 +721,7 @@ handleEvents(double timeout)
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
dispatchPendingKeyRepeats();
|
||||
glfw_ibus_dispatch(&_glfw.wl.xkb.ibus);
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a theme cursor name
|
||||
@@ -1094,7 +1057,8 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
// TODO: Use an actual Wayland API to implement this when one becomes available
|
||||
int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC);
|
||||
static char tty[L_ctermid + 1];
|
||||
int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC);
|
||||
if (fd > -1) {
|
||||
int ret = write(fd, "\x07", 1) == 1 ? GLFW_TRUE : GLFW_FALSE;
|
||||
close(fd);
|
||||
@@ -1200,16 +1164,19 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
wl_display_dispatch_pending(_glfw.wl.display);
|
||||
handleEvents(0);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
handleEvents(-1);
|
||||
double timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
{
|
||||
if (wl_display_dispatch_pending(_glfw.wl.display) > 0) timeout = 0;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
@@ -1546,6 +1513,11 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
return err;
|
||||
}
|
||||
|
||||
void
|
||||
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
|
||||
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
|
||||
14
glfw/x11_init.c
vendored
14
glfw/x11_init.c
vendored
@@ -37,6 +37,7 @@
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
// Check whether the specified atom is supported
|
||||
@@ -640,8 +641,8 @@ int _glfwPlatformInit(void)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
initPollData(_glfw.x11.eventLoopData.fds, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
||||
_glfw.x11.eventLoopData.fds[2].events = POLLIN;
|
||||
initPollData(&_glfw.x11.eventLoopData, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
||||
glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData);
|
||||
|
||||
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
|
||||
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
|
||||
@@ -658,8 +659,12 @@ int _glfwPlatformInit(void)
|
||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||
|
||||
#if defined(__linux__)
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
@@ -689,6 +694,7 @@ void _glfwPlatformTerminate(void)
|
||||
}
|
||||
|
||||
glfw_xkb_release(&_glfw.x11.xkb);
|
||||
glfw_dbus_terminate(&_glfw.x11.dbus);
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
free(_glfw.x11.clipboardString);
|
||||
|
||||
|
||||
28
glfw/x11_monitor.c
vendored
28
glfw/x11_monitor.c
vendored
@@ -152,6 +152,11 @@ void _glfwPollMonitorsX11(void)
|
||||
}
|
||||
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
|
||||
if (!ci)
|
||||
{
|
||||
XRRFreeOutputInfo(oi);
|
||||
continue;
|
||||
}
|
||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||
{
|
||||
widthMM = oi->mm_height;
|
||||
@@ -321,13 +326,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
|
||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
if (ci)
|
||||
{
|
||||
if (xpos)
|
||||
*xpos = ci->x;
|
||||
if (ypos)
|
||||
*ypos = ci->y;
|
||||
|
||||
if (xpos)
|
||||
*xpos = ci->x;
|
||||
if (ypos)
|
||||
*ypos = ci->y;
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
}
|
||||
@@ -406,9 +413,13 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
*mode = vidmodeFromModeInfo(getModeInfo(sr, ci->mode), ci);
|
||||
if (ci) {
|
||||
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||
if (mi) // mi can be NULL if the monitor has been disconnected
|
||||
*mode = vidmodeFromModeInfo(mi, ci);
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
@@ -501,4 +512,3 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
||||
return monitor->x11.output;
|
||||
}
|
||||
|
||||
|
||||
9
glfw/x11_platform.h
vendored
9
glfw/x11_platform.h
vendored
@@ -53,6 +53,7 @@
|
||||
|
||||
// The libxkb library is used for improved keyboard support
|
||||
#include "xkb_glfw.h"
|
||||
#include "backend_utils.h"
|
||||
|
||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
||||
@@ -103,8 +104,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
||||
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
|
||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
@@ -315,6 +314,7 @@ typedef struct _GLFWlibraryX11
|
||||
} randr;
|
||||
|
||||
_GLFWXKBData xkb;
|
||||
_GLFWDBUSData dbus;
|
||||
|
||||
struct {
|
||||
int count;
|
||||
@@ -382,10 +382,7 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||
} xrender;
|
||||
|
||||
struct {
|
||||
struct pollfd fds[3];
|
||||
int wakeupFds[2];
|
||||
} eventLoopData;
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
} _GLFWlibraryX11;
|
||||
|
||||
|
||||
113
glfw/x11_window.c
vendored
113
glfw/x11_window.c
vendored
@@ -54,47 +54,32 @@
|
||||
// This avoids blocking other threads via the per-display Xlib lock that also
|
||||
// covers GLX functions
|
||||
//
|
||||
static GLFWbool waitForEvent(double* timeout)
|
||||
{
|
||||
nfds_t count = 2;
|
||||
GLFWbool _glfwDispatchX11Events(void);
|
||||
|
||||
#if defined(__linux__)
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
{
|
||||
count = 3;
|
||||
_glfw.x11.eventLoopData.fds[2].fd = _glfw.linjs.inotify;
|
||||
}
|
||||
#endif
|
||||
for (;;)
|
||||
{
|
||||
for (nfds_t i = 0; i < count; i++) _glfw.x11.eventLoopData.fds[i].revents = 0;
|
||||
if (timeout)
|
||||
{
|
||||
const uint64_t base = _glfwPlatformGetTimerValue();
|
||||
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, count, *timeout);
|
||||
*timeout -= (_glfwPlatformGetTimerValue() - base) /
|
||||
(double) _glfwPlatformGetTimerFrequency();
|
||||
static void
|
||||
handleEvents(double timeout) {
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
|
||||
if (display_read_ok) _glfwDispatchX11Events();
|
||||
glfw_ibus_dispatch(&_glfw.x11.xkb.ibus);
|
||||
}
|
||||
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
if (result == 0)
|
||||
return GLFW_FALSE;
|
||||
if (*timeout > 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
static GLFWbool
|
||||
waitForX11Event(double timeout) {
|
||||
// returns true iff there is X11 data waiting to be read, does not run watches and timers
|
||||
double end_time = glfwGetTime() + timeout;
|
||||
while(GLFW_TRUE) {
|
||||
if (timeout >= 0) {
|
||||
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout);
|
||||
if (result > 0) return GLFW_TRUE;
|
||||
timeout = end_time - glfwGetTime();
|
||||
if (timeout <= 0) return GLFW_FALSE;
|
||||
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
return GLFW_FALSE;
|
||||
} else {
|
||||
const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1);
|
||||
if (result > 0) return GLFW_TRUE;
|
||||
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
else {
|
||||
const int result = poll(_glfw.x11.eventLoopData.fds, count, -1);
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
if (result == 0)
|
||||
return GLFW_FALSE;
|
||||
if (errno != EINTR && errno != EAGAIN) return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,14 +90,13 @@ static GLFWbool waitForEvent(double* timeout)
|
||||
static GLFWbool waitForVisibilityNotify(_GLFWwindow* window)
|
||||
{
|
||||
XEvent dummy;
|
||||
double timeout = 0.1;
|
||||
|
||||
while (!XCheckTypedWindowEvent(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
VisibilityNotify,
|
||||
&dummy))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
if (!waitForX11Event(0.1))
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -984,7 +968,7 @@ static const char* getSelectionString(Atom selection)
|
||||
SelectionNotify,
|
||||
¬ification))
|
||||
{
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
|
||||
if (notification.xselection.property == None)
|
||||
@@ -1020,7 +1004,7 @@ static const char* getSelectionString(Atom selection)
|
||||
isSelPropNewValueNotify,
|
||||
(XPointer) ¬ification))
|
||||
{
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
@@ -1325,13 +1309,13 @@ static void processEvent(XEvent *event)
|
||||
|
||||
// Modern X provides scroll events as mouse button presses
|
||||
else if (event->xbutton.button == Button4)
|
||||
_glfwInputScroll(window, 0.0, 1.0);
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0);
|
||||
else if (event->xbutton.button == Button5)
|
||||
_glfwInputScroll(window, 0.0, -1.0);
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0);
|
||||
else if (event->xbutton.button == Button6)
|
||||
_glfwInputScroll(window, 1.0, 0.0);
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0);
|
||||
else if (event->xbutton.button == Button7)
|
||||
_glfwInputScroll(window, -1.0, 0.0);
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0);
|
||||
|
||||
else
|
||||
{
|
||||
@@ -1852,7 +1836,7 @@ void _glfwPushSelectionToManagerX11(void)
|
||||
}
|
||||
}
|
||||
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2142,7 +2126,6 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS)
|
||||
{
|
||||
XEvent event;
|
||||
double timeout = 0.5;
|
||||
|
||||
// Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to
|
||||
// function before the window is mapped
|
||||
@@ -2159,7 +2142,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
isFrameExtentsEvent,
|
||||
(XPointer) window))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
if (!waitForX11Event(0.5))
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
|
||||
@@ -2560,9 +2543,10 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
PropModeReplace, (unsigned char*) &value, 1);
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
GLFWbool
|
||||
_glfwDispatchX11Events(void) {
|
||||
_GLFWwindow* window;
|
||||
GLFWbool dispatched = GLFW_FALSE;
|
||||
|
||||
#if defined(__linux__)
|
||||
_glfwDetectJoystickConnectionLinux();
|
||||
@@ -2574,6 +2558,7 @@ void _glfwPlatformPollEvents(void)
|
||||
XEvent event;
|
||||
XNextEvent(_glfw.x11.display, &event);
|
||||
processEvent(&event);
|
||||
dispatched = GLFW_TRUE;
|
||||
}
|
||||
|
||||
window = _glfw.x11.disabledCursorWindow;
|
||||
@@ -2592,25 +2577,25 @@ void _glfwPlatformPollEvents(void)
|
||||
}
|
||||
|
||||
XFlush(_glfw.x11.display);
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
_glfwDispatchX11Events();
|
||||
handleEvents(0);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
while (!XPending(_glfw.x11.display))
|
||||
waitForEvent(NULL);
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
double timeout = _glfwDispatchX11Events() ? 0 : -1;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
{
|
||||
while (!XPending(_glfw.x11.display))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
if (_glfwDispatchX11Events()) timeout = 0;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformPostEmptyEvent(void)
|
||||
@@ -2894,6 +2879,10 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
|
||||
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
|
||||
311
glfw/xkb_glfw.c
vendored
311
glfw/xkb_glfw.c
vendored
@@ -32,6 +32,7 @@
|
||||
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
|
||||
|
||||
#define map_key(key) { \
|
||||
switch(key) { \
|
||||
S(space, SPACE); \
|
||||
@@ -170,36 +171,29 @@ glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
release_keyboard_data(_GLFWXKBData *xkb) {
|
||||
#define US(group, state, unref) if (xkb->group.state) { unref(xkb->group.state); xkb->group.state = NULL; }
|
||||
#define UK(keymap) if(xkb->keymap) { xkb_keymap_unref(xkb->keymap); xkb->keymap = NULL; }
|
||||
US(states, composeState, xkb_compose_state_unref);
|
||||
UK(keymap);
|
||||
UK(default_keymap);
|
||||
US(states, state, xkb_state_unref);
|
||||
US(states, clean_state, xkb_state_unref);
|
||||
US(states, default_state, xkb_state_unref);
|
||||
#undef US
|
||||
#undef UK
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_release(_GLFWXKBData *xkb) {
|
||||
if (xkb->composeState) {
|
||||
xkb_compose_state_unref(xkb->composeState);
|
||||
xkb->composeState = NULL;
|
||||
}
|
||||
if (xkb->keymap) {
|
||||
xkb_keymap_unref(xkb->keymap);
|
||||
xkb->keymap = NULL;
|
||||
}
|
||||
if (xkb->default_keymap) {
|
||||
xkb_keymap_unref(xkb->default_keymap);
|
||||
xkb->default_keymap = NULL;
|
||||
}
|
||||
if (xkb->state) {
|
||||
xkb_state_unref(xkb->state);
|
||||
xkb->state = NULL;
|
||||
}
|
||||
if (xkb->clean_state) {
|
||||
xkb_state_unref(xkb->clean_state);
|
||||
xkb->clean_state = NULL;
|
||||
}
|
||||
if (xkb->default_state) {
|
||||
xkb_state_unref(xkb->default_state);
|
||||
xkb->default_state = NULL;
|
||||
}
|
||||
release_keyboard_data(xkb);
|
||||
if (xkb->context) {
|
||||
xkb_context_unref(xkb->context);
|
||||
xkb->context = NULL;
|
||||
}
|
||||
glfw_ibus_terminate(&xkb->ibus);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
@@ -211,115 +205,116 @@ glfw_xkb_create_context(_GLFWXKBData *xkb) {
|
||||
"Failed to initialize XKB context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
glfw_connect_to_ibus(&xkb->ibus);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static const char*
|
||||
load_keymaps(_GLFWXKBData *xkb, const char *map_str) {
|
||||
(void)(map_str); // not needed on X11
|
||||
xkb_glfw_load_keymap(xkb->keymap, map_str);
|
||||
if (!xkb->keymap) return "Failed to compile XKB keymap";
|
||||
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
||||
// env var, see
|
||||
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
||||
static struct xkb_rule_names default_rule_names = {0};
|
||||
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if (!xkb->default_keymap) return "Failed to create default XKB keymap";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char*
|
||||
load_states(_GLFWXKBData *xkb) {
|
||||
xkb_glfw_load_state(xkb->keymap, xkb->states.state);
|
||||
xkb->states.clean_state = xkb_state_new(xkb->keymap);
|
||||
xkb->states.default_state = xkb_state_new(xkb->default_keymap);
|
||||
if (!xkb->states.state || !xkb->states.clean_state || !xkb->states.default_state) return "Failed to create XKB state";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
load_compose_tables(_GLFWXKBData *xkb) {
|
||||
/* Look up the preferred locale, falling back to "C" as default. */
|
||||
struct xkb_compose_table* compose_table = NULL;
|
||||
const char *locale = getenv("LC_ALL");
|
||||
if (!locale) locale = getenv("LC_CTYPE");
|
||||
if (!locale) locale = getenv("LANG");
|
||||
if (!locale) locale = "C";
|
||||
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if (!compose_table) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale);
|
||||
return;
|
||||
}
|
||||
xkb->states.composeState = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
if (!xkb->states.composeState) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||
const char* locale = NULL;
|
||||
struct xkb_state* state = NULL, *clean_state = NULL;
|
||||
struct xkb_keymap* keymap = NULL;
|
||||
struct xkb_compose_table* compose_table = NULL;
|
||||
struct xkb_compose_state* compose_state = NULL;
|
||||
(void)(map_str); // not needed on X11
|
||||
GLFWbool ok = GLFW_FALSE;
|
||||
|
||||
xkb_glfw_load_keymap(keymap, map_str);
|
||||
if (!keymap) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to compile XKB keymap");
|
||||
else {
|
||||
xkb_glfw_load_state(keymap, state);
|
||||
clean_state = xkb_state_new(keymap);
|
||||
if (!state || !clean_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB state");
|
||||
xkb_keymap_unref(keymap); keymap = NULL;
|
||||
} else {
|
||||
ok = GLFW_TRUE;
|
||||
/* Look up the preferred locale, falling back to "C" as default. */
|
||||
locale = getenv("LC_ALL");
|
||||
if (!locale) locale = getenv("LC_CTYPE");
|
||||
if (!locale) locale = getenv("LANG");
|
||||
if (!locale) locale = "C";
|
||||
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if (!compose_table) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table");
|
||||
} else {
|
||||
compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
xkb_compose_table_unref(compose_table); compose_table = NULL;
|
||||
if (!compose_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *err;
|
||||
release_keyboard_data(xkb);
|
||||
err = load_keymaps(xkb, map_str);
|
||||
if (err) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||
release_keyboard_data(xkb);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (keymap && state && clean_state) {
|
||||
if (xkb->composeState) xkb_compose_state_unref(xkb->composeState);
|
||||
xkb->composeState = compose_state;
|
||||
if (xkb->keymap) xkb_keymap_unref(xkb->keymap);
|
||||
xkb->keymap = keymap;
|
||||
if (xkb->state) xkb_state_unref(xkb->state);
|
||||
xkb->state = state;
|
||||
if (xkb->clean_state) xkb_state_unref(xkb->clean_state);
|
||||
xkb->clean_state = clean_state;
|
||||
err = load_states(xkb);
|
||||
if (err) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||
release_keyboard_data(xkb);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (xkb->keymap) {
|
||||
load_compose_tables(xkb);
|
||||
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
|
||||
S(control, XKB_MOD_NAME_CTRL);
|
||||
S(alt, XKB_MOD_NAME_ALT);
|
||||
S(shift, XKB_MOD_NAME_SHIFT);
|
||||
S(super, XKB_MOD_NAME_LOGO);
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
S(control, XKB_MOD_NAME_CTRL);
|
||||
S(alt, XKB_MOD_NAME_ALT);
|
||||
S(shift, XKB_MOD_NAME_SHIFT);
|
||||
S(super, XKB_MOD_NAME_LOGO);
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
#undef S
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
xkb->modifiers = 0;
|
||||
xkb->activeUnknownModifiers = 0;
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
if (!xkb->default_keymap && xkb->context) {
|
||||
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
||||
// env var, see
|
||||
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
||||
static struct xkb_rule_names default_rule_names = {0};
|
||||
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if (xkb->default_keymap) {
|
||||
xkb->default_state = xkb_state_new(keymap);
|
||||
if (!xkb->default_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB state, fallback key processing is unavailable");
|
||||
}
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB keymap, fallback key processing is unavailable");
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
xkb->states.modifiers = 0;
|
||||
xkb->states.activeUnknownModifiers = 0;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static inline xkb_mod_mask_t
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb) {
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||
size_t i = 0;
|
||||
xkb_mod_mask_t ans = 0;
|
||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||
if (xkb_state_mod_index_is_active(xkb->state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
i++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static void
|
||||
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
||||
if (!xkb->keymap) return;
|
||||
xkb->modifiers = 0;
|
||||
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||
xkb->states.modifiers = 0;
|
||||
xkb_state_update_mask(xkb->states.state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||
// We have to update the groups in clean_state, as they change for
|
||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||
xkb_state_update_mask(xkb->clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->activeUnknownModifiers = active_unknown_modifiers(xkb);
|
||||
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
update_modifiers(xkb, &xkb->states);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
@@ -331,16 +326,16 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
||||
}
|
||||
|
||||
|
||||
static char text[256];
|
||||
static KeyEvent key_event = {};
|
||||
|
||||
static inline xkb_keysym_t
|
||||
compose_symbol(_GLFWXKBData *xkb, xkb_keysym_t sym) {
|
||||
if (sym == XKB_KEY_NoSymbol || !xkb->composeState) return sym;
|
||||
if (xkb_compose_state_feed(xkb->composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||
switch (xkb_compose_state_get_status(xkb->composeState)) {
|
||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym) {
|
||||
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(xkb->composeState, text, sizeof(text));
|
||||
return xkb_compose_state_get_one_sym(xkb->composeState);
|
||||
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
||||
return xkb_compose_state_get_one_sym(composeState);
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
return XKB_KEY_NoSymbol;
|
||||
@@ -402,29 +397,75 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) {
|
||||
int x = 0, y = 0;
|
||||
switch(which) {
|
||||
case 1:
|
||||
glfw_ibus_set_focused(&xkb->ibus, a ? GLFW_TRUE : GLFW_FALSE);
|
||||
break;
|
||||
case 2:
|
||||
_glfwPlatformGetWindowPos(w, &x, &y);
|
||||
x += a; y += b;
|
||||
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool 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);
|
||||
}
|
||||
static xkb_keycode_t last_handled_press_keycode = 0;
|
||||
// We filter out release events that correspond to the last press event
|
||||
// handled by the IME system. This wont fix the case of multiple key
|
||||
// presses before a release, but is better than nothing. For that case
|
||||
// you'd need to implement a ring buffer to store pending key presses.
|
||||
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
|
||||
last_handled_press_keycode = 0;
|
||||
GLFWbool 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)) {
|
||||
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), key_event.text
|
||||
);
|
||||
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, key_event.text, 0);
|
||||
} else debug("↳ discarded\n");
|
||||
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
|
||||
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;
|
||||
#ifdef _GLFW_WAYLAND
|
||||
code_for_sym += 8;
|
||||
#else
|
||||
key_event.ibus_keycode -= 8;
|
||||
#endif
|
||||
debug("scancode: 0x%x release: %d ", scancode, action == GLFW_RELEASE);
|
||||
int num_syms = xkb_state_key_get_syms(xkb->state, code_for_sym, &syms);
|
||||
int num_clean_syms = xkb_state_key_get_syms(xkb->clean_state, code_for_sym, &clean_syms);
|
||||
text[0] = 0;
|
||||
debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode);
|
||||
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;
|
||||
// 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("scancode: 0x%x num_syms: %d num_clean_syms: %d ignoring event\n", scancode, num_syms, num_clean_syms);
|
||||
debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms);
|
||||
return;
|
||||
}
|
||||
glfw_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";
|
||||
glfw_sym = compose_symbol(xkb, syms[0]);
|
||||
glfw_sym = compose_symbol(sg->composeState, syms[0]);
|
||||
if (glfw_sym == XKB_KEY_NoSymbol) {
|
||||
debug("compose not complete, ignoring.\n");
|
||||
return;
|
||||
@@ -435,21 +476,21 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
// are active (for example if ISO_Shift_Level_* mods are active
|
||||
// they are not reported by GLFW so the key should be the shifted
|
||||
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
|
||||
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(xkb->state, code_for_sym) & xkb->activeUnknownModifiers;
|
||||
if (xkb->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", xkb->activeUnknownModifiers));
|
||||
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];
|
||||
// xkb returns text even if alt and/or super are pressed
|
||||
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & xkb->modifiers) == 0) xkb_state_key_get_utf8(xkb->state, code_for_sym, text, sizeof(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_event.text, sizeof(key_event.text));
|
||||
text_type = "text";
|
||||
}
|
||||
if ((1 <= text[0] && text[0] <= 31) || text[0] == 127) text[0] = 0; // dont send text for ascii control codes
|
||||
if (text[0]) { debug("%s: %s ", 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); }
|
||||
}
|
||||
int glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||
GLFWbool is_fallback = GLFW_FALSE;
|
||||
if (glfw_keycode == GLFW_KEY_UNKNOWN && !text[0]) {
|
||||
int num_default_syms = xkb_state_key_get_syms(xkb->default_state, code_for_sym, &default_syms);
|
||||
if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.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);
|
||||
@@ -457,10 +498,18 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
}
|
||||
}
|
||||
debug(
|
||||
"%s%s: %s xkb_key_name: %s\n",
|
||||
format_mods(xkb->modifiers),
|
||||
is_fallback ? "glfw_fallback_key" : "glfw_key", _glfwGetKeyName(glfw_keycode),
|
||||
glfw_xkb_keysym_name(glfw_sym)
|
||||
"%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)
|
||||
);
|
||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
|
||||
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));
|
||||
} else {
|
||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0);
|
||||
}
|
||||
}
|
||||
|
||||
20
glfw/xkb_glfw.h
vendored
20
glfw/xkb_glfw.h
vendored
@@ -32,14 +32,23 @@
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#endif
|
||||
|
||||
#include "ibus_glfw.h"
|
||||
|
||||
typedef struct {
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_keymap* default_keymap;
|
||||
struct xkb_state* state;
|
||||
struct xkb_state* clean_state;
|
||||
struct xkb_state* default_state;
|
||||
struct xkb_compose_state* composeState;
|
||||
xkb_mod_mask_t activeUnknownModifiers;
|
||||
unsigned int modifiers;
|
||||
} XKBStateGroup;
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_keymap* default_keymap;
|
||||
XKBStateGroup states;
|
||||
|
||||
xkb_mod_index_t controlIdx;
|
||||
xkb_mod_index_t altIdx;
|
||||
@@ -53,9 +62,8 @@ typedef struct {
|
||||
xkb_mod_mask_t superMask;
|
||||
xkb_mod_mask_t capsLockMask;
|
||||
xkb_mod_mask_t numLockMask;
|
||||
xkb_mod_mask_t activeUnknownModifiers;
|
||||
unsigned int modifiers;
|
||||
xkb_mod_index_t unknownModifiers[256];
|
||||
_GLFWIBUSData ibus;
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
int32_t keyboard_device_id;
|
||||
@@ -84,3 +92,5 @@ 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);
|
||||
int glfw_xkb_keysym_from_name(const char *name, GLFWbool 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, GLFWbool handled_by_ime, GLFWbool failed);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
|
||||
from ..tui.handler import Handler
|
||||
@@ -13,7 +15,6 @@ class Clipboard(Handler):
|
||||
|
||||
def __init__(self, data_to_send, args):
|
||||
self.args = args
|
||||
self.print_on_fail = None
|
||||
self.clipboard_contents = None
|
||||
self.data_to_send = data_to_send
|
||||
|
||||
@@ -63,16 +64,13 @@ def main(args):
|
||||
data = None
|
||||
if not sys.stdin.isatty():
|
||||
data = sys.stdin.buffer.read()
|
||||
sys.stdin = open('/dev/tty', 'r')
|
||||
sys.stdin = open(os.ctermid(), 'r')
|
||||
loop = Loop()
|
||||
handler = Clipboard(data, args)
|
||||
loop.loop(handler)
|
||||
if loop.return_code == 0 and handler.clipboard_contents:
|
||||
sys.stdout.write(handler.clipboard_contents)
|
||||
sys.stdout.flush()
|
||||
if handler.print_on_fail:
|
||||
print(handler.print_on_fail, file=sys.stderr)
|
||||
input('Press Enter to quit')
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ def highlight_collection(collection, aliases=None):
|
||||
jobs = {}
|
||||
ans = {}
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
highlight_collection.processes = executor._processes
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type != 'rename':
|
||||
for p in (path, other_path):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
@@ -24,7 +25,7 @@ from .collect import (
|
||||
set_highlight_data
|
||||
)
|
||||
from .config import init_config
|
||||
from .patch import Differ, set_diff_command
|
||||
from .patch import Differ, set_diff_command, worker_processes
|
||||
from .render import ImageSupportWarning, LineRef, render_diff
|
||||
from .search import BadRegex, Search
|
||||
|
||||
@@ -42,7 +43,7 @@ def generate_diff(collection, context):
|
||||
|
||||
for path, item_type, changed_path in collection:
|
||||
if item_type == 'diff':
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
is_binary = isinstance(data_for_path(path), bytes) or isinstance(data_for_path(changed_path), bytes)
|
||||
if not is_binary:
|
||||
d.add_diff(path, changed_path)
|
||||
|
||||
@@ -106,10 +107,67 @@ class DiffHandler(Handler):
|
||||
return
|
||||
|
||||
def create_collection(self):
|
||||
self.start_job('collect', create_collection, self.left, self.right)
|
||||
|
||||
def collect_done(collection):
|
||||
self.collection = collection
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
|
||||
def collect(left, right):
|
||||
collection = create_collection(left, right)
|
||||
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
||||
|
||||
def generate_diff(self):
|
||||
self.start_job('diff', generate_diff, self.collection, self.current_context_count)
|
||||
|
||||
def diff_done(diff_map):
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.state = DIFFED
|
||||
self.diff_map = diff_map
|
||||
self.calculate_statistics()
|
||||
self.render_diff()
|
||||
self.scroll_pos = 0
|
||||
if self.restore_position is not None:
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
initialize_highlighter(self.opts.pygments_style)
|
||||
except StyleNotFound as e:
|
||||
self.report_traceback_on_exit = str(e)
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.syntax_highlight()
|
||||
|
||||
def diff(collection, current_context_count):
|
||||
diff_map = generate_diff(collection, current_context_count)
|
||||
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
||||
|
||||
def syntax_highlight(self):
|
||||
|
||||
def highlighting_done(hdata):
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = hdata
|
||||
self.quit_loop(1)
|
||||
return
|
||||
set_highlight_data(hdata)
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def highlight(*a):
|
||||
result = highlight_collection(*a)
|
||||
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
||||
|
||||
def calculate_statistics(self):
|
||||
self.added_count = self.collection.added_count
|
||||
@@ -413,50 +471,6 @@ class DiffHandler(Handler):
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
if 'tb' in job_result:
|
||||
self.report_traceback_on_exit = job_result['tb']
|
||||
self.quit_loop(1)
|
||||
return
|
||||
if job_id == 'collect':
|
||||
self.collection = job_result['result']
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
elif job_id == 'diff':
|
||||
diff_map = job_result['result']
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.state = DIFFED
|
||||
self.diff_map = diff_map
|
||||
self.calculate_statistics()
|
||||
self.render_diff()
|
||||
self.scroll_pos = 0
|
||||
if self.restore_position is not None:
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
initialize_highlighter(self.opts.pygments_style)
|
||||
except StyleNotFound as e:
|
||||
self.report_traceback_on_exit = str(e)
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.start_job('highlight', highlight_collection, self.collection, self.opts.syntax_aliases)
|
||||
elif job_id == 'highlight':
|
||||
hdata = job_result['result']
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
set_highlight_data(hdata)
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
@@ -495,6 +509,14 @@ help_text = 'Show a side-by-side diff of the specified files/directories'
|
||||
usage = 'file_or_directory_left file_or_directory_right'
|
||||
|
||||
|
||||
def terminate_processes(processes):
|
||||
for pid in processes:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main(args):
|
||||
warnings.showwarning = showwarning
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
||||
@@ -517,6 +539,9 @@ def main(args):
|
||||
for message in showwarning.warnings:
|
||||
from kitty.utils import safe_print
|
||||
safe_print(message, file=sys.stderr)
|
||||
highlight_processes = getattr(highlight_collection, 'processes', ())
|
||||
terminate_processes(tuple(highlight_processes))
|
||||
terminate_processes(tuple(worker_processes))
|
||||
if loop.return_code != 0:
|
||||
if handler.report_traceback_on_exit:
|
||||
print(handler.report_traceback_on_exit, file=sys.stderr)
|
||||
|
||||
@@ -14,6 +14,7 @@ from .diff_speedup import changed_center
|
||||
left_lines = right_lines = None
|
||||
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
||||
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
||||
worker_processes = []
|
||||
|
||||
|
||||
def find_differ():
|
||||
@@ -43,8 +44,10 @@ def run_diff(file1, file2, context=3):
|
||||
p = subprocess.Popen(
|
||||
cmd + [path1, path2],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||
worker_processes.append(p.pid)
|
||||
stdout, stderr = p.communicate()
|
||||
returncode = p.wait()
|
||||
worker_processes.remove(p.pid)
|
||||
if returncode in (0, 1):
|
||||
return True, returncode == 1, stdout.decode('utf-8')
|
||||
return False, returncode, stderr.decode('utf-8')
|
||||
@@ -205,9 +208,13 @@ def parse_patch(raw):
|
||||
|
||||
class Differ:
|
||||
|
||||
diff_executor = None
|
||||
|
||||
def __init__(self):
|
||||
self.jmap = {}
|
||||
self.jobs = []
|
||||
if Differ.diff_executor is None:
|
||||
Differ.diff_executor = self.diff_executor = concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count())
|
||||
|
||||
def add_diff(self, file1, file2):
|
||||
self.jmap[file1] = file2
|
||||
@@ -216,26 +223,26 @@ class Differ:
|
||||
def __call__(self, context=3):
|
||||
global left_lines, right_lines
|
||||
ans = {}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
left_path, right_path = key, self.jmap[key]
|
||||
try:
|
||||
ok, returncode, output = future.result()
|
||||
except FileNotFoundError as err:
|
||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||
except Exception as e:
|
||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||
if not ok:
|
||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
left_lines = lines_for_path(left_path)
|
||||
right_lines = lines_for_path(right_path)
|
||||
try:
|
||||
patch = parse_patch(output)
|
||||
except Exception:
|
||||
import traceback
|
||||
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
else:
|
||||
ans[key] = patch
|
||||
executor = Differ.diff_executor
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
left_path, right_path = key, self.jmap[key]
|
||||
try:
|
||||
ok, returncode, output = future.result()
|
||||
except FileNotFoundError as err:
|
||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||
except Exception as e:
|
||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||
if not ok:
|
||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
left_lines = lines_for_path(left_path)
|
||||
right_lines = lines_for_path(right_path)
|
||||
try:
|
||||
patch = parse_patch(output)
|
||||
except Exception:
|
||||
import traceback
|
||||
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
else:
|
||||
ans[key] = patch
|
||||
return ans
|
||||
|
||||
@@ -453,7 +453,9 @@ def render_diff(collection, diff_map, args, columns, image_manager):
|
||||
for i, (path, item_type, other_path) in enumerate(collection):
|
||||
item_ref = Reference(path)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
is_img = is_binary and is_image(path) and images_supported()
|
||||
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
|
||||
is_binary = True
|
||||
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
|
||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||
if item_type == 'diff':
|
||||
if is_binary:
|
||||
|
||||
@@ -152,7 +152,6 @@ class Hints(Handler):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
@@ -171,7 +170,7 @@ def regex_finditer(pat, minimum_match_length, text):
|
||||
yield s, e
|
||||
|
||||
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
||||
opening_brackets = ''.join(closing_bracket_map)
|
||||
postprocessor_map = {}
|
||||
|
||||
@@ -190,15 +189,15 @@ def url(text, s, e):
|
||||
e -= len(url) - idx
|
||||
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||
e -= 1
|
||||
# truncate url at closing bracket/quote
|
||||
if s > 0 and e <= len(text) and text[s-1] in opening_brackets:
|
||||
q = closing_bracket_map[text[s-1]]
|
||||
idx = text.find(q, s)
|
||||
if idx > s:
|
||||
e = idx
|
||||
# Restructured Text URLs
|
||||
if e > 3 and text[e-2:e] == '`_':
|
||||
e -= 2
|
||||
# Remove trailing bracket if matched by leading bracket
|
||||
if s > 0 and e < len(text) and text[s-1] in opening_brackets and text[e-1] == closing_bracket_map[text[s-1]]:
|
||||
e -= 1
|
||||
# Remove trailing quote if matched by leading quote
|
||||
if s > 0 and e < len(text) and text[s-1] in '\'"' and text[e-1] == text[s-1]:
|
||||
e -= 1
|
||||
|
||||
return s, e
|
||||
|
||||
@@ -256,7 +255,7 @@ def functions_for(args):
|
||||
)
|
||||
post_processors.append(url)
|
||||
elif args.type == 'path':
|
||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'
|
||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})'
|
||||
post_processors.extend((brackets, quotes))
|
||||
elif args.type == 'line':
|
||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||
@@ -382,7 +381,7 @@ def main(args):
|
||||
return
|
||||
else:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
sys.stdin = open(os.ctermid())
|
||||
try:
|
||||
args, items = parse_hints_args(args[1:])
|
||||
except SystemExit as e:
|
||||
|
||||
@@ -260,13 +260,13 @@ def main(args=sys.argv):
|
||||
|
||||
if args.print_window_size:
|
||||
screen_size_function.ans = None
|
||||
with open('/dev/tty') as tty:
|
||||
with open(os.ctermid()) as tty:
|
||||
ss = screen_size_function(tty)()
|
||||
print('{}x{}'.format(ss.width, ss.height), end='')
|
||||
raise SystemExit(0)
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open('/dev/tty', 'w')
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
screen_size = screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from kitty.constants import is_macos, is_wayland
|
||||
from kitty.cli import parse_args
|
||||
|
||||
from kitty.constants import is_macos, is_wayland
|
||||
|
||||
OPTIONS = r'''
|
||||
--lines
|
||||
@@ -123,7 +123,8 @@ def main(sys_args):
|
||||
global args
|
||||
if is_macos or is_wayland:
|
||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||
call_xprop('-version', silent=True) # ensure xprop is available
|
||||
if not shutil.which('xprop'):
|
||||
raise SystemExit('The xprop program is required for the panel kitten')
|
||||
args, items = parse_panel_args(sys_args[1:])
|
||||
if not items:
|
||||
raise SystemExit('You must specify the program to run')
|
||||
|
||||
@@ -74,7 +74,6 @@ class Resize(Handler):
|
||||
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
|
||||
@@ -17,23 +17,27 @@ def resolved_kitten(k):
|
||||
|
||||
def import_kitten_main_module(config_dir, kitten):
|
||||
if kitten.endswith('.py'):
|
||||
path_modified = False
|
||||
path = os.path.expanduser(kitten)
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
path = os.path.abspath(path)
|
||||
if os.path.dirname(path):
|
||||
sys.path.insert(0, os.path.dirname(path))
|
||||
path_modified = True
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
code = compile(src, path, 'exec')
|
||||
g = {'__name__': 'kitten'}
|
||||
exec(code, g)
|
||||
hr = g.get('handle_result', lambda *a, **kw: None)
|
||||
if path_modified:
|
||||
del sys.path[0]
|
||||
return {'start': g['main'], 'end': hr}
|
||||
else:
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
|
||||
|
||||
def create_kitten_handler(kitten, orig_args):
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from ..tui.operations import styled
|
||||
|
||||
from ..tui.operations import styled
|
||||
|
||||
OPTIONS = '''\
|
||||
--title
|
||||
@@ -17,7 +18,7 @@ The title for the error message.
|
||||
|
||||
def real_main(args):
|
||||
error_message = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
sys.stdin = open(os.ctermid())
|
||||
msg = 'Show an error message'
|
||||
args, items = parse_args(args, OPTIONS, '', msg, 'hints')
|
||||
print(styled(args.title, fg_intense=True, fg='red', bold=True))
|
||||
|
||||
@@ -102,7 +102,7 @@ def main(args):
|
||||
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
|
||||
if len(server_args) > 1:
|
||||
command_to_execute = ["'{}'".format(c.replace("'", """'"'"'""")) for c in server_args[1:]]
|
||||
command_to_execute = 'cmd=({}); exec "$cmd"'.format(' '.join(command_to_execute))
|
||||
command_to_execute = 'exec ' + ' '.join(command_to_execute)
|
||||
else:
|
||||
command_to_execute = ''
|
||||
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
|
||||
|
||||
@@ -10,14 +10,19 @@ class Handler:
|
||||
|
||||
image_manager_class = None
|
||||
|
||||
def _initialize(self, screen_size, quit_loop, wakeup, start_job, debug, image_manager=None):
|
||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
||||
self.wakeup = wakeup
|
||||
def _initialize(self, screen_size, term_manager, schedule_write, tui_loop, debug, image_manager=None):
|
||||
self.screen_size = screen_size
|
||||
self._term_manager = term_manager
|
||||
self._tui_loop = tui_loop
|
||||
self._schedule_write = schedule_write
|
||||
self.debug = debug
|
||||
self.start_job = start_job
|
||||
self.cmd = commander(self)
|
||||
self.image_manager = image_manager
|
||||
|
||||
@property
|
||||
def asyncio_loop(self):
|
||||
return self._tui_loop.asycio_loop
|
||||
|
||||
def add_shortcut(self, action, key, mods=None, is_text=False):
|
||||
if not hasattr(self, '_text_shortcuts'):
|
||||
self._text_shortcuts, self._key_shortcuts = {}, {}
|
||||
@@ -38,7 +43,6 @@ class Handler:
|
||||
self.initialize()
|
||||
|
||||
def __exit__(self, *a):
|
||||
del self.write_buf[:]
|
||||
del self.debug.fobj
|
||||
self.finalize()
|
||||
if self.image_manager is not None:
|
||||
@@ -53,8 +57,11 @@ class Handler:
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
|
||||
def quit_loop(self, return_code=None):
|
||||
self._tui_loop.quit(return_code)
|
||||
|
||||
def on_term(self):
|
||||
self.quit_loop(1)
|
||||
self._tui_loop.quit(1)
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
pass
|
||||
@@ -71,12 +78,6 @@ class Handler:
|
||||
def on_eot(self):
|
||||
pass
|
||||
|
||||
def on_wakeup(self):
|
||||
pass
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
pass
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
pass
|
||||
|
||||
@@ -86,7 +87,7 @@ class Handler:
|
||||
def write(self, data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
self.write_buf.append(data)
|
||||
self._schedule_write(data)
|
||||
|
||||
def print(self, *args, sep=' ', end='\r\n'):
|
||||
data = sep.join(map(str, args)) + end
|
||||
|
||||
@@ -71,13 +71,15 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
||||
from tempfile import NamedTemporaryFile
|
||||
width, height = m.width, m.height
|
||||
cmd = ['convert', '-background', 'none', path]
|
||||
scaled = False
|
||||
if scale_up:
|
||||
if width < available_width:
|
||||
r = available_width / width
|
||||
width, height = available_width, int(height * r)
|
||||
if width > available_width or height > available_height:
|
||||
scaled = True
|
||||
if scaled or width > available_width or height > available_height:
|
||||
width, height = fit_image(width, height, available_width, available_height)
|
||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
||||
cmd += ['-resize', '{}x{}!'.format(width, height)]
|
||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
|
||||
run_imagemagick(path, cmd + [outfile.name])
|
||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||
@@ -88,7 +90,10 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
||||
if sz < expected_size:
|
||||
missing = expected_size - sz
|
||||
if missing % (bytes_per_pixel * width) != 0:
|
||||
raise ConvertFailed(path, 'ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
||||
raise ConvertFailed(
|
||||
path, 'ImageMagick failed to convert {} correctly,'
|
||||
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
|
||||
path, sz, expected_size, width, height, bytes_per_pixel))
|
||||
height -= missing // (bytes_per_pixel * width)
|
||||
|
||||
return outfile.name, width, height
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import asyncio
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
@@ -10,13 +11,12 @@ import selectors
|
||||
import signal
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from contextlib import closing, contextmanager
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from queue import Empty, Queue
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty,
|
||||
safe_pipe
|
||||
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty
|
||||
)
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
||||
@@ -74,29 +74,6 @@ class TermManager:
|
||||
del self.tty_fd, self.original_termios
|
||||
|
||||
|
||||
class SignalManager:
|
||||
|
||||
def __init__(self, on_sigwinch, on_sigterm, on_sigint):
|
||||
self.on_sigwinch = on_sigwinch
|
||||
self.on_sigterm = on_sigterm
|
||||
self.on_sigint = on_sigint
|
||||
|
||||
def __enter__(self):
|
||||
for x in ('winch', 'term', 'int'):
|
||||
attr = 'on_sig' + x
|
||||
handler = getattr(self, attr)
|
||||
old_handler = signal.signal(getattr(signal, 'SIG' + x.upper()), handler)
|
||||
setattr(self, attr, old_handler)
|
||||
|
||||
def __exit__(self, *a):
|
||||
for x in ('winch', 'term', 'int'):
|
||||
attr = 'on_sig' + x
|
||||
val = getattr(self, attr)
|
||||
if val is None:
|
||||
val = signal.SIG_DFL
|
||||
signal.signal(getattr(signal, 'SIG' + x.upper()), val)
|
||||
|
||||
|
||||
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
|
||||
DRAG = REPEAT
|
||||
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
|
||||
@@ -150,22 +127,38 @@ class UnhandledException(Handler):
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
on_eot = on_term = on_interrupt
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
class SignalManager:
|
||||
|
||||
def __init__(self, loop, on_winch, on_interrupt, on_term):
|
||||
self.asycio_loop = loop
|
||||
self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term
|
||||
|
||||
def __enter__(self):
|
||||
tuple(map(lambda x: self.asycio_loop.add_signal_handler(*x), (
|
||||
(signal.SIGWINCH, self.on_winch),
|
||||
(signal.SIGINT, self.on_interrupt),
|
||||
(signal.SIGTERM, self.on_term)
|
||||
)))
|
||||
|
||||
def __exit__(self, *a):
|
||||
tuple(map(self.asycio_loop.remove_signal_handler, (
|
||||
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
|
||||
|
||||
|
||||
class Loop:
|
||||
|
||||
def __init__(self,
|
||||
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
|
||||
self.wakeup_read_fd, self.wakeup_write_fd = safe_pipe()
|
||||
# For some reason on macOS the DefaultSelector fails when tty_fd is
|
||||
# open('/dev/tty')
|
||||
self.sel = s = selectors.PollSelector()
|
||||
s.register(self.wakeup_read_fd, selectors.EVENT_READ)
|
||||
if is_macos:
|
||||
# On macOS PTY devices are not supported by the KqueueSelector
|
||||
self.asycio_loop = asyncio.SelectorEventLoop(selectors.PollSelector())
|
||||
asyncio.set_event_loop(self.asycio_loop)
|
||||
else:
|
||||
self.asycio_loop = asyncio.get_event_loop()
|
||||
self.return_code = 0
|
||||
self.read_allowed = True
|
||||
self.read_buf = ''
|
||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
try:
|
||||
@@ -178,28 +171,8 @@ class Loop:
|
||||
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
|
||||
if self.sanitize_bracketed_paste:
|
||||
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
|
||||
self.jobs_queue = Queue()
|
||||
|
||||
def start_job(self, job_id, func, *args, **kw):
|
||||
from threading import Thread
|
||||
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def _run_job(self, job_id, func, *args, **kw):
|
||||
try:
|
||||
result = func(*args, **kw)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
|
||||
else:
|
||||
entry = {'id': job_id, 'result': result}
|
||||
self.jobs_queue.put(entry)
|
||||
self._wakeup_write(b'j')
|
||||
|
||||
def _read_ready(self, handler, fd):
|
||||
if not self.read_allowed:
|
||||
return
|
||||
try:
|
||||
data = os.read(fd, io.DEFAULT_BUFFER_SIZE)
|
||||
except BlockingIOError:
|
||||
@@ -219,6 +192,7 @@ class Loop:
|
||||
finally:
|
||||
del self.handler
|
||||
|
||||
# terminal input callbacks {{{
|
||||
def _on_text(self, text):
|
||||
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
|
||||
text = self.sanitize_ibp_pat.sub('', text)
|
||||
@@ -292,127 +266,99 @@ class Loop:
|
||||
elif apc.startswith('G'):
|
||||
if self.handler.image_manager is not None:
|
||||
self.handler.image_manager.handle_response(apc)
|
||||
# }}}
|
||||
|
||||
def _write_ready(self, handler, fd):
|
||||
if len(handler.write_buf) > self.iov_limit:
|
||||
handler.write_buf[self.iov_limit - 1] = b''.join(handler.write_buf[self.iov_limit - 1:])
|
||||
del handler.write_buf[self.iov_limit:]
|
||||
sizes = tuple(map(len, handler.write_buf))
|
||||
if len(self.write_buf) > self.iov_limit:
|
||||
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
||||
del self.write_buf[self.iov_limit:]
|
||||
sizes = tuple(map(len, self.write_buf))
|
||||
try:
|
||||
written = os.writev(fd, handler.write_buf)
|
||||
written = os.writev(fd, self.write_buf)
|
||||
except BlockingIOError:
|
||||
return
|
||||
if not written:
|
||||
raise EOFError('The output stream is closed')
|
||||
if written >= sum(sizes):
|
||||
handler.write_buf = []
|
||||
self.write_buf = []
|
||||
self.asycio_loop.remove_writer(fd)
|
||||
self.waiting_for_writes = False
|
||||
else:
|
||||
consumed = 0
|
||||
for i, buf in enumerate(handler.write_buf):
|
||||
for i, buf in enumerate(self.write_buf):
|
||||
if not written:
|
||||
break
|
||||
if len(buf) <= written:
|
||||
written -= len(buf)
|
||||
consumed += 1
|
||||
continue
|
||||
handler.write_buf[i] = buf[written:]
|
||||
self.write_buf[i] = buf[written:]
|
||||
break
|
||||
del handler.write_buf[:consumed]
|
||||
|
||||
def _wakeup_ready(self, handler, fd):
|
||||
data = os.read(fd, 1024)
|
||||
if b'r' in data:
|
||||
self._get_screen_size.changed = True
|
||||
handler.on_resize(self._get_screen_size())
|
||||
if b't' in data:
|
||||
handler.on_term()
|
||||
if b'i' in data:
|
||||
handler.on_interrupt()
|
||||
if b'j' in data:
|
||||
while True:
|
||||
try:
|
||||
entry = self.jobs_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
job_id = entry.pop('id')
|
||||
handler.on_job_done(job_id, entry)
|
||||
if b'1' in data:
|
||||
handler.on_wakeup()
|
||||
|
||||
def _wakeup_write(self, val):
|
||||
while not os.write(self.wakeup_write_fd, val):
|
||||
pass
|
||||
|
||||
def _on_sigwinch(self, signum, frame):
|
||||
self._wakeup_write(b'r')
|
||||
|
||||
def _on_sigterm(self, signum, frame):
|
||||
self._wakeup_write(b't')
|
||||
|
||||
def _on_sigint(self, signum, frame):
|
||||
self._wakeup_write(b'i')
|
||||
del self.write_buf[:consumed]
|
||||
|
||||
def quit(self, return_code=None):
|
||||
self.read_allowed = False
|
||||
if return_code is not None:
|
||||
self.return_code = return_code
|
||||
self.asycio_loop.stop()
|
||||
|
||||
def wakeup(self):
|
||||
self._wakeup_write(b'1')
|
||||
def loop_impl(self, handler, term_manager, image_manager=None):
|
||||
self.write_buf = []
|
||||
tty_fd = term_manager.tty_fd
|
||||
tb = None
|
||||
self.waiting_for_writes = True
|
||||
|
||||
def _modify_output_selector(self, tty_fd, waiting_for_write):
|
||||
events = selectors.EVENT_READ
|
||||
if waiting_for_write:
|
||||
events |= selectors.EVENT_WRITE
|
||||
self.sel.modify(tty_fd, events)
|
||||
def schedule_write(data):
|
||||
self.write_buf.append(data)
|
||||
if not self.waiting_for_writes:
|
||||
self.asycio_loop.add_writer(tty_fd, self._write_ready, handler, tty_fd)
|
||||
self.waiting_for_writes = True
|
||||
|
||||
def loop_impl(self, handler, tty_fd, image_manager=None, waiting_for_write=True):
|
||||
read_ready, write_ready, wakeup_ready = self._read_ready, self._write_ready, self._wakeup_ready
|
||||
select = self.sel.select
|
||||
handler._initialize(self._get_screen_size(), self.quit, self.wakeup, self.start_job, debug, image_manager)
|
||||
def handle_exception(loop, context):
|
||||
nonlocal tb
|
||||
loop.stop()
|
||||
tb = context['message']
|
||||
exc = context.get('exception')
|
||||
if exc is not None:
|
||||
import traceback
|
||||
tb += '\n' + ''.join(traceback.format_exception(exc.__class__, exc, exc.__traceback__))
|
||||
|
||||
self.asycio_loop.set_exception_handler(handle_exception)
|
||||
handler._initialize(self._get_screen_size(), term_manager, schedule_write, self, debug, image_manager)
|
||||
with handler:
|
||||
while True:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(tty_fd, waiting_for_write)
|
||||
for key, mask in select():
|
||||
fd = key.fd
|
||||
if fd == tty_fd:
|
||||
if mask & selectors.EVENT_READ:
|
||||
read_ready(handler, fd)
|
||||
if mask & selectors.EVENT_WRITE:
|
||||
write_ready(handler, fd)
|
||||
else:
|
||||
wakeup_ready(handler, fd)
|
||||
self.asycio_loop.add_reader(
|
||||
tty_fd, self._read_ready, handler, tty_fd)
|
||||
self.asycio_loop.add_writer(
|
||||
tty_fd, self._write_ready, handler, tty_fd)
|
||||
self.asycio_loop.run_forever()
|
||||
self.asycio_loop.remove_reader(tty_fd)
|
||||
if self.waiting_for_writes:
|
||||
self.asycio_loop.remove_writer(tty_fd)
|
||||
return tb
|
||||
|
||||
def loop(self, handler):
|
||||
tb = None
|
||||
signal_manager = SignalManager(self._on_sigwinch, self._on_sigterm, self._on_sigint)
|
||||
with closing(self.sel), TermManager() as term_manager, signal_manager:
|
||||
self.sel.register(term_manager.tty_fd, selectors.EVENT_READ | selectors.EVENT_WRITE)
|
||||
|
||||
def _on_sigwinch():
|
||||
self._get_screen_size.changed = True
|
||||
handler.screen_size = self._get_screen_size()
|
||||
handler.on_resize(handler.screen_size)
|
||||
|
||||
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
||||
with TermManager() as term_manager, signal_manager:
|
||||
self._get_screen_size = screen_size_function(term_manager.tty_fd)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
image_manager = None
|
||||
if handler.image_manager_class is not None:
|
||||
image_manager = handler.image_manager_class(handler)
|
||||
try:
|
||||
self.loop_impl(handler, term_manager.tty_fd, image_manager)
|
||||
tb = self.loop_impl(handler, term_manager, image_manager)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
|
||||
term_manager.extra_finalize = b''.join(handler.write_buf).decode('utf-8')
|
||||
term_manager.extra_finalize = b''.join(self.write_buf).decode('utf-8')
|
||||
if tb is not None:
|
||||
self.return_code = 1
|
||||
self._report_error_loop(tb, term_manager)
|
||||
|
||||
def _report_error_loop(self, tb, term_manager):
|
||||
handler = UnhandledException(tb)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
self.loop_impl(handler, term_manager.tty_fd, waiting_for_write=False)
|
||||
self.loop_impl(UnhandledException(tb), term_manager)
|
||||
|
||||
@@ -455,7 +455,6 @@ class UnicodeInput(Handler):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.refresh()
|
||||
|
||||
|
||||
|
||||
2
kittens/unicode_input/names.h
generated
2
kittens/unicode_input/names.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-06-14
|
||||
// unicode data, built from the unicode standard on: 2018-08-04
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -118,10 +118,13 @@ class Boss:
|
||||
return os_window_id
|
||||
|
||||
def list_os_windows(self):
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'tabs': list(tm.list_tabs()),
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -152,11 +155,18 @@ class Boss:
|
||||
return
|
||||
if w is not None:
|
||||
yield w
|
||||
return
|
||||
if field == 'env':
|
||||
kp, vp = exp.partition('=')[::2]
|
||||
if vp:
|
||||
pat = tuple(map(re.compile, (kp, vp)))
|
||||
else:
|
||||
pat = re.compile(kp), None
|
||||
else:
|
||||
pat = re.compile(exp)
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
|
||||
def tab_for_window(self, window):
|
||||
for tab in self.all_tabs:
|
||||
@@ -183,17 +193,20 @@ class Boss:
|
||||
yield tab
|
||||
|
||||
def set_active_window(self, window):
|
||||
for tm in self.os_window_map.values():
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
for tab in tm:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
if tab is not self.active_tab:
|
||||
tm.set_active_tab(tab)
|
||||
tab.set_active_window(w)
|
||||
return
|
||||
return os_window_id
|
||||
|
||||
def _new_os_window(self, args, cwd_from=None):
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
if isinstance(args, SpecialWindowInstance):
|
||||
sw = args
|
||||
else:
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
||||
return self.add_os_window(startup_session)
|
||||
|
||||
@@ -315,6 +328,23 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.resize()
|
||||
|
||||
def clear_terminal(self, action, only_active):
|
||||
if only_active:
|
||||
windows = []
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
windows.append(w)
|
||||
else:
|
||||
windows = self.all_windows
|
||||
reset = action == 'reset'
|
||||
how = 3 if action == 'scrollback' else 2
|
||||
for w in windows:
|
||||
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||
if reset:
|
||||
w.screen.reset()
|
||||
else:
|
||||
w.screen.erase_in_display(how, False)
|
||||
|
||||
def increase_font_size(self): # legacy
|
||||
cfs = global_font_size()
|
||||
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
|
||||
@@ -478,6 +508,8 @@ class Boss:
|
||||
if w is not None:
|
||||
tm = self.os_window_map.get(w.os_window_id)
|
||||
if tm is not None:
|
||||
tm.update_tab_bar_data()
|
||||
tm.mark_tab_bar_dirty()
|
||||
t = tm.tab_for_id(w.tab_id)
|
||||
if t is not None:
|
||||
t.relayout_borders()
|
||||
@@ -575,9 +607,14 @@ class Boss:
|
||||
output += str(s.linebuf.line(i))
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), input_data=None):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
|
||||
if window is None:
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
else:
|
||||
w = window
|
||||
tab = w.tabref()
|
||||
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
orig_args, args = list(args), list(args)
|
||||
from kittens.runner import create_kitten_handler
|
||||
@@ -606,11 +643,14 @@ class Boss:
|
||||
stdin=data,
|
||||
env={
|
||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||
'KITTY_CHILD_PID': w.child.pid,
|
||||
'PYTHONWARNINGS': 'ignore',
|
||||
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
|
||||
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
|
||||
},
|
||||
overlay_for=w.id))
|
||||
cwd=w.cwd_of_child,
|
||||
overlay_for=w.id
|
||||
))
|
||||
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||
return overlay_window
|
||||
|
||||
@@ -649,23 +689,19 @@ class Boss:
|
||||
break
|
||||
|
||||
def kitty_shell(self, window_type):
|
||||
cmd = [kitty_exe(), '@']
|
||||
cmd = ['@', kitty_exe(), '@']
|
||||
if window_type == 'tab':
|
||||
window = self._new_tab(cmd).active_window
|
||||
self._new_tab(cmd).active_window
|
||||
elif window_type == 'os_window':
|
||||
os_window_id = self._new_os_window(cmd)
|
||||
window = self.os_window_map[os_window_id].active_window
|
||||
self.os_window_map[os_window_id].active_window
|
||||
elif window_type == 'overlay':
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
||||
else:
|
||||
window = None
|
||||
tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
||||
else:
|
||||
window = self._new_window(cmd)
|
||||
if window is not None:
|
||||
window.allow_remote_control = True
|
||||
self._new_window(cmd)
|
||||
|
||||
def switch_focus_to(self, window_idx):
|
||||
tab = self.active_tab
|
||||
@@ -749,7 +785,7 @@ class Boss:
|
||||
if arg == '@ansi_screen':
|
||||
return w.as_text(as_ansi=True)
|
||||
|
||||
if args[0].startswith('@'):
|
||||
if args[0].startswith('@') and args[0] != '@':
|
||||
stdin = data_for_at(args[0]) or None
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode('utf-8')
|
||||
|
||||
@@ -22,8 +22,10 @@ uniform sampler2DArray sprites;
|
||||
in float effective_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 cursor_pos;
|
||||
in vec3 strike_pos;
|
||||
in vec3 foreground;
|
||||
in vec4 cursor_color_vec;
|
||||
in vec3 decoration_fg;
|
||||
in float colored_sprite;
|
||||
#endif
|
||||
@@ -58,10 +60,12 @@ vec4 calculate_foreground() {
|
||||
float text_alpha = text_fg.a;
|
||||
float underline_alpha = texture(sprites, underline_pos).a;
|
||||
float strike_alpha = texture(sprites, strike_pos).a;
|
||||
float cursor_alpha = texture(sprites, cursor_pos).a;
|
||||
// Since strike and text are the same color, we simply add the alpha values
|
||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||
// Underline color might be different, so alpha blend
|
||||
return alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
||||
vec4 ans = alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
||||
return mix(ans, cursor_color_vec, cursor_alpha);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
||||
#define STRIKE_SHIFT {STRIKE_SHIFT}
|
||||
#define DIM_SHIFT {DIM_SHIFT}
|
||||
#define CURSOR_TEXT_COLOR {CURSOR_TEXT_COLOR}
|
||||
|
||||
// Inputs {{{
|
||||
layout(std140) uniform CellRenderData {
|
||||
@@ -12,7 +13,8 @@ layout(std140) uniform CellRenderData {
|
||||
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
|
||||
uint xnum, ynum, cursor_x, cursor_y, cursor_w;
|
||||
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||
float cursor_x, cursor_y, cursor_w;
|
||||
|
||||
uint color_table[256];
|
||||
};
|
||||
@@ -53,6 +55,8 @@ uniform float inactive_text_alpha;
|
||||
uniform float dim_opacity;
|
||||
out vec3 sprite_pos;
|
||||
out vec3 underline_pos;
|
||||
out vec3 cursor_pos;
|
||||
out vec4 cursor_color_vec;
|
||||
out vec3 strike_pos;
|
||||
out vec3 foreground;
|
||||
out vec3 decoration_fg;
|
||||
@@ -110,9 +114,18 @@ vec3 choose_color(float q, vec3 a, vec3 b) {
|
||||
return mix(b, a, q);
|
||||
}
|
||||
|
||||
float is_cursor(uint x, uint y) {
|
||||
if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0;
|
||||
return 0.0;
|
||||
float are_integers_equal(float a, float b) { // return 1 iff equal otherwise 0
|
||||
float delta = abs(a - b); // delta can be 0, 1 or larger
|
||||
return step(delta, 0.5); // 0 if 0.5 < delta else 1
|
||||
}
|
||||
|
||||
float is_cursor(uint xi, uint y) {
|
||||
float x = float(xi);
|
||||
float y_equal = are_integers_equal(float(y), cursor_y);
|
||||
float x1_equal = are_integers_equal(x, cursor_x);
|
||||
float x2_equal = are_integers_equal(x, cursor_w);
|
||||
float x_equal = step(0.5, x1_equal + x2_equal);
|
||||
return step(2.0, x_equal + y_equal);
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -141,7 +154,9 @@ void main() {
|
||||
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
|
||||
int fg_index = fg_index_map[is_inverted];
|
||||
int bg_index = 1 - fg_index;
|
||||
float cursor = is_cursor(c, r);
|
||||
float cell_has_cursor = is_cursor(c, r);
|
||||
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
|
||||
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
|
||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||
// }}}
|
||||
|
||||
@@ -166,8 +181,10 @@ void main() {
|
||||
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
|
||||
|
||||
// Cursor
|
||||
foreground = choose_color(cursor, bg, foreground);
|
||||
decoration_fg = choose_color(cursor, bg, decoration_fg);
|
||||
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
|
||||
foreground = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, foreground);
|
||||
decoration_fg = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, decoration_fg);
|
||||
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
@@ -182,17 +199,17 @@ void main() {
|
||||
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
|
||||
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
|
||||
// Cursor must not be affected by background_opacity
|
||||
bg_alpha = mix(bg_alpha, 1.0, cursor);
|
||||
bg_alpha = mix(bg_alpha, 1.0, cell_has_block_cursor);
|
||||
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
|
||||
#endif
|
||||
|
||||
#if defined(SPECIAL) || defined(SIMPLE)
|
||||
// Selection and cursor
|
||||
bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg);
|
||||
background = choose_color(cursor, color_to_vec(cursor_color), bg);
|
||||
background = choose_color(cell_has_block_cursor, color_to_vec(cursor_color), bg);
|
||||
#ifdef SPECIAL
|
||||
// bg_alpha should be 1 if cursor/selection otherwise 0
|
||||
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cursor));
|
||||
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cell_has_block_cursor));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ extern PyTypeObject Screen_Type;
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static void (*parse_func)(Screen*, PyObject*);
|
||||
static void (*parse_func)(Screen*, PyObject*, double);
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
@@ -246,9 +246,18 @@ add_child(ChildMonitor *self, PyObject *args) {
|
||||
}
|
||||
|
||||
bool
|
||||
schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
schedule_write_to_child(unsigned long id, unsigned int num, ...) {
|
||||
ChildMonitor *self = the_monitor;
|
||||
bool found = false;
|
||||
const char *data;
|
||||
size_t sz = 0;
|
||||
va_list ap;
|
||||
va_start(ap, num);
|
||||
for (unsigned int i = 0; i < num; i++) {
|
||||
data = va_arg(ap, const char*);
|
||||
sz += va_arg(ap, size_t);
|
||||
}
|
||||
va_end(ap);
|
||||
children_mutex(lock);
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].id == id) {
|
||||
@@ -266,8 +275,14 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
||||
}
|
||||
memcpy(screen->write_buf + screen->write_buf_used, data, sz);
|
||||
screen->write_buf_used += sz;
|
||||
va_start(ap, num);
|
||||
for (unsigned int i = 0; i < num; i++) {
|
||||
data = va_arg(ap, const char*);
|
||||
size_t dsz = va_arg(ap, size_t);
|
||||
memcpy(screen->write_buf + screen->write_buf_used, data, dsz);
|
||||
screen->write_buf_used += dsz;
|
||||
}
|
||||
va_end(ap);
|
||||
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
|
||||
screen->write_buf_sz = BUFSIZ;
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
@@ -288,7 +303,7 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
||||
unsigned long id, sz;
|
||||
const char *data;
|
||||
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
||||
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
|
||||
if (schedule_write_to_child(id, 1, data, (size_t)sz)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
@@ -314,13 +329,17 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
static inline void
|
||||
do_parse(ChildMonitor *self, Screen *screen, double now) {
|
||||
screen_mutex(lock, read);
|
||||
if (screen->read_buf_sz) {
|
||||
if (screen->read_buf_sz || screen->pending_mode.used) {
|
||||
double time_since_new_input = now - screen->new_input_at;
|
||||
if (time_since_new_input >= OPT(input_delay)) {
|
||||
parse_func(screen, self->dump_callback);
|
||||
if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
|
||||
screen->read_buf_sz = 0;
|
||||
bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ;
|
||||
parse_func(screen, self->dump_callback, now);
|
||||
if (read_buf_full) wakeup_io_loop(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);
|
||||
set_maximum_wait(screen->pending_mode.wait_time - time_since_pending);
|
||||
}
|
||||
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
|
||||
}
|
||||
screen_mutex(unlock, read);
|
||||
@@ -491,14 +510,6 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
|
||||
|
||||
static double last_render_at = -DBL_MAX;
|
||||
|
||||
static inline double
|
||||
cursor_width(double w, bool vert, OSWindow *os_window) {
|
||||
double dpi = vert ? os_window->fonts_data->logical_dpi_x : os_window->fonts_data->logical_dpi_y;
|
||||
double ans = w * dpi / 72.0; // as pixels
|
||||
double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height);
|
||||
return ans * factor;
|
||||
}
|
||||
|
||||
extern void cocoa_update_title(PyObject*);
|
||||
|
||||
static inline void
|
||||
@@ -526,23 +537,6 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
|
||||
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
|
||||
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
|
||||
ans->is_focused = os_window->is_focused;
|
||||
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
|
||||
double left = rd->xstart + cursor->x * rd->dx;
|
||||
double top = rd->ystart - cursor->y * rd->dy;
|
||||
unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
|
||||
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
|
||||
double bottom = top - rd->dy;
|
||||
switch (ans->shape) {
|
||||
case CURSOR_UNDERLINE:
|
||||
top = bottom + cursor_width(2.0, false, os_window);
|
||||
break;
|
||||
case CURSOR_BLOCK:
|
||||
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -614,9 +608,6 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
||||
if (w->visible && WD.screen) {
|
||||
bool is_active_window = i == tab->active_window;
|
||||
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window, true);
|
||||
if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
|
||||
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
|
||||
}
|
||||
if (WD.screen->start_visual_bell_at != 0) {
|
||||
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
||||
set_maximum_wait(bell_left);
|
||||
@@ -1065,9 +1056,6 @@ io_loop(void *data) {
|
||||
|
||||
// {{{ Talk thread functions
|
||||
|
||||
#define MAX_PEERS 256
|
||||
#define MAX_LISTENERS 2
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
@@ -1086,15 +1074,18 @@ static PeerWriteData empty_pwd = {.fd = -1, 0};
|
||||
|
||||
typedef struct {
|
||||
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
||||
struct pollfd fds[MAX_PEERS + MAX_LISTENERS + 1];
|
||||
PeerReadData reads[MAX_LISTENERS];
|
||||
PeerWriteData writes[MAX_LISTENERS];
|
||||
PeerWriteData queued_writes[MAX_LISTENERS];
|
||||
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
|
||||
struct pollfd *fds;
|
||||
PeerReadData *reads;
|
||||
PeerWriteData *writes;
|
||||
PeerWriteData *queued_writes;
|
||||
int wakeup_fds[2];
|
||||
pthread_mutex_t peer_lock;
|
||||
} TalkData;
|
||||
|
||||
static TalkData talk_data = {0};
|
||||
typedef struct pollfd PollFD;
|
||||
#define PEER_LIMIT 256
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
|
||||
|
||||
static inline bool
|
||||
@@ -1106,11 +1097,16 @@ accept_peer(int listen_fd, bool shutting_down) {
|
||||
return false;
|
||||
}
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_reads < arraysz(talk_data.reads)) {
|
||||
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
||||
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
|
||||
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
||||
talk_data.num_talk_fds++;
|
||||
} else nuke_socket(peer);
|
||||
} else {
|
||||
log_error("Too many peers want to talk, ignoring one.");
|
||||
nuke_socket(peer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1239,8 +1235,10 @@ move_queued_writes() {
|
||||
while (talk_data.num_queued_writes) {
|
||||
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_writes < arraysz(talk_data.writes)) {
|
||||
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
||||
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
|
||||
talk_data.writes[talk_data.num_writes++] = *src;
|
||||
talk_data.num_talk_fds++;
|
||||
} else {
|
||||
@@ -1259,6 +1257,7 @@ talk_loop(void *data) {
|
||||
set_thread_name("KittyPeerMon");
|
||||
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
||||
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
|
||||
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
|
||||
#define add_listener(which) \
|
||||
if (self->which > -1) { \
|
||||
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
||||
@@ -1289,6 +1288,7 @@ talk_loop(void *data) {
|
||||
}
|
||||
end:
|
||||
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
|
||||
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1296,7 +1296,8 @@ static inline bool
|
||||
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
||||
bool ok = false;
|
||||
peer_mutex(lock);
|
||||
if (talk_data.num_queued_writes < arraysz(talk_data.queued_writes)) {
|
||||
if (talk_data.num_queued_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
|
||||
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
||||
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
||||
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
static inline char**
|
||||
serialize_string_tuple(PyObject *src) {
|
||||
@@ -44,18 +45,29 @@ write_to_stderr(const char *text) {
|
||||
}
|
||||
}
|
||||
|
||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||
|
||||
static inline void
|
||||
wait_for_terminal_ready(int fd) {
|
||||
char data;
|
||||
while(1) {
|
||||
int ret = read(fd, &data, 1);
|
||||
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *argv_p, *env_p;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
|
||||
char *cwd, *exe;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiiiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd)) return NULL;
|
||||
char name[2048] = {0};
|
||||
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
char **argv = serialize_string_tuple(argv_p);
|
||||
char **env = serialize_string_tuple(env_p);
|
||||
|
||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||
pid_t pid = fork();
|
||||
switch(pid) {
|
||||
case 0:
|
||||
@@ -63,6 +75,17 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
// Use only signal-safe functions (man 7 signal-safety)
|
||||
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
|
||||
if (setsid() == -1) exit_on_err("setsid() in child process failed");
|
||||
|
||||
// Establish the controlling terminal (see man 7 credentials)
|
||||
int tfd = open(name, O_RDWR);
|
||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||
#ifdef TIOCSTTY
|
||||
// On BSD open() does not establish the controlling terminal
|
||||
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||
#endif
|
||||
close(tfd);
|
||||
|
||||
// Redirect stdin/stdout/stderr to the pty
|
||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (stdin_read_fd > -1) {
|
||||
@@ -74,12 +97,14 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
}
|
||||
close(slave);
|
||||
close(master);
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
// Establish the controlling terminal (see man 7 credentials)
|
||||
int tfd = open(name, O_RDWR);
|
||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||
close(tfd);
|
||||
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
||||
close(ready_write_fd);
|
||||
wait_for_terminal_ready(ready_read_fd);
|
||||
close(ready_read_fd);
|
||||
|
||||
// Close any extra fds inherited from parent
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
environ = env;
|
||||
execvp(exe, argv);
|
||||
|
||||
@@ -9,33 +9,81 @@ import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import is_macos, shell_path, terminfo_dir
|
||||
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process
|
||||
|
||||
def cwd_of_process(pid):
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cwd_of_process
|
||||
ans = cwd_of_process(pid)
|
||||
else:
|
||||
def cwd_of_process(pid):
|
||||
return os.path.realpath(_cwd(pid))
|
||||
|
||||
else:
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
||||
|
||||
def cwd_of_process(pid):
|
||||
ans = '/proc/{}/cwd'.format(pid)
|
||||
return os.path.realpath(ans)
|
||||
return os.path.realpath(ans)
|
||||
|
||||
def _environ_of_process(pid):
|
||||
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
|
||||
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
if is_macos:
|
||||
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
|
||||
raise NotImplementedError()
|
||||
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
||||
def parse_environ_block(data):
|
||||
"""Parse a C environ block of environment variables into a dictionary."""
|
||||
# The block is usually raw data from the target process. It might contain
|
||||
# trailing garbage and lines that do not look like assignments.
|
||||
ret = {}
|
||||
pos = 0
|
||||
|
||||
while True:
|
||||
next_pos = data.find("\0", pos)
|
||||
# nul byte at the beginning or double nul byte means finish
|
||||
if next_pos <= pos:
|
||||
break
|
||||
# there might not be an equals sign
|
||||
equal_pos = data.find("=", pos, next_pos)
|
||||
if equal_pos > pos:
|
||||
key = data[pos:equal_pos]
|
||||
value = data[equal_pos + 1:next_pos]
|
||||
ret[key] = value
|
||||
pos = next_pos + 1
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def environ_of_process(pid):
|
||||
return parse_environ_block(_environ_of_process(pid))
|
||||
|
||||
|
||||
def remove_cloexec(fd):
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
|
||||
|
||||
|
||||
def default_env():
|
||||
try:
|
||||
return default_env.env
|
||||
except AttributeError:
|
||||
return os.environ
|
||||
|
||||
|
||||
def set_default_env(val=None):
|
||||
env = os.environ.copy()
|
||||
if val:
|
||||
env.update(val)
|
||||
default_env.env = env
|
||||
|
||||
|
||||
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:]
|
||||
self.argv = argv
|
||||
if cwd_from is not None:
|
||||
try:
|
||||
@@ -58,12 +106,14 @@ class Child:
|
||||
remove_cloexec(slave)
|
||||
fast_data_types.set_iutf8(master, True)
|
||||
stdin, self.stdin = self.stdin, None
|
||||
ready_read_fd, ready_write_fd = os.pipe()
|
||||
remove_cloexec(ready_read_fd)
|
||||
if stdin is not None:
|
||||
stdin_read_fd, stdin_write_fd = os.pipe()
|
||||
remove_cloexec(stdin_read_fd)
|
||||
else:
|
||||
stdin_read_fd = stdin_write_fd = -1
|
||||
env = os.environ.copy()
|
||||
env = default_env().copy()
|
||||
env.update(self.env)
|
||||
env['TERM'] = self.opts.term
|
||||
env['COLORTERM'] = 'truecolor'
|
||||
@@ -76,16 +126,22 @@ class Child:
|
||||
# Some macOS machines need the shell to have argv[0] prefixed by
|
||||
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
|
||||
argv[0] = ('-' + exe.split('/')[-1])
|
||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd)
|
||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
self.child_fd = master
|
||||
if stdin is not None:
|
||||
os.close(stdin_read_fd)
|
||||
fast_data_types.thread_write(stdin_write_fd, stdin)
|
||||
os.close(ready_read_fd)
|
||||
self.terminal_ready_fd = ready_write_fd
|
||||
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
return pid
|
||||
|
||||
def mark_terminal_ready(self):
|
||||
os.close(self.terminal_ready_fd)
|
||||
self.terminal_ready_fd = -1
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
try:
|
||||
@@ -93,6 +149,13 @@ class Child:
|
||||
except Exception:
|
||||
return list(self.argv)
|
||||
|
||||
@property
|
||||
def environ(self):
|
||||
try:
|
||||
return environ_of_process(self.pid)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
@property
|
||||
def current_cwd(self):
|
||||
try:
|
||||
|
||||
286
kitty/cli.py
286
kitty/cli.py
@@ -8,9 +8,7 @@ import sys
|
||||
from collections import deque
|
||||
|
||||
from .conf.utils import resolve_config
|
||||
from .config import defaults, load_config
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||
|
||||
CONFIG_HELP = '''\
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
@@ -30,140 +28,10 @@ directory is always used and the above searching does not happen.
|
||||
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
|
||||
priority) than any user config files. It can be used to specify system-wide
|
||||
defaults for all users.
|
||||
'''.replace('{macos_confpath}', ':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else '')
|
||||
|
||||
OPTIONS = '''
|
||||
--class
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property
|
||||
|
||||
|
||||
--name
|
||||
condition=not is_macos
|
||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||
|
||||
|
||||
--title -T
|
||||
Set the window title. This will override any title set by the program running inside kitty. So
|
||||
only use this if you are running a program that does not set titles.
|
||||
|
||||
|
||||
--config -c
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
|
||||
|
||||
--detach
|
||||
type=bool-set
|
||||
condition=not is_macos
|
||||
Detach from the controlling terminal, if any
|
||||
|
||||
|
||||
--session
|
||||
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
||||
See the README file for details and an example.
|
||||
|
||||
|
||||
--hold
|
||||
type=bool-set
|
||||
Remain open after child process exits. Note that this only affects the first
|
||||
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
||||
|
||||
|
||||
--single-instance -1
|
||||
type=bool-set
|
||||
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
||||
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
||||
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
||||
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
||||
:option:`kitty --instance-group` option
|
||||
|
||||
|
||||
--instance-group
|
||||
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
||||
with the same :option:`kitty --instance-group` will result in new windows being created
|
||||
in the first :italic:`{appname}` instance within that group
|
||||
|
||||
|
||||
--wait-for-single-instance-window-close
|
||||
type=bool-set
|
||||
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||
regardless of this option.
|
||||
|
||||
|
||||
--listen-on
|
||||
Tell kitty to listen on the specified address for control
|
||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract
|
||||
UNIX sockets, not associated with a file, like this: :option:`{appname} --listen-on`=unix:@mykitty.
|
||||
To control kitty, you can send it commands with :italic:`kitty @` using the :option:`kitty @ --to` option
|
||||
to specify this address. Note that this option will be ignored, unless you set
|
||||
:opt:`allow_remote_control` to yes in :file:`kitty.conf`.
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
type=bool-set
|
||||
The current {appname} version
|
||||
|
||||
|
||||
--dump-commands
|
||||
type=bool-set
|
||||
Output commands received from child process to stdout
|
||||
|
||||
|
||||
--replay-commands
|
||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||
can open a new kitty window to replay the commands with::
|
||||
|
||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||
|
||||
|
||||
--dump-bytes
|
||||
Path to file in which to store the raw bytes received from the child process
|
||||
|
||||
|
||||
--debug-gl
|
||||
type=bool-set
|
||||
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
||||
instead of ignoring them. Useful when debugging rendering problems
|
||||
|
||||
|
||||
--debug-keyboard
|
||||
type=bool-set
|
||||
This option will cause kitty to print out key events as they are received
|
||||
|
||||
|
||||
--debug-font-fallback
|
||||
type=bool-set
|
||||
Print out information about the selection of fallback fonts for characters not present in the main font.
|
||||
|
||||
|
||||
--debug-config
|
||||
type=bool-set
|
||||
Print out information about the system and kitty configuration.
|
||||
|
||||
|
||||
--execute -e
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
'''.replace(
|
||||
'{macos_confpath}',
|
||||
(':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
|
||||
)
|
||||
|
||||
|
||||
def surround(x, start, end):
|
||||
@@ -232,7 +100,9 @@ def file(x):
|
||||
return italic(x)
|
||||
|
||||
|
||||
def parse_option_spec(spec=OPTIONS):
|
||||
def parse_option_spec(spec=None):
|
||||
if spec is None:
|
||||
spec = options_spec()
|
||||
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
||||
state = NORMAL
|
||||
lines = spec.splitlines()
|
||||
@@ -589,6 +459,141 @@ def parse_cmdline(oc, disabled, args=None):
|
||||
|
||||
def options_spec():
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
OPTIONS = '''
|
||||
--class
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property
|
||||
|
||||
|
||||
--name
|
||||
condition=not is_macos
|
||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||
|
||||
|
||||
--title -T
|
||||
Set the window title. This will override any title set by the program running inside kitty. So
|
||||
only use this if you are running a program that does not set titles.
|
||||
|
||||
|
||||
--config -c
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
|
||||
|
||||
--detach
|
||||
type=bool-set
|
||||
condition=not is_macos
|
||||
Detach from the controlling terminal, if any
|
||||
|
||||
|
||||
--session
|
||||
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
||||
See the README file for details and an example.
|
||||
|
||||
|
||||
--hold
|
||||
type=bool-set
|
||||
Remain open after child process exits. Note that this only affects the first
|
||||
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
||||
|
||||
|
||||
--single-instance -1
|
||||
type=bool-set
|
||||
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
||||
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
||||
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
||||
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
||||
:option:`kitty --instance-group` option
|
||||
|
||||
|
||||
--instance-group
|
||||
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
||||
with the same :option:`kitty --instance-group` will result in new windows being created
|
||||
in the first :italic:`{appname}` instance within that group
|
||||
|
||||
|
||||
--wait-for-single-instance-window-close
|
||||
type=bool-set
|
||||
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||
regardless of this option.
|
||||
|
||||
|
||||
--listen-on
|
||||
Tell kitty to listen on the specified address for control
|
||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
|
||||
also use abstract UNIX sockets, not associated with a file, like this:
|
||||
:option:`{appname} --listen-on`=unix:@mykitty. To control kitty, you can send
|
||||
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
|
||||
specify this address. This option will be ignored, unless you set
|
||||
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
|
||||
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
|
||||
option as it is read automatically from the environment.
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
type=bool-set
|
||||
The current {appname} version
|
||||
|
||||
|
||||
--dump-commands
|
||||
type=bool-set
|
||||
Output commands received from child process to stdout
|
||||
|
||||
|
||||
--replay-commands
|
||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||
can open a new kitty window to replay the commands with::
|
||||
|
||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||
|
||||
|
||||
--dump-bytes
|
||||
Path to file in which to store the raw bytes received from the child process
|
||||
|
||||
|
||||
--debug-gl
|
||||
type=bool-set
|
||||
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
||||
instead of ignoring them. Useful when debugging rendering problems
|
||||
|
||||
|
||||
--debug-keyboard
|
||||
type=bool-set
|
||||
This option will cause kitty to print out key events as they are received
|
||||
|
||||
|
||||
--debug-font-fallback
|
||||
type=bool-set
|
||||
Print out information about the selection of fallback fonts for characters not present in the main font.
|
||||
|
||||
|
||||
--debug-config
|
||||
type=bool-set
|
||||
Print out information about the system and kitty configuration.
|
||||
|
||||
|
||||
--execute -e
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
options_spec.ans = OPTIONS.format(
|
||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
||||
|
||||
@@ -596,6 +601,10 @@ def options_spec():
|
||||
return options_spec.ans
|
||||
|
||||
|
||||
def options_for_completion():
|
||||
return parse_option_spec(options_spec())[0]
|
||||
|
||||
|
||||
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
|
||||
options = parse_option_spec(ospec())
|
||||
seq, disabled = options
|
||||
@@ -631,6 +640,7 @@ def print_shortcut(key_sequence, action):
|
||||
names.append(name)
|
||||
if key:
|
||||
if is_native:
|
||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
|
||||
else:
|
||||
names.append(krmap[key])
|
||||
@@ -665,6 +675,7 @@ def flatten_sequence_map(m):
|
||||
|
||||
|
||||
def compare_opts(opts):
|
||||
from .config import defaults, load_config
|
||||
print('\nConfig options different from defaults:')
|
||||
default_opts = load_config()
|
||||
changed_opts = [
|
||||
@@ -686,6 +697,7 @@ def compare_opts(opts):
|
||||
|
||||
|
||||
def create_opts(args, debug_config=False):
|
||||
from .config import load_config
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
if debug_config:
|
||||
print(version(add_rev=True))
|
||||
|
||||
@@ -122,6 +122,10 @@ def draw(*a):
|
||||
write(' '.join(a))
|
||||
|
||||
|
||||
def screen_manipulate_title_stack(op, which):
|
||||
write(CSI + '%d;%dt' % (op, which))
|
||||
|
||||
|
||||
def report_device_attributes(mode, char):
|
||||
x = CSI
|
||||
if char:
|
||||
|
||||
127
kitty/cmds.py
127
kitty/cmds.py
@@ -9,6 +9,7 @@ import sys
|
||||
from .cli import parse_args
|
||||
from .config import parse_config, parse_send_text_bytes
|
||||
from .constants import appname
|
||||
from .fast_data_types import focus_os_window
|
||||
from .tabs import SpecialWindow
|
||||
from .utils import natsort_ints
|
||||
|
||||
@@ -26,6 +27,11 @@ class OpacityError(ValueError):
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
class UnknownLayout(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
||||
|
||||
def w(func):
|
||||
@@ -46,18 +52,20 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num.
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
|
||||
You can use the :italic:`ls` command to get a list of windows. Note that for
|
||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||
not a regular expression. The field num refers to the window position in the current tab,
|
||||
starting from zero and counting clockwise (this is the same as the order in which the
|
||||
windows are reported by the :italic:`ls` command). The window id of the current window
|
||||
is available as the KITTY_WINDOW_ID environment variable.
|
||||
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
|
||||
to match on environment variables you can specify only the environment variable name or a name
|
||||
and value, for example, :italic:`env:MY_ENV_VAR=2`
|
||||
'''
|
||||
MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
The tab to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline.
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, env, cmdline.
|
||||
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
||||
numeric fields such as id and pid the expression is interpreted as a number,
|
||||
not a regular expression. When using title or id, first a matching tab is
|
||||
@@ -72,8 +80,8 @@ for that window is used.
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
|
||||
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
|
||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)` and'
|
||||
' :italic:`command-line` of the process running in the window.\n\n'
|
||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`, '
|
||||
' :italic:`command-line` and :italic:`environment` of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
||||
argspec=''
|
||||
)
|
||||
@@ -270,6 +278,41 @@ def set_tab_title(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# set_layout {{{
|
||||
@cmd(
|
||||
'Set the window layout',
|
||||
'Set the window layout in the specified tab (or the active tab if not specified).'
|
||||
' You can use special match value :italic:`all` to set the layout in all tabs.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
argspec='LAYOUT_NAME'
|
||||
)
|
||||
def cmd_goto_layout(global_opts, opts, args):
|
||||
try:
|
||||
return {'layout': args[0], 'match': opts.match}
|
||||
except IndexError:
|
||||
raise SystemExit('No layout specified')
|
||||
|
||||
|
||||
def goto_layout(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
if match == 'all':
|
||||
tabs = tuple(boss.all_tabs)
|
||||
else:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
try:
|
||||
tab.goto_layout(payload['layout'], raise_exception=True)
|
||||
except ValueError:
|
||||
raise UnknownLayout('The layout {} is unknown or disabled'.format(payload['layout']))
|
||||
# }}}
|
||||
|
||||
|
||||
# close_window {{{
|
||||
@cmd(
|
||||
'Close the specified window(s)',
|
||||
@@ -381,7 +424,7 @@ def close_tab(boss, window, payload):
|
||||
'Open new window',
|
||||
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
|
||||
' the first matching tab is used. Otherwise the currently active tab is used.'
|
||||
' Prints out the id of the newly opened window. Any command line arguments'
|
||||
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
|
||||
' are assumed to be the command line used to run in the new window, if none'
|
||||
' are provided, the default shell is run. For example:\n'
|
||||
':italic:`kitty @ new-window --title Email mutt`',
|
||||
@@ -400,6 +443,12 @@ type=bool-set
|
||||
Keep the current window focused instead of switching to the newly opened window
|
||||
|
||||
|
||||
--window-type
|
||||
default=kitty
|
||||
choices=kitty,os
|
||||
What kind of window to open. A kitty window or a top-level OS window.
|
||||
|
||||
|
||||
--new-tab
|
||||
type=bool-set
|
||||
Open a new tab
|
||||
@@ -407,12 +456,23 @@ Open a new tab
|
||||
|
||||
--tab-title
|
||||
When using --new-tab set the title of the tab.
|
||||
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response giving the id of the newly opened window. Note that
|
||||
using this option means that you will not be notified of failures and that
|
||||
the id of the new window will not be printed out.
|
||||
''',
|
||||
argspec='[CMD ...]'
|
||||
)
|
||||
def cmd_new_window(global_opts, opts, args):
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
||||
'window_type': opts.window_type, 'no_response': opts.no_response,
|
||||
'keep_focus': opts.keep_focus, 'args': args or []}
|
||||
|
||||
|
||||
@@ -427,7 +487,16 @@ def new_window(boss, window, payload):
|
||||
wid = boss.active_window.id
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(wid)
|
||||
return None if payload['no_response'] else str(wid)
|
||||
|
||||
if payload['window_type'] == 'os':
|
||||
boss._new_os_window(w)
|
||||
wid = boss.active_window.id
|
||||
if payload['keep_focus'] and old_window:
|
||||
os_window_id = boss.set_active_window(old_window)
|
||||
if os_window_id:
|
||||
focus_os_window(os_window_id)
|
||||
return None if payload['no_response'] else str(wid)
|
||||
|
||||
match = payload['match']
|
||||
if match:
|
||||
@@ -440,7 +509,7 @@ def new_window(boss, window, payload):
|
||||
w = tab.new_special_window(w)
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(w.id)
|
||||
return None if payload['no_response'] else str(w.id)
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -448,11 +517,19 @@ def new_window(boss, window, payload):
|
||||
@cmd(
|
||||
'Focus the specified window',
|
||||
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n\n
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response from kitty. This means that even if no matching window is found,
|
||||
the command will exit with a success code.
|
||||
'''
|
||||
)
|
||||
def cmd_focus_window(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'no_response': opts.no_response}
|
||||
|
||||
|
||||
def focus_window(boss, window, payload):
|
||||
@@ -680,6 +757,36 @@ def set_background_opacity(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# kitten {{{
|
||||
@cmd(
|
||||
'Run a kitten',
|
||||
'Run a kitten over the specified window (active window by default).'
|
||||
' The :italic:`kitten_name` can be either the name of a builtin kitten'
|
||||
' or the path to a python file containing a custom kitten. If a relative path'
|
||||
' is used it is searched for in the kitty config directory.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='kitten_name',
|
||||
)
|
||||
def cmd_kitten(global_opts, opts, args):
|
||||
if len(args) < 1:
|
||||
raise SystemExit('Must specify kitten name')
|
||||
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
|
||||
|
||||
|
||||
def kitten(boss, window, payload):
|
||||
windows = [window or boss.active_window]
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss._run_kitten(payload['kitten'], args=tuple(payload['args']), window=window)
|
||||
break
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
typedef void* rusage_info_t; // needed for libproc.h
|
||||
#include <libproc.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
@@ -220,6 +218,12 @@ cocoa_make_window_resizable(void *w, bool resizable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_focus_window(void *w) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
[window makeKeyWindow];
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_get_lang(PyObject UNUSED *self) {
|
||||
NSString* locale = nil;
|
||||
@@ -234,15 +238,6 @@ cocoa_get_lang(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("s", [locale UTF8String]);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
struct proc_vnodepathinfo vpi;
|
||||
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
||||
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_hide_from_tasks(void) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
@@ -276,7 +271,6 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
326
kitty/complete.py
Normal file
326
kitty/complete.py
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from kittens.runner import get_kitten_cli_docs, all_kitten_names
|
||||
|
||||
from .cli import options_for_completion, parse_option_spec
|
||||
from .cmds import cmap
|
||||
from .shell import options_for_cmd
|
||||
|
||||
parsers, serializers = {}, {}
|
||||
|
||||
|
||||
def debug(*a, **kw):
|
||||
kw['file'] = sys.stderr
|
||||
print(*a, **kw)
|
||||
|
||||
|
||||
class Completions:
|
||||
|
||||
def __init__(self):
|
||||
self.match_groups = {}
|
||||
self.no_space_groups = set()
|
||||
self.files_groups = set()
|
||||
|
||||
|
||||
# Shell specific code {{{
|
||||
|
||||
|
||||
completion_scripts = {
|
||||
'zsh': '''
|
||||
_kitty() {
|
||||
local src
|
||||
# Send all words upto the word the cursor is currently on
|
||||
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
fi
|
||||
}
|
||||
compdef _kitty kitty
|
||||
''',
|
||||
'bash': '''
|
||||
kitty_completions() {
|
||||
local src
|
||||
local limit
|
||||
# Send all words up to the word the cursor is currently on
|
||||
let limit=1+$COMP_CWORD
|
||||
src=$(printf "%s\n" "${COMP_WORDS[@]: 0:$limit}" | kitty +complete bash)
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o nospace -F kitty_completions kitty
|
||||
''',
|
||||
}
|
||||
|
||||
|
||||
def input_parser(func):
|
||||
name = func.__name__.split('_')[0]
|
||||
parsers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
def output_serializer(func):
|
||||
name = func.__name__.split('_')[0]
|
||||
serializers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
@input_parser
|
||||
def zsh_input_parser(data):
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def bash_input_parser(data):
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@output_serializer
|
||||
def zsh_output_serializer(ans):
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(description)]
|
||||
if description in ans.no_space_groups:
|
||||
cmd += ['-S', '""']
|
||||
if description in ans.files_groups:
|
||||
cmd.append('-f')
|
||||
cmd.append('--')
|
||||
for word, description in matches.items():
|
||||
cmd.append(shlex.quote(word))
|
||||
lines.append(' '.join(cmd) + ';')
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
@output_serializer
|
||||
def bash_output_serializer(ans):
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
needs_space = description not in ans.no_space_groups
|
||||
for word in matches:
|
||||
if needs_space:
|
||||
word += ' '
|
||||
lines.append('COMPREPLY+=({})'.format(shlex.quote(word)))
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
# }}}
|
||||
|
||||
|
||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||
cmds = ['@' + c for c in cmap]
|
||||
ans.match_groups['Entry points'] = {
|
||||
k: None for k in
|
||||
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
||||
if not prefix or k.startswith(prefix)
|
||||
}
|
||||
|
||||
|
||||
def kitty_cli_opts(ans, prefix=None):
|
||||
matches = {}
|
||||
for opt in options_for_completion():
|
||||
if isinstance(opt, str):
|
||||
continue
|
||||
aliases = frozenset(x for x in opt['aliases'] if x.startswith(prefix)) if prefix else opt['aliases']
|
||||
for alias in aliases:
|
||||
matches[alias] = opt['help'].strip()
|
||||
ans.match_groups['Options'] = matches
|
||||
|
||||
|
||||
def complete_kitty_cli_arg(ans, opt, prefix):
|
||||
prefix = prefix or ''
|
||||
if opt and opt['dest'] == 'override':
|
||||
from kitty.config import option_names_for_completion
|
||||
k = 'Config directives'
|
||||
ans.match_groups[k] = {k+'=': None for k in option_names_for_completion() if k.startswith(prefix)}
|
||||
ans.no_space_groups.add(k)
|
||||
|
||||
|
||||
def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
|
||||
expecting_arg = False
|
||||
opt = None
|
||||
last_word = words[-1] if words else ''
|
||||
for w in words:
|
||||
if expecting_arg:
|
||||
if w is last_word and not new_word:
|
||||
if opt is not None:
|
||||
complete_args(ans, opt, w)
|
||||
return
|
||||
expecting_arg = False
|
||||
continue
|
||||
opt = option_map.get(w)
|
||||
if w is last_word and not new_word:
|
||||
if w.startswith('-'):
|
||||
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(last_word)}
|
||||
else:
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, last_word)
|
||||
return
|
||||
if opt is None:
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, '' if new_word else last_word)
|
||||
return # some non-option word encountered
|
||||
expecting_arg = not opt.get('type', '').startswith('bool-')
|
||||
if expecting_arg:
|
||||
if opt is not None and complete_args is not None:
|
||||
complete_args(ans, opt, '' if new_word else last_word)
|
||||
else:
|
||||
prefix = '' if new_word else last_word
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, prefix)
|
||||
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
|
||||
|
||||
|
||||
def complete_cli(ans, words, new_word, seq, complete_args=lambda *a: None):
|
||||
option_map = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, complete_args)
|
||||
|
||||
|
||||
def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
aliases, alias_map = options_for_cmd(cmd_name)
|
||||
if not alias_map:
|
||||
return
|
||||
complete_alias_map(ans, words, new_word, alias_map)
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=lambda filename: True):
|
||||
dirs, files = [], []
|
||||
base = '.'
|
||||
if prefix.endswith('/'):
|
||||
base = prefix
|
||||
elif '/' in prefix:
|
||||
base = os.path.dirname(prefix)
|
||||
for x in os.scandir(base):
|
||||
q = os.path.relpath(x.path)
|
||||
if x.is_dir():
|
||||
if q.startswith(prefix):
|
||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||
else:
|
||||
if q.startswith(prefix) and predicate(q):
|
||||
files.append(q)
|
||||
if dirs:
|
||||
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||
if files:
|
||||
ans.match_groups[files_group_name] = dict.fromkeys(files)
|
||||
ans.files_groups.add(files_group_name)
|
||||
|
||||
|
||||
def complete_icat_args(ans, opt, prefix):
|
||||
from mimetypes import guess_type
|
||||
|
||||
def icat_file_predicate(filename):
|
||||
mt = guess_type(filename)[0]
|
||||
if mt and mt.startswith('image/'):
|
||||
return True
|
||||
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||
|
||||
|
||||
def complete_kitten(ans, kitten, words, new_word):
|
||||
try:
|
||||
cd = get_kitten_cli_docs(kitten)
|
||||
except SystemExit:
|
||||
cd = None
|
||||
if cd is None:
|
||||
return
|
||||
options = cd['options']()
|
||||
seq = parse_option_spec(options)[0]
|
||||
option_map = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, {
|
||||
'icat': complete_icat_args
|
||||
}.get(kitten))
|
||||
|
||||
|
||||
def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
ans = Completions()
|
||||
if not words or words[0] != 'kitty':
|
||||
return ans
|
||||
words = words[1:]
|
||||
if not words or (len(words) == 1 and not new_word):
|
||||
prefix = words[0] if words else ''
|
||||
completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points)
|
||||
kitty_cli_opts(ans, prefix)
|
||||
return ans
|
||||
if words[0] == '@':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Remote control commands'] = {c: None for c in cmap if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[1], words[2:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('@'):
|
||||
if len(words) == 1 and not new_word:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: None for c in cmap if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||
if words[0] == '+':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if words[1] == 'kitten':
|
||||
if len(words) == 2 or (len(words) == 3 and not new_word):
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith('' if len(words) == 2 else words[2]))
|
||||
else:
|
||||
complete_kitten(ans, words[2], words[3:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('+'):
|
||||
if len(words) == 1:
|
||||
if new_word:
|
||||
if words[0] == '+kitten':
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(all_kitten_names())
|
||||
else:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if len(words) == 2 and not new_word:
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith(words[1]))
|
||||
else:
|
||||
if words[0] == '+kitten':
|
||||
complete_kitten(ans, words[1], words[2:], new_word)
|
||||
else:
|
||||
complete_cli(ans, words, new_word, options_for_completion(), complete_kitty_cli_arg)
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
def setup(cstyle):
|
||||
print(completion_scripts[cstyle])
|
||||
|
||||
|
||||
def main(args, entry_points, namespaced_entry_points):
|
||||
if not args:
|
||||
raise SystemExit('Must specify completion style')
|
||||
cstyle = args[0]
|
||||
if cstyle == 'setup':
|
||||
return setup(args[1])
|
||||
data = sys.stdin.read()
|
||||
try:
|
||||
parser = parsers[cstyle]
|
||||
serializer = serializers[cstyle]
|
||||
except KeyError:
|
||||
raise SystemExit('Unknown completion style: {}'.format(cstyle))
|
||||
words, new_word = parser(data)
|
||||
ans = find_completions(words, new_word, entry_points, namespaced_entry_points)
|
||||
print(serializer(ans), end='')
|
||||
@@ -119,7 +119,16 @@ def merged_opts(all_options, opt, i):
|
||||
|
||||
|
||||
def remove_markup(text):
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', r'\2', text, flags=re.DOTALL)
|
||||
|
||||
def sub(m):
|
||||
if m.group(1) == 'ref':
|
||||
return {
|
||||
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
|
||||
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
|
||||
}[m.group(2)]
|
||||
return m.group(2)
|
||||
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
|
||||
def iter_blocks(lines):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
@@ -40,6 +39,7 @@ def to_cmdline(x):
|
||||
|
||||
|
||||
def python_string(text):
|
||||
import ast
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
|
||||
|
||||
|
||||
|
||||
@@ -121,6 +121,17 @@ def parse_change_font_size(func, rest):
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('clear_terminal')
|
||||
def clear_terminal(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('clear_terminal needs two arguments, using defaults')
|
||||
args = ['reset', 'active']
|
||||
else:
|
||||
args = [vals[0].lower(), vals[1].lower() == 'active']
|
||||
return func, args
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
parts = action.split(' ', 1)
|
||||
func = parts[0]
|
||||
@@ -245,28 +256,73 @@ def parse_send_text(val, key_definitions):
|
||||
return parse_key(key_str, key_definitions)
|
||||
|
||||
|
||||
special_handlers = {}
|
||||
|
||||
|
||||
def special_handler(func):
|
||||
special_handlers[func.__name__.partition('_')[2]] = func
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_map(key, val, ans):
|
||||
parse_key(val, ans['key_definitions'])
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_symbol_map(key, val, ans):
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_send_text(key, val, ans):
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['key_definitions'])
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_clear_all_shortcuts(key, val, ans):
|
||||
if to_bool(val):
|
||||
ans['key_definitions'] = [None]
|
||||
|
||||
|
||||
def expandvars(val, env):
|
||||
|
||||
def sub(m):
|
||||
key = m.group(1)
|
||||
result = env.get(key)
|
||||
if result is None:
|
||||
result = os.environ.get(key)
|
||||
if result is None:
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'$\{(\S+)\}', sub, val)
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_env(key, val, ans):
|
||||
key, val = val.partition('=')[::2]
|
||||
key, val = key.strip(), val.strip()
|
||||
ans['env'][key] = expandvars(val, ans['env'])
|
||||
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['key_definitions'])
|
||||
func = special_handlers.get(key)
|
||||
if func is not None:
|
||||
func(key, val, ans)
|
||||
return True
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
return True
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['key_definitions'])
|
||||
return True
|
||||
if key == 'clear_all_shortcuts':
|
||||
if to_bool(val):
|
||||
ans['key_definitions'] = [None]
|
||||
return
|
||||
|
||||
|
||||
defaults = None
|
||||
|
||||
|
||||
def option_names_for_completion():
|
||||
yield from defaults
|
||||
yield from special_handlers
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
|
||||
@@ -266,7 +266,16 @@ def to_cursor_shape(x):
|
||||
)
|
||||
|
||||
|
||||
def cursor_text_color(x):
|
||||
if x.lower() == 'background':
|
||||
return
|
||||
return to_color(x)
|
||||
|
||||
|
||||
o('cursor', '#cccccc', _('Default cursor color'), option_type=to_color)
|
||||
o('cursor_text_color', '#111111', option_type=cursor_text_color, long_text=_('''
|
||||
Choose the color of text under the cursor. If you want it rendered with the
|
||||
background color of the cell underneath instead, use the special keyword: background'''))
|
||||
o('cursor_shape', 'block', option_type=to_cursor_shape, long_text=_(
|
||||
'The cursor shape can be one of (block, beam, underline)'))
|
||||
o('cursor_blink_interval', 0.5, option_type=positive_float, long_text=_('''
|
||||
@@ -293,8 +302,9 @@ INPUT_LINE_NUMBER in the command line above will be replaced by an integer
|
||||
representing which line should be at the top of the screen.'''))
|
||||
|
||||
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||
Modify the amount scrolled by the mouse wheel or touchpad. Use
|
||||
negative numbers to change scroll direction.'''))
|
||||
Modify the amount scrolled by the mouse wheel. Note this is only used for low
|
||||
precision scrolling devices, not for high precision scrolling on platforms such
|
||||
as macOS and Wayland. Use negative numbers to change scroll direction.'''))
|
||||
# }}}
|
||||
|
||||
g('mouse') # {{{
|
||||
@@ -637,6 +647,19 @@ opening new windows, closing windows, reading the content of windows, etc.
|
||||
Note that this even works over ssh connections.
|
||||
'''))
|
||||
|
||||
o(
|
||||
'+env', '',
|
||||
add_to_default=False,
|
||||
long_text=_('''
|
||||
Specify environment variables to set in all child processes. Note that
|
||||
environment variables are expanded recursively, so if you use::
|
||||
|
||||
env MYVAR1=a
|
||||
env MYVAR2=${MYVAR}/${HOME}/b
|
||||
|
||||
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
|
||||
'''))
|
||||
|
||||
|
||||
def startup_session(x):
|
||||
if x.lower() == 'none':
|
||||
@@ -793,6 +816,13 @@ 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
|
||||
|
||||
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
|
||||
'''))
|
||||
if is_macos:
|
||||
k('new_os_window', 'cmd+n', 'new_os_window', _('New OS window'))
|
||||
@@ -817,6 +847,8 @@ k('tenth_window', 'kitty_mod+0', 'tenth_window', _('Tenth window'))
|
||||
# }}}
|
||||
|
||||
g('shortcuts.tab') # {{{
|
||||
if is_macos:
|
||||
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
|
||||
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
||||
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
||||
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
||||
@@ -863,7 +895,7 @@ Useful with git, which uses sha1 hashes to identify commits'''))
|
||||
|
||||
g('shortcuts.misc') # {{{
|
||||
k('toggle_fullscreen', 'kitty_mod+f11', 'toggle_fullscreen', _('Toggle fullscreen'))
|
||||
k('input_unicode_character', 'kitty_mod+u', 'input_unicode_character', _('Unicode input'))
|
||||
k('input_unicode_character', 'kitty_mod+u', 'kitten unicode_input', _('Unicode input'))
|
||||
k('edit_config_file', 'kitty_mod+f2', 'edit_config_file', _('Edit config file'))
|
||||
k('kitty_shell', 'kitty_mod+escape', 'kitty_shell window', _('Open the kitty command shell'), long_text=_('''
|
||||
Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.'''))
|
||||
@@ -871,6 +903,17 @@ k('increase_background_opacity', 'kitty_mod+a>m', 'set_background_opacity +0.1',
|
||||
k('decrease_background_opacity', 'kitty_mod+a>l', 'set_background_opacity -0.1', _('Decrease background opacity'))
|
||||
k('full_background_opacity', 'kitty_mod+a>1', 'set_background_opacity 1', _('Make background fully opaque'))
|
||||
k('reset_background_opacity', 'kitty_mod+a>d', 'set_background_opacity default', _('Reset background opacity'))
|
||||
k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset the terminal'),
|
||||
long_text=_('''
|
||||
You can create shortcuts to clear/reset the terminal. For example::
|
||||
|
||||
map kitty_mod+f9 clear_terminal reset active
|
||||
map kitty_mod+f10 clear_terminal clear active
|
||||
map kitty_mod+f11 clear_terminal scrollback active
|
||||
|
||||
These will reset screen/clear screen/clear screen+scrollback respectively. If you want to
|
||||
operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
|
||||
'''))
|
||||
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
||||
add_to_default=False, long_text=_('''
|
||||
You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||
|
||||
@@ -7,10 +7,9 @@ import pwd
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 11, 3)
|
||||
version = (0, 12, 0)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
@@ -94,6 +93,7 @@ def get_boss():
|
||||
|
||||
|
||||
def set_boss(m):
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
get_boss.boss = m
|
||||
set_c_boss(m)
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#version GLSL_VERSION
|
||||
uniform vec3 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = vec4(color, 1);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#version GLSL_VERSION
|
||||
uniform vec4 pos;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
|
||||
void main() {
|
||||
vec2 xpos = vec2(pos[0], pos[2]);
|
||||
vec2 ypos = vec2(pos[1], pos[3]);
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#ifdef WITH_PROFILER
|
||||
#include <gperftools/profiler.h>
|
||||
#endif
|
||||
@@ -98,7 +99,7 @@ start_profiler(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
stop_profiler(PyObject UNUSED *self) {
|
||||
stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
|
||||
ProfilerStop();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -123,7 +124,8 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL;
|
||||
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||
int fd = open("/dev/tty", flags);
|
||||
static char ctty[L_ctermid+1];
|
||||
int fd = open(ctermid(ctty), flags);
|
||||
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; }
|
||||
@@ -215,6 +217,7 @@ extern bool init_png_reader(PyObject *module);
|
||||
#ifdef __APPLE__
|
||||
extern int init_CoreText(PyObject *);
|
||||
extern bool init_cocoa(PyObject *module);
|
||||
extern bool init_macos_process_info(PyObject *module);
|
||||
#else
|
||||
extern bool init_freetype_library(PyObject*);
|
||||
#endif
|
||||
@@ -249,6 +252,7 @@ PyInit_fast_data_types(void) {
|
||||
if (!init_kittens(m)) return NULL;
|
||||
if (!init_png_reader(m)) return NULL;
|
||||
#ifdef __APPLE__
|
||||
if (!init_macos_process_info(m)) return NULL;
|
||||
if (!init_CoreText(m)) return NULL;
|
||||
if (!init_cocoa(m)) return NULL;
|
||||
#else
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
void log_error(const char *fmt, ...);
|
||||
void log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }
|
||||
|
||||
typedef unsigned long long id_type;
|
||||
@@ -197,7 +197,6 @@ typedef struct {
|
||||
bool is_visible, is_focused;
|
||||
CursorShape shape;
|
||||
unsigned int x, y;
|
||||
double left, right, top, bottom;
|
||||
color_type color;
|
||||
} CursorRenderInfo;
|
||||
|
||||
@@ -272,7 +271,7 @@ const char* cursor_as_sgr(Cursor*, Cursor*);
|
||||
|
||||
double monotonic();
|
||||
PyObject* cm_thread_write(PyObject *self, PyObject *args);
|
||||
bool schedule_write_to_child(unsigned long id, const char *data, size_t sz);
|
||||
bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
|
||||
bool set_iutf8(int, bool);
|
||||
|
||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||
@@ -281,10 +280,11 @@ void colorprofile_push_dynamic_colors(ColorProfile*);
|
||||
void colorprofile_pop_dynamic_colors(ColorProfile*);
|
||||
|
||||
void set_mouse_cursor(MouseShape);
|
||||
void enter_event();
|
||||
void mouse_event(int, int);
|
||||
void focus_in_event();
|
||||
void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double);
|
||||
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);
|
||||
|
||||
2
kitty/emoji.h
generated
2
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-06-14
|
||||
// unicode data, built from the unicode standard on: 2018-08-04
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -1076,10 +1076,11 @@ send_prerendered_sprites(FontGroup *fg) {
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas);
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIII", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness);
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
||||
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
||||
if (y > 0) { fatal("too many prerendered sprites for your GPU"); }
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||
@@ -1105,7 +1106,7 @@ initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) {
|
||||
Py_CLEAR(face);
|
||||
if (!ok) {
|
||||
if (PyErr_Occurred()) { PyErr_Print(); }
|
||||
fatal("Failed to initialize %s font: %d", ftype, idx);
|
||||
fatal("Failed to initialize %s font: %zu", ftype, idx);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -166,12 +166,43 @@ def render_special(
|
||||
return ans
|
||||
|
||||
|
||||
def prerender_function(cell_width, cell_height, baseline, underline_position, underline_thickness):
|
||||
# Pre-render the special underline, strikethrough and missing cells
|
||||
def render_cursor(which, cell_width=0, cell_height=0, dpi_x=0, dpi_y=0):
|
||||
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
ans = CharTexture()
|
||||
|
||||
def vert(edge, width_pt=1):
|
||||
width = max(1, int(round(width_pt * dpi_x / 72.0)))
|
||||
left = 0 if edge == 'left' else max(0, cell_width - width)
|
||||
for y in range(cell_height):
|
||||
offset = y * cell_width + left
|
||||
for x in range(offset, offset + width):
|
||||
ans[x] = 255
|
||||
|
||||
def horz(edge, height_pt=1):
|
||||
height = max(1, int(round(height_pt * dpi_y / 72.0)))
|
||||
top = 0 if edge == 'top' else max(0, cell_height - height)
|
||||
for y in range(top, top + height):
|
||||
offset = y * cell_width
|
||||
for x in range(cell_width):
|
||||
ans[offset + x] = 255
|
||||
|
||||
if which == 1: # beam
|
||||
vert('left', 1.5)
|
||||
elif which == 2: # underline
|
||||
horz('bottom', 2.0)
|
||||
elif which == 3: # hollow
|
||||
vert('left'), vert('right'), horz('top'), horz('bottom')
|
||||
return ans
|
||||
|
||||
|
||||
def prerender_function(cell_width, cell_height, baseline, underline_position, underline_thickness, dpi_x, dpi_y):
|
||||
# Pre-render the special underline, strikethrough and missing and cursor cells
|
||||
f = partial(
|
||||
render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline,
|
||||
underline_position=underline_position, underline_thickness=underline_thickness)
|
||||
cells = f(1), f(2), f(3), f(0, True), f(missing=True)
|
||||
c = partial(
|
||||
render_cursor, cell_width=cell_width, cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y)
|
||||
cells = f(1), f(2), f(3), f(0, True), f(missing=True), c(1), c(2), c(3)
|
||||
return tuple(map(ctypes.addressof, cells)) + (cells,)
|
||||
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
static void
|
||||
downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_height) {
|
||||
// Downsample using a simple area averaging algorithm. Could probably do
|
||||
// better with bi-cubic or lanczos, but at these small sizes I dont think
|
||||
// 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);
|
||||
|
||||
3
kitty/glfw-wrapper.c
generated
3
kitty/glfw-wrapper.c
generated
@@ -257,6 +257,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwSetKeyboardCallback_impl) = dlsym(handle, "glfwSetKeyboardCallback");
|
||||
if (glfwSetKeyboardCallback_impl == NULL) fail("Failed to load glfw function glfwSetKeyboardCallback with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwUpdateIMEState_impl) = dlsym(handle, "glfwUpdateIMEState");
|
||||
if (glfwUpdateIMEState_impl == NULL) fail("Failed to load glfw function glfwUpdateIMEState with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetMouseButtonCallback_impl) = dlsym(handle, "glfwSetMouseButtonCallback");
|
||||
if (glfwSetMouseButtonCallback_impl == NULL) fail("Failed to load glfw function glfwSetMouseButtonCallback with error: %s", dlerror());
|
||||
|
||||
|
||||
13
kitty/glfw-wrapper.h
generated
13
kitty/glfw-wrapper.h
generated
@@ -819,6 +819,7 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
* @{ */
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
@@ -1133,15 +1134,17 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* @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.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
*
|
||||
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
||||
* @since Changed in version 4.0. Added `flags` parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
|
||||
/*! @brief The function signature for key callbacks.
|
||||
*
|
||||
@@ -1160,7 +1163,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
* @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] reserved Reserved for future use.
|
||||
* @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.
|
||||
*
|
||||
* @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
|
||||
@@ -1699,6 +1704,10 @@ typedef GLFWkeyboardfun (*glfwSetKeyboardCallback_func)(GLFWwindow*, GLFWkeyboar
|
||||
glfwSetKeyboardCallback_func glfwSetKeyboardCallback_impl;
|
||||
#define glfwSetKeyboardCallback glfwSetKeyboardCallback_impl
|
||||
|
||||
typedef void (*glfwUpdateIMEState_func)(GLFWwindow*, int, int, int, int, int);
|
||||
glfwUpdateIMEState_func glfwUpdateIMEState_impl;
|
||||
#define glfwUpdateIMEState glfwUpdateIMEState_impl
|
||||
|
||||
typedef GLFWmousebuttonfun (*glfwSetMouseButtonCallback_func)(GLFWwindow*, GLFWmousebuttonfun);
|
||||
glfwSetMouseButtonCallback_func glfwSetMouseButtonCallback_impl;
|
||||
#define glfwSetMouseButtonCallback glfwSetMouseButtonCallback_impl
|
||||
|
||||
44
kitty/glfw.c
44
kitty/glfw.c
@@ -9,6 +9,7 @@
|
||||
#include <structmember.h>
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w, bool);
|
||||
extern void cocoa_focus_window(void *w);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_set_hide_from_tasks(void);
|
||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||
@@ -138,6 +139,18 @@ key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const c
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
cursor_enter_callback(GLFWwindow *w, int entered) {
|
||||
if (!set_callback_window(w)) return;
|
||||
if (entered) {
|
||||
show_mouse_cursor(w);
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (is_window_ready_for_callbacks()) enter_event();
|
||||
}
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
@@ -165,12 +178,12 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
|
||||
}
|
||||
|
||||
static void
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset);
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags);
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
@@ -215,6 +228,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
|
||||
global_state.callback_os_window->cursor_blink_zero_time = now;
|
||||
if (is_window_ready_for_callbacks()) {
|
||||
WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False);
|
||||
glfwUpdateIMEState(global_state.callback_os_window->handle, 1, focused, 0, 0, 0);
|
||||
}
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -381,7 +395,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
|
||||
// We dont use depth and stencil buffers
|
||||
// We don't use depth and stencil buffers
|
||||
glfwWindowHint(GLFW_DEPTH_BITS, 0);
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 0);
|
||||
#ifdef __APPLE__
|
||||
@@ -396,7 +410,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
||||
if (OPT(x11_hide_window_decorations)) {
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -419,7 +433,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#endif
|
||||
|
||||
GLFWwindow *temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
|
||||
if (temp_window == NULL) { fatal("Failed to create GLFW temp window!"); }
|
||||
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."); }
|
||||
|
||||
double dpi_x, dpi_y;
|
||||
get_window_dpi(temp_window, &dpi_x, &dpi_y);
|
||||
@@ -468,7 +482,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
w->handle = glfw_window;
|
||||
update_os_window_references();
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
// On some platforms (macOS) newly created windows dont get the initial focus in event
|
||||
// On some platforms (macOS) newly created windows don't get the initial focus in event
|
||||
OSWindow *q = global_state.os_windows + i;
|
||||
q->is_focused = q == w ? true : false;
|
||||
}
|
||||
@@ -488,6 +502,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
|
||||
glfwSetWindowContentScaleCallback(glfw_window, dpi_change_callback);
|
||||
glfwSetWindowRefreshCallback(glfw_window, refresh_callback);
|
||||
glfwSetCursorEnterCallback(glfw_window, cursor_enter_callback);
|
||||
glfwSetMouseButtonCallback(glfw_window, mouse_button_callback);
|
||||
glfwSetScrollCallback(glfw_window, scroll_callback);
|
||||
glfwSetCursorPosCallback(glfw_window, cursor_pos_callback);
|
||||
@@ -542,6 +557,19 @@ destroy_os_window(OSWindow *w) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
focus_os_window(OSWindow *w, bool also_raise) {
|
||||
if (w->handle) {
|
||||
#ifdef __APPLE__
|
||||
if (!also_raise) cocoa_focus_window(glfwGetCocoaWindow(w->handle));
|
||||
else glfwFocusWindow(w->handle);
|
||||
#else
|
||||
(void)also_raise;
|
||||
glfwFocusWindow(w->handle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
bool
|
||||
application_quit_requested() {
|
||||
@@ -565,6 +593,10 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
|
||||
glfwSetErrorCallback(error_callback);
|
||||
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
|
||||
// Joysticks cause slow startup on some linux systems, see
|
||||
// https://github.com/kovidgoyal/kitty/issues/830
|
||||
glfwInitHint(GLFW_ENABLE_JOYSTICKS, 0);
|
||||
global_state.opts.debug_keyboard = debug_keyboard != 0;
|
||||
#ifdef __APPLE__
|
||||
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
|
||||
glfwInitHint(GLFW_COCOA_MENUBAR, 0);
|
||||
|
||||
60
kitty/keys.c
60
kitty/keys.c
@@ -80,12 +80,12 @@ send_key_to_child(Window *w, int key, int mods, int action) {
|
||||
const char *data = key_to_bytes(key, screen->modes.mDECCKM, screen->modes.mEXTENDED_KEYBOARD, mods, action);
|
||||
if (data) {
|
||||
if (screen->modes.mEXTENDED_KEYBOARD) {
|
||||
if (*data == 1) schedule_write_to_child(w->id, (data + 1), 1);
|
||||
if (*data == 1) schedule_write_to_child(w->id, 1, (data + 1), 1);
|
||||
else write_escape_code_to_child(screen, APC, data + 1);
|
||||
} else {
|
||||
if (*data > 2 && data[1] == 0x1b && data[2] == '[') { // CSI code
|
||||
write_escape_code_to_child(screen, CSI, data + 3);
|
||||
} else schedule_write_to_child(w->id, (data + 1), *data);
|
||||
} else schedule_write_to_child(w->id, 1, (data + 1), *data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,18 +113,52 @@ check_if_special(int key, int mods, int scancode) {
|
||||
return special;
|
||||
}
|
||||
|
||||
static inline void
|
||||
update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
|
||||
unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
|
||||
unsigned int left = w->geometry.left, top = w->geometry.top;
|
||||
left += screen->cursor->x * cell_width;
|
||||
top += screen->cursor->y * cell_height;
|
||||
glfwUpdateIMEState(global_state.callback_os_window->handle, 2, left, top, cell_width, cell_height);
|
||||
}
|
||||
|
||||
#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 UNUSED) {
|
||||
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
|
||||
Window *w = active_window();
|
||||
if (!w) return;
|
||||
debug("on_key_input: glfw key: %d native_code: %d action: %s mods: 0x%x text: '%s' state: %d ",
|
||||
key, scancode,
|
||||
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||
mods, text, state);
|
||||
if (!w) { debug("no active window, ignoring\n"); return; }
|
||||
Screen *screen = w->render_data.screen;
|
||||
switch(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) {
|
||||
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");
|
||||
return;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
debug("invalid state, ignoring\n");
|
||||
return;
|
||||
}
|
||||
if (global_state.in_sequence_mode) {
|
||||
debug("in sequence mode, handling as shortcut\n");
|
||||
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);
|
||||
return;
|
||||
}
|
||||
Screen *screen = w->render_data.screen;
|
||||
bool has_text = text && !is_ascii_control_char(text[0]);
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
if (check_if_special(key, mods, scancode)) {
|
||||
@@ -133,21 +167,31 @@ on_key_input(int key, int scancode, int action, int mods, const char* text, int
|
||||
else {
|
||||
bool consumed = ret == Py_True;
|
||||
Py_DECREF(ret);
|
||||
if (consumed) return;
|
||||
if (consumed) {
|
||||
debug("handled as shortcut\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action == GLFW_REPEAT && !screen->modes.mDECARM) return;
|
||||
if (action == GLFW_REPEAT && !screen->modes.mDECARM) {
|
||||
debug("discarding repeat key event as DECARM is off\n");
|
||||
return;
|
||||
}
|
||||
if (screen->scrolled_by && action == GLFW_PRESS && !is_modifier_key(key)) {
|
||||
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
||||
}
|
||||
bool ok_to_send = action == GLFW_PRESS || action == GLFW_REPEAT || screen->modes.mEXTENDED_KEYBOARD;
|
||||
if (ok_to_send) {
|
||||
if (has_text) {
|
||||
schedule_write_to_child(w->id, text, strlen(text));
|
||||
schedule_write_to_child(w->id, 1, text, strlen(text));
|
||||
debug("sent text to child\n");
|
||||
} else {
|
||||
send_key_to_child(w, key, mods, action);
|
||||
debug("sent key to child\n");
|
||||
}
|
||||
} else {
|
||||
debug("ignoring as keyboard mode does not allow %s events\n", action == GLFW_RELEASE ? "release" : "repeat");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
kitty/keys.h
generated
16
kitty/keys.h
generated
@@ -598,9 +598,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
case 59: // UP
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x41";
|
||||
case 60: // PAGE_UP
|
||||
return "\x04\x1b\x5b\x35\x7e";
|
||||
return "\x06\x1b\x5b\x35\x3b\x32\x7e";
|
||||
case 61: // PAGE_DOWN
|
||||
return "\x04\x1b\x5b\x36\x7e";
|
||||
return "\x06\x1b\x5b\x36\x3b\x32\x7e";
|
||||
case 62: // HOME
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x48";
|
||||
case 63: // END
|
||||
@@ -2225,9 +2225,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
case 59: // UP
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x41";
|
||||
case 60: // PAGE_UP
|
||||
return "\x04\x1b\x5b\x35\x7e";
|
||||
return "\x06\x1b\x5b\x35\x3b\x32\x7e";
|
||||
case 61: // PAGE_DOWN
|
||||
return "\x04\x1b\x5b\x36\x7e";
|
||||
return "\x06\x1b\x5b\x36\x3b\x32\x7e";
|
||||
case 62: // HOME
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x48";
|
||||
case 63: // END
|
||||
@@ -3861,9 +3861,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
case 59: // UP
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x41";
|
||||
case 60: // PAGE_UP
|
||||
return "\x04\x1b\x5b\x35\x7e";
|
||||
return "\x06\x1b\x5b\x35\x3b\x32\x7e";
|
||||
case 61: // PAGE_DOWN
|
||||
return "\x04\x1b\x5b\x36\x7e";
|
||||
return "\x06\x1b\x5b\x36\x3b\x32\x7e";
|
||||
case 62: // HOME
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x48";
|
||||
case 63: // END
|
||||
@@ -5488,9 +5488,9 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
case 59: // UP
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x41";
|
||||
case 60: // PAGE_UP
|
||||
return "\x04\x1b\x5b\x35\x7e";
|
||||
return "\x06\x1b\x5b\x35\x3b\x32\x7e";
|
||||
case 61: // PAGE_DOWN
|
||||
return "\x04\x1b\x5b\x36\x7e";
|
||||
return "\x06\x1b\x5b\x36\x3b\x32\x7e";
|
||||
case 62: // HOME
|
||||
return "\x06\x1b\x5b\x31\x3b\x32\x48";
|
||||
case 63: // END
|
||||
|
||||
@@ -48,6 +48,8 @@ SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
||||
defines.GLFW_KEY_UP: key_as_bytes('kri'),
|
||||
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
|
||||
defines.GLFW_KEY_PAGE_UP: modify_complex_key('kpp', 2),
|
||||
defines.GLFW_KEY_PAGE_DOWN: modify_complex_key('knp', 2),
|
||||
}
|
||||
|
||||
control_alt_codes = {
|
||||
|
||||
18
kitty/line.c
18
kitty/line.c
@@ -132,10 +132,11 @@ line_url_start_at(Line *self, index_type x) {
|
||||
}
|
||||
|
||||
index_type
|
||||
line_url_end_at(Line *self, index_type x, bool check_short) {
|
||||
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel) {
|
||||
index_type ans = x;
|
||||
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
|
||||
while (ans < self->xnum && is_url_char(self->cpu_cells[ans].ch)) ans++;
|
||||
if (sentinel) { while (ans < self->xnum && self->cpu_cells[ans].ch != sentinel && is_url_char(self->cpu_cells[ans].ch)) ans++; }
|
||||
else { while (ans < self->xnum && is_url_char(self->cpu_cells[ans].ch)) ans++; }
|
||||
if (ans) ans--;
|
||||
while (ans > x && can_strip_from_end_of_url(self->cpu_cells[ans].ch)) ans--;
|
||||
return ans;
|
||||
@@ -148,9 +149,11 @@ url_start_at(Line *self, PyObject *x) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
url_end_at(Line *self, PyObject *x) {
|
||||
url_end_at(Line *self, PyObject *args) {
|
||||
#define url_end_at_doc "url_end_at(x) -> Return the end cell number for a URL containing x or 0 if not found"
|
||||
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, PyLong_AsUnsignedLong(x), true));
|
||||
unsigned int x, sentinel = 0;
|
||||
if (!PyArg_ParseTuple(args, "I|I", &x, &sentinel)) return NULL;
|
||||
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel));
|
||||
}
|
||||
|
||||
// }}}
|
||||
@@ -301,7 +304,10 @@ width(Line *self, PyObject *val) {
|
||||
void
|
||||
line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
|
||||
CPUCell *cell = self->cpu_cells + x;
|
||||
if (!cell->ch) return; // dont allow adding combining chars to a null cell
|
||||
if (!cell->ch) {
|
||||
if (x > 0 && (self->gpu_cells[x-1].attrs & WIDTH_MASK) == 2 && self->cpu_cells[x-1].ch) cell = self->cpu_cells + x - 1;
|
||||
else return; // don't allow adding combining chars to a null cell
|
||||
}
|
||||
for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) {
|
||||
if (!cell->cc_idx[i]) { cell->cc_idx[i] = mark_for_codepoint(ch); return; }
|
||||
}
|
||||
@@ -560,7 +566,7 @@ static PyMethodDef methods[] = {
|
||||
METHOD(is_continued, METH_NOARGS)
|
||||
METHOD(width, METH_O)
|
||||
METHOD(url_start_at, METH_O)
|
||||
METHOD(url_end_at, METH_O)
|
||||
METHOD(url_end_at, METH_VARARGS)
|
||||
METHOD(sprite_at, METH_O)
|
||||
|
||||
{NULL} /* Sentinel */
|
||||
|
||||
@@ -41,6 +41,17 @@ xlimit_for_line(Line *line) {
|
||||
return xlimit;
|
||||
}
|
||||
|
||||
static inline void
|
||||
line_save_cells(Line *line, index_type start, index_type num, GPUCell *gpu_cells, CPUCell *cpu_cells) {
|
||||
memcpy(gpu_cells + start, line->gpu_cells + start, sizeof(GPUCell) * num);
|
||||
memcpy(cpu_cells + start, line->cpu_cells + start, sizeof(CPUCell) * num);
|
||||
}
|
||||
|
||||
static inline void
|
||||
line_reset_cells(Line *line, index_type start, index_type num, GPUCell *gpu_cells, CPUCell *cpu_cells) {
|
||||
memcpy(line->gpu_cells + start, gpu_cells + start, sizeof(GPUCell) * num);
|
||||
memcpy(line->cpu_cells + start, cpu_cells + start, sizeof(CPUCell) * num);
|
||||
}
|
||||
|
||||
void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch);
|
||||
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char);
|
||||
@@ -48,7 +59,7 @@ void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bo
|
||||
void line_right_shift(Line *, unsigned int , unsigned int );
|
||||
void line_add_combining_char(Line *, uint32_t , unsigned int );
|
||||
index_type line_url_start_at(Line *self, index_type x);
|
||||
index_type line_url_end_at(Line *self, index_type x, bool);
|
||||
index_type line_url_end_at(Line *self, index_type x, bool, char_type);
|
||||
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
|
||||
unsigned int line_length(Line *self);
|
||||
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
|
||||
|
||||
268
kitty/macos_process_info.c
Normal file
268
kitty/macos_process_info.c
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* macos_process_info.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
typedef void* rusage_info_t; // needed for libproc.h
|
||||
#include <libproc.h>
|
||||
|
||||
static PyObject*
|
||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); return NULL; }
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); return NULL; }
|
||||
struct proc_vnodepathinfo vpi;
|
||||
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
||||
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
// Read the maximum argument size for processes
|
||||
static inline int
|
||||
get_argmax() {
|
||||
int argmax;
|
||||
int mib[] = { CTL_KERN, KERN_ARGMAX };
|
||||
size_t size = sizeof(argmax);
|
||||
|
||||
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0)
|
||||
return argmax;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
// Taken from psutil, with thanks (BSD 3-clause license)
|
||||
int mib[3];
|
||||
int nargs;
|
||||
size_t len;
|
||||
char *procargs = NULL;
|
||||
char *arg_ptr;
|
||||
char *arg_end;
|
||||
char *curr_arg;
|
||||
size_t argmax;
|
||||
|
||||
PyObject *py_arg = NULL;
|
||||
PyObject *py_retlist = NULL;
|
||||
if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); goto error; }
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); goto error; }
|
||||
|
||||
// special case for PID 0 (kernel_task) where cmdline cannot be fetched
|
||||
if (pid == 0)
|
||||
return Py_BuildValue("[]");
|
||||
|
||||
// read argmax and allocate memory for argument space.
|
||||
argmax = get_argmax();
|
||||
if (!argmax) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
procargs = (char *)malloc(argmax);
|
||||
if (NULL == procargs) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// read argument space
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROCARGS2;
|
||||
mib[2] = (pid_t)pid;
|
||||
if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
|
||||
// In case of zombie process or non-existant process we'll get EINVAL.
|
||||
if (errno == EINVAL)
|
||||
PyErr_Format(PyExc_ValueError, "process with pid %ld either does not exist or is a zombie", pid);
|
||||
else
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
arg_end = &procargs[argmax];
|
||||
// copy the number of arguments to nargs
|
||||
memcpy(&nargs, procargs, sizeof(nargs));
|
||||
|
||||
arg_ptr = procargs + sizeof(nargs);
|
||||
len = strlen(arg_ptr);
|
||||
arg_ptr += len + 1;
|
||||
|
||||
if (arg_ptr == arg_end) {
|
||||
free(procargs);
|
||||
return Py_BuildValue("[]");
|
||||
}
|
||||
|
||||
// skip ahead to the first argument
|
||||
for (; arg_ptr < arg_end; arg_ptr++) {
|
||||
if (*arg_ptr != '\0')
|
||||
break;
|
||||
}
|
||||
|
||||
// iterate through arguments
|
||||
curr_arg = arg_ptr;
|
||||
py_retlist = Py_BuildValue("[]");
|
||||
if (!py_retlist)
|
||||
goto error;
|
||||
while (arg_ptr < arg_end && nargs > 0) {
|
||||
if (*arg_ptr++ == '\0') {
|
||||
py_arg = PyUnicode_DecodeFSDefault(curr_arg);
|
||||
if (! py_arg)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_arg))
|
||||
goto error;
|
||||
Py_DECREF(py_arg);
|
||||
// iterate to next arg and decrement # of args
|
||||
curr_arg = arg_ptr;
|
||||
nargs--;
|
||||
}
|
||||
}
|
||||
|
||||
free(procargs);
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_arg);
|
||||
Py_XDECREF(py_retlist);
|
||||
if (procargs != NULL)
|
||||
free(procargs);
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
PyObject *
|
||||
environ_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
// Taken from psutil, with thanks (BSD 3-clause license)
|
||||
int mib[3];
|
||||
int nargs;
|
||||
char *procargs = NULL;
|
||||
char *procenv = NULL;
|
||||
char *arg_ptr;
|
||||
char *arg_end;
|
||||
char *env_start;
|
||||
size_t argmax;
|
||||
PyObject *py_ret = NULL;
|
||||
if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); goto error; }
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); goto error; }
|
||||
|
||||
// special case for PID 0 (kernel_task) where cmdline cannot be fetched
|
||||
if (pid == 0)
|
||||
goto empty;
|
||||
|
||||
// read argmax and allocate memory for argument space.
|
||||
argmax = get_argmax();
|
||||
if (! argmax) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
procargs = (char *)malloc(argmax);
|
||||
if (NULL == procargs) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// read argument space
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROCARGS2;
|
||||
mib[2] = (pid_t)pid;
|
||||
if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
|
||||
// In case of zombie process or a non-existant process we'll get EINVAL
|
||||
// to NSP and _psosx.py will translate it to ZP.
|
||||
if (errno == EINVAL)
|
||||
PyErr_Format(PyExc_ValueError, "process with pid %ld either does not exist or is a zombie", pid);
|
||||
else
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
arg_end = &procargs[argmax];
|
||||
// copy the number of arguments to nargs
|
||||
memcpy(&nargs, procargs, sizeof(nargs));
|
||||
|
||||
// skip executable path
|
||||
arg_ptr = procargs + sizeof(nargs);
|
||||
arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr);
|
||||
|
||||
if (arg_ptr == NULL || arg_ptr == arg_end)
|
||||
goto empty;
|
||||
|
||||
// skip ahead to the first argument
|
||||
for (; arg_ptr < arg_end; arg_ptr++) {
|
||||
if (*arg_ptr != '\0')
|
||||
break;
|
||||
}
|
||||
|
||||
// iterate through arguments
|
||||
while (arg_ptr < arg_end && nargs > 0) {
|
||||
if (*arg_ptr++ == '\0')
|
||||
nargs--;
|
||||
}
|
||||
|
||||
// build an environment variable block
|
||||
env_start = arg_ptr;
|
||||
|
||||
procenv = calloc(1, arg_end - arg_ptr);
|
||||
if (procenv == NULL) {
|
||||
PyErr_NoMemory();
|
||||
goto error;
|
||||
}
|
||||
|
||||
while (*arg_ptr != '\0' && arg_ptr < arg_end) {
|
||||
char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr);
|
||||
|
||||
if (s == NULL)
|
||||
break;
|
||||
|
||||
memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr);
|
||||
|
||||
arg_ptr = s + 1;
|
||||
}
|
||||
|
||||
py_ret = PyUnicode_DecodeFSDefaultAndSize(
|
||||
procenv, arg_ptr - env_start + 1);
|
||||
if (!py_ret) {
|
||||
// XXX: don't want to free() this as per:
|
||||
// https://github.com/giampaolo/psutil/issues/926
|
||||
// It sucks but not sure what else to do.
|
||||
procargs = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
free(procargs);
|
||||
free(procenv);
|
||||
|
||||
return py_ret;
|
||||
|
||||
empty:
|
||||
if (procargs != NULL)
|
||||
free(procargs);
|
||||
return Py_BuildValue("s", "");
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_ret);
|
||||
if (procargs != NULL)
|
||||
free(procargs);
|
||||
if (procenv != NULL)
|
||||
free(procargs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
||||
{"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""},
|
||||
{"environ_of_process", (PyCFunction)environ_of_process, METH_O, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
bool
|
||||
init_macos_process_info(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ from contextlib import contextmanager
|
||||
|
||||
from .borders import load_borders_program
|
||||
from .boss import Boss
|
||||
from .child import set_default_env
|
||||
from .cli import create_opts, parse_args
|
||||
from .config import cached_values_for, initial_window_size_func
|
||||
from .constants import (
|
||||
@@ -82,10 +83,13 @@ def talk_to_instance(args):
|
||||
|
||||
|
||||
def load_all_shaders(semi_transparent=0):
|
||||
load_shader_programs(semi_transparent)
|
||||
load_shader_programs(semi_transparent, load_all_shaders.cursor_text_color)
|
||||
load_borders_program()
|
||||
|
||||
|
||||
load_all_shaders.cursor_text_color = None
|
||||
|
||||
|
||||
def init_glfw(debug_keyboard=False):
|
||||
glfw_module = 'cocoa' if is_macos else ('wayland' if is_wayland else 'x11')
|
||||
if not glfw_init(glfw_path(glfw_module), debug_keyboard):
|
||||
@@ -116,8 +120,11 @@ def get_new_os_window_trigger(opts):
|
||||
|
||||
def _run_app(opts, args):
|
||||
new_os_window_trigger = get_new_os_window_trigger(opts)
|
||||
if is_macos:
|
||||
if False and is_macos:
|
||||
# This is disabled because using custom cursors fails
|
||||
# on dual GPU machines: https://github.com/kovidgoyal/kitty/issues/794
|
||||
set_custom_ibeam_cursor()
|
||||
load_all_shaders.cursor_text_color = opts.cursor_text_color
|
||||
with cached_values_for(run_app.cached_values_name) as cached_values:
|
||||
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
|
||||
window_id = create_os_window(
|
||||
@@ -198,6 +205,15 @@ def macos_cmdline():
|
||||
return ans
|
||||
|
||||
|
||||
def setup_environment(opts, args):
|
||||
extra_env = opts.env.copy()
|
||||
if opts.editor != '.':
|
||||
os.environ['EDITOR'] = opts.editor
|
||||
if args.listen_on:
|
||||
os.environ['KITTY_LISTEN_ON'] = args.listen_on
|
||||
set_default_env(extra_env)
|
||||
|
||||
|
||||
def _main():
|
||||
try:
|
||||
sys.setswitchinterval(1000.0) # we have only a single python thread
|
||||
@@ -210,12 +226,12 @@ def _main():
|
||||
except Exception:
|
||||
if not is_macos:
|
||||
raise
|
||||
print('Failed to set locale with LANG:', os.environ.get('LANG'), file=sys.stderr)
|
||||
os.environ.pop('LANG')
|
||||
log_error('Failed to set locale with LANG:', os.environ.get('LANG'))
|
||||
os.environ.pop('LANG', None)
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except Exception:
|
||||
print('Failed to set locale with no LANG, ignoring', file=sys.stderr)
|
||||
log_error('Failed to set locale with no LANG, ignoring')
|
||||
|
||||
# Ensure kitty is in PATH
|
||||
rpath = os.path.dirname(kitty_exe())
|
||||
@@ -252,8 +268,7 @@ def _main():
|
||||
return
|
||||
init_glfw(args.debug_keyboard) # needed for parsing native keysyms
|
||||
opts = create_opts(args)
|
||||
if opts.editor != '.':
|
||||
os.environ['EDITOR'] = opts.editor
|
||||
setup_environment(opts, args)
|
||||
try:
|
||||
with setup_profiling(args):
|
||||
# Avoid needing to launch threads to reap zombies
|
||||
|
||||
110
kitty/mouse.c
110
kitty/mouse.c
@@ -209,32 +209,57 @@ extend_selection(Window *w) {
|
||||
}
|
||||
|
||||
static inline void
|
||||
extend_url(Screen *screen, Line *line, index_type *x, index_type *y) {
|
||||
extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type sentinel) {
|
||||
unsigned int count = 0;
|
||||
while(count++ < 10) {
|
||||
if (*x != line->xnum - 1) break;
|
||||
line = screen_visual_line(screen, *y + 1);
|
||||
if (!line) break; // we deliberately allow non-continued lines as some programs, like mutt split URLs with newlines at line boundaries
|
||||
index_type new_x = line_url_end_at(line, 0, false);
|
||||
index_type new_x = line_url_end_at(line, 0, false, sentinel);
|
||||
if (!new_x) break;
|
||||
*y += 1; *x = new_x;
|
||||
}
|
||||
}
|
||||
|
||||
static inline char_type
|
||||
get_url_sentinel(Line *line, index_type url_start) {
|
||||
char_type before = 0, sentinel;
|
||||
if (url_start > 0 && url_start < line->xnum) before = line->cpu_cells[url_start - 1].ch;
|
||||
switch(before) {
|
||||
case '"':
|
||||
case '\'':
|
||||
case '*':
|
||||
sentinel = before; break;
|
||||
case '(':
|
||||
sentinel = ')'; break;
|
||||
case '[':
|
||||
sentinel = ']'; break;
|
||||
case '{':
|
||||
sentinel = '}'; break;
|
||||
case '<':
|
||||
sentinel = '>'; break;
|
||||
default:
|
||||
sentinel = 0; break;
|
||||
}
|
||||
return sentinel;
|
||||
}
|
||||
|
||||
static inline void
|
||||
detect_url(Screen *screen, unsigned int x, unsigned int y) {
|
||||
bool has_url = false;
|
||||
index_type url_start, url_end = 0;
|
||||
Line *line = screen_visual_line(screen, y);
|
||||
char_type sentinel;
|
||||
if (line) {
|
||||
url_start = line_url_start_at(line, x);
|
||||
if (url_start < line->xnum) url_end = line_url_end_at(line, x, true);
|
||||
sentinel = get_url_sentinel(line, url_start);
|
||||
if (url_start < line->xnum) url_end = line_url_end_at(line, x, true, sentinel);
|
||||
has_url = url_end > url_start;
|
||||
}
|
||||
if (has_url) {
|
||||
mouse_cursor_shape = HAND;
|
||||
index_type y_extended = y;
|
||||
extend_url(screen, line, &url_end, &y_extended);
|
||||
extend_url(screen, line, &url_end, &y_extended, sentinel);
|
||||
screen_mark_url(screen, url_start, y, url_end, y_extended);
|
||||
} else {
|
||||
mouse_cursor_shape = BEAM;
|
||||
@@ -451,6 +476,17 @@ focus_in_event() {
|
||||
if (w && w->render_data.screen) screen_mark_url(w->render_data.screen, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void
|
||||
enter_event() {
|
||||
#ifdef __APPLE__
|
||||
// On cocoa there is no way to configure the window manager to
|
||||
// focus windows on mouse enter, so we do it ourselves
|
||||
if (OPT(focus_follows_mouse) && !global_state.callback_os_window->is_focused) {
|
||||
focus_os_window(global_state.callback_os_window, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mouse_event(int button, int modifiers) {
|
||||
MouseShape old_cursor = mouse_cursor_shape;
|
||||
@@ -491,27 +527,59 @@ mouse_event(int button, int modifiers) {
|
||||
}
|
||||
|
||||
void
|
||||
scroll_event(double UNUSED xoffset, double yoffset) {
|
||||
// glfw inverts the y-axis when reporting scroll events under wayland
|
||||
// Until this is fixed in upstream, invert y ourselves.
|
||||
if (global_state.is_wayland) yoffset *= -1;
|
||||
int s = (int) round(yoffset * OPT(wheel_scroll_multiplier));
|
||||
if (s == 0) return;
|
||||
bool upwards = s > 0;
|
||||
scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
bool in_tab_bar;
|
||||
unsigned int window_idx = 0;
|
||||
Window *w = window_for_event(&window_idx, &in_tab_bar);
|
||||
if (w) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
} else {
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
|
||||
if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
|
||||
} else {
|
||||
fake_scroll(abs(s), upwards);
|
||||
if (!w && !in_tab_bar) {
|
||||
// allow scroll events even if window is not currently focused (in
|
||||
// which case on some platforms such as macOS the mouse location is zeroed so
|
||||
// window_for_event() does not work).
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
if (t) w = t->windows + t->active_window;
|
||||
}
|
||||
if (!w) return;
|
||||
|
||||
int s;
|
||||
bool is_high_resolution = flags & 1;
|
||||
if (is_high_resolution) {
|
||||
if (yoffset * global_state.callback_os_window->pending_scroll_pixels < 0) {
|
||||
global_state.callback_os_window->pending_scroll_pixels = 0; // change of direction
|
||||
}
|
||||
double pixels = global_state.callback_os_window->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;
|
||||
return;
|
||||
}
|
||||
s = abs(((int)round(pixels))) / global_state.callback_os_window->fonts_data->cell_height;
|
||||
if (pixels < 0) s *= -1;
|
||||
global_state.callback_os_window->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height;
|
||||
} else {
|
||||
s = (int) round(yoffset * OPT(wheel_scroll_multiplier));
|
||||
global_state.callback_os_window->pending_scroll_pixels = 0;
|
||||
}
|
||||
if (s == 0) return;
|
||||
bool upwards = s > 0;
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
} else {
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
if (is_high_resolution) {
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
write_escape_code_to_child(screen, CSI, mouse_event_buf);
|
||||
}
|
||||
} else {
|
||||
// Since we are sending a mouse button 4/5 event, we ignore 's'
|
||||
// and simply send one event per received scroll event
|
||||
write_escape_code_to_child(screen, CSI, mouse_event_buf);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fake_scroll(abs(s), upwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
319
kitty/parse-graphics-command.h
Normal file
319
kitty/parse-graphics-command.h
Normal file
@@ -0,0 +1,319 @@
|
||||
// This file is generated by ./gen-apc-parsers.py do not edit!
|
||||
|
||||
#pragma once
|
||||
|
||||
static inline void parse_graphics_code(Screen *screen,
|
||||
PyObject UNUSED *dump_callback) {
|
||||
unsigned int pos = 1;
|
||||
enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD };
|
||||
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||
static GraphicsCommand g;
|
||||
unsigned int i, code;
|
||||
uint64_t lcode;
|
||||
bool is_negative;
|
||||
memset(&g, 0, sizeof(g));
|
||||
size_t sz;
|
||||
static uint8_t payload[4096];
|
||||
|
||||
enum KEYS {
|
||||
action = 'a',
|
||||
delete_action = 'd',
|
||||
transmission_type = 't',
|
||||
compressed = 'o',
|
||||
format = 'f',
|
||||
more = 'm',
|
||||
id = 'i',
|
||||
width = 'w',
|
||||
height = 'h',
|
||||
x_offset = 'x',
|
||||
y_offset = 'y',
|
||||
data_height = 'v',
|
||||
data_width = 's',
|
||||
data_sz = 'S',
|
||||
data_offset = 'O',
|
||||
num_cells = 'c',
|
||||
num_lines = 'r',
|
||||
cell_x_offset = 'X',
|
||||
cell_y_offset = 'Y',
|
||||
z_index = 'z'
|
||||
};
|
||||
|
||||
enum KEYS key = 'a';
|
||||
|
||||
while (pos < screen->parser_buf_pos) {
|
||||
switch (state) {
|
||||
case KEY:
|
||||
key = screen->parser_buf[pos++];
|
||||
state = EQUAL;
|
||||
switch (key) {
|
||||
case action:
|
||||
value_state = FLAG;
|
||||
break;
|
||||
case delete_action:
|
||||
value_state = FLAG;
|
||||
break;
|
||||
case transmission_type:
|
||||
value_state = FLAG;
|
||||
break;
|
||||
case compressed:
|
||||
value_state = FLAG;
|
||||
break;
|
||||
case format:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case more:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case width:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case height:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case x_offset:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case y_offset:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case data_height:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case data_width:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case data_sz:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case data_offset:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case num_cells:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case num_lines:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case cell_x_offset:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case cell_y_offset:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case z_index:
|
||||
value_state = INT;
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, invalid key "
|
||||
"character: 0x%x",
|
||||
key);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case EQUAL:
|
||||
if (screen->parser_buf[pos++] != '=') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, no = after key, "
|
||||
"found: 0x%x instead",
|
||||
screen->parser_buf[pos - 1]);
|
||||
return;
|
||||
}
|
||||
state = value_state;
|
||||
break;
|
||||
|
||||
case FLAG:
|
||||
switch (key) {
|
||||
|
||||
case action: {
|
||||
g.action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.action != 'q' && g.action != 'd' && g.action != 't' &&
|
||||
g.action != 'T' && g.action != 'p') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for action: 0x%x",
|
||||
g.action);
|
||||
return;
|
||||
};
|
||||
} break;
|
||||
|
||||
case delete_action: {
|
||||
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.delete_action != 'q' && g.delete_action != 'c' &&
|
||||
g.delete_action != 'Z' && g.delete_action != 'z' &&
|
||||
g.delete_action != 'x' && g.delete_action != 'X' &&
|
||||
g.delete_action != 'I' && g.delete_action != 'i' &&
|
||||
g.delete_action != 'P' && g.delete_action != 'y' &&
|
||||
g.delete_action != 'a' && g.delete_action != 'A' &&
|
||||
g.delete_action != 'Q' && g.delete_action != 'C' &&
|
||||
g.delete_action != 'p' && g.delete_action != 'Y') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for delete_action: 0x%x",
|
||||
g.delete_action);
|
||||
return;
|
||||
};
|
||||
} break;
|
||||
|
||||
case transmission_type: {
|
||||
g.transmission_type = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.transmission_type != 's' && g.transmission_type != 'f' &&
|
||||
g.transmission_type != 't' && g.transmission_type != 'd') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for transmission_type: 0x%x",
|
||||
g.transmission_type);
|
||||
return;
|
||||
};
|
||||
} break;
|
||||
|
||||
case compressed: {
|
||||
g.compressed = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.compressed != 'z') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for compressed: 0x%x",
|
||||
g.compressed);
|
||||
return;
|
||||
};
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
|
||||
case INT:
|
||||
#define READ_UINT \
|
||||
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) { \
|
||||
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') \
|
||||
break; \
|
||||
} \
|
||||
if (i == pos) { \
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, expecting an " \
|
||||
"integer value for key: %c", \
|
||||
key & 0xFF); \
|
||||
return; \
|
||||
} \
|
||||
lcode = utoi(screen->parser_buf + pos, i - pos); \
|
||||
pos = i; \
|
||||
if (lcode > UINT32_MAX) { \
|
||||
REPORT_ERROR( \
|
||||
"Malformed GraphicsCommand control block, number is too large"); \
|
||||
return; \
|
||||
} \
|
||||
code = lcode;
|
||||
|
||||
is_negative = false;
|
||||
if (screen->parser_buf[pos] == '-') {
|
||||
is_negative = true;
|
||||
pos++;
|
||||
}
|
||||
#define I(x) \
|
||||
case x: \
|
||||
g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; \
|
||||
break
|
||||
READ_UINT;
|
||||
switch (key) {
|
||||
I(z_index);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef I
|
||||
case UINT:
|
||||
READ_UINT;
|
||||
#define U(x) \
|
||||
case x: \
|
||||
g.x = code; \
|
||||
break
|
||||
switch (key) {
|
||||
U(format);
|
||||
U(more);
|
||||
U(id);
|
||||
U(width);
|
||||
U(height);
|
||||
U(x_offset);
|
||||
U(y_offset);
|
||||
U(data_height);
|
||||
U(data_width);
|
||||
U(data_sz);
|
||||
U(data_offset);
|
||||
U(num_cells);
|
||||
U(num_lines);
|
||||
U(cell_x_offset);
|
||||
U(cell_y_offset);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef U
|
||||
#undef READ_UINT
|
||||
|
||||
case AFTER_VALUE:
|
||||
switch (screen->parser_buf[pos++]) {
|
||||
default:
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, expecting a "
|
||||
"comma or semi-colon after a value, found: 0x%x",
|
||||
screen->parser_buf[pos - 1]);
|
||||
return;
|
||||
case ',':
|
||||
state = KEY;
|
||||
break;
|
||||
case ';':
|
||||
state = PAYLOAD;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PAYLOAD: {
|
||||
sz = screen->parser_buf_pos - pos;
|
||||
const char *err = base64_decode(screen->parser_buf + pos, sz, payload,
|
||||
sizeof(payload), &g.payload_sz);
|
||||
if (err != NULL) {
|
||||
REPORT_ERROR(
|
||||
"Failed to parse GraphicsCommand command payload with error: %s",
|
||||
err);
|
||||
return;
|
||||
}
|
||||
pos = screen->parser_buf_pos;
|
||||
} break;
|
||||
|
||||
} // end switch
|
||||
} // end while
|
||||
|
||||
switch (state) {
|
||||
case EQUAL:
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, no = after key");
|
||||
return;
|
||||
case INT:
|
||||
case UINT:
|
||||
REPORT_ERROR(
|
||||
"Malformed GraphicsCommand control block, expecting an integer value");
|
||||
return;
|
||||
case FLAG:
|
||||
REPORT_ERROR(
|
||||
"Malformed GraphicsCommand control block, expecting a flag value");
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
REPORT_VA_COMMAND(
|
||||
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si sI} y#",
|
||||
"graphics_command", "action", g.action, "delete_action", g.delete_action,
|
||||
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
||||
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
|
||||
(unsigned int)g.id, "width", (unsigned int)g.width, "height",
|
||||
(unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset",
|
||||
(unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height,
|
||||
"data_width", (unsigned int)g.data_width, "data_sz",
|
||||
(unsigned int)g.data_sz, "data_offset", (unsigned int)g.data_offset,
|
||||
"num_cells", (unsigned int)g.num_cells, "num_lines",
|
||||
(unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset,
|
||||
"cell_y_offset", (unsigned int)g.cell_y_offset, "z_index", (int)g.z_index,
|
||||
"payload_sz", g.payload_sz, payload, g.payload_sz);
|
||||
|
||||
screen_handle_graphics_command(screen, &g, payload);
|
||||
}
|
||||
436
kitty/parser.c
436
kitty/parser.c
@@ -703,20 +703,32 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
REPORT_ERROR("Unknown CSI s sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
||||
break;
|
||||
case 't':
|
||||
if (!start_modifier && !end_modifier && num_params == 1) {
|
||||
switch(params[0]) {
|
||||
case 14:
|
||||
case 16:
|
||||
case 18:
|
||||
CALL_CSI_HANDLER1(screen_report_size, 0);
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown CSI t sequences with parameter: '%u'", params[0]);
|
||||
break;
|
||||
}
|
||||
if (!num_params) {
|
||||
REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c' and no parameters", start_modifier, end_modifier);
|
||||
break;
|
||||
}
|
||||
REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
||||
if (start_modifier || end_modifier) {
|
||||
REPORT_ERROR("Unknown CSI t sequence with start and end modifiers: '%c' '%c', %u parameters and first parameter: %u", start_modifier, end_modifier, num_params, params[0]);
|
||||
break;
|
||||
}
|
||||
switch(params[0]) {
|
||||
case 4:
|
||||
case 8:
|
||||
log_error("Escape codes to resize text area are not supported");
|
||||
break;
|
||||
case 14:
|
||||
case 16:
|
||||
case 18:
|
||||
CALL_CSI_HANDLER1(screen_report_size, 0);
|
||||
break;
|
||||
case 22:
|
||||
case 23:
|
||||
CALL_CSI_HANDLER2(screen_manipulate_title_stack, 22, 0);
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown CSI t window manipulation sequence with %u parameters and first parameter: %u", num_params, params[0]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (!start_modifier && !end_modifier && !num_params) {
|
||||
@@ -775,6 +787,8 @@ startswith(const uint32_t *string, size_t sz, const char *prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#define PENDING_MODE_CHAR '='
|
||||
|
||||
static inline void
|
||||
dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
if (screen->parser_buf_pos < 2) return;
|
||||
@@ -792,6 +806,19 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]);
|
||||
}
|
||||
break;
|
||||
case PENDING_MODE_CHAR:
|
||||
if (screen->parser_buf_pos > 2 && (screen->parser_buf[1] == '1' || screen->parser_buf[2] == '2') && screen->parser_buf[2] == 's') {
|
||||
if (screen->parser_buf[1] == '1') {
|
||||
screen->pending_mode.activated_at = monotonic();
|
||||
REPORT_COMMAND(screen_start_pending_mode);
|
||||
} else {
|
||||
// ignore stop without matching start
|
||||
REPORT_COMMAND(screen_stop_pending_mode);
|
||||
}
|
||||
} else {
|
||||
REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]);
|
||||
}
|
||||
break;
|
||||
case '@':
|
||||
#define CMD_PREFIX "kitty-cmd{"
|
||||
if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, CMD_PREFIX)) {
|
||||
@@ -824,161 +851,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
|
||||
// APC mode {{{
|
||||
|
||||
static inline void
|
||||
parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
unsigned int pos = 1;
|
||||
enum GR_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD };
|
||||
enum GR_STATES state = KEY, value_state = FLAG;
|
||||
enum KEYS {
|
||||
action='a',
|
||||
delete_action='d',
|
||||
transmission_type='t',
|
||||
compressed='o',
|
||||
format = 'f',
|
||||
more = 'm',
|
||||
id = 'i',
|
||||
width = 'w',
|
||||
height = 'h',
|
||||
x_offset = 'x',
|
||||
y_offset = 'y',
|
||||
data_height = 'v',
|
||||
data_width = 's',
|
||||
data_sz = 'S',
|
||||
data_offset = 'O',
|
||||
num_cells = 'c',
|
||||
num_lines = 'r',
|
||||
cell_x_offset = 'X',
|
||||
cell_y_offset = 'Y',
|
||||
z_index = 'z'
|
||||
};
|
||||
enum KEYS key = 'a';
|
||||
static GraphicsCommand g;
|
||||
unsigned int i, code;
|
||||
uint64_t lcode;
|
||||
bool is_negative;
|
||||
memset(&g, 0, sizeof(g));
|
||||
static uint8_t payload[4096];
|
||||
size_t sz;
|
||||
const char *err;
|
||||
|
||||
while (pos < screen->parser_buf_pos) {
|
||||
switch(state) {
|
||||
|
||||
case KEY:
|
||||
key = screen->parser_buf[pos++];
|
||||
switch(key) {
|
||||
#define KS(n, vs) case n: state = EQUAL; value_state = vs; break
|
||||
#define U(x) KS(x, UINT)
|
||||
KS(action, FLAG); KS(delete_action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT);
|
||||
U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset);
|
||||
#undef U
|
||||
#undef KS
|
||||
default:
|
||||
REPORT_ERROR("Malformed graphics control block, invalid key character: 0x%x", key);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case EQUAL:
|
||||
if (screen->parser_buf[pos++] != '=') {
|
||||
REPORT_ERROR("Malformed graphics control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]);
|
||||
return;
|
||||
}
|
||||
state = value_state;
|
||||
break;
|
||||
|
||||
case FLAG:
|
||||
switch(key) {
|
||||
#define F(a) case a: g.a = screen->parser_buf[pos++] & 0xff; break
|
||||
F(action); F(delete_action); F(transmission_type); F(compressed);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef F
|
||||
|
||||
case INT:
|
||||
#define READ_UINT \
|
||||
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) { \
|
||||
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \
|
||||
} \
|
||||
if (i == pos) { REPORT_ERROR("Malformed graphics control block, expecting an integer value for key: %c", key & 0xFF); return; } \
|
||||
lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \
|
||||
if (lcode > UINT32_MAX) { REPORT_ERROR("id is too large"); return; } \
|
||||
code = lcode;
|
||||
|
||||
is_negative = false;
|
||||
if(screen->parser_buf[pos] == '-') { is_negative = true; pos++; }
|
||||
#define U(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
|
||||
READ_UINT;
|
||||
switch(key) {
|
||||
U(z_index);
|
||||
default: break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef U
|
||||
case UINT:
|
||||
READ_UINT;
|
||||
#define U(x) case x: g.x = code; break
|
||||
switch(key) {
|
||||
U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset);
|
||||
default: break;
|
||||
}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef U
|
||||
#undef SET_ATTR
|
||||
#undef READ_UINT
|
||||
case AFTER_VALUE:
|
||||
switch (screen->parser_buf[pos++]) {
|
||||
case ',':
|
||||
state = KEY;
|
||||
break;
|
||||
case ';':
|
||||
state = PAYLOAD;
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Malformed graphics control block, expecting a comma or semi-colon after a value, found: 0x%x", screen->parser_buf[pos - 1]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case PAYLOAD:
|
||||
sz = screen->parser_buf_pos - pos;
|
||||
err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz);
|
||||
if (err != NULL) { REPORT_ERROR("Failed to parse graphics command payload with error: %s", err); return; }
|
||||
pos = screen->parser_buf_pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch(state) {
|
||||
case EQUAL:
|
||||
REPORT_ERROR("Malformed graphics control block, no = after key"); return;
|
||||
case INT:
|
||||
case UINT:
|
||||
REPORT_ERROR("Malformed graphics control block, expecting an integer value"); return;
|
||||
case FLAG:
|
||||
REPORT_ERROR("Malformed graphics control block, expecting a flag value"); return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#define A(x) #x, g.x
|
||||
#define U(x) #x, (unsigned int)(g.x)
|
||||
#define I(x) #x, (int)(g.x)
|
||||
REPORT_VA_COMMAND("s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command",
|
||||
A(action), A(delete_action), A(transmission_type), A(compressed),
|
||||
U(format), U(more), U(id), U(data_sz), U(data_offset),
|
||||
U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset),
|
||||
U(payload_sz), I(z_index),
|
||||
payload, g.payload_sz
|
||||
);
|
||||
#undef U
|
||||
#undef A
|
||||
#undef I
|
||||
screen_handle_graphics_command(screen, &g, payload);
|
||||
}
|
||||
#include "parse-graphics-command.h"
|
||||
|
||||
static inline void
|
||||
dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
@@ -1157,45 +1030,45 @@ END_ALLOW_CASE_RANGE
|
||||
#undef ENSURE_SPACE
|
||||
}
|
||||
|
||||
static inline void
|
||||
dispatch_unicode_char(Screen *screen, uint32_t codepoint, PyObject DUMP_UNUSED *dump_callback) {
|
||||
#define HANDLE(name) handle_##name(screen, codepoint, dump_callback); break
|
||||
switch(screen->parser_state) {
|
||||
case ESC:
|
||||
HANDLE(esc_mode_char);
|
||||
case CSI:
|
||||
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); }
|
||||
break;
|
||||
case OSC:
|
||||
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); }
|
||||
break;
|
||||
case APC:
|
||||
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch_apc(screen, dump_callback); SET_STATE(0); }
|
||||
break;
|
||||
case PM:
|
||||
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch_pm(screen, dump_callback); SET_STATE(0); }
|
||||
break;
|
||||
case DCS:
|
||||
if (accumulate_dcs(screen, codepoint, dump_callback)) { dispatch_dcs(screen, dump_callback); SET_STATE(0); }
|
||||
if (screen->parser_state == ESC) { HANDLE(esc_mode_char); }
|
||||
break;
|
||||
default:
|
||||
HANDLE(normal_mode_char);
|
||||
}
|
||||
#undef HANDLE
|
||||
}
|
||||
#define dispatch_unicode_char(codepoint, watch_for_pending) { \
|
||||
switch(screen->parser_state) { \
|
||||
case ESC: \
|
||||
handle_esc_mode_char(screen, codepoint, dump_callback); \
|
||||
break; \
|
||||
case CSI: \
|
||||
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); } \
|
||||
break; \
|
||||
case OSC: \
|
||||
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } \
|
||||
break; \
|
||||
case APC: \
|
||||
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch_apc(screen, dump_callback); SET_STATE(0); } \
|
||||
break; \
|
||||
case PM: \
|
||||
if (accumulate_oth(screen, codepoint, dump_callback)) { dispatch_pm(screen, dump_callback); SET_STATE(0); } \
|
||||
break; \
|
||||
case DCS: \
|
||||
if (accumulate_dcs(screen, codepoint, dump_callback)) { dispatch_dcs(screen, dump_callback); SET_STATE(0); watch_for_pending; } \
|
||||
if (screen->parser_state == ESC) { handle_esc_mode_char(screen, codepoint, dump_callback); break; } \
|
||||
break; \
|
||||
default: \
|
||||
handle_normal_mode_char(screen, codepoint, dump_callback); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
|
||||
extern uint32_t *latin1_charset;
|
||||
|
||||
static inline void
|
||||
_parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
||||
_parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
||||
uint32_t prev = screen->utf8_state;
|
||||
for (unsigned int i = 0; i < (unsigned int)len; i++) {
|
||||
if (screen->use_latin1) dispatch_unicode_char(screen, latin1_charset[buf[i]], dump_callback);
|
||||
else {
|
||||
if (screen->use_latin1) {
|
||||
dispatch_unicode_char(latin1_charset[buf[i]], ;);
|
||||
} else {
|
||||
switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, buf[i])) {
|
||||
case UTF8_ACCEPT:
|
||||
dispatch_unicode_char(screen, screen->utf8_codepoint, dump_callback);
|
||||
dispatch_unicode_char(screen->utf8_codepoint, ;);
|
||||
break;
|
||||
case UTF8_REJECT:
|
||||
screen->utf8_state = UTF8_ACCEPT;
|
||||
@@ -1207,6 +1080,160 @@ _parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED
|
||||
}
|
||||
FLUSH_DRAW;
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
_parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
||||
uint32_t prev = screen->utf8_state;
|
||||
size_t i = 0;
|
||||
while(i < (size_t)len) {
|
||||
uint8_t ch = buf[i++];
|
||||
if (screen->use_latin1) {
|
||||
dispatch_unicode_char(latin1_charset[ch], if (screen->pending_mode.activated_at) goto end);
|
||||
} else {
|
||||
switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, ch)) {
|
||||
case UTF8_ACCEPT:
|
||||
dispatch_unicode_char(screen->utf8_codepoint, if (screen->pending_mode.activated_at) goto end);
|
||||
break;
|
||||
case UTF8_REJECT:
|
||||
screen->utf8_state = UTF8_ACCEPT;
|
||||
if (prev != UTF8_ACCEPT && i > 0) i--;
|
||||
break;
|
||||
}
|
||||
prev = screen->utf8_state;
|
||||
}
|
||||
}
|
||||
end:
|
||||
FLUSH_DRAW;
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static inline size_t
|
||||
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
|
||||
size_t pos = 0;
|
||||
enum STATE { NORMAL, MAYBE_DCS, IN_DCS, EXPECTING_DATA, EXPECTING_SLASH };
|
||||
enum STATE state = screen->pending_mode.state;
|
||||
#define COPY(what) screen->pending_mode.buf[screen->pending_mode.used++] = what
|
||||
#define COPY_STOP_BUF { \
|
||||
COPY(0x1b); COPY('P'); COPY(PENDING_MODE_CHAR); \
|
||||
for (size_t i = 0; i < screen->pending_mode.stop_buf_pos; i++) { \
|
||||
COPY(screen->pending_mode.stop_buf[i]); \
|
||||
} \
|
||||
screen->pending_mode.stop_buf_pos = 0;}
|
||||
|
||||
while (pos < len) {
|
||||
uint8_t ch = buf[pos++];
|
||||
switch(state) {
|
||||
case NORMAL:
|
||||
if (ch == ESC) state = MAYBE_DCS;
|
||||
else COPY(ch);
|
||||
break;
|
||||
case MAYBE_DCS:
|
||||
if (ch == 'P') state = IN_DCS;
|
||||
else {
|
||||
state = NORMAL;
|
||||
COPY(0x1b); COPY(ch);
|
||||
}
|
||||
break;
|
||||
case IN_DCS:
|
||||
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; screen->pending_mode.stop_buf_pos = 0; }
|
||||
else {
|
||||
state = NORMAL;
|
||||
COPY(0x1b); COPY('P'); COPY(ch);
|
||||
}
|
||||
break;
|
||||
case EXPECTING_DATA:
|
||||
if (ch == 0x1b) state = EXPECTING_SLASH;
|
||||
else {
|
||||
screen->pending_mode.stop_buf[screen->pending_mode.stop_buf_pos++] = ch;
|
||||
if (screen->pending_mode.stop_buf_pos >= sizeof(screen->pending_mode.stop_buf)) {
|
||||
state = NORMAL;
|
||||
COPY_STOP_BUF;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EXPECTING_SLASH:
|
||||
if (
|
||||
ch == '\\' &&
|
||||
screen->pending_mode.stop_buf_pos >= 2 &&
|
||||
(screen->pending_mode.stop_buf[0] == '1' || screen->pending_mode.stop_buf[0] == '2') &&
|
||||
screen->pending_mode.stop_buf[1] == 's'
|
||||
) {
|
||||
// We found a pending mode sequence
|
||||
if (screen->pending_mode.stop_buf[0] == '2') {
|
||||
REPORT_COMMAND(screen_stop_pending_mode);
|
||||
screen->pending_mode.activated_at = 0;
|
||||
goto end;
|
||||
} else {
|
||||
REPORT_COMMAND(screen_start_pending_mode);
|
||||
screen->pending_mode.activated_at = monotonic();
|
||||
}
|
||||
} else {
|
||||
state = NORMAL;
|
||||
COPY_STOP_BUF; COPY(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
screen->pending_mode.state = state;
|
||||
return pos;
|
||||
#undef COPY
|
||||
#undef COPY_STOP_BUF
|
||||
}
|
||||
|
||||
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) {
|
||||
enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING};
|
||||
enum STATE state = START;
|
||||
size_t read_buf_pos = 0;
|
||||
|
||||
do {
|
||||
switch(state) {
|
||||
case START:
|
||||
if (screen->pending_mode.activated_at) {
|
||||
if (screen->pending_mode.activated_at + screen->pending_mode.wait_time < now) {
|
||||
screen->pending_mode.activated_at = 0;
|
||||
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
||||
} else state = QUEUE_PENDING;
|
||||
} else {
|
||||
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSE_PENDING:
|
||||
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback);
|
||||
screen->pending_mode.used = 0; screen->pending_mode.state = 0;
|
||||
screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
|
||||
state = START;
|
||||
break;
|
||||
|
||||
case PARSE_READ_BUF:
|
||||
screen->pending_mode.activated_at = 0; screen->pending_mode.state = 0;
|
||||
read_buf_pos += _parse_bytes_watching_for_pending(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
||||
state = START;
|
||||
break;
|
||||
|
||||
case QUEUE_PENDING: {
|
||||
if (screen->pending_mode.capacity - screen->pending_mode.used < read_buf_sz + sizeof(screen->pending_mode.stop_buf)) {
|
||||
if (screen->pending_mode.capacity >= READ_BUF_SZ) {
|
||||
// Too much pending data, drain it
|
||||
screen->pending_mode.activated_at = 0;
|
||||
state = START;
|
||||
break;
|
||||
}
|
||||
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + read_buf_sz);
|
||||
screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
|
||||
if (!screen->pending_mode.buf) fatal("Out of memory");
|
||||
}
|
||||
read_buf_pos += _queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
||||
state = START;
|
||||
} break;
|
||||
}
|
||||
} while(read_buf_pos < read_buf_sz || (!screen->pending_mode.activated_at && screen->pending_mode.used));
|
||||
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Entry points {{{
|
||||
@@ -1226,17 +1253,20 @@ FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) {
|
||||
#else
|
||||
if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL;
|
||||
#endif
|
||||
_parse_bytes(screen, pybuf.buf, pybuf.len, dump_callback);
|
||||
do_parse_bytes(screen, pybuf.buf, pybuf.len, monotonic(), dump_callback);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback) {
|
||||
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, double now) {
|
||||
#ifdef DUMP_COMMANDS
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear();
|
||||
if (screen->read_buf_sz) {
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear();
|
||||
}
|
||||
#endif
|
||||
_parse_bytes(screen, screen->read_buf, screen->read_buf_sz, dump_callback);
|
||||
#undef FNAME
|
||||
do_parse_bytes(screen, screen->read_buf, screen->read_buf_sz, now, dump_callback);
|
||||
screen->read_buf_sz = 0;
|
||||
}
|
||||
#undef FNAME
|
||||
// }}}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
@@ -18,16 +19,24 @@ from .utils import TTYIO, parse_address_spec
|
||||
def handle_cmd(boss, window, cmd):
|
||||
cmd = json.loads(cmd)
|
||||
v = cmd['version']
|
||||
no_response = cmd['no_response']
|
||||
if tuple(v)[:2] > version[:2]:
|
||||
if no_response:
|
||||
return
|
||||
return {'ok': False, 'error': 'The kitty client you are using to send remote commands is newer than this kitty instance. This is not supported.'}
|
||||
c = cmap[cmd['cmd']]
|
||||
func = partial(c.impl(), boss, window)
|
||||
payload = cmd.get('payload')
|
||||
ans = func() if payload is None else func(payload)
|
||||
try:
|
||||
ans = func() if payload is None else func(payload)
|
||||
except Exception:
|
||||
if no_response: # don't report errors if --no-response was used
|
||||
return
|
||||
raise
|
||||
response = {'ok': True}
|
||||
if ans is not None:
|
||||
response['data'] = ans
|
||||
if not c.no_response:
|
||||
if not c.no_response and not no_response:
|
||||
return response
|
||||
|
||||
|
||||
@@ -137,6 +146,7 @@ def parse_rc_args(args):
|
||||
|
||||
def main(args):
|
||||
global_opts, items = parse_rc_args(args)
|
||||
global_opts.no_command_response = None
|
||||
|
||||
if not items:
|
||||
from kitty.shell import main
|
||||
@@ -156,7 +166,16 @@ def main(args):
|
||||
}
|
||||
if payload is not None:
|
||||
send['payload'] = payload
|
||||
response = do_io(global_opts.to, send, func.no_response)
|
||||
if global_opts.no_command_response is not None:
|
||||
no_response = global_opts.no_command_response
|
||||
else:
|
||||
no_response = func.no_response
|
||||
send['no_response'] = no_response
|
||||
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||
response = do_io(global_opts.to, send, no_response)
|
||||
if no_response:
|
||||
return
|
||||
if not response.get('ok'):
|
||||
if response.get('tb'):
|
||||
print(response['tb'], file=sys.stderr)
|
||||
|
||||
172
kitty/screen.c
172
kitty/screen.c
@@ -30,7 +30,6 @@ typedef struct {
|
||||
unsigned int x; int y;
|
||||
} FullSelectionBoundary;
|
||||
|
||||
|
||||
// Constructor/destructor {{{
|
||||
|
||||
static inline void
|
||||
@@ -41,6 +40,22 @@ init_tabstops(bool *tabstops, index_type count) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
init_overlay_line(Screen *self, index_type columns) {
|
||||
PyMem_Free(self->overlay_line.cpu_cells);
|
||||
PyMem_Free(self->overlay_line.gpu_cells);
|
||||
self->overlay_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell));
|
||||
self->overlay_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell));
|
||||
if (!self->overlay_line.cpu_cells || !self->overlay_line.gpu_cells) {
|
||||
PyErr_NoMemory(); return false;
|
||||
}
|
||||
self->overlay_line.is_active = false;
|
||||
self->overlay_line.xnum = 0;
|
||||
self->overlay_line.ynum = 0;
|
||||
self->overlay_line.xstart = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define RESET_CHARSETS \
|
||||
self->g0_charset = translation_table(0); \
|
||||
self->g1_charset = self->g0_charset; \
|
||||
@@ -96,6 +111,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self->main_grman = grman_alloc();
|
||||
self->alt_grman = grman_alloc();
|
||||
self->grman = self->main_grman;
|
||||
self->pending_mode.wait_time = 2.0;
|
||||
self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool));
|
||||
if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL || self->alt_grman == NULL || self->color_profile == NULL) {
|
||||
Py_CLEAR(self); return NULL;
|
||||
@@ -104,13 +120,18 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self->tabstops = self->main_tabstops;
|
||||
init_tabstops(self->main_tabstops, self->columns);
|
||||
init_tabstops(self->alt_tabstops, self->columns);
|
||||
if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; }
|
||||
}
|
||||
return (PyObject*) self;
|
||||
}
|
||||
|
||||
static void deactivate_overlay_line(Screen *self);
|
||||
static inline Line* range_line_(Screen *self, int y);
|
||||
|
||||
void
|
||||
screen_reset(Screen *self) {
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||
historybuf_clear(self->historybuf);
|
||||
grman_clear(self->grman, false, self->cell_size);
|
||||
@@ -125,6 +146,7 @@ screen_reset(Screen *self) {
|
||||
init_tabstops(self->alt_tabstops, self->columns);
|
||||
cursor_reset(self->cursor);
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
screen_cursor_position(self, 1, 1);
|
||||
set_dynamic_color(self, 110, NULL);
|
||||
set_dynamic_color(self, 111, NULL);
|
||||
@@ -159,6 +181,7 @@ realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *n
|
||||
|
||||
static bool
|
||||
screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
||||
lines = MAX(1, lines); columns = MAX(1, columns);
|
||||
|
||||
bool is_main = self->linebuf == self->main_linebuf;
|
||||
@@ -170,6 +193,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
cursor_is_beyond_content = num_content_lines_before > 0 && self->cursor->y >= num_content_lines_before; \
|
||||
num_content_lines = num_content_lines_after; \
|
||||
}
|
||||
// Resize overlay line
|
||||
if (!init_overlay_line(self, columns)) return false;
|
||||
|
||||
// Resize main linebuf
|
||||
HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns);
|
||||
@@ -246,7 +271,10 @@ dealloc(Screen* self) {
|
||||
Py_CLEAR(self->alt_linebuf);
|
||||
Py_CLEAR(self->historybuf);
|
||||
Py_CLEAR(self->color_profile);
|
||||
PyMem_Free(self->overlay_line.cpu_cells);
|
||||
PyMem_Free(self->overlay_line.gpu_cells);
|
||||
PyMem_Free(self->main_tabstops);
|
||||
free(self->pending_mode.buf);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
} // }}}
|
||||
|
||||
@@ -305,6 +333,14 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type
|
||||
*dest_gpu = src_gpu;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
selection_has_screen_line(Selection *s, int y) {
|
||||
if (s->start_scrolled_by == s->end_scrolled_by && s->start_x == s->end_x && s->start_y == s->end_y) return false;
|
||||
int top = (int)s->start_y - s->start_scrolled_by;
|
||||
int bottom = (int)s->end_y - s->end_scrolled_by;
|
||||
return top <= y && y <= bottom;
|
||||
}
|
||||
|
||||
static inline void
|
||||
draw_combining_char(Screen *self, char_type ch) {
|
||||
bool has_prev_char = false;
|
||||
@@ -323,6 +359,7 @@ draw_combining_char(Screen *self, char_type ch) {
|
||||
if (has_prev_char) {
|
||||
line_add_combining_char(self->linebuf->line, ch, xpos);
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, ypos)) self->selection = EMPTY_SELECTION;
|
||||
linebuf_mark_line_dirty(self->linebuf, ypos);
|
||||
if (ch == 0xfe0f) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji
|
||||
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
|
||||
@@ -371,9 +408,41 @@ screen_draw(Screen *self, uint32_t och) {
|
||||
self->cursor->x++;
|
||||
}
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION;
|
||||
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
|
||||
}
|
||||
|
||||
void
|
||||
screen_draw_overlay_text(Screen *self, const char *utf8_text) {
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
||||
if (!utf8_text || !utf8_text[0]) return;
|
||||
Line *line = range_line_(self, self->cursor->y);
|
||||
if (!line) return;
|
||||
line_save_cells(line, 0, self->columns, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells);
|
||||
self->overlay_line.is_active = true;
|
||||
self->overlay_line.ynum = self->cursor->y;
|
||||
self->overlay_line.xstart = self->cursor->x;
|
||||
self->overlay_line.xnum = 0;
|
||||
uint32_t codepoint = 0, state = UTF8_ACCEPT;
|
||||
bool orig_line_wrap_mode = self->modes.mDECAWM;
|
||||
self->modes.mDECAWM = false;
|
||||
self->cursor->reverse ^= true;
|
||||
index_type before;
|
||||
while (*utf8_text) {
|
||||
switch(decode_utf8(&state, &codepoint, *(utf8_text++))) {
|
||||
case UTF8_ACCEPT:
|
||||
before = self->cursor->x;
|
||||
screen_draw(self, codepoint);
|
||||
self->overlay_line.xnum += self->cursor->x - before;
|
||||
break;
|
||||
case UTF8_REJECT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->cursor->reverse ^= true;
|
||||
self->modes.mDECAWM = orig_line_wrap_mode;
|
||||
}
|
||||
|
||||
void
|
||||
screen_align(Screen *self) {
|
||||
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||
@@ -430,10 +499,15 @@ select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count,
|
||||
} else cursor_from_sgr(self->cursor, params, count);
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_to_test_child(Screen *self, const char *data, size_t sz) {
|
||||
PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r);
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_to_child(Screen *self, const char *data, size_t sz) {
|
||||
if (self->window_id) schedule_write_to_child(self->window_id, data, sz);
|
||||
if (self->test_child != Py_None) { PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r); }
|
||||
if (self->window_id) schedule_write_to_child(self->window_id, 1, data, sz);
|
||||
if (self->test_child != Py_None) { write_to_test_child(self, data, sz); }
|
||||
}
|
||||
|
||||
void
|
||||
@@ -458,9 +532,18 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data)
|
||||
default:
|
||||
fatal("Unknown escape code to write: %u", which);
|
||||
}
|
||||
write_to_child(self, prefix, strlen(prefix));
|
||||
write_to_child(self, data, strlen(data));
|
||||
if (suffix[0]) write_to_child(self, suffix, strlen(suffix));
|
||||
if (self->window_id) {
|
||||
if (suffix[0]) {
|
||||
schedule_write_to_child(self->window_id, 3, prefix, strlen(prefix), data, strlen(data), suffix, strlen(suffix));
|
||||
} else {
|
||||
schedule_write_to_child(self->window_id, 2, prefix, strlen(prefix), data, strlen(data));
|
||||
}
|
||||
}
|
||||
if (self->test_child != Py_None) {
|
||||
write_to_test_child(self, prefix, strlen(prefix));
|
||||
write_to_test_child(self, data, strlen(data));
|
||||
if (suffix[0]) write_to_test_child(self, suffix, strlen(suffix));
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -505,6 +588,7 @@ screen_toggle_screen_buffer(Screen *self) {
|
||||
}
|
||||
screen_history_scroll(self, SCROLL_FULL, false);
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
|
||||
void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
|
||||
@@ -603,6 +687,7 @@ void
|
||||
screen_set_8bit_controls(Screen *self, bool yes) {
|
||||
self->modes.eight_bit_controls = yes;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Cursor {{{
|
||||
@@ -723,6 +808,22 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
index_selection(Screen *self, Selection *s, bool up) {
|
||||
if (s->start_scrolled_by == s->end_scrolled_by && s->start_x == s->end_x && s->start_y == s->end_y) return;
|
||||
if (up) {
|
||||
if (s->start_y == 0) s->start_scrolled_by += 1;
|
||||
else s->start_y--;
|
||||
if (s->end_y == 0) s->end_scrolled_by += 1;
|
||||
else s->end_y--;
|
||||
} else {
|
||||
if (s->start_y >= self->lines - 1) s->start_scrolled_by -= 1;
|
||||
else s->start_y++;
|
||||
if (s->end_y >= self->lines - 1) s->end_scrolled_by -= 1;
|
||||
else s->end_y++;
|
||||
}
|
||||
}
|
||||
|
||||
#define INDEX_GRAPHICS(amtv) { \
|
||||
bool is_main = self->linebuf == self->main_linebuf; \
|
||||
static ScrollData s; \
|
||||
@@ -733,6 +834,7 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
|
||||
}
|
||||
|
||||
#define INDEX_UP \
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self); \
|
||||
linebuf_index(self->linebuf, top, bottom); \
|
||||
INDEX_GRAPHICS(-1) \
|
||||
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
|
||||
@@ -742,7 +844,8 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
|
||||
self->history_line_added_count++; \
|
||||
} \
|
||||
linebuf_clear_line(self->linebuf, bottom); \
|
||||
self->is_dirty = true;
|
||||
self->is_dirty = true; \
|
||||
index_selection(self, &self->selection, true);
|
||||
|
||||
void
|
||||
screen_index(Screen *self) {
|
||||
@@ -764,10 +867,12 @@ screen_scroll(Screen *self, unsigned int count) {
|
||||
}
|
||||
|
||||
#define INDEX_DOWN \
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self); \
|
||||
linebuf_reverse_index(self->linebuf, top, bottom); \
|
||||
linebuf_clear_line(self->linebuf, top); \
|
||||
INDEX_GRAPHICS(1) \
|
||||
self->is_dirty = true;
|
||||
self->is_dirty = true; \
|
||||
index_selection(self, &self->selection, false);
|
||||
|
||||
void
|
||||
screen_reverse_index(Screen *self) {
|
||||
@@ -951,6 +1056,7 @@ screen_erase_in_line(Screen *self, unsigned int how, bool private) {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, s, n, true);
|
||||
}
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION;
|
||||
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
|
||||
}
|
||||
}
|
||||
@@ -994,6 +1100,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
|
||||
linebuf_mark_line_dirty(self->linebuf, i);
|
||||
}
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
if (how != 2) {
|
||||
screen_erase_in_line(self, how, private);
|
||||
@@ -1014,6 +1121,7 @@ screen_insert_lines(Screen *self, unsigned int count) {
|
||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||
linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom);
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
screen_carriage_return(self);
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1133,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
|
||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||
linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom);
|
||||
self->is_dirty = true;
|
||||
self->selection = EMPTY_SELECTION;
|
||||
screen_carriage_return(self);
|
||||
}
|
||||
}
|
||||
@@ -1041,6 +1150,7 @@ screen_insert_characters(Screen *self, unsigned int count) {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, x, num, true);
|
||||
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,6 +1167,7 @@ screen_delete_characters(Screen *self, unsigned int count) {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true);
|
||||
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,6 +1181,7 @@ screen_erase_characters(Screen *self, unsigned int count) {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, x, num, true);
|
||||
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
|
||||
self->is_dirty = true;
|
||||
if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION;
|
||||
}
|
||||
|
||||
// }}}
|
||||
@@ -1142,9 +1254,18 @@ screen_report_size(Screen *self, unsigned int which) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) {
|
||||
CALLBACK("manipulate_title_stack", "OOO",
|
||||
op == 23 ? Py_True : Py_False,
|
||||
which == 0 || which == 2 ? Py_True : Py_False,
|
||||
which == 0 || which == 1 ? Py_True : Py_False
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
report_device_status(Screen *self, unsigned int which, bool private) {
|
||||
// We dont implement the private device status codes, since I haven't come
|
||||
// We don't implement the private device status codes, since I haven't come
|
||||
// across any programs that use them
|
||||
unsigned int x, y;
|
||||
static char buf[64];
|
||||
@@ -1336,10 +1457,10 @@ screen_reset_dirty(Screen *self) {
|
||||
}
|
||||
|
||||
void
|
||||
screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz, FONTS_DATA_HANDLE fonts_data) {
|
||||
screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data) {
|
||||
unsigned int history_line_added_count = self->history_line_added_count;
|
||||
index_type lnum;
|
||||
bool selection_must_be_cleared = self->is_dirty ? true : false;
|
||||
bool was_dirty = self->is_dirty;
|
||||
if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
|
||||
screen_reset_dirty(self);
|
||||
self->scroll_changed = false;
|
||||
@@ -1361,8 +1482,8 @@ screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz, FONTS_DAT
|
||||
}
|
||||
update_line_data(self->linebuf->line, y, address);
|
||||
}
|
||||
if (selection_must_be_cleared) {
|
||||
self->selection = EMPTY_SELECTION; self->url_range = EMPTY_SELECTION;
|
||||
if (was_dirty) {
|
||||
self->url_range = EMPTY_SELECTION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1518,6 +1639,22 @@ screen_open_url(Screen *self) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
deactivate_overlay_line(Screen *self) {
|
||||
if (self->overlay_line.is_active && self->overlay_line.xnum && self->overlay_line.ynum < self->lines) {
|
||||
Line *line = range_line_(self, self->overlay_line.ynum);
|
||||
line_reset_cells(line, self->overlay_line.xstart, self->overlay_line.xnum, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells);
|
||||
if (self->cursor->y == self->overlay_line.ynum) self->cursor->x = self->overlay_line.xstart;
|
||||
self->is_dirty = true;
|
||||
linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum);
|
||||
}
|
||||
self->overlay_line.is_active = false;
|
||||
self->overlay_line.ynum = 0;
|
||||
self->overlay_line.xnum = 0;
|
||||
self->overlay_line.xstart = 0;
|
||||
}
|
||||
|
||||
|
||||
// }}}
|
||||
|
||||
// Python interface {{{
|
||||
@@ -1529,6 +1666,14 @@ screen_open_url(Screen *self) {
|
||||
#define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; }
|
||||
#define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; }
|
||||
|
||||
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);
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_text(Screen *self, PyObject *args) {
|
||||
as_text_generic(args, self, visual_line_, self->lines, self->columns);
|
||||
@@ -2021,6 +2166,7 @@ static PyMethodDef methods[] = {
|
||||
MND(cursor_down1, METH_VARARGS)
|
||||
MND(cursor_forward, METH_VARARGS)
|
||||
{"index", (PyCFunction)xxx_index, METH_VARARGS, ""},
|
||||
MND(set_pending_timeout, METH_O)
|
||||
MND(as_text, METH_VARARGS)
|
||||
MND(as_text_non_visual, METH_VARARGS)
|
||||
MND(tab, METH_NOARGS)
|
||||
|
||||
@@ -53,12 +53,20 @@ typedef struct {
|
||||
index_type start_of_data, count;
|
||||
} SavemodesBuffer;
|
||||
|
||||
typedef struct {
|
||||
CPUCell *cpu_cells;
|
||||
GPUCell *gpu_cells;
|
||||
bool is_active;
|
||||
index_type xstart, ynum, xnum;
|
||||
} OverlayLine;
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by, last_selection_scrolled_by;
|
||||
CellPixelSize cell_size;
|
||||
OverlayLine overlay_line;
|
||||
id_type window_id;
|
||||
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
|
||||
unsigned int current_charset;
|
||||
@@ -89,11 +97,19 @@ typedef struct {
|
||||
|
||||
CursorRenderInfo cursor_render_info;
|
||||
|
||||
struct {
|
||||
size_t capacity, used, stop_buf_pos;
|
||||
uint8_t *buf;
|
||||
double activated_at, wait_time;
|
||||
int state;
|
||||
uint8_t stop_buf[32];
|
||||
} pending_mode;
|
||||
|
||||
} Screen;
|
||||
|
||||
|
||||
void parse_worker(Screen *screen, PyObject *dump_callback);
|
||||
void parse_worker_dump(Screen *screen, PyObject *dump_callback);
|
||||
void parse_worker(Screen *screen, PyObject *dump_callback, double now);
|
||||
void parse_worker_dump(Screen *screen, PyObject *dump_callback, double now);
|
||||
void screen_align(Screen*);
|
||||
void screen_restore_cursor(Screen *);
|
||||
void screen_save_cursor(Screen *);
|
||||
@@ -160,7 +176,7 @@ void screen_apply_selection(Screen *self, void *address, size_t size);
|
||||
bool screen_is_selection_dirty(Screen *self);
|
||||
bool screen_has_selection(Screen*);
|
||||
bool screen_invert_colors(Screen *self);
|
||||
void screen_update_cell_data(Screen *self, void *address, size_t sz, FONTS_DATA_HANDLE);
|
||||
void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE);
|
||||
bool screen_is_cursor_visible(Screen *self);
|
||||
bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end);
|
||||
bool screen_selection_range_for_word(Screen *self, index_type x, index_type *, index_type *, index_type *start, index_type *end);
|
||||
@@ -175,6 +191,8 @@ bool screen_open_url(Screen*);
|
||||
void screen_dirty_sprite_positions(Screen *self);
|
||||
void screen_rescale_images(Screen *self);
|
||||
void screen_report_size(Screen *, unsigned int which);
|
||||
void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which);
|
||||
void screen_draw_overlay_text(Screen *self, const char *utf8_text);
|
||||
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
|
||||
DECLARE_CH_SCREEN_HANDLER(bell)
|
||||
DECLARE_CH_SCREEN_HANDLER(backspace)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "fonts.h"
|
||||
#include "gl.h"
|
||||
|
||||
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, BLIT_PROGRAM, NUM_PROGRAMS };
|
||||
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, BLIT_PROGRAM, NUM_PROGRAMS };
|
||||
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT };
|
||||
|
||||
// Sprites {{{
|
||||
@@ -113,7 +113,7 @@ static inline void
|
||||
ensure_sprite_map(FONTS_DATA_HANDLE fg) {
|
||||
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
|
||||
if (!sprite_map->texture_id) realloc_sprite_texture(fg);
|
||||
// We have to rebind since we dont know if the texture was ever bound
|
||||
// We have to rebind since we don't know if the texture was ever bound
|
||||
// in the context of the current OSWindow
|
||||
glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
|
||||
@@ -158,7 +158,7 @@ static ssize_t blit_vertex_array;
|
||||
|
||||
static void
|
||||
init_cell_program() {
|
||||
for (int i = CELL_PROGRAM; i < CURSOR_PROGRAM; i++) {
|
||||
for (int i = CELL_PROGRAM; i < BORDERS_PROGRAM; i++) {
|
||||
cell_program_layouts[i].render_data.index = block_index(i, "CellRenderData");
|
||||
cell_program_layouts[i].render_data.size = block_size(i, cell_program_layouts[i].render_data.index);
|
||||
cell_program_layouts[i].color_table.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE);
|
||||
@@ -167,7 +167,7 @@ init_cell_program() {
|
||||
}
|
||||
// Sanity check to ensure the attribute location binding worked
|
||||
#define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); }
|
||||
for (int p = CELL_PROGRAM; p < CURSOR_PROGRAM; p++) {
|
||||
for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) {
|
||||
C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2);
|
||||
}
|
||||
#undef C
|
||||
@@ -223,7 +223,8 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
|
||||
|
||||
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
|
||||
GLuint xnum, ynum, cursor_x, cursor_y, cursor_w;
|
||||
GLuint xnum, ynum, cursor_fg_sprite_idx;
|
||||
GLfloat cursor_x, cursor_y, cursor_w;
|
||||
};
|
||||
static struct CellRenderData *rd;
|
||||
|
||||
@@ -233,11 +234,19 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
|
||||
copy_color_table_to_buffer(screen->color_profile, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint));
|
||||
}
|
||||
// Cursor position
|
||||
if (cursor->is_visible && cursor->shape == CURSOR_BLOCK && cursor->is_focused) {
|
||||
if (cursor->is_visible) {
|
||||
rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y;
|
||||
} else {
|
||||
rd->cursor_x = screen->columns, rd->cursor_y = screen->lines;
|
||||
}
|
||||
if (cursor->is_focused) {
|
||||
switch(cursor->shape) {
|
||||
default:
|
||||
rd->cursor_fg_sprite_idx = 0; break;
|
||||
case CURSOR_BEAM:
|
||||
rd->cursor_fg_sprite_idx = 6; break;
|
||||
case CURSOR_UNDERLINE:
|
||||
rd->cursor_fg_sprite_idx = 7; break;
|
||||
}
|
||||
} else rd->cursor_fg_sprite_idx = 8;
|
||||
} else rd->cursor_x = screen->columns, rd->cursor_y = screen->lines;
|
||||
rd->cursor_w = rd->cursor_x + MAX(1, screen_current_char_width(screen)) - 1;
|
||||
|
||||
rd->xnum = screen->columns; rd->ynum = screen->lines;
|
||||
@@ -269,7 +278,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
|
||||
if (screen->scroll_changed || screen->is_dirty) {
|
||||
sz = sizeof(GPUCell) * screen->lines * screen->columns;
|
||||
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
|
||||
screen_update_cell_data(screen, address, sz, fonts_data);
|
||||
screen_update_cell_data(screen, address, fonts_data);
|
||||
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;
|
||||
changed = true;
|
||||
}
|
||||
@@ -447,12 +456,15 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
|
||||
float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0 : 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
|
||||
// overlap with the pixels outside the draw area, see https://github.com/kovidgoyal/kitty/issues/741
|
||||
// for a test case (the scissor is also used by draw_cells_interleaved_premult to blit the framebuffer)
|
||||
#define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x))
|
||||
glScissor(
|
||||
(GLint)(SCALE(width, (xstart + 1.0f) / 2.0f)),
|
||||
(GLint)(SCALE(height, ((ystart - h) + 1.0f) / 2.0f)),
|
||||
(GLsizei)(ceilf(SCALE(width, w / 2.0f))),
|
||||
(GLsizei)(ceilf(SCALE(height, h / 2.0f)))
|
||||
(GLint)(ceilf(SCALE(width, (xstart + 1.0f) / 2.0f))),
|
||||
(GLint)(ceilf(SCALE(height, ((ystart - h) + 1.0f) / 2.0f))),
|
||||
(GLsizei)(floorf(SCALE(width, w / 2.0f))-1),
|
||||
(GLsizei)(floorf(SCALE(height, h / 2.0f))-1)
|
||||
);
|
||||
#undef SCALE
|
||||
if (os_window->is_semi_transparent) {
|
||||
@@ -465,36 +477,6 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
|
||||
}
|
||||
// }}}
|
||||
|
||||
// Cursor {{{
|
||||
enum CursorUniforms { CURSOR_color, CURSOR_pos, NUM_CURSOR_UNIFORMS };
|
||||
static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0};
|
||||
static ssize_t cursor_vertex_array;
|
||||
|
||||
static void
|
||||
init_cursor_program() {
|
||||
Program *p = programs + CURSOR_PROGRAM;
|
||||
int left = NUM_CURSOR_UNIFORMS;
|
||||
cursor_vertex_array = create_vao();
|
||||
for (int i = 0; i < p->num_of_uniforms; i++, left--) {
|
||||
#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location
|
||||
SET_LOC(color);
|
||||
else SET_LOC(pos);
|
||||
else { fatal("Unknown uniform in cursor program: %s", p->uniforms[i].name); }
|
||||
}
|
||||
if (left) { fatal("Left over uniforms in cursor program"); }
|
||||
#undef SET_LOC
|
||||
}
|
||||
|
||||
void
|
||||
draw_cursor(CursorRenderInfo *cursor, bool is_focused) {
|
||||
bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array);
|
||||
glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0);
|
||||
glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom);
|
||||
glDrawArrays(is_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4);
|
||||
unbind_vertex_array(); unbind_program();
|
||||
}
|
||||
// }}}
|
||||
|
||||
// Borders {{{
|
||||
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, NUM_BORDER_UNIFORMS };
|
||||
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
|
||||
@@ -620,8 +602,6 @@ ONE_INT(bind_vertex_array)
|
||||
NO_ARG(unbind_vertex_array)
|
||||
TWO_INT(unmap_vao_buffer)
|
||||
|
||||
NO_ARG(init_cursor_program)
|
||||
|
||||
NO_ARG(init_borders_program)
|
||||
|
||||
NO_ARG(init_cell_program)
|
||||
@@ -648,7 +628,6 @@ static PyMethodDef module_methods[] = {
|
||||
MW(unmap_vao_buffer, METH_VARARGS),
|
||||
MW(bind_program, METH_O),
|
||||
MW(unbind_program, METH_NOARGS),
|
||||
MW(init_cursor_program, METH_NOARGS),
|
||||
MW(init_borders_program, METH_NOARGS),
|
||||
MW(init_cell_program, METH_NOARGS),
|
||||
|
||||
@@ -658,7 +637,7 @@ static PyMethodDef module_methods[] = {
|
||||
bool
|
||||
init_shaders(PyObject *module) {
|
||||
#define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; }
|
||||
C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(CURSOR_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(BLIT_PROGRAM);
|
||||
C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(BLIT_PROGRAM);
|
||||
C(GLSL_VERSION);
|
||||
C(GL_VERSION);
|
||||
C(GL_VENDOR);
|
||||
|
||||
@@ -44,6 +44,8 @@ def options_for_cmd(cmd):
|
||||
func = cmap[cmd]
|
||||
except KeyError:
|
||||
return (), alias_map
|
||||
if not func.options_spec:
|
||||
return (), alias_map
|
||||
seq, disabled = parse_option_spec(func.options_spec)
|
||||
ans = []
|
||||
for opt in seq:
|
||||
@@ -134,6 +136,7 @@ def run_cmd(global_opts, cmd, func, opts, items):
|
||||
send = {
|
||||
'cmd': cmd,
|
||||
'version': version,
|
||||
'no_response': False,
|
||||
}
|
||||
if payload is not None:
|
||||
send['payload'] = payload
|
||||
@@ -155,7 +158,10 @@ def real_main(global_opts):
|
||||
|
||||
while True:
|
||||
try:
|
||||
cmdline = input('🐱 ')
|
||||
try:
|
||||
cmdline = input('🐱 ')
|
||||
except UnicodeEncodeError:
|
||||
cmdline = input('kitty> ')
|
||||
except EOFError:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
|
||||
@@ -497,6 +497,17 @@ PYWRAP1(mark_os_window_for_close) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
PYWRAP1(focus_os_window) {
|
||||
id_type os_window_id;
|
||||
int also_raise = 1;
|
||||
PA("K|p", &os_window_id, &also_raise);
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
if (!os_window->is_focused) focus_os_window(os_window, also_raise);
|
||||
Py_RETURN_TRUE;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_titlebar_color) {
|
||||
id_type os_window_id;
|
||||
unsigned int color;
|
||||
@@ -718,6 +729,7 @@ static PyMethodDef module_methods[] = {
|
||||
MW(cell_size_for_window, METH_VARARGS),
|
||||
MW(mark_os_window_for_close, METH_VARARGS),
|
||||
MW(set_titlebar_color, METH_VARARGS),
|
||||
MW(focus_os_window, METH_VARARGS),
|
||||
MW(mark_tab_bar_dirty, METH_O),
|
||||
MW(change_background_opacity, METH_VARARGS),
|
||||
MW(background_opacity_of, METH_O),
|
||||
|
||||
@@ -34,6 +34,7 @@ typedef struct {
|
||||
bool sync_to_monitor;
|
||||
bool close_on_child_death;
|
||||
bool window_alert_on_bell;
|
||||
bool debug_keyboard;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
@@ -123,6 +124,7 @@ typedef struct {
|
||||
float background_opacity;
|
||||
FONTS_DATA_HANDLE fonts_data;
|
||||
id_type temp_font_group_id;
|
||||
double pending_scroll_pixels;
|
||||
} OSWindow;
|
||||
|
||||
|
||||
@@ -169,6 +171,7 @@ void swap_window_buffers(OSWindow *w);
|
||||
void make_window_context_current(OSWindow *w);
|
||||
void hide_mouse(OSWindow *w);
|
||||
void destroy_os_window(OSWindow *w);
|
||||
void focus_os_window(OSWindow *w, bool also_raise);
|
||||
void set_os_window_title(OSWindow *w, const char *title);
|
||||
OSWindow* os_window_for_kitty_window(id_type);
|
||||
OSWindow* add_os_window();
|
||||
@@ -181,7 +184,6 @@ ssize_t create_graphics_vao();
|
||||
ssize_t create_border_vao();
|
||||
bool send_cell_data_to_gpu(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *);
|
||||
void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *, bool, bool);
|
||||
void draw_cursor(CursorRenderInfo *, bool);
|
||||
void update_surface_size(int, int, uint32_t);
|
||||
void free_texture(uint32_t*);
|
||||
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
|
||||
|
||||
@@ -157,9 +157,11 @@ class Tab: # {{{
|
||||
self.current_layout = self.create_layout_object(nl)
|
||||
self.relayout()
|
||||
|
||||
def goto_layout(self, layout_name):
|
||||
def goto_layout(self, layout_name, raise_exception=False):
|
||||
layout_name = layout_name.lower()
|
||||
if layout_name not in self.enabled_layouts:
|
||||
if raise_exception:
|
||||
raise ValueError(layout_name)
|
||||
log_error('Unknown or disabled layout: {}'.format(layout_name))
|
||||
return
|
||||
self.current_layout = self.create_layout_object(layout_name)
|
||||
@@ -272,9 +274,9 @@ class Tab: # {{{
|
||||
def move_window_backward(self):
|
||||
self.move_window(-1)
|
||||
|
||||
def list_windows(self):
|
||||
def list_windows(self, active_window):
|
||||
for w in self:
|
||||
yield w.as_dict()
|
||||
yield w.as_dict(is_focused=w is active_window)
|
||||
|
||||
def matches(self, field, pat):
|
||||
if field == 'id':
|
||||
@@ -410,12 +412,13 @@ class TabManager: # {{{
|
||||
def __len__(self):
|
||||
return len(self.tabs)
|
||||
|
||||
def list_tabs(self):
|
||||
def list_tabs(self, active_tab, active_window):
|
||||
for tab in self:
|
||||
yield {
|
||||
'id': tab.id,
|
||||
'is_focused': tab is active_tab,
|
||||
'title': tab.name or tab.title,
|
||||
'windows': list(tab.list_windows()),
|
||||
'windows': list(tab.list_windows(active_window)),
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -38,7 +38,7 @@ bool_capabilities = {
|
||||
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
|
||||
'Su',
|
||||
|
||||
# The following are entries that we dont use
|
||||
# The following are entries that we don't use
|
||||
# # background color erase
|
||||
# 'bce',
|
||||
}
|
||||
@@ -283,7 +283,7 @@ string_capabilities = {
|
||||
# Set RGB background color (non-standard used by neovim)
|
||||
'setrgbb': r'\E[48:2:%p1%d:%p2%d:%p3%dm',
|
||||
|
||||
# The following are entries that we dont use
|
||||
# The following are entries that we don't use
|
||||
# # display status line
|
||||
# 'dsl': r'\E]2;\007',
|
||||
# # return from status line
|
||||
@@ -438,7 +438,7 @@ queryable_capabilities.update(string_capabilities)
|
||||
extra = (bool_capabilities | numeric_capabilities.keys() | string_capabilities.keys()) - set(termcap_aliases.values())
|
||||
no_termcap_for = frozenset('Su Tc setrgbf setrgbb'.split())
|
||||
if extra - no_termcap_for:
|
||||
raise Exception('Termcap aliases not complete, missing: {}'.format(extra))
|
||||
raise Exception('Termcap aliases not complete, missing: {}'.format(extra - no_termcap_for))
|
||||
del extra
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@ def get_capabilities(query_string):
|
||||
try:
|
||||
qname = termcap_aliases[name]
|
||||
val = queryable_capabilities[qname]
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
from .utils import log_error
|
||||
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
||||
raise
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user