Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ba7b5f5a3 | ||
|
|
f895b7f8cb | ||
|
|
16542e96bf | ||
|
|
3005b8b9d6 | ||
|
|
35653ef4b4 | ||
|
|
36b3582825 | ||
|
|
c17c801a31 | ||
|
|
460c353898 | ||
|
|
3fa7007e91 | ||
|
|
4cf6373203 | ||
|
|
e0b3bb1830 | ||
|
|
47ec372c30 | ||
|
|
d5366a421f | ||
|
|
3d5977669e | ||
|
|
36da668e6f | ||
|
|
052913c656 | ||
|
|
0ef055a2b2 | ||
|
|
a7bd6922e5 | ||
|
|
8d9ba4a772 | ||
|
|
222beedc2a | ||
|
|
8ba8320189 | ||
|
|
3501a2a1da | ||
|
|
819ee94a70 | ||
|
|
7e7dbc5ecf | ||
|
|
3a6bc94d25 | ||
|
|
07a4cec616 | ||
|
|
60de840d69 | ||
|
|
928167c0b2 | ||
|
|
2aac220cdb | ||
|
|
d8a74e8a3c | ||
|
|
019e6efeda | ||
|
|
a7a7216b30 | ||
|
|
ae8ca5272b | ||
|
|
06c1236cc6 | ||
|
|
4d3982c5cf | ||
|
|
a0ff625f61 | ||
|
|
b44345d78e | ||
|
|
96229becd1 | ||
|
|
b269cab0b8 | ||
|
|
0b3c8210f3 | ||
|
|
074614bc8e | ||
|
|
4ceb4cfc22 | ||
|
|
0bb08d8bdd | ||
|
|
b9e6557f72 | ||
|
|
7ed268ea2f | ||
|
|
a91b3b9487 | ||
|
|
cdf236d01b | ||
|
|
f54efb7886 | ||
|
|
0ffee56170 | ||
|
|
ab97646576 | ||
|
|
33b62dcabc | ||
|
|
39e6e91d69 | ||
|
|
dedd80c6b5 | ||
|
|
df11da74e9 | ||
|
|
9991dd6ce4 | ||
|
|
e777364f91 | ||
|
|
a32ed5ae5f | ||
|
|
db1e48848d | ||
|
|
c02fa79591 | ||
|
|
6f0a59c696 | ||
|
|
bc715d4348 | ||
|
|
55e4761302 | ||
|
|
18fe2e8dfa | ||
|
|
18a07a78b5 | ||
|
|
aa7024b264 | ||
|
|
c67ab2a6ff | ||
|
|
d15eab31da | ||
|
|
a6900443d5 | ||
|
|
235bcacc7b | ||
|
|
3e26f96744 | ||
|
|
718111582a | ||
|
|
f5cdbe53ea | ||
|
|
bf761a7189 | ||
|
|
05ee01b4dd | ||
|
|
53166d659e | ||
|
|
34aea3bca1 | ||
|
|
f26519863f | ||
|
|
a57e96500a | ||
|
|
5e27c21856 | ||
|
|
4601cd13dc | ||
|
|
ad92541ff3 | ||
|
|
3f77a2044e | ||
|
|
75e57a48e0 | ||
|
|
160fbd2071 | ||
|
|
f5743f4610 | ||
|
|
82f9aecacb | ||
|
|
be6556c884 | ||
|
|
f941ffb1fe | ||
|
|
cbdba0b6aa | ||
|
|
ce04f102a7 | ||
|
|
fc3bd2d15f | ||
|
|
a958cabf01 | ||
|
|
d0a2eab79e | ||
|
|
b835afefee | ||
|
|
d0a7be90f9 | ||
|
|
1f3550b84c | ||
|
|
939212e1d4 | ||
|
|
7db6318806 | ||
|
|
e5d9182606 | ||
|
|
d519553581 | ||
|
|
e99b1be023 | ||
|
|
e460866ebc | ||
|
|
23aea68e32 | ||
|
|
65c2cbe93a | ||
|
|
ee31ba0c76 | ||
|
|
3ed9d9a069 | ||
|
|
721e51c117 | ||
|
|
09daaf1ec3 | ||
|
|
b2d62f10d2 | ||
|
|
0d542b2f60 | ||
|
|
04fd058e0f | ||
|
|
31a0a9427e | ||
|
|
04f10ba570 | ||
|
|
34b7b2e4e8 | ||
|
|
09183772d3 | ||
|
|
3e4eca5816 | ||
|
|
30c6c4697f | ||
|
|
b650275d45 | ||
|
|
174bd5c57f | ||
|
|
ec51a219a7 | ||
|
|
cca2fb967c | ||
|
|
5b547d9b06 | ||
|
|
facb2df3f6 | ||
|
|
8395076da5 | ||
|
|
e113e0cba7 | ||
|
|
407d19c2dc | ||
|
|
382367506a | ||
|
|
bd5a85a4e7 | ||
|
|
631f97eb26 | ||
|
|
0c0ce4844c | ||
|
|
6bf290149e | ||
|
|
60fa812b60 | ||
|
|
61f3a39aa0 | ||
|
|
a6949df727 | ||
|
|
d839a31d7f | ||
|
|
1de510f46f | ||
|
|
0359bbe4be | ||
|
|
40b355e593 | ||
|
|
adf9c4fc5f | ||
|
|
ae9d049011 | ||
|
|
d24977d164 | ||
|
|
3035d64127 | ||
|
|
70c9c058e2 | ||
|
|
1d9f73adec | ||
|
|
fca95af49b | ||
|
|
62fe392041 | ||
|
|
097d11819b | ||
|
|
691286f376 | ||
|
|
c910f6c832 | ||
|
|
65f9ac32ef | ||
|
|
27f2a2e3d5 | ||
|
|
88aab20376 | ||
|
|
a045ac3bd7 | ||
|
|
722b605407 | ||
|
|
7f53745077 | ||
|
|
7f902c4c03 | ||
|
|
1205c2055f | ||
|
|
f08f0f9dbd | ||
|
|
093c05e16a | ||
|
|
787100a4dc | ||
|
|
c493583f25 | ||
|
|
b4afbe47b3 | ||
|
|
6c4aeeeadd | ||
|
|
5b01519367 | ||
|
|
c494759606 | ||
|
|
2102c48b87 | ||
|
|
c498f4e8ab | ||
|
|
05a0166ebd | ||
|
|
0484365de6 | ||
|
|
cbab3b56de | ||
|
|
715f7985c7 | ||
|
|
2637018b53 | ||
|
|
1fa66ca45d | ||
|
|
13b10f691d | ||
|
|
3fe7d91713 | ||
|
|
98864091ff | ||
|
|
7750a461aa | ||
|
|
ac98b85157 | ||
|
|
21f6c6a5a8 | ||
|
|
b8d1491b99 | ||
|
|
d9c168d824 | ||
|
|
ee7f4ee1a4 | ||
|
|
ee3ce3c7d8 | ||
|
|
a57f38dbd5 | ||
|
|
3c4cb3c35b | ||
|
|
12cfe6d1d1 | ||
|
|
c64e8fc434 | ||
|
|
15f534a8f9 | ||
|
|
8e09e809aa | ||
|
|
fec5a80b89 | ||
|
|
1d50a188c4 | ||
|
|
b0d34b136f | ||
|
|
7eb234c250 | ||
|
|
d52434c0aa | ||
|
|
efc0b830cf | ||
|
|
4a8c4c4601 | ||
|
|
e498cedf56 | ||
|
|
6c8a52875e | ||
|
|
c1397fd366 | ||
|
|
3323ddcdef | ||
|
|
1be394cda7 | ||
|
|
f292092ffc | ||
|
|
b82e74f99a | ||
|
|
9b293ad66a | ||
|
|
618b486fd5 | ||
|
|
1986403e42 | ||
|
|
cf51f80b79 | ||
|
|
2fe17aa889 | ||
|
|
f76c8ad30b | ||
|
|
c421d3bb59 | ||
|
|
3d862da2be | ||
|
|
10b15caad9 | ||
|
|
e08238d5a0 | ||
|
|
d0104660c8 | ||
|
|
a4c2a9ef0d | ||
|
|
ee39d7ba14 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ logo/*.iconset
|
||||
compile_commands.json
|
||||
glad/out
|
||||
asan-launcher
|
||||
asan-launcher.dSYM
|
||||
kitty-profile
|
||||
dev
|
||||
__pycache__
|
||||
|
||||
@@ -89,9 +89,10 @@ matrix:
|
||||
language: generic
|
||||
env: SWBASE=/Users/Shared/buildbot/sw SW=$SWBASE/sw PATH=$SW/bin:$PATH
|
||||
|
||||
- os: osx
|
||||
language: generic
|
||||
env: USE_BREW=1 BUILD_PKG=osx-bundle
|
||||
# Disabled because harfbuzz is currently broken in brew
|
||||
# - os: osx
|
||||
# language: generic
|
||||
# env: USE_BREW=1 BUILD_PKG=osx-bundle
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -106,7 +107,7 @@ install: |
|
||||
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
||||
if [[ "$USE_BREW" == "1" ]]; then
|
||||
brew update;
|
||||
brew upgrade python;
|
||||
# brew upgrade python;
|
||||
brew install harfbuzz --without-graphite2 --without-icu4c --without-freetype;
|
||||
brew install imagemagick
|
||||
brew install optipng
|
||||
|
||||
@@ -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:
|
||||
``libdbus-1-dev``, ``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``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev`` and ``libpython-dev``.
|
||||
if they are not already installed by your distro.
|
||||
|
||||
Install and run from source
|
||||
@@ -57,6 +57,25 @@ If that works, you can create a script to launch |kitty|:
|
||||
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
|
||||
just ``kitty``.
|
||||
|
||||
|
||||
Building kitty.app on macOS from source
|
||||
-------------------------------------------
|
||||
|
||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
||||
for the logo generation step). And run::
|
||||
|
||||
make app
|
||||
|
||||
This :file:`kitty.app` unlike the released one does not include its own copy of
|
||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||
you might have to rebuild the app.
|
||||
|
||||
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
|
||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
|
||||
Note for Linux/macOS packagers
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -3,6 +3,125 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
0.13.1 [2018-12-06]
|
||||
------------------------------
|
||||
|
||||
- Fix passing input via the pipe action to a program without a window not
|
||||
working.
|
||||
|
||||
- Linux: Fix a regression in the previous release that caused automatic
|
||||
selection of bold/italic fonts when using aliases such as "monospace" to not
|
||||
work (:iss:`1209`)
|
||||
|
||||
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||
be properly unwrapped (:iss:`1206`)
|
||||
|
||||
0.13.0 [2018-12-05]
|
||||
------------------------------
|
||||
|
||||
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
|
||||
extended scrollback to use when viewing the scrollback buffer in a pager
|
||||
(:iss:`970`)
|
||||
|
||||
- Modify the kittens sub-system to allow creating custom kittens without any
|
||||
user interface. This is useful for creating more complex actions that can
|
||||
be bound to key presses in :file:`kitty.conf`. See
|
||||
doc:`kittens/custom`. (:iss:`870`)
|
||||
|
||||
- Add a new ``nth_window`` action that can be used to go to the nth window and
|
||||
also previously active windows, using negative numbers. Similarly,
|
||||
``goto_tab`` now accepts negative numbers to go to previously active tabs
|
||||
(:iss:`1040`)
|
||||
|
||||
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
|
||||
``hidden``. (:iss:`1014`)
|
||||
|
||||
- Allow private use unicode characters to stretch over more than a single
|
||||
neighboring space (:pull:`1036`)
|
||||
|
||||
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
|
||||
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
|
||||
|
||||
- icat kitten: Implement reading image data from STDIN, if STDIN is not
|
||||
connected to a terminal (:iss:`1130`)
|
||||
|
||||
- hints kitten: Insert trailing spaces after matches when using the
|
||||
``--multiple`` option. Also add a separate ``--add-trailing-space``
|
||||
option to control this behavior (:pull:`1132`)
|
||||
|
||||
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
|
||||
than the underlying window's cwd (:iss:`1045`)
|
||||
|
||||
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
|
||||
|
||||
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
|
||||
|
||||
- macOS: Fix IME input for East Asian languages (:iss:`910`)
|
||||
|
||||
- macOS: Fix rendering frames-per-second very low when processing
|
||||
large amounts of input in small chunks (:pull:`1082`)
|
||||
|
||||
- macOS: Fix incorrect text sizes calculated when using an external display
|
||||
that is set to mirror the main display (:iss:`1056`)
|
||||
|
||||
- macOS: Use the system default double click interval (:pull:`1090`)
|
||||
|
||||
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
|
||||
|
||||
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
|
||||
small sizes (:iss:`1173`)
|
||||
|
||||
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
|
||||
respected (:iss:`1085`)
|
||||
|
||||
- Linux: Fix a crash when using the GNU Unifont as a fallback font
|
||||
(:iss:`1087`)
|
||||
|
||||
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
|
||||
|
||||
- Wayland: Add support for the primary selection protocol
|
||||
implemented by some compositors (:pull:`1095`)
|
||||
|
||||
- Fix expansion of env vars not working in the :opt:`env` directive
|
||||
(:iss:`1075`)
|
||||
|
||||
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
|
||||
blink or key press (:iss:`1073`)
|
||||
|
||||
- Fix the ``set_background_opacity`` action not working correctly
|
||||
(:pull:`1147`)
|
||||
|
||||
- Fix second cell of emoji created using variation selectors not having
|
||||
the same attributes as the first cell (:iss:`1109`)
|
||||
|
||||
- Fix focusing neighboring windows in the grid layout with less than 4 windows
|
||||
not working (:iss:`1115`)
|
||||
|
||||
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
|
||||
modes (:iss:`1114`)
|
||||
|
||||
- Add a terminfo entry for full keyboard mode.
|
||||
|
||||
- Fix incorrect text-antialiasing when using very low background opacity
|
||||
(:iss:`1005`)
|
||||
|
||||
- When double or triple clicking ignore clicks if they are "far" from each
|
||||
other (:iss:`1093`)
|
||||
|
||||
- Follow xterm's behavior for the menu key (:iss:`597`)
|
||||
|
||||
- Fix hover detection of URLs not working when hovering over the first colon
|
||||
and slash characters in short URLs (:iss:`1201`)
|
||||
|
||||
0.12.3 [2018-09-29]
|
||||
------------------------------
|
||||
|
||||
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
|
||||
moved or resized at least once (:iss:`887`)
|
||||
|
||||
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
|
||||
|
||||
|
||||
0.12.2 [2018-09-24]
|
||||
------------------------------
|
||||
|
||||
@@ -133,6 +252,9 @@ Changelog
|
||||
- Add an option :opt:`env` to set environment variables in child processes
|
||||
from kitty.conf
|
||||
|
||||
- Add an action to the ``clear_terminal`` function to scroll the screen
|
||||
contents into the scrollback buffer (:iss:`1113`)
|
||||
|
||||
- Implement high precision scrolling with the trackpad on platforms such as
|
||||
macOS and Wayland that implement it. (:pull:`819`)
|
||||
|
||||
@@ -342,7 +464,7 @@ Changelog
|
||||
|
||||
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
|
||||
|
||||
- Add a config option (:opt:`x11_hide_window_decorations`) to hide window
|
||||
- Add a config option (``x11_hide_window_decorations``) to hide window
|
||||
decorations under X11/Wayland (:iss:`607`)
|
||||
|
||||
- Add an option to @set-window-title to make the title change non-permanent
|
||||
|
||||
18
docs/faq.rst
18
docs/faq.rst
@@ -25,24 +25,6 @@ these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
|
||||
|
||||
How do I build kitty.app on macOS?
|
||||
----------------------------------------
|
||||
|
||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
||||
for the logo generation step). And run::
|
||||
|
||||
make app
|
||||
|
||||
This :file:`kitty.app` unlike the released one does not include its own copy of
|
||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||
you might have to rebuild the app.
|
||||
|
||||
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
|
||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -75,6 +75,47 @@ You can also use the *CSI t* escape code to get the screen size. Send
|
||||
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
|
||||
pixels. This escape code is supported in many terminals, not just kitty.
|
||||
|
||||
A minimal example
|
||||
------------------
|
||||
|
||||
Some minimal python code to display PNG images in kitty, using the most basic
|
||||
features of the graphics protocol:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
|
||||
def serialize_gr_command(cmd, payload=None):
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
def write_chunked(cmd, data):
|
||||
data = standard_b64encode(data)
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
cmd['m'] = m
|
||||
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
|
||||
write_chunked({'a': 'T', 'f': 100}, open(sys.argv[-1], 'rb').read())
|
||||
|
||||
|
||||
Save this script as :file:`png.py`, then you can use it to display any PNG
|
||||
file in kitty as::
|
||||
|
||||
python png.py file.png
|
||||
|
||||
|
||||
The graphics escape code
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -160,6 +160,13 @@ windows and move windows around (similar to window movement in vim)::
|
||||
map shift+down move_window up
|
||||
...
|
||||
|
||||
You can also define a shortcut to switch to the previously active window::
|
||||
|
||||
map ctrl+p nth_window -1
|
||||
|
||||
``nth_window`` will focus the nth window for positive numbers and the
|
||||
previously active windows for negative numbers.
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
@@ -370,6 +377,9 @@ arbitrary, command running in a new window, tab or overlay, for example::
|
||||
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
|
||||
key. See :sc:`show_scrollback` for details.
|
||||
|
||||
If you wish to store very large amounts of scrollback to view using the piping or
|
||||
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
|
||||
option.
|
||||
|
||||
Frequently Asked Questions
|
||||
---------------------------------
|
||||
|
||||
@@ -78,3 +78,45 @@ This will send the plain text of the active window to the kitten's
|
||||
instead. If you want line wrap markers as well, use ``screen-ansi``
|
||||
or just ``screen``. For the scrollback buffer as well, use
|
||||
``history``, ``ansi-history`` or ``screen-history``.
|
||||
|
||||
|
||||
Using kittens to script kitty, without any terminal UI
|
||||
-----------------------------------------------------------
|
||||
|
||||
If you would like your kitten to script kitty, without bothering to write a
|
||||
terminal program, you can tell the kittens system to run the
|
||||
``handle_result()`` function without first running the ``main()`` function.
|
||||
|
||||
For example, here is a kitten that "zooms/unzooms" the current terminal window
|
||||
by switching to the stack layout or back to the previous layout.
|
||||
|
||||
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
def main(args):
|
||||
pass
|
||||
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
tab = boss.active_tab
|
||||
if tab is not None:
|
||||
if tab.current_layout.name == 'stack':
|
||||
tab.last_used_layout()
|
||||
else:
|
||||
tab.goto_layout('stack')
|
||||
|
||||
handle_result.no_ui = True
|
||||
|
||||
|
||||
Now in kitty.conf add::
|
||||
|
||||
map f11 kitten zoom_toggle.py
|
||||
|
||||
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
|
||||
more fancy, switching the kitty OS window to fullscreen as well as changing the
|
||||
layout, by simply adding the line::
|
||||
|
||||
boss.toggle_fullscreen()
|
||||
|
||||
|
||||
to the ``handle_result()`` function, above.
|
||||
|
||||
88
docs/pipe.rst
Normal file
88
docs/pipe.rst
Normal file
@@ -0,0 +1,88 @@
|
||||
Working with the screen and history buffer contents
|
||||
======================================================
|
||||
|
||||
|
||||
You can pipe the contents of the current screen and history buffer as
|
||||
:file:`STDIN` to an arbitrary program using the ``pipe`` function. The program
|
||||
can be displayed in a kitty window or overlay.
|
||||
|
||||
For example, the following in :file:`kitty.conf` will open the scrollback
|
||||
buffer in less in an overlay window, when you press :kbd:`F1`::
|
||||
|
||||
map f1 pipe @ansi overlay less +G -R
|
||||
|
||||
The syntax of the ``pipe`` function is::
|
||||
|
||||
pipe <input placeholder> <destination window type> <command line to run>
|
||||
|
||||
|
||||
The piping environment
|
||||
--------------------------
|
||||
|
||||
The program to which the data is piped has a special environment variable
|
||||
declared, ``KITTY_PIPE_DATA`` whose contents are::
|
||||
|
||||
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
|
||||
|
||||
where ``scrolled_by`` is the number of lines kitty is currently scrolled by,
|
||||
``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)``
|
||||
being the top left corner and ``{lines},{columns}`` being the number of rows
|
||||
and columns of the screen.
|
||||
|
||||
You can choose where to run the pipe program:
|
||||
|
||||
``overlay``
|
||||
An overlay window over the current kitty window
|
||||
|
||||
``window``
|
||||
A new kitty window
|
||||
|
||||
``os_window``
|
||||
A new top-level window
|
||||
|
||||
``tab``
|
||||
A new window in a new tab
|
||||
|
||||
``none``
|
||||
Run it in the background
|
||||
|
||||
|
||||
Input placeholders
|
||||
--------------------
|
||||
|
||||
There are various different kinds of placeholders
|
||||
|
||||
``@text``
|
||||
Plain text, current screen + scrollback buffer
|
||||
|
||||
``@ansi``
|
||||
Text with formatting, current screen + scrollback buffer
|
||||
|
||||
``@screen``
|
||||
Plain text, only current screen
|
||||
|
||||
``@ansi_screen``
|
||||
Text with formatting, only current screen
|
||||
|
||||
``@alternate``
|
||||
Plain text, secondary screen. The secondary screen is the screen not currently displayed. For
|
||||
example if you run a fullscreen terminal application, the secondary screen will
|
||||
be the screen you return to when quitting the application.
|
||||
|
||||
``@ansi_alternate``
|
||||
Text with formatting, secondary screen.
|
||||
|
||||
``@alternate_scrollback``
|
||||
Plain text, secondary screen + scrollback, if any.
|
||||
|
||||
``@ansi_alternate_scrollback``
|
||||
Text with formatting, secondary screen + scrollback, if any.
|
||||
|
||||
``none``
|
||||
No input
|
||||
|
||||
|
||||
You can also add the suffix ``_wrap`` to the placeholder, in which case kitty
|
||||
will insert the carriage return at every line wrap location (where long lines
|
||||
are wrapped at screen edges). This is useful if you want to pipe to program
|
||||
that wants to duplicate the screen layout of the screen.
|
||||
@@ -6,7 +6,7 @@ These are typically in the form of new or re-purposed escape codes. While these
|
||||
extensions are currently |kitty| specific, it would be nice to get some of them
|
||||
adopted more broadly, to push the state of terminal emulators forward.
|
||||
|
||||
The goal of these extensions is to be as small an unobtrusive as possible,
|
||||
The goal of these extensions is to be as small and unobtrusive as possible,
|
||||
while filling in some gaps in the existing xterm protocol. In particular, one
|
||||
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
||||
The tty should remain what it is -- a device for efficiently processing text
|
||||
@@ -141,6 +141,8 @@ For example::
|
||||
This encoding means each key event is represented by 8 or 9 printable ascii
|
||||
only bytes, for maximum robustness.
|
||||
|
||||
Support for this mode is indicated by the ``fullkbd`` boolean capability
|
||||
in the terminfo database, in case querying for it via DECQRM is inconvenient.
|
||||
|
||||
.. _ext_styles:
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ def parse_ucd():
|
||||
if category.startswith('M'):
|
||||
marks.add(codepoint)
|
||||
|
||||
# Some common synonyms
|
||||
word_search_map['bee'] |= word_search_map['honeybee']
|
||||
word_search_map['lambda'] |= word_search_map['lamda']
|
||||
word_search_map['lamda'] |= word_search_map['lambda']
|
||||
|
||||
|
||||
def split_two(line):
|
||||
spec, rest = line.split(';', 1)
|
||||
@@ -360,16 +365,15 @@ def gen_names():
|
||||
root = TrieNode()
|
||||
all_trie_nodes.append(root)
|
||||
|
||||
def add_word(word_idx):
|
||||
word = word_map[word_idx]
|
||||
def add_word(word_idx, word):
|
||||
parent = root
|
||||
for letter in map(ord, word):
|
||||
idx = parent.add_letter(letter)
|
||||
parent = all_trie_nodes[idx]
|
||||
parent.match_offset = offsets_array[word_idx]
|
||||
|
||||
for i in range(len(word_map)):
|
||||
add_word(i)
|
||||
for i, word in enumerate(word_map):
|
||||
add_word(i, word)
|
||||
children_array = [0]
|
||||
for node in all_trie_nodes:
|
||||
if node.children:
|
||||
|
||||
4
glfw/cocoa_platform.h
vendored
4
glfw/cocoa_platform.h
vendored
@@ -27,12 +27,10 @@
|
||||
#include <stdint.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#if defined(__OBJC__)
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
typedef void* id;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@
|
||||
#define NSEventTypeKeyUp NSKeyUp
|
||||
#endif
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||
#define NSPasteboardTypeFileURL NSFilenamesPboardType
|
||||
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
|
||||
#define NSPasteboardTypeString NSStringPboardType
|
||||
#endif
|
||||
|
||||
// Returns the style mask corresponding to the window settings
|
||||
//
|
||||
@@ -450,6 +455,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
updateCursorMode(window);
|
||||
if (_glfw.ns.disabledCursorWindow != window && cursorInClientArea(window))
|
||||
{
|
||||
double x = 0, y = 0;
|
||||
_glfwPlatformGetCursorPos(window, &x, &y);
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
@@ -549,8 +560,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
markedText = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
[self updateTrackingAreas];
|
||||
[self registerForDraggedTypes:[NSArray arrayWithObjects:
|
||||
NSFilenamesPboardType, nil]];
|
||||
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL]];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -587,6 +597,21 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateLayer
|
||||
{
|
||||
if (window->context.client != GLFW_NO_API) {
|
||||
@try {
|
||||
[window->context.nsgl.object update];
|
||||
} @catch (NSException *e) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Failed to update NSGL Context object with error: %s (%s)",
|
||||
[[e name] UTF8String], [[e reason] UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputWindowDamage(window);
|
||||
}
|
||||
|
||||
- (id)makeBackingLayer
|
||||
{
|
||||
if (window->ns.layer)
|
||||
@@ -828,28 +853,22 @@ is_ascii_control_char(char x) {
|
||||
window->ns.deadKeyState = 0;
|
||||
return;
|
||||
}
|
||||
debug_key(@"scancode: 0x%x (%s) %schar_count: %lu deadKeyState: %u ", scancode, safe_name_for_scancode(scancode), format_mods(mods), char_count, window->ns.deadKeyState);
|
||||
if (process_text) {
|
||||
// We check if cocoa wants to insert text, as UCKeyTranslate
|
||||
// inserts text even when the cmd key is pressed. For instance,
|
||||
// cmd+a will result in the text a.
|
||||
// this will call insertText which will fill up _glfw.ns.text
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
|
||||
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
|
||||
_glfw.ns.text[0] = 0;
|
||||
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
|
||||
} else {
|
||||
window->ns.deadKeyState = 0;
|
||||
}
|
||||
if (window->ns.deadKeyState && char_count == 0) {
|
||||
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
|
||||
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
|
||||
// 0x75 is the delete key which needs to be ignored during a compose sequence
|
||||
debug_key(@"Ignoring dead key.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
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; // don't send text for ascii control codes
|
||||
debug_key(@"text: %s glfw_key: %s\n",
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
@@ -889,6 +908,12 @@ is_ascii_control_char(char x) {
|
||||
deltaX = [event scrollingDeltaX];
|
||||
deltaY = [event scrollingDeltaY];
|
||||
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
||||
if (flags) {
|
||||
float xscale = 1, yscale = 1;
|
||||
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||
if (xscale > 0) deltaX *= xscale;
|
||||
if (yscale > 0) deltaY *= yscale;
|
||||
}
|
||||
|
||||
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
@@ -896,45 +921,34 @@ is_ascii_control_char(char x) {
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
|
||||
== NSDragOperationGeneric)
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
return NSDragOperationGeneric;
|
||||
}
|
||||
|
||||
return NSDragOperationNone;
|
||||
}
|
||||
|
||||
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
return YES;
|
||||
// HACK: We don't know what to say here because we don't know what the
|
||||
// application wants to do with the paths
|
||||
return NSDragOperationGeneric;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||
NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType];
|
||||
|
||||
const NSRect contentRect = [window->ns.view frame];
|
||||
_glfwInputCursorPos(window,
|
||||
[sender draggingLocation].x,
|
||||
contentRect.size.height - [sender draggingLocation].y);
|
||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
||||
NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
|
||||
options:options];
|
||||
if (!urls) return NO;
|
||||
const NSUInteger count = [urls count];
|
||||
|
||||
const NSUInteger count = [files count];
|
||||
if (count)
|
||||
{
|
||||
NSEnumerator* e = [files objectEnumerator];
|
||||
char** paths = calloc(count, sizeof(char*));
|
||||
NSUInteger i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
paths[i] = _glfw_strdup([[urls objectAtIndex:i] fileSystemRepresentation]);
|
||||
|
||||
_glfwInputDrop(window, (int) count, (const char**) paths);
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
free(paths[i]);
|
||||
free(paths);
|
||||
}
|
||||
@@ -942,11 +956,6 @@ is_ascii_control_char(char x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [markedText length] > 0;
|
||||
@@ -1569,6 +1578,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = (float) (pixels.size.height / points.size.height);
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return [NSEvent doubleClickInterval];
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
[window->ns.object miniaturize:nil];
|
||||
@@ -1791,7 +1805,7 @@ void _glfwPlatformPollEvents(void)
|
||||
if (event == nil)
|
||||
break;
|
||||
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
}
|
||||
|
||||
[_glfw.ns.autoreleasePool drain];
|
||||
@@ -1807,7 +1821,7 @@ void _glfwPlatformWaitEvents(void)
|
||||
untilDate:[NSDate distantFuture]
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
@@ -1819,8 +1833,7 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
untilDate:date
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
if (event)
|
||||
[NSApp sendEvent:event];
|
||||
if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
@@ -1940,7 +1953,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||
bytesPerRow:src->width * 4
|
||||
bitsPerPixel:32];
|
||||
if (rep == nil)
|
||||
@@ -2002,26 +2015,26 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
|
||||
NSArray* types = [NSArray arrayWithObjects:NSPasteboardTypeString, nil];
|
||||
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard declareTypes:types owner:nil];
|
||||
[pasteboard setString:[NSString stringWithUTF8String:string]
|
||||
forType:NSStringPboardType];
|
||||
forType:NSPasteboardTypeString];
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
|
||||
if (![[pasteboard types] containsObject:NSStringPboardType])
|
||||
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"Cocoa: Failed to retrieve string from pasteboard");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSString* object = [pasteboard stringForType:NSStringPboardType];
|
||||
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
|
||||
if (!object)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
|
||||
5
glfw/egl_context.h
vendored
5
glfw/egl_context.h
vendored
@@ -43,10 +43,6 @@ typedef Window EGLNativeWindowType;
|
||||
#define EGLAPIENTRY
|
||||
typedef struct wl_display* EGLNativeDisplayType;
|
||||
typedef struct wl_egl_window* EGLNativeWindowType;
|
||||
#elif defined(_GLFW_MIR)
|
||||
#define EGLAPIENTRY
|
||||
typedef MirEGLNativeDisplayType EGLNativeDisplayType;
|
||||
typedef MirEGLNativeWindowType EGLNativeWindowType;
|
||||
#else
|
||||
#error "No supported EGL platform selected"
|
||||
#endif
|
||||
@@ -216,4 +212,3 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
|
||||
const _GLFWfbconfig* fbconfig,
|
||||
Visual** visual, int* depth);
|
||||
#endif /*_GLFW_X11*/
|
||||
|
||||
|
||||
74
glfw/glfw.py
74
glfw/glfw.py
@@ -5,7 +5,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
@@ -22,7 +21,7 @@ def wayland_protocol_file_name(base, ext='c'):
|
||||
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
||||
|
||||
|
||||
def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
ans = env.copy()
|
||||
ans.cflags = [
|
||||
x for x in ans.cflags
|
||||
@@ -32,10 +31,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
ans.cflags.append('-pthread')
|
||||
ans.ldpaths.append('-pthread')
|
||||
ans.cflags.append('-fpic')
|
||||
ans.cflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cflags.append('-D_GLFW_BUILD_DLL')
|
||||
ans.cppflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||
|
||||
if is_macos:
|
||||
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||
ans.ldpaths.extend(
|
||||
"-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo".
|
||||
split()
|
||||
@@ -76,6 +76,14 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
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'))
|
||||
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
int main(void) {
|
||||
return syscall(__NR_memfd_create, "test", 0);
|
||||
}''')
|
||||
if has_memfd_create:
|
||||
ans.cppflags.append('-DHAS_MEMFD_CREATE')
|
||||
|
||||
return ans
|
||||
|
||||
@@ -179,7 +187,7 @@ class Function:
|
||||
return ans
|
||||
|
||||
|
||||
def generate_wrappers(glfw_header, glfw_native_header):
|
||||
def generate_wrappers(glfw_header):
|
||||
src = open(glfw_header).read()
|
||||
functions = []
|
||||
first = None
|
||||
@@ -192,6 +200,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
functions.append(Function(decl))
|
||||
for line in '''\
|
||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
@@ -199,9 +208,10 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
void glfwSetX11SelectionString(const char* string)
|
||||
const char* glfwGetX11SelectionString(void)
|
||||
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
|
||||
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
||||
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
|
||||
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
||||
'''.splitlines():
|
||||
if line:
|
||||
functions.append(Function(line.strip(), check_fail=False))
|
||||
@@ -220,7 +230,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
|
||||
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||
{}
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
@@ -258,53 +268,9 @@ unload_glfw() {
|
||||
f.write(code)
|
||||
|
||||
|
||||
def from_glfw(glfw_dir):
|
||||
os.chdir(glfw_dir)
|
||||
sinfo = collect_source_information()
|
||||
files_to_copy = set()
|
||||
for x in sinfo.values():
|
||||
if isinstance(x, dict):
|
||||
headers, sources = x['headers'], x['sources']
|
||||
for name in headers + sources:
|
||||
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
|
||||
glfw_header = os.path.abspath('include/GLFW/glfw3.h')
|
||||
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
|
||||
os.chdir(base)
|
||||
for x in os.listdir('.'):
|
||||
if x.rpartition('.') in ('c', 'h'):
|
||||
os.unlink(x)
|
||||
for src in files_to_copy:
|
||||
shutil.copy2(src, '.')
|
||||
shutil.copy2(glfw_header, '.')
|
||||
json.dump(
|
||||
sinfo,
|
||||
open('source-info.json', 'w'),
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True
|
||||
)
|
||||
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')
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
generate_wrappers('glfw3.h')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
26
glfw/glfw3.h
vendored
26
glfw/glfw3.h
vendored
@@ -2930,6 +2930,32 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
|
||||
*/
|
||||
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
|
||||
|
||||
/*! @brief Returns the double click time interval.
|
||||
*
|
||||
* This function returns the maximum time between clicks to count as a
|
||||
* double click.
|
||||
*
|
||||
* The double click interval is a positive finite number greater than zero,
|
||||
* where zero means that no click is ever recognized as a double click. If the
|
||||
* system does not support a double click interval, this function always returns one half.
|
||||
*
|
||||
* @return The double click interval.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref double_click
|
||||
* @sa @ref click_interval
|
||||
* @sa @ref double_click_interval
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window);
|
||||
|
||||
/*! @brief Returns the opacity of the whole window.
|
||||
*
|
||||
* This function returns the opacity of the window, including any decorations.
|
||||
|
||||
16
glfw/input.c
vendored
16
glfw/input.c
vendored
@@ -1375,6 +1375,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
|
||||
return _glfwPlatformGetClipboardString();
|
||||
}
|
||||
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string)
|
||||
{
|
||||
assert(string != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
_glfwPlatformSetPrimarySelectionString(string);
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return _glfwPlatformGetPrimarySelectionString();
|
||||
}
|
||||
#endif
|
||||
|
||||
GLFWAPI double glfwGetTime(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);
|
||||
|
||||
10
glfw/internal.h
vendored
10
glfw/internal.h
vendored
@@ -129,7 +129,6 @@ typedef enum VkStructureType
|
||||
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
|
||||
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
@@ -191,8 +190,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
||||
#include "x11_platform.h"
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
#include "wl_platform.h"
|
||||
#elif defined(_GLFW_MIR)
|
||||
#include "mir_platform.h"
|
||||
#elif defined(_GLFW_OSMESA)
|
||||
#include "null_platform.h"
|
||||
#else
|
||||
@@ -571,8 +568,6 @@ struct _GLFWlibrary
|
||||
GLFWbool KHR_xcb_surface;
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
GLFWbool KHR_wayland_surface;
|
||||
#elif defined(_GLFW_MIR)
|
||||
GLFWbool KHR_mir_surface;
|
||||
#endif
|
||||
} vk;
|
||||
|
||||
@@ -629,6 +624,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string);
|
||||
const char* _glfwPlatformGetClipboardString(void);
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string);
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||
#endif
|
||||
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||
@@ -658,6 +657,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* right, int* bottom);
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
float* xscale, float* yscale);
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
|
||||
|
||||
3
glfw/linux_joystick.c
vendored
3
glfw/linux_joystick.c
vendored
@@ -228,7 +228,7 @@ static GLFWbool openJoystickDevice(const char* path)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
strncpy(linjs.path, path, sizeof(linjs.path));
|
||||
strncpy(linjs.path, path, sizeof(linjs.path) - 1);
|
||||
memcpy(&js->linjs, &linjs, sizeof(linjs));
|
||||
|
||||
pollAbsState(js);
|
||||
@@ -431,4 +431,3 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
54
glfw/memfd.h
vendored
Normal file
54
glfw/memfd.h
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_MEMFD_CREATE
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
static inline int memfd_create(const char *name, unsigned int flags) {
|
||||
return syscall(__NR_memfd_create, name, flags);
|
||||
}
|
||||
|
||||
#ifndef F_LINUX_SPECIFIC_BASE
|
||||
#define F_LINUX_SPECIFIC_BASE 1024
|
||||
#endif
|
||||
|
||||
#ifndef F_ADD_SEALS
|
||||
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
|
||||
#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
|
||||
|
||||
#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
|
||||
#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
|
||||
#define F_SEAL_GROW 0x0004 /* prevent file from growing */
|
||||
#define F_SEAL_WRITE 0x0008 /* prevent writes */
|
||||
#endif
|
||||
|
||||
#ifndef MFD_CLOEXEC
|
||||
#define MFD_CLOEXEC 0x0001U
|
||||
#define MFD_ALLOW_SEALING 0x0002U
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static inline int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -27,6 +27,11 @@
|
||||
#include "internal.h"
|
||||
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
|
||||
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
|
||||
#endif
|
||||
|
||||
static void makeContextCurrentNSGL(_GLFWwindow* window)
|
||||
{
|
||||
if (window)
|
||||
@@ -49,7 +54,7 @@ static void swapIntervalNSGL(int interval)
|
||||
|
||||
GLint sync = interval;
|
||||
[window->context.nsgl.object setValues:&sync
|
||||
forParameter:NSOpenGLCPSwapInterval];
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
}
|
||||
|
||||
static int extensionSupportedNSGL(const char* extension)
|
||||
@@ -299,7 +304,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
if (fbconfig->transparent)
|
||||
{
|
||||
GLint opaque = 0;
|
||||
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
|
||||
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity];
|
||||
}
|
||||
|
||||
[window->context.nsgl.object setView:window->ns.view];
|
||||
@@ -332,4 +337,3 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
|
||||
|
||||
return window->context.nsgl.object;
|
||||
}
|
||||
|
||||
|
||||
5
glfw/null_window.c
vendored
5
glfw/null_window.c
vendored
@@ -148,6 +148,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = 1.f;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@
|
||||
"stable/viewporter/viewporter.xml",
|
||||
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
|
||||
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
|
||||
"unstable/primary-selection/primary-selection-unstable-v1.xml"
|
||||
],
|
||||
"sources": [
|
||||
"wl_init.c",
|
||||
@@ -91,7 +93,7 @@
|
||||
},
|
||||
"wayland_protocols": [
|
||||
1,
|
||||
12
|
||||
17
|
||||
],
|
||||
"win32": {
|
||||
"headers": [
|
||||
@@ -145,4 +147,4 @@
|
||||
"null_joystick.c"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
glfw/vulkan.c
vendored
4
glfw/vulkan.c
vendored
@@ -136,9 +136,6 @@ GLFWbool _glfwInitVulkan(int mode)
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
|
||||
_glfw.vk.KHR_wayland_surface = GLFW_TRUE;
|
||||
#elif defined(_GLFW_MIR)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_mir_surface") == 0)
|
||||
_glfw.vk.KHR_mir_surface = GLFW_TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -326,4 +323,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
|
||||
|
||||
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
|
||||
}
|
||||
|
||||
|
||||
5
glfw/win32_window.c
vendored
5
glfw/win32_window.c
vendored
@@ -1602,6 +1602,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
ShowWindow(window->win32.handle, SW_MINIMIZE);
|
||||
|
||||
9
glfw/window.c
vendored
9
glfw/window.c
vendored
@@ -721,6 +721,15 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
|
||||
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
|
||||
}
|
||||
|
||||
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
|
||||
return _glfwPlatformGetDoubleClickInterval(window);
|
||||
}
|
||||
|
||||
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
||||
49
glfw/wl_init.c
vendored
49
glfw/wl_init.c
vendored
@@ -29,6 +29,8 @@
|
||||
#include "backend_utils.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -407,7 +409,7 @@ 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);
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||
}
|
||||
|
||||
@@ -561,8 +563,11 @@ static void registryHandleGlobal(void* data,
|
||||
_glfw.wl.seatVersion);
|
||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||
}
|
||||
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||
_glfwSetupWaylandDataDevice();
|
||||
if (_glfw.wl.seat) {
|
||||
if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice();
|
||||
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
@@ -571,6 +576,12 @@ static void registryHandleGlobal(void* data,
|
||||
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
||||
}
|
||||
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
|
||||
{
|
||||
_glfw.wl.decorationManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "wp_viewporter") == 0)
|
||||
{
|
||||
_glfw.wl.viewporter =
|
||||
@@ -607,6 +618,17 @@ static void registryHandleGlobal(void* data,
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
|
||||
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
|
||||
{
|
||||
_glfw.wl.primarySelectionDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zwp_primary_selection_device_manager_v1_interface,
|
||||
1);
|
||||
if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -714,7 +736,17 @@ int _glfwPlatformInit(void)
|
||||
|
||||
if (_glfw.wl.pointer && _glfw.wl.shm)
|
||||
{
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
|
||||
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
|
||||
char *cursorSizeEnd;
|
||||
int cursorSize = 32;
|
||||
if (cursorSizeStr)
|
||||
{
|
||||
errno = 0;
|
||||
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
|
||||
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
|
||||
cursorSize = (int)cursorSizeLong;
|
||||
}
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
|
||||
if (!_glfw.wl.cursorTheme)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -763,6 +795,8 @@ void _glfwPlatformTerminate(void)
|
||||
wl_shell_destroy(_glfw.wl.shell);
|
||||
if (_glfw.wl.viewporter)
|
||||
wp_viewporter_destroy(_glfw.wl.viewporter);
|
||||
if (_glfw.wl.decorationManager)
|
||||
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
|
||||
if (_glfw.wl.wmBase)
|
||||
xdg_wm_base_destroy(_glfw.wl.wmBase);
|
||||
if (_glfw.wl.pointer)
|
||||
@@ -788,6 +822,10 @@ void _glfwPlatformTerminate(void)
|
||||
wl_data_device_destroy(_glfw.wl.dataDevice);
|
||||
if (_glfw.wl.dataDeviceManager)
|
||||
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
|
||||
if (_glfw.wl.primarySelectionDevice)
|
||||
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
||||
if (_glfw.wl.primarySelectionDeviceManager)
|
||||
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
||||
if (_glfw.wl.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
@@ -797,7 +835,8 @@ void _glfwPlatformTerminate(void)
|
||||
}
|
||||
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
||||
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
||||
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
|
||||
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
|
||||
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
|
||||
|
||||
}
|
||||
|
||||
|
||||
49
glfw/wl_monitor.c
vendored
49
glfw/wl_monitor.c
vendored
@@ -32,16 +32,16 @@
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
static void geometry(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
int32_t physicalWidth,
|
||||
int32_t physicalHeight,
|
||||
int32_t subpixel,
|
||||
const char* make,
|
||||
const char* model,
|
||||
int32_t transform)
|
||||
static void outputHandleGeometry(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
int32_t physicalWidth,
|
||||
int32_t physicalHeight,
|
||||
int32_t subpixel,
|
||||
const char* make,
|
||||
const char* model,
|
||||
int32_t transform)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
char name[1024];
|
||||
@@ -55,12 +55,12 @@ static void geometry(void* data,
|
||||
monitor->name = _glfw_strdup(name);
|
||||
}
|
||||
|
||||
static void mode(void* data,
|
||||
struct wl_output* output,
|
||||
uint32_t flags,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t refresh)
|
||||
static void outputHandleMode(void* data,
|
||||
struct wl_output* output,
|
||||
uint32_t flags,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t refresh)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
GLFWvidmode mode;
|
||||
@@ -81,16 +81,16 @@ static void mode(void* data,
|
||||
monitor->wl.currentMode = monitor->modeCount - 1;
|
||||
}
|
||||
|
||||
static void done(void* data, struct wl_output* output)
|
||||
static void outputHandleDone(void* data, struct wl_output* output)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
||||
}
|
||||
|
||||
static void scale(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t factor)
|
||||
static void outputHandleScale(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t factor)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
|
||||
@@ -98,10 +98,10 @@ static void scale(void* data,
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
geometry,
|
||||
mode,
|
||||
done,
|
||||
scale,
|
||||
outputHandleGeometry,
|
||||
outputHandleMode,
|
||||
outputHandleDone,
|
||||
outputHandleScale,
|
||||
};
|
||||
|
||||
|
||||
@@ -205,4 +205,3 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return monitor->wl.output;
|
||||
}
|
||||
|
||||
|
||||
43
glfw/wl_platform.h
vendored
43
glfw/wl_platform.h
vendored
@@ -56,9 +56,11 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-viewporter-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
@@ -143,6 +145,7 @@ typedef struct _GLFWwindowWayland
|
||||
struct {
|
||||
struct xdg_surface* surface;
|
||||
struct xdg_toplevel* toplevel;
|
||||
struct zxdg_toplevel_decoration_v1* decoration;
|
||||
} xdg;
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
@@ -169,18 +172,34 @@ typedef struct _GLFWwindowWayland
|
||||
GLFWbool justCreated;
|
||||
|
||||
struct {
|
||||
GLFWbool serverSide;
|
||||
struct wl_buffer* buffer;
|
||||
_GLFWdecorationWayland top, left, right, bottom;
|
||||
int focus;
|
||||
} decorations;
|
||||
|
||||
struct {
|
||||
unsigned long long id;
|
||||
void(*callback)(unsigned long long id);
|
||||
struct wl_callback *current_wl_callback;
|
||||
} frameCallbackData;
|
||||
|
||||
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
typedef enum _GLFWWaylandOfferType
|
||||
{
|
||||
EXPIRED,
|
||||
CLIPBOARD,
|
||||
DRAG_AND_DROP,
|
||||
PRIMARY_SELECTION
|
||||
}_GLFWWaylandOfferType ;
|
||||
|
||||
typedef struct _GLFWWaylandDataOffer
|
||||
{
|
||||
struct wl_data_offer *id;
|
||||
const char *mime;
|
||||
int offer_type;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
@@ -189,6 +208,17 @@ typedef struct _GLFWWaylandDataOffer
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandDataOffer;
|
||||
|
||||
typedef struct _GLFWWaylandPrimaryOffer
|
||||
{
|
||||
struct zwp_primary_selection_offer_v1 *id;
|
||||
const char *mime;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandPrimaryOffer;
|
||||
|
||||
// Wayland-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryWayland
|
||||
@@ -203,6 +233,7 @@ typedef struct _GLFWlibraryWayland
|
||||
struct wl_pointer* pointer;
|
||||
struct wl_keyboard* keyboard;
|
||||
struct xdg_wm_base* wmBase;
|
||||
struct zxdg_decoration_manager_v1* decorationManager;
|
||||
struct wp_viewporter* viewporter;
|
||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||
@@ -210,6 +241,9 @@ typedef struct _GLFWlibraryWayland
|
||||
struct wl_data_device_manager* dataDeviceManager;
|
||||
struct wl_data_device* dataDevice;
|
||||
struct wl_data_source* dataSourceForClipboard;
|
||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
@@ -250,11 +284,13 @@ typedef struct _GLFWlibraryWayland
|
||||
} egl;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
char* pasteString;
|
||||
char* clipboardString;
|
||||
char* clipboardSourceString;
|
||||
struct wl_data_offer* clipboardSourceOffer;
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
char* primarySelectionString;
|
||||
size_t primarySelectionOffersCounter;
|
||||
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -285,4 +321,5 @@ typedef struct _GLFWcursorWayland
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwSetupWaylandDataDevice();
|
||||
void _glfwSetupWaylandPrimarySelectionDevice();
|
||||
void animateCursorImage(id_type timer_id, void *data);
|
||||
|
||||
361
glfw/wl_window.c
vendored
361
glfw/wl_window.c
vendored
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include "backend_utils.h"
|
||||
#include "memfd.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -115,18 +116,6 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
|
||||
handlePopupDone
|
||||
};
|
||||
|
||||
static int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new, unique, anonymous file of the given size, and
|
||||
* return the file descriptor for it. The file descriptor is set
|
||||
@@ -150,11 +139,21 @@ createTmpfileCloexec(char* tmpname)
|
||||
static int
|
||||
createAnonymousFile(off_t size)
|
||||
{
|
||||
int ret;
|
||||
#ifdef HAS_MEMFD_CREATE
|
||||
int fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (fd < 0) return -1;
|
||||
// We can add this seal before calling posix_fallocate(), as the file
|
||||
// is currently zero-sized anyway.
|
||||
//
|
||||
// There is also no need to check for the return value, we couldn’t do
|
||||
// anything with it anyway.
|
||||
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
|
||||
#else
|
||||
static const char template[] = "/glfw-shared-XXXXXX";
|
||||
const char* path;
|
||||
char* name;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
path = getenv("XDG_RUNTIME_DIR");
|
||||
if (!path)
|
||||
@@ -173,6 +172,7 @@ createAnonymousFile(off_t size)
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
#endif
|
||||
ret = posix_fallocate(fd, 0, size);
|
||||
if (ret != 0)
|
||||
{
|
||||
@@ -198,7 +198,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Creating a buffer file for %d B failed: %m",
|
||||
length);
|
||||
return GLFW_FALSE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
@@ -207,7 +207,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: mmap failed: %m");
|
||||
close(fd);
|
||||
return GLFW_FALSE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
|
||||
@@ -272,11 +272,13 @@ static void createDecorations(_GLFWwindow* window)
|
||||
const GLFWimage image = { 1, 1, data };
|
||||
GLFWbool opaque = (data[3] == 255);
|
||||
|
||||
if (!_glfw.wl.viewporter)
|
||||
if (!_glfw.wl.viewporter || !window->decorated || window->wl.decorations.serverSide)
|
||||
return;
|
||||
|
||||
if (!window->wl.decorations.buffer)
|
||||
window->wl.decorations.buffer = createShmBuffer(&image);
|
||||
if (!window->wl.decorations.buffer)
|
||||
return;
|
||||
|
||||
createDecoration(&window->wl.decorations.top, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
@@ -317,6 +319,20 @@ static void destroyDecorations(_GLFWwindow* window)
|
||||
destroyDecoration(&window->wl.decorations.bottom);
|
||||
}
|
||||
|
||||
static void xdgDecorationHandleConfigure(void* data,
|
||||
struct zxdg_toplevel_decoration_v1* decoration,
|
||||
uint32_t mode)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
if (!window->wl.decorations.serverSide)
|
||||
createDecorations(window);
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
|
||||
xdgDecorationHandleConfigure,
|
||||
};
|
||||
|
||||
// Makes the surface considered as XRGB instead of ARGB.
|
||||
static void setOpaqueRegion(_GLFWwindow* window)
|
||||
{
|
||||
@@ -489,9 +505,6 @@ static GLFWbool createSurface(_GLFWwindow* window,
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
|
||||
if (window->decorated && !window->monitor)
|
||||
createDecorations(window);
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -512,7 +525,8 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refres
|
||||
monitor->wl.output);
|
||||
}
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
destroyDecorations(window);
|
||||
if (!window->wl.decorations.serverSide)
|
||||
destroyDecorations(window);
|
||||
}
|
||||
|
||||
static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
@@ -548,11 +562,13 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
{
|
||||
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
createDecorations(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
createDecorations(window);
|
||||
}
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
@@ -641,6 +657,27 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
xdgSurfaceHandleConfigure
|
||||
};
|
||||
|
||||
static void setXdgDecorations(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.wl.decorationManager)
|
||||
{
|
||||
window->wl.xdg.decoration =
|
||||
zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
_glfw.wl.decorationManager, window->wl.xdg.toplevel);
|
||||
zxdg_toplevel_decoration_v1_add_listener(window->wl.xdg.decoration,
|
||||
&xdgDecorationListener,
|
||||
window);
|
||||
zxdg_toplevel_decoration_v1_set_mode(
|
||||
window->wl.xdg.decoration,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
}
|
||||
else
|
||||
{
|
||||
window->wl.decorations.serverSide = GLFW_FALSE;
|
||||
createDecorations(window);
|
||||
}
|
||||
}
|
||||
|
||||
static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
|
||||
@@ -688,10 +725,12 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
setXdgDecorations(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
setXdgDecorations(window);
|
||||
}
|
||||
if (strlen(window->wl.appId))
|
||||
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
|
||||
@@ -752,6 +791,7 @@ incrementCursorImage(_GLFWwindow* window)
|
||||
cursor->wl.currentImage += 1;
|
||||
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
|
||||
setCursorImage(&cursor->wl);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -912,6 +952,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
window->context.destroy(window);
|
||||
|
||||
destroyDecorations(window);
|
||||
if (window->wl.xdg.decoration)
|
||||
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
||||
if (window->wl.decorations.buffer)
|
||||
wl_buffer_destroy(window->wl.decorations.buffer);
|
||||
|
||||
@@ -932,6 +974,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
|
||||
free(window->wl.title);
|
||||
free(window->wl.monitors);
|
||||
if (window->wl.frameCallbackData.current_wl_callback)
|
||||
wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
@@ -1025,7 +1069,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* left, int* top,
|
||||
int* right, int* bottom)
|
||||
{
|
||||
if (window->decorated && !window->monitor)
|
||||
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
|
||||
{
|
||||
if (top)
|
||||
*top = _GLFW_DECORATION_TOP;
|
||||
@@ -1047,6 +1091,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = (float) window->wl.scale;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.wl.wmBase)
|
||||
@@ -1167,7 +1216,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
if (window->decorated)
|
||||
if (!_glfw.wl.decorationManager)
|
||||
createDecorations(window);
|
||||
}
|
||||
_glfwInputWindowMonitor(window, monitor);
|
||||
@@ -1304,6 +1353,8 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
cursor->wl.buffer = createShmBuffer(image);
|
||||
if (!cursor->wl.buffer)
|
||||
return GLFW_FALSE;
|
||||
cursor->wl.width = image->width;
|
||||
cursor->wl.height = image->height;
|
||||
cursor->wl.xhot = xhot;
|
||||
@@ -1491,13 +1542,13 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
}
|
||||
|
||||
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd)
|
||||
static void send_text(char *text, int fd)
|
||||
{
|
||||
if (_glfw.wl.clipboardString) {
|
||||
size_t len = strlen(_glfw.wl.clipboardString), pos = 0;
|
||||
if (text) {
|
||||
size_t len = strlen(text), pos = 0;
|
||||
double start = glfwGetTime();
|
||||
while (pos < len && glfwGetTime() - start < 2.0) {
|
||||
ssize_t ret = write(fd, _glfw.wl.clipboardString + pos, len - pos);
|
||||
ssize_t ret = write(fd, text + pos, len - pos);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -1513,22 +1564,27 @@ static void _glfwSendClipboardText(void *data, struct wl_data_source *data_sourc
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) {
|
||||
send_text(_glfw.wl.clipboardString, fd);
|
||||
}
|
||||
|
||||
static void _glfwSendPrimarySelectionText(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source,
|
||||
const char *mime_type, int fd) {
|
||||
send_text(_glfw.wl.primarySelectionString, fd);
|
||||
}
|
||||
|
||||
static char* read_offer_string(int data_pipe) {
|
||||
wl_display_flush(_glfw.wl.display);
|
||||
size_t sz = 0, capacity = 0;
|
||||
char *buf = NULL;
|
||||
struct pollfd fds;
|
||||
fds.fd = pipefd[0];
|
||||
fds.fd = data_pipe;
|
||||
fds.events = POLLIN;
|
||||
double start = glfwGetTime();
|
||||
#define bail(...) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||
free(buf); buf = NULL; \
|
||||
close(pipefd[0]); \
|
||||
close(data_pipe); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
@@ -1548,12 +1604,12 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime)
|
||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||
}
|
||||
}
|
||||
ret = read(pipefd[0], buf + sz, capacity - sz - 1);
|
||||
ret = read(data_pipe, buf + sz, capacity - sz - 1);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (ret == 0) { close(pipefd[0]); buf[sz] = 0; return buf; }
|
||||
if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; }
|
||||
sz += ret;
|
||||
start = glfwGetTime();
|
||||
}
|
||||
@@ -1562,13 +1618,20 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime)
|
||||
|
||||
}
|
||||
|
||||
static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime)
|
||||
{
|
||||
if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString)
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
free(_glfw.wl.clipboardSourceString);
|
||||
_glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime);
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
}
|
||||
|
||||
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
|
||||
@@ -1577,6 +1640,12 @@ static void data_source_canceled(void *data, struct wl_data_source *wl_data_sour
|
||||
wl_data_source_destroy(wl_data_source);
|
||||
}
|
||||
|
||||
static void primary_selection_source_canceled(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source) {
|
||||
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
|
||||
_glfw.wl.dataSourceForPrimarySelection = NULL;
|
||||
zwp_primary_selection_source_v1_destroy(primary_selection_source);
|
||||
}
|
||||
|
||||
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
|
||||
}
|
||||
|
||||
@@ -1586,6 +1655,11 @@ const static struct wl_data_source_listener data_source_listener = {
|
||||
.target = data_source_target,
|
||||
};
|
||||
|
||||
const static struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||
.send = _glfwSendPrimarySelectionText,
|
||||
.cancelled = primary_selection_source_canceled,
|
||||
};
|
||||
|
||||
static void prune_unclaimed_data_offers() {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
@@ -1595,29 +1669,63 @@ static void prune_unclaimed_data_offers() {
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_unclaimed_primary_selection_offers() {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
|
||||
{
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == data_offer) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 1;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == 1) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer
|
||||
_glfw.wl.dataOffers[i].offer_type = CLIPBOARD;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void mark_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1* primary_selection_device,
|
||||
struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION;
|
||||
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_primary_selection_offers();
|
||||
}
|
||||
|
||||
static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
offer->mime = "text/plain;charset=utf-8";
|
||||
else if (!offer->mime && strcmp(mime, "text/plain") == 0)
|
||||
offer->mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
offer->is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
offer->has_uri_list = 1;
|
||||
}
|
||||
|
||||
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8";
|
||||
else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
_glfw.wl.dataOffers[i].is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
_glfw.wl.dataOffers[i].has_uri_list = 1;
|
||||
set_offer_mimetype(&_glfw.wl.dataOffers[i], mime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer_mimetype(void *data, struct zwp_primary_selection_offer_v1* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == id) {
|
||||
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1648,6 +1756,10 @@ static const struct wl_data_offer_listener data_offer_listener = {
|
||||
.action = data_offer_action,
|
||||
};
|
||||
|
||||
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
||||
.offer = handle_primary_selection_offer_mimetype,
|
||||
};
|
||||
|
||||
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
@@ -1669,15 +1781,36 @@ end:
|
||||
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) {
|
||||
smallest_idx = _glfw.wl.primarySelectionOffers[i].idx;
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == NULL) {
|
||||
_glfw.wl.primarySelectionOffers[i].id = id;
|
||||
_glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
_glfw.wl.primarySelectionOffers[pos].id = id;
|
||||
_glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
end:
|
||||
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 2;
|
||||
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP;
|
||||
_glfw.wl.dataOffers[i].surface = surface;
|
||||
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
|
||||
wl_data_offer_accept(id, serial, mime);
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // previous drag offer
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
@@ -1685,18 +1818,16 @@ static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32
|
||||
|
||||
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void drop(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
|
||||
if (uri_list) {
|
||||
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||
@@ -1738,24 +1869,38 @@ const static struct wl_data_device_listener data_device_listener = {
|
||||
.leave = drag_leave,
|
||||
};
|
||||
|
||||
const static struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
||||
.data_offer = handle_primary_selection_offer,
|
||||
.selection = mark_primary_selection_offer,
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (!_glfw.wl.dataDevice) return;
|
||||
if (data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
const static struct wl_callback_listener copy_callback_listener = {
|
||||
.done = copy_callback_done
|
||||
};
|
||||
static void
|
||||
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
|
||||
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
void _glfwSetupWaylandDataDevice() {
|
||||
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
|
||||
}
|
||||
|
||||
void _glfwSetupWaylandPrimarySelectionDevice() {
|
||||
_glfw.wl.primarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
|
||||
}
|
||||
|
||||
static inline GLFWbool _glfwEnsureDataDevice() {
|
||||
if (!_glfw.wl.dataDeviceManager)
|
||||
{
|
||||
@@ -1804,15 +1949,71 @@ void _glfwPlatformSetClipboardString(const char* string)
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
wl_callback_add_listener(callback, ©_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||
const static struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == 1) {
|
||||
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
|
||||
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||
return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
if (!_glfw.wl.primarySelectionDevice) {
|
||||
static GLFWbool warned_about_primary_selection_device = GLFW_FALSE;
|
||||
if (!warned_about_primary_selection_device) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy no primary selection device available");
|
||||
warned_about_primary_selection_device = GLFW_TRUE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_glfw.wl.primarySelectionString == string) return;
|
||||
free(_glfw.wl.primarySelectionString);
|
||||
_glfw.wl.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
if (_glfw.wl.dataSourceForPrimarySelection)
|
||||
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
|
||||
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
|
||||
if (!_glfw.wl.dataSourceForPrimarySelection)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy failed to create primary selection source");
|
||||
return;
|
||||
}
|
||||
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
const static struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
{
|
||||
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
|
||||
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) {
|
||||
return _glfw.wl.primarySelectionString;
|
||||
}
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
@@ -1886,6 +2087,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int
|
||||
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
static void
|
||||
frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) data;
|
||||
if (callback == window->wl.frameCallbackData.current_wl_callback) {
|
||||
window->wl.frameCallbackData.callback(window->wl.frameCallbackData.id);
|
||||
window->wl.frameCallbackData.current_wl_callback = NULL;
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
@@ -1907,3 +2117,16 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
|
||||
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, void(*callback)(unsigned long long id)) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
|
||||
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||
window->wl.frameCallbackData.id = id;
|
||||
window->wl.frameCallbackData.callback = callback;
|
||||
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
||||
if (window->wl.frameCallbackData.current_wl_callback) {
|
||||
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
}
|
||||
|
||||
78
glfw/x11_window.c
vendored
78
glfw/x11_window.c
vendored
@@ -873,6 +873,7 @@ static const char* getSelectionString(Atom selection)
|
||||
Atom actualType;
|
||||
int actualFormat;
|
||||
unsigned long itemCount, bytesAfter;
|
||||
double start = glfwGetTime();
|
||||
XEvent notification, dummy;
|
||||
|
||||
XConvertSelection(_glfw.x11.display,
|
||||
@@ -887,7 +888,10 @@ static const char* getSelectionString(Atom selection)
|
||||
SelectionNotify,
|
||||
¬ification))
|
||||
{
|
||||
waitForX11Event(-1);
|
||||
double time = glfwGetTime();
|
||||
if (time - start > 2)
|
||||
return "";
|
||||
waitForX11Event(2.0 - (time - start));
|
||||
}
|
||||
|
||||
if (notification.xselection.property == None)
|
||||
@@ -918,12 +922,16 @@ static const char* getSelectionString(Atom selection)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
start = glfwGetTime();
|
||||
while (!XCheckIfEvent(_glfw.x11.display,
|
||||
&dummy,
|
||||
isSelPropNewValueNotify,
|
||||
(XPointer) ¬ification))
|
||||
{
|
||||
waitForX11Event(-1);
|
||||
double time = glfwGetTime();
|
||||
if (time - start > 2)
|
||||
return "";
|
||||
waitForX11Event(2.0 - (time - start));
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
@@ -1291,12 +1299,20 @@ static void processEvent(XEvent *event)
|
||||
|
||||
case EnterNotify:
|
||||
{
|
||||
// XEnterWindowEvent is XCrossingEvent
|
||||
const int x = event->xcrossing.x;
|
||||
const int y = event->xcrossing.y;
|
||||
|
||||
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
||||
// ignore the defined cursor for hidden cursor mode
|
||||
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
updateCursorImage(window);
|
||||
|
||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
|
||||
window->x11.lastCursorPosX = x;
|
||||
window->x11.lastCursorPosY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2100,6 +2116,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = _glfw.x11.contentScaleY;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->x11.overrideRedirect)
|
||||
@@ -2278,6 +2299,10 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
Atom* states;
|
||||
unsigned long i;
|
||||
GLFWbool maximized = GLFW_FALSE;
|
||||
if (!_glfw.x11.NET_WM_STATE ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
return maximized;
|
||||
const unsigned long count =
|
||||
_glfwGetWindowPropertyX11(window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
@@ -2667,6 +2692,29 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
return getSelectionString(_glfw.x11.CLIPBOARD);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of primary selection");
|
||||
}
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
{
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface)
|
||||
@@ -2838,32 +2886,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
|
||||
return window->x11.handle;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetX11SelectionString(const char* string)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of primary selection");
|
||||
}
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetX11SelectionString(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
|
||||
1
glfw/xkb_glfw.c
vendored
1
glfw/xkb_glfw.c
vendored
@@ -255,6 +255,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
|
||||
if (!xkb->states.composeState) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
xkb_compose_table_unref(compose_table);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
|
||||
@@ -84,9 +84,17 @@ class Hints(Handler):
|
||||
self.args = args
|
||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
||||
self.multiple = args.multiple
|
||||
self.match_suffix = self.get_match_suffix(args)
|
||||
self.chosen = []
|
||||
self.reset()
|
||||
|
||||
def get_match_suffix(self, args):
|
||||
if args.add_trailing_space == 'always':
|
||||
return ' '
|
||||
if args.add_trailing_space == 'never':
|
||||
return ''
|
||||
return ' ' if args.multiple else ''
|
||||
|
||||
def reset(self):
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
@@ -112,7 +120,7 @@ class Hints(Handler):
|
||||
if encode_hint(idx).startswith(self.current_input)
|
||||
]
|
||||
if len(matches) == 1:
|
||||
self.chosen.append(matches[0].text)
|
||||
self.chosen.append(matches[0].text + self.match_suffix)
|
||||
if self.multiple:
|
||||
self.ignore_mark_indices.add(matches[0].index)
|
||||
self.reset()
|
||||
@@ -130,7 +138,7 @@ class Hints(Handler):
|
||||
elif key_event is enter_key and self.current_input:
|
||||
try:
|
||||
idx = decode_hint(self.current_input)
|
||||
self.chosen.append(self.index_map[idx].text)
|
||||
self.chosen.append(self.index_map[idx].text + self.match_suffix)
|
||||
self.ignore_mark_indices.add(idx)
|
||||
except Exception:
|
||||
self.current_input = ''
|
||||
@@ -363,6 +371,13 @@ The minimum number of characters to consider a match.
|
||||
type=bool-set
|
||||
Select multiple matches and perform the action on all of them together at the end.
|
||||
In this mode, press :kbd:`Esc` to finish selecting.
|
||||
|
||||
|
||||
--add-trailing-space
|
||||
default=auto
|
||||
choices=auto,always,never
|
||||
Add trailing space after matched text. Defaults to auto, which adds the space
|
||||
when used together with --multiple.
|
||||
'''.format(','.join(sorted(URL_PREFIXES))).format
|
||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
usage = ''
|
||||
|
||||
@@ -174,7 +174,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
def process(path, args):
|
||||
def process(path, args, is_tempfile):
|
||||
m = identify(path)
|
||||
ss = screen_size()
|
||||
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
||||
@@ -183,7 +183,7 @@ def process(path, args):
|
||||
needs_scaling = needs_scaling or args.scale_up
|
||||
if m.fmt == 'png' and not needs_scaling:
|
||||
outfile = path
|
||||
transmit_mode = 'f'
|
||||
transmit_mode = 't' if is_tempfile else 'f'
|
||||
fmt = 100
|
||||
width, height = m.width, m.height
|
||||
else:
|
||||
@@ -249,7 +249,8 @@ def parse_place(raw):
|
||||
help_text = (
|
||||
'A cat like utility to display images in the terminal.'
|
||||
' You can specify multiple image files and/or directories.'
|
||||
' Directories are scanned recursively for image files.'
|
||||
' Directories are scanned recursively for image files. If STDIN'
|
||||
' is not a terminal, image data will be read from it as well.'
|
||||
)
|
||||
usage = 'image-file ...'
|
||||
|
||||
@@ -267,6 +268,13 @@ def main(args=sys.argv):
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
stdin_data = None
|
||||
if not sys.stdin.isatty():
|
||||
stdin_data = sys.stdin.buffer.read()
|
||||
items.insert(0, stdin_data)
|
||||
sys.stdin.close()
|
||||
sys.stdin = open(os.ctermid(), 'r')
|
||||
|
||||
screen_size = screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
@@ -298,16 +306,22 @@ def main(args=sys.argv):
|
||||
if not items:
|
||||
raise SystemExit('You must specify at least one file to cat')
|
||||
if args.place:
|
||||
if len(items) > 1 or os.path.isdir(items[0]):
|
||||
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
|
||||
raise SystemExit('The --place option can only be used with a single image')
|
||||
sys.stdout.buffer.write(b'\0337') # save cursor
|
||||
for item in items:
|
||||
is_tempfile = False
|
||||
try:
|
||||
if isinstance(item, bytes):
|
||||
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
|
||||
tf.write(item), tf.close()
|
||||
item = tf.name
|
||||
is_tempfile = True
|
||||
if os.path.isdir(item):
|
||||
for x in scan(item):
|
||||
process(item, args)
|
||||
else:
|
||||
process(item, args)
|
||||
process(item, args, is_tempfile)
|
||||
except NoImageMagick as e:
|
||||
raise SystemExit(str(e))
|
||||
except ConvertFailed as e:
|
||||
|
||||
@@ -46,6 +46,7 @@ def create_kitten_handler(kitten, orig_args):
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
ans = partial(m['end'], [kitten] + orig_args)
|
||||
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
||||
ans.no_ui = getattr(m['end'], 'no_ui', False)
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from ..tui.line_edit import LineEdit
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
|
||||
clear_screen, colored, cursor, faint, set_line_wrapping,
|
||||
set_window_title, sgr, styled
|
||||
)
|
||||
|
||||
@@ -159,30 +159,32 @@ class Table:
|
||||
self.last_cols, self.last_rows = cols, rows
|
||||
self.layout_dirty = False
|
||||
|
||||
def safe_chr(codepoint):
|
||||
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
|
||||
|
||||
if self.mode is NAME:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
|
||||
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), name(codepoint)
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
is_current = i == self.current_idx
|
||||
if is_current:
|
||||
yield sgr(color_code('gray', base=40))
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'black' if is_current else 'gray', True) + ' '
|
||||
text = colored(idx, 'green') + ' ' + sgr('49') + c + ' '
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
text += ' ' * (2 - w)
|
||||
if len(desc) > space_for_desc:
|
||||
desc = desc[:space_for_desc - 1] + '…'
|
||||
yield faint(desc)
|
||||
text += desc[:space_for_desc - 1] + '…'
|
||||
else:
|
||||
text += desc
|
||||
extra = space_for_desc - len(desc)
|
||||
if extra > 0:
|
||||
yield ' ' * extra
|
||||
if is_current:
|
||||
yield sgr('49')
|
||||
text += ' ' * extra
|
||||
|
||||
yield styled(text, reverse=True if is_current else None)
|
||||
|
||||
else:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
|
||||
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), ''
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
yield colored(idx, 'green') + ' '
|
||||
|
||||
30346
kittens/unicode_input/names.h
generated
30346
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -48,21 +48,29 @@ def listen_on(spec):
|
||||
return s.fileno()
|
||||
|
||||
|
||||
def data_for_at(w, arg):
|
||||
def data_for_at(w, arg, add_wrap_markers=False):
|
||||
def as_text(**kw):
|
||||
kw['add_wrap_markers'] = add_wrap_markers
|
||||
return w.as_text(**kw)
|
||||
|
||||
if arg == '@selection':
|
||||
return w.text_for_selection()
|
||||
if arg == '@ansi':
|
||||
return w.as_text(as_ansi=True, add_history=True)
|
||||
return as_text(as_ansi=True, add_history=True)
|
||||
if arg == '@text':
|
||||
return w.as_text(add_history=True)
|
||||
return as_text(add_history=True)
|
||||
if arg == '@screen':
|
||||
return w.as_text()
|
||||
return as_text()
|
||||
if arg == '@ansi_screen':
|
||||
return w.as_text(as_ansi=True)
|
||||
return as_text(as_ansi=True)
|
||||
if arg == '@alternate':
|
||||
return w.as_text(alternate_screen=True)
|
||||
return as_text(alternate_screen=True)
|
||||
if arg == '@alternate_scrollback':
|
||||
return as_text(alternate_screen=True, add_history=True)
|
||||
if arg == '@ansi_alternate':
|
||||
return w.as_text(as_ansi=True, alternate_screen=True)
|
||||
return as_text(as_ansi=True, alternate_screen=True)
|
||||
if arg == '@ansi_alternate_scrollback':
|
||||
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
|
||||
|
||||
|
||||
class DumpCommands: # {{{
|
||||
@@ -236,8 +244,15 @@ class Boss:
|
||||
def new_os_window(self, *args):
|
||||
self._new_os_window(args)
|
||||
|
||||
def new_os_window_with_cwd(self, *args):
|
||||
@property
|
||||
def active_window_for_cwd(self):
|
||||
w = self.active_window
|
||||
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
|
||||
w = self.window_id_map[w.overlay_for]
|
||||
return w
|
||||
|
||||
def new_os_window_with_cwd(self, *args):
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
self._new_os_window(args, cwd_from)
|
||||
|
||||
@@ -362,6 +377,9 @@ class Boss:
|
||||
reset = action == 'reset'
|
||||
how = 3 if action == 'scrollback' else 2
|
||||
for w in windows:
|
||||
if action == 'scroll':
|
||||
w.screen.scroll_until_cursor()
|
||||
continue
|
||||
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||
if reset:
|
||||
w.screen.reset()
|
||||
@@ -445,10 +463,10 @@ class Boss:
|
||||
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
|
||||
os_window_id = window.os_window_id
|
||||
if opacity[0] in '+-':
|
||||
opacity = background_opacity_of(os_window_id)
|
||||
if opacity is None:
|
||||
old_opacity = background_opacity_of(os_window_id)
|
||||
if old_opacity is None:
|
||||
return
|
||||
opacity += float(opacity)
|
||||
opacity = old_opacity + float(opacity)
|
||||
elif opacity == 'default':
|
||||
opacity = self.opts.background_opacity
|
||||
else:
|
||||
@@ -541,6 +559,8 @@ class Boss:
|
||||
if key_action is not None:
|
||||
f = getattr(self, key_action.func, None)
|
||||
if f is not None:
|
||||
if self.args.debug_keyboard:
|
||||
print('Keypress matched action:', f.__name__)
|
||||
passthrough = f(*key_action.args)
|
||||
if passthrough is not True:
|
||||
return True
|
||||
@@ -554,6 +574,8 @@ class Boss:
|
||||
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
|
||||
if f is not None:
|
||||
passthrough = f(*key_action.args)
|
||||
if self.args.debug_keyboard:
|
||||
print('Keypress matched action:', f.__name__)
|
||||
if passthrough is not True:
|
||||
return True
|
||||
return False
|
||||
@@ -631,17 +653,20 @@ class Boss:
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
|
||||
orig_args, args = list(args), list(args)
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
if window is None:
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
else:
|
||||
w = window
|
||||
tab = w.tabref()
|
||||
if end_kitten.no_ui:
|
||||
end_kitten(None, getattr(w, 'id', None), self)
|
||||
return
|
||||
|
||||
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
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
args[0:0] = [config_dir, kitten]
|
||||
if input_data is None:
|
||||
type_of_input = end_kitten.type_of_input
|
||||
@@ -793,12 +818,27 @@ class Boss:
|
||||
|
||||
prev_tab = previous_tab
|
||||
|
||||
def process_stdin_source(self, window=None, stdin=None):
|
||||
w = window or self.active_window
|
||||
env = None
|
||||
if stdin:
|
||||
add_wrap_markers = stdin.endswith('_wrap')
|
||||
if add_wrap_markers:
|
||||
stdin = stdin[:-len('_wrap')]
|
||||
stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers)
|
||||
if stdin is not None:
|
||||
pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else {}
|
||||
if pipe_data:
|
||||
env = {
|
||||
'KITTY_PIPE_DATA':
|
||||
'{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data)
|
||||
}
|
||||
stdin = stdin.encode('utf-8')
|
||||
return env, stdin
|
||||
|
||||
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
|
||||
w = window or self.active_window
|
||||
if stdin:
|
||||
stdin = data_for_at(w, stdin)
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode('utf-8')
|
||||
env, stdin = self.process_stdin_source(w, stdin)
|
||||
cmdline = []
|
||||
for arg in cmd:
|
||||
if arg == '@selection':
|
||||
@@ -807,7 +847,7 @@ class Boss:
|
||||
continue
|
||||
cmdline.append(arg)
|
||||
overlay_for = w.id if as_overlay and w.overlay_for is None else None
|
||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for)
|
||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
|
||||
|
||||
def pipe(self, source, dest, exe, *args):
|
||||
cmd = [exe] + list(args)
|
||||
@@ -830,7 +870,12 @@ class Boss:
|
||||
self._new_os_window(create_window(), cwd_from=cwd_from)
|
||||
else:
|
||||
import subprocess
|
||||
subprocess.Popen(cmd)
|
||||
env, stdin = self.process_stdin_source(stdin=source, window=window)
|
||||
if stdin:
|
||||
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE)
|
||||
p.communicate(stdin)
|
||||
else:
|
||||
subprocess.Popen(cmd)
|
||||
|
||||
def args_to_special_window(self, args, cwd_from=None):
|
||||
args = list(args)
|
||||
@@ -874,7 +919,7 @@ class Boss:
|
||||
self._create_tab(args)
|
||||
|
||||
def new_tab_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
self._create_tab(args, cwd_from=cwd_from)
|
||||
|
||||
@@ -890,7 +935,7 @@ class Boss:
|
||||
self._new_window(args)
|
||||
|
||||
def new_window_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
w = self.active_window_for_cwd
|
||||
if w is None:
|
||||
return self.new_window(*args)
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
|
||||
@@ -102,11 +102,11 @@ void main() {
|
||||
// SIMPLE
|
||||
#ifdef TRANSPARENT
|
||||
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
|
||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
||||
final_color = vec4(final_color.rgb, final_color.a);
|
||||
#else
|
||||
// since background alpha is 1.0, it is effectively pre-multiplied
|
||||
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
|
||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
||||
final_color = vec4(final_color.rgb, final_color.a);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -565,7 +565,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
||||
}
|
||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||
}
|
||||
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) {
|
||||
if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at);
|
||||
}
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
*active_window_bg = OPT(background);
|
||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||
@@ -599,6 +602,8 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
||||
|
||||
static inline void
|
||||
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
|
||||
// ensure all pixels are cleared to background color at least once in every buffer
|
||||
if (os_window->clear_count++ < 3) blank_os_window(os_window);
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
BorderRects *br = &tab->border_rects;
|
||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, os_window);
|
||||
@@ -620,6 +625,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
||||
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
|
||||
os_window->focused_at_last_render = os_window->is_focused;
|
||||
os_window->is_damaged = false;
|
||||
if (global_state.is_wayland && OPT(sync_to_monitor)) wayland_request_frame_render(os_window);
|
||||
#undef WD
|
||||
#undef TD
|
||||
}
|
||||
@@ -635,6 +641,10 @@ render(double now) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
|
||||
if (global_state.is_wayland && w->wayland_render_state != RENDER_FRAME_READY && OPT(sync_to_monitor)) {
|
||||
if (w->wayland_render_state == RENDER_FRAME_NOT_REQUESTED) wayland_request_frame_render(w);
|
||||
continue;
|
||||
}
|
||||
bool needs_render = w->is_damaged;
|
||||
make_os_window_context_current(w);
|
||||
if (w->viewport_size_dirty) {
|
||||
@@ -864,7 +874,7 @@ read_bytes(int fd, Screen *screen) {
|
||||
while(true) {
|
||||
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
|
||||
if (len < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EINTR || errno == EAGAIN) continue;
|
||||
if (errno != EIO) perror("Call to read() from child fd failed");
|
||||
return false;
|
||||
}
|
||||
@@ -981,7 +991,8 @@ io_loop(void *data) {
|
||||
// The I/O thread loop
|
||||
size_t i;
|
||||
int ret;
|
||||
bool has_more, data_received;
|
||||
bool has_more, data_received, has_pending_wakeups = false;
|
||||
double last_main_loop_wakeup_at = -1, now = -1;
|
||||
Screen *screen;
|
||||
ChildMonitor *self = (ChildMonitor*)data;
|
||||
set_thread_name("KittyChildMon");
|
||||
@@ -1000,7 +1011,14 @@ io_loop(void *data) {
|
||||
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
|
||||
screen_mutex(unlock, read); screen_mutex(unlock, write);
|
||||
}
|
||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||
if (has_pending_wakeups) {
|
||||
now = monotonic();
|
||||
double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
|
||||
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta));
|
||||
else ret = 0;
|
||||
} else {
|
||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||
}
|
||||
if (ret > 0) {
|
||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||
if (fds[1].revents && POLLIN) {
|
||||
@@ -1044,8 +1062,17 @@ io_loop(void *data) {
|
||||
perror("Call to poll() failed");
|
||||
}
|
||||
}
|
||||
if (data_received) wakeup_main_loop();
|
||||
#define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; }
|
||||
// we only wakeup the main loop after input_delay as wakeup is an expensive operation
|
||||
// on some platforms, such as cocoa
|
||||
if (data_received) {
|
||||
if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||
else has_pending_wakeups = true;
|
||||
} else {
|
||||
if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||
}
|
||||
}
|
||||
#undef WAKEUP
|
||||
children_mutex(lock);
|
||||
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
|
||||
remove_children(self);
|
||||
|
||||
@@ -275,7 +275,10 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
||||
import subprocess
|
||||
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
||||
p.communicate(text.encode('utf-8'))
|
||||
try:
|
||||
p.communicate(text.encode('utf-8'))
|
||||
except KeyboardInterrupt:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(p.wait())
|
||||
else:
|
||||
print(text)
|
||||
|
||||
@@ -16,7 +16,7 @@ from .conf.utils import (
|
||||
parse_config_base, python_string, to_bool, to_cmdline
|
||||
)
|
||||
from .config_data import all_options, parse_mods, type_map
|
||||
from .constants import cache_dir, defconf
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .utils import log_error
|
||||
|
||||
named_keys = {
|
||||
@@ -173,6 +173,16 @@ def pipe(func, rest):
|
||||
return func, rest
|
||||
|
||||
|
||||
@func_with_args('nth_window')
|
||||
def nth_window(func, rest):
|
||||
try:
|
||||
num = int(rest)
|
||||
except Exception:
|
||||
log_error('Invalid nth_window number: {}'.format(rest))
|
||||
num = 1
|
||||
return func, [num]
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
parts = action.split(' ', 1)
|
||||
func = parts[0]
|
||||
@@ -302,6 +312,15 @@ special_handlers = {}
|
||||
|
||||
def special_handler(func):
|
||||
special_handlers[func.__name__.partition('_')[2]] = func
|
||||
return func
|
||||
|
||||
|
||||
def deprecated_handler(*names):
|
||||
def special_handler(func):
|
||||
for name in names:
|
||||
special_handlers[name] = func
|
||||
return func
|
||||
return special_handler
|
||||
|
||||
|
||||
@special_handler
|
||||
@@ -326,6 +345,15 @@ def handle_clear_all_shortcuts(key, val, ans):
|
||||
ans['key_definitions'] = [None]
|
||||
|
||||
|
||||
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
|
||||
def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
|
||||
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
|
||||
handle_deprecated_hide_window_decorations_aliases.key = True
|
||||
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
|
||||
if to_bool(val):
|
||||
ans['hide_window_decorations'] = True
|
||||
|
||||
|
||||
def expandvars(val, env):
|
||||
|
||||
def sub(m):
|
||||
@@ -337,7 +365,7 @@ def expandvars(val, env):
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'$\{(\S+)\}', sub, val)
|
||||
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||
|
||||
|
||||
@special_handler
|
||||
@@ -542,4 +570,6 @@ def load_config(*paths, overrides=None):
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
if (is_macos and getattr(opts, 'macos_hide_titlebar', False)) or (not is_macos and getattr(opts, 'x11_hide_window_decorations', False)):
|
||||
opts.hide_window_decorations = True
|
||||
return opts
|
||||
|
||||
@@ -140,7 +140,8 @@ For example::
|
||||
'shortcuts.tab': [
|
||||
_('Tab management'), '',
|
||||
_('''\
|
||||
You can also create shortcuts to go to specific tabs, with 1 being the first tab::
|
||||
You can also create shortcuts to go to specific tabs, with 1 being the first
|
||||
tab, 2 the second tab and -1 being the previously active tab::
|
||||
|
||||
map ctrl+alt+1 goto_tab 1
|
||||
map ctrl+alt+2 goto_tab 2
|
||||
@@ -323,10 +324,15 @@ def scrollback_lines(x):
|
||||
return x
|
||||
|
||||
|
||||
def scrollback_pager_history_size(x):
|
||||
ans = int(max(0, float(x)) * 1024 * 1024)
|
||||
return min(ans, 4096 * 1024 * 1024 - 1)
|
||||
|
||||
|
||||
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||
very large scrollback is not recommended a it can slow down resizing of the terminal
|
||||
very large scrollback is not recommended as it can slow down resizing of the terminal
|
||||
and also use large amounts of RAM.'''))
|
||||
|
||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||
@@ -336,10 +342,26 @@ use can handle ANSI escape sequences for colors and text formatting.
|
||||
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
|
||||
representing which line should be at the top of the screen.'''))
|
||||
|
||||
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
|
||||
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
|
||||
This separate buffer is not available for interactive scrolling but will be
|
||||
piped to the pager program when viewing scrollback buffer in a separate window.
|
||||
The current implementation stores one character in 4 bytes, so approximatively
|
||||
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
|
||||
this feature. The maximum allowed size is 4GB.'''))
|
||||
|
||||
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||
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.'''))
|
||||
|
||||
o('touch_scroll_multiplier', 1.0, long_text=_('''
|
||||
Modify the amount scrolled by a touchpad. Note this is only used for high
|
||||
precision scrolling devices on platforms such as macOS and Wayland.
|
||||
Use negative numbers to change scroll direction.'''))
|
||||
|
||||
# }}}
|
||||
|
||||
# }}}
|
||||
|
||||
g('mouse') # {{{
|
||||
@@ -372,7 +394,7 @@ operating system's default URL handler.'''))
|
||||
o('copy_on_select', False, long_text=_('''
|
||||
Copy to clipboard on select. With this enabled, simply selecting text with
|
||||
the mouse will cause the text to be copied to clipboard. Useful on platforms
|
||||
such as macOS/Wayland that do not have the concept of primary selections. Note
|
||||
such as macOS that do not have the concept of primary selections. Note
|
||||
that this is a security risk, as all programs, including websites open in your
|
||||
browser can read the contents of the clipboard.'''))
|
||||
|
||||
@@ -385,9 +407,9 @@ Characters considered part of a word when double clicking. In addition to these
|
||||
any character that is marked as an alpha-numeric character in the unicode
|
||||
database will be matched.'''))
|
||||
|
||||
o('click_interval', 0.5, option_type=positive_float, long_text=_('''
|
||||
The interval between successive clicks to detect
|
||||
double/triple clicks (in seconds)'''))
|
||||
o('click_interval', -1.0, option_type=float, long_text=_('''
|
||||
The interval between successive clicks to detect double/triple clicks (in seconds).
|
||||
Negative numbers will use the system default instead, if available, or fallback to 0.5.'''))
|
||||
|
||||
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
|
||||
Hide mouse cursor after the specified number of seconds
|
||||
@@ -524,6 +546,12 @@ o('inactive_text_alpha', 1.0, option_type=unit_float, long_text=_('''
|
||||
Fade the text in inactive windows by the specified amount (a number between
|
||||
zero and one, with zero being fully faded).
|
||||
'''))
|
||||
|
||||
o('hide_window_decorations', False, long_text=_('''
|
||||
Hide the window decorations (title-bar and window borders).
|
||||
Whether this works and exactly what effect it has depends on the
|
||||
window manager/operating system.
|
||||
'''))
|
||||
# }}}
|
||||
|
||||
g('tabbar') # {{{
|
||||
@@ -558,8 +586,8 @@ Which edge to show the tab bar on, top or bottom'''))
|
||||
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||
The margin to the left and right of the tab bar (in pts)'''))
|
||||
|
||||
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator'), long_text=_('''
|
||||
The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style,
|
||||
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_('''
|
||||
The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style,
|
||||
each tab's edges fade into the background color, in the separator style, tabs are
|
||||
separated by a configurable separator.
|
||||
'''))
|
||||
@@ -690,7 +718,7 @@ 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
|
||||
env MYVAR2=${MYVAR1}/${HOME}/b
|
||||
|
||||
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
|
||||
'''))
|
||||
@@ -724,9 +752,13 @@ program, even one running on a remote server via SSH can read your clipboard.
|
||||
'''))
|
||||
|
||||
o('term', 'xterm-kitty', long_text=_('''
|
||||
The value of the TERM environment variable to set. Changing this can break
|
||||
many terminal programs, only change it if you know what you are doing, not
|
||||
because you read some advice on Stack Overflow to change it.
|
||||
The value of the TERM environment variable to set. Changing this can break many
|
||||
terminal programs, only change it if you know what you are doing, not because
|
||||
you read some advice on Stack Overflow to change it. The TERM variable if used
|
||||
by various programs to get information about the capabilities and behavior of
|
||||
the terminal. If you change it, depending on what programs you run, and how
|
||||
different the terminal you are changing it to is, various things from
|
||||
key-presses, to colors, to various advanced features may not work.
|
||||
'''))
|
||||
|
||||
# }}}
|
||||
@@ -751,18 +783,9 @@ an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option
|
||||
using a hack, as there is no proper Cocoa API for it. It sets the background
|
||||
color of the entire window and makes the titlebar transparent. As such it is
|
||||
incompatible with :opt:`background_opacity`. If you want to use both, you are
|
||||
probably better off just hiding the titlebar with :opt:`macos_hide_titlebar`.
|
||||
probably better off just hiding the titlebar with :opt:`hide_window_decorations`.
|
||||
'''))
|
||||
|
||||
o('macos_hide_titlebar', False, long_text=_('''
|
||||
Hide the kitty window's title bar on macOS.'''))
|
||||
|
||||
o('x11_hide_window_decorations', False, long_text=_('''
|
||||
Hide the window decorations (title bar and window borders) on X11 and Wayland.
|
||||
Whether this works and exactly what effect it has depends on the window
|
||||
manager, as it is the job of the window manager/compositor to draw window
|
||||
decorations.'''))
|
||||
|
||||
o('macos_option_as_alt', True, long_text=_('''
|
||||
Use the option key as an alt key. With this set to no, kitty will use
|
||||
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
|
||||
@@ -854,18 +877,9 @@ the following opens the scrollback buffer in less in an overlay window::
|
||||
|
||||
map f1 pipe @ansi overlay less +G -R
|
||||
|
||||
Placeholders available are: @text (which is plain text) and @ansi (which
|
||||
includes text styling escape codes). For only the current screen, use @screen
|
||||
or @ansi_screen. For the secondary screen, use @alternate and @ansi_alternate.
|
||||
The secondary screen is the screen not currently displayed. For
|
||||
example if you run a fullscreen terminal application, the secondary screen will
|
||||
be the screen you return to when quitting the application. You can also use
|
||||
``none`` for no :file:`STDIN` input.
|
||||
|
||||
To open in a new window, tab or new OS window, use ``window``, ``tab``, or
|
||||
``os_window`` respectively. You can also use ``none`` in which case the data
|
||||
will be piped into the program without creating any windows, useful if the
|
||||
program is a GUI program that creates its own windows. '''))
|
||||
For more details on piping screen and buffer contents to external programs,
|
||||
see :doc:`pipe`.
|
||||
'''))
|
||||
|
||||
|
||||
# }}}
|
||||
@@ -973,12 +987,21 @@ k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset
|
||||
long_text=_('''
|
||||
You can create shortcuts to clear/reset the terminal. For example::
|
||||
|
||||
# Reset the terminal
|
||||
map kitty_mod+f9 clear_terminal reset active
|
||||
# Clear the terminal screen by erasing all contents
|
||||
map kitty_mod+f10 clear_terminal clear active
|
||||
# Clear the terminal scrollback by erasing it
|
||||
map kitty_mod+f11 clear_terminal scrollback active
|
||||
# Scroll the contents of the screen into the scrollback
|
||||
map kitty_mod+f12 clear_terminal scroll 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`.
|
||||
If you want to operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
|
||||
|
||||
It is also possible to remap Ctrl+L to both scroll the current screen contents into the scrollback buffer
|
||||
and clear the screen, instead of just clearing the screen::
|
||||
|
||||
map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
|
||||
'''))
|
||||
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
||||
add_to_default=False, long_text=_('''
|
||||
|
||||
@@ -7,9 +7,8 @@ import pwd
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 12, 2)
|
||||
version = (0, 13, 1)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
@@ -61,6 +60,18 @@ def _get_config_dir():
|
||||
os.makedirs(ans, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
|
||||
except PermissionError:
|
||||
import tempfile
|
||||
import atexit
|
||||
ans = tempfile.mkdtemp(prefix='kitty-conf-')
|
||||
|
||||
def cleanup():
|
||||
import shutil
|
||||
try:
|
||||
shutil.rmtree(ans)
|
||||
except Exception:
|
||||
pass
|
||||
atexit.register(cleanup)
|
||||
return ans
|
||||
|
||||
|
||||
@@ -125,4 +136,4 @@ if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ an
|
||||
is_wayland = True
|
||||
|
||||
|
||||
supports_primary_selection = not is_macos and not is_wayland
|
||||
supports_primary_selection = not is_macos
|
||||
|
||||
@@ -221,6 +221,15 @@ is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
return bounds.size.width <= 0;
|
||||
}
|
||||
|
||||
int
|
||||
get_glyph_width(PyObject *s, glyph_index g) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
CGGlyph gg = g;
|
||||
CGRect bounds;
|
||||
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
|
||||
return bounds.size.width;
|
||||
}
|
||||
|
||||
static inline float
|
||||
scaled_point_sz(FONTS_DATA_HANDLE fg) {
|
||||
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
|
||||
@@ -383,7 +392,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
|
||||
}
|
||||
|
||||
static inline bool
|
||||
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg) {
|
||||
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
unsigned int canvas_width = cell_width * num_cells;
|
||||
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
|
||||
if (allow_resize) {
|
||||
@@ -394,7 +403,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
|
||||
CGFloat sz = CTFontGetSize(ct_font);
|
||||
sz *= canvas_width / right;
|
||||
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
|
||||
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg);
|
||||
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
|
||||
CFRelease(new_font);
|
||||
return ret;
|
||||
}
|
||||
@@ -420,10 +429,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
|
||||
}
|
||||
|
||||
bool
|
||||
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
|
||||
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
|
||||
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg);
|
||||
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -211,55 +211,15 @@ END_ALLOW_CASE_RANGE
|
||||
#undef RANGE
|
||||
}
|
||||
|
||||
static inline int
|
||||
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
|
||||
switch(val & 0xff) {
|
||||
case 1:
|
||||
val >>= 8;
|
||||
if (val < 16 && simple_code) {
|
||||
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
|
||||
}
|
||||
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
|
||||
case 2:
|
||||
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
|
||||
default:
|
||||
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
decoration_as_sgr(uint8_t decoration) {
|
||||
switch(decoration) {
|
||||
case 1: return "4";
|
||||
case 2: return "4:2";
|
||||
case 3: return "4:3";
|
||||
default: return "24";
|
||||
}
|
||||
}
|
||||
|
||||
const char*
|
||||
cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
static char buf[128];
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); }
|
||||
char *p = buf;
|
||||
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
|
||||
if (intensity_differs) {
|
||||
if (!self->bold && !self->dim) { P("%d", 22); }
|
||||
else { if (self->bold) P("%d", 1); if (self->dim) P("%d", 2); }
|
||||
}
|
||||
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
|
||||
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
||||
if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29);
|
||||
if (self->decoration != prev->decoration) P("%s", decoration_as_sgr(self->decoration));
|
||||
if (self->fg != prev->fg) p += color_as_sgr(p, SZ, self->fg, 30, 90, 38);
|
||||
if (self->bg != prev->bg) p += color_as_sgr(p, SZ, self->bg, 40, 100, 48);
|
||||
if (self->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, self->decoration_fg, 0, 0, DECORATION_FG_CODE);
|
||||
#undef P
|
||||
#undef SZ
|
||||
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
|
||||
*p = 0; // ensure string is null-terminated
|
||||
return buf;
|
||||
cursor_as_sgr(const Cursor *self) {
|
||||
GPUCell blank_cell = { 0 }, cursor_cell = {
|
||||
.attrs = CURSOR_TO_ATTRS(self, 1),
|
||||
.fg = self->fg & COL_MASK,
|
||||
.bg = self->bg & COL_MASK,
|
||||
.decoration_fg = self->decoration_fg & COL_MASK,
|
||||
};
|
||||
return cell_as_sgr(&cursor_cell, &blank_cell);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
||||
@@ -173,11 +173,20 @@ typedef struct {
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBufSegment;
|
||||
|
||||
typedef struct {
|
||||
index_type bufsize, maxsz;
|
||||
Py_UCS4 *buffer;
|
||||
index_type start, end;
|
||||
index_type bufend;
|
||||
bool rewrap_needed;
|
||||
} PagerHistoryBuf;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
index_type xnum, ynum, num_segments;
|
||||
HistoryBufSegment* segments;
|
||||
HistoryBufSegment *segments;
|
||||
PagerHistoryBuf *pagerhist;
|
||||
Line *line;
|
||||
index_type start_of_data, count;
|
||||
} HistoryBuf;
|
||||
@@ -254,7 +263,7 @@ const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, siz
|
||||
Line* alloc_line();
|
||||
Cursor* alloc_cursor();
|
||||
LineBuf* alloc_linebuf(unsigned int, unsigned int);
|
||||
HistoryBuf* alloc_historybuf(unsigned int, unsigned int);
|
||||
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int);
|
||||
ColorProfile* alloc_color_profile();
|
||||
PyObject* create_256_color_table();
|
||||
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
|
||||
@@ -267,7 +276,8 @@ void cursor_copy_to(Cursor *src, Cursor *dest);
|
||||
void cursor_reset_display_attrs(Cursor*);
|
||||
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
|
||||
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
|
||||
const char* cursor_as_sgr(Cursor*, Cursor*);
|
||||
const char* cell_as_sgr(GPUCell *, GPUCell *);
|
||||
const char* cursor_as_sgr(const Cursor *);
|
||||
|
||||
double monotonic();
|
||||
PyObject* cm_thread_write(PyObject *self, PyObject *args);
|
||||
|
||||
@@ -114,6 +114,7 @@ _fc_match(FcPattern *pat) {
|
||||
FcResult result;
|
||||
FcConfigSubstitute(NULL, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
/* printf("fc_match = %s\n", FcNameUnparse(pat)); */
|
||||
match = FcFontMatch(NULL, pat, &result);
|
||||
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
|
||||
ans = pattern_as_dict(match);
|
||||
@@ -145,16 +146,22 @@ end:
|
||||
static PyObject*
|
||||
fc_match(PyObject UNUSED *self, PyObject *args) {
|
||||
char *family = NULL;
|
||||
int bold = 0, italic = 0, allow_bitmapped_fonts = 0;
|
||||
int bold = 0, italic = 0, allow_bitmapped_fonts = 0, monospaced = 0;
|
||||
double size_in_pts = 0, dpi = 0;
|
||||
FcPattern *pat = NULL;
|
||||
PyObject *ans = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &monospaced, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
|
||||
pat = FcPatternCreate();
|
||||
if (pat == NULL) return PyErr_NoMemory();
|
||||
|
||||
if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
|
||||
if (monospaced) {
|
||||
// pass the family,monospace as the family parameter to fc-match,
|
||||
// which will fallback to using monospace if the family does not match.
|
||||
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family");
|
||||
AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing");
|
||||
}
|
||||
if (!allow_bitmapped_fonts) {
|
||||
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
|
||||
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#define MISSING_GLYPH 4
|
||||
#define MAX_NUM_EXTRA_GLYPHS 8
|
||||
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
|
||||
#define MAX_NUM_EXTRA_GLYPHS_PUA 4
|
||||
|
||||
typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
|
||||
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
|
||||
@@ -395,7 +396,7 @@ calc_cell_metrics(FontGroup *fg) {
|
||||
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
|
||||
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
|
||||
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_width *= OPT(adjust_column_width_frac);
|
||||
int line_height_adjustment = cell_height - before_cell_height;
|
||||
if (cell_height < 4) fatal("line height too small after adjustment");
|
||||
if (cell_height > 1000) fatal("line height too large after adjustment");
|
||||
@@ -635,8 +636,13 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_private_use(char_type ch) {
|
||||
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
|
||||
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, bool center_glyph) {
|
||||
static SpritePosition* sprite_position[16];
|
||||
int error = 0;
|
||||
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
|
||||
@@ -651,7 +657,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
|
||||
|
||||
clear_canvas(fg);
|
||||
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
@@ -901,17 +907,17 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
|
||||
|
||||
static inline void
|
||||
merge_groups_for_pua_space_ligature() {
|
||||
if (G(group_idx) == 1) {
|
||||
while (G(group_idx) > 0) {
|
||||
Group *g = G(groups), *g1 = G(groups) + 1;
|
||||
g->num_cells += g1->num_cells;
|
||||
g->num_glyphs += g1->num_glyphs;
|
||||
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
|
||||
G(group_idx) = 0;
|
||||
G(group_idx)--;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_groups(FontGroup *fg, Font *font) {
|
||||
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
|
||||
unsigned idx = 0;
|
||||
ExtraGlyphs ed;
|
||||
while (idx <= G(group_idx)) {
|
||||
@@ -924,7 +930,7 @@ render_groups(FontGroup *fg, Font *font) {
|
||||
int last = -1;
|
||||
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
|
||||
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
|
||||
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
|
||||
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
@@ -970,12 +976,12 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
#undef G
|
||||
|
||||
static inline void
|
||||
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
|
||||
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph) {
|
||||
switch(font_idx) {
|
||||
default:
|
||||
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx]);
|
||||
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
|
||||
render_groups(fg, &fg->fonts[font_idx]);
|
||||
render_groups(fg, &fg->fonts[font_idx], center_glyph);
|
||||
break;
|
||||
case BLANK_FONT:
|
||||
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
|
||||
@@ -989,16 +995,12 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_private_use(char_type ch) {
|
||||
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||
}
|
||||
|
||||
void
|
||||
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph);
|
||||
FontGroup *fg = (FontGroup*)fg_;
|
||||
ssize_t run_font_idx = NO_FONT;
|
||||
bool center_glyph = false;
|
||||
index_type first_cell_in_run, i;
|
||||
attrs_type prev_width = 0;
|
||||
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
|
||||
@@ -1006,21 +1008,48 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line) {
|
||||
CPUCell *cpu_cell = line->cpu_cells + i;
|
||||
GPUCell *gpu_cell = line->gpu_cells + i;
|
||||
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell);
|
||||
if (is_private_use(cpu_cell->ch) && i + 1 < line->xnum && (line->cpu_cells[i+1].ch == ' ' || line->cpu_cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
|
||||
// We have a private use char followed by a space char, render it as a two cell ligature.
|
||||
GPUCell *space_cell = line->gpu_cells + i+1;
|
||||
// Ensure the space cell uses the foreground colors from the PUA cell
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg;
|
||||
RENDER;
|
||||
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, 2, cell_font_idx, true);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + 2;
|
||||
prev_width = line->gpu_cells[i+1].attrs & WIDTH_MASK;
|
||||
i++;
|
||||
continue;
|
||||
|
||||
if (is_private_use(cpu_cell->ch)
|
||||
&& cell_font_idx != BOX_FONT
|
||||
&& cell_font_idx != MISSING_FONT) {
|
||||
int desired_cells;
|
||||
if (cell_font_idx > 0) {
|
||||
Font *font = (fg->fonts + cell_font_idx);
|
||||
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
|
||||
|
||||
int width = get_glyph_width(font->face, glyph_id);
|
||||
desired_cells = ceilf((float)width / fg->cell_width);
|
||||
} else {
|
||||
desired_cells = 1;
|
||||
}
|
||||
|
||||
int num_spaces = 0;
|
||||
while ((line->cpu_cells[i+num_spaces+1].ch == ' ' || line->cpu_cells[i+num_spaces+1].ch == 0)
|
||||
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
|
||||
&& num_spaces < desired_cells
|
||||
&& i + num_spaces + 1 < line->xnum) {
|
||||
num_spaces++;
|
||||
// We have a private use char followed by space(s), render it as a multi-cell ligature.
|
||||
GPUCell *space_cell = line->gpu_cells + i + num_spaces;
|
||||
// Ensure the space cell uses the foreground/background colors from the PUA cell.
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = gpu_cell->fg;
|
||||
space_cell->bg = gpu_cell->bg;
|
||||
space_cell->decoration_fg = gpu_cell->decoration_fg;
|
||||
}
|
||||
if (num_spaces) {
|
||||
center_glyph = true;
|
||||
RENDER;
|
||||
center_glyph = false;
|
||||
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + num_spaces + 1;
|
||||
prev_width = line->gpu_cells[i+num_spaces].attrs & WIDTH_MASK;
|
||||
i += num_spaces;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev_width = gpu_cell->attrs & WIDTH_MASK;
|
||||
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
// API that font backends need to implement
|
||||
typedef uint16_t glyph_index;
|
||||
unsigned int glyph_id_for_codepoint(PyObject *, char_type);
|
||||
int get_glyph_width(PyObject *, glyph_index);
|
||||
bool is_glyph_empty(PyObject *, glyph_index);
|
||||
hb_font_t* harfbuzz_font_for_face(PyObject*);
|
||||
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
|
||||
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
|
||||
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE);
|
||||
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
|
||||
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
|
||||
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
|
||||
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
|
||||
|
||||
@@ -44,8 +44,12 @@ def list_fonts():
|
||||
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
|
||||
|
||||
|
||||
def family_name_to_key(family):
|
||||
return re.sub(r'\s+', ' ', family.lower())
|
||||
|
||||
|
||||
def find_best_match(family, bold=False, italic=False, monospaced=True):
|
||||
q = re.sub(r'\s+', ' ', family.lower())
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
def score(candidate):
|
||||
@@ -61,6 +65,22 @@ def find_best_match(family, bold=False, italic=False, monospaced=True):
|
||||
candidates.sort(key=score)
|
||||
return candidates[0]
|
||||
|
||||
# Use fc-match to see if we can find a monospaced font that matches family
|
||||
possibility = fc_match(family, False, False)
|
||||
for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')):
|
||||
val = possibility.get(key)
|
||||
if val:
|
||||
candidates = font_map[map_key].get(family_name_to_key(val))
|
||||
if candidates:
|
||||
if len(candidates) == 1:
|
||||
# happens if the family name is an alias, so we search with
|
||||
# the actual family name to see if we can find all the
|
||||
# fonts in the family.
|
||||
family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family']))
|
||||
if family_name_candidates and len(family_name_candidates) > 1:
|
||||
candidates = family_name_candidates
|
||||
return sorted(candidates, key=score)[0]
|
||||
|
||||
# Use fc-match with a generic family
|
||||
family = 'monospace' if monospaced else 'sans-serif'
|
||||
return fc_match(family, bold, italic)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import ctypes
|
||||
import sys
|
||||
from functools import partial
|
||||
from math import ceil, pi, cos, sqrt
|
||||
from math import ceil, pi, cos, floor
|
||||
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import is_macos
|
||||
@@ -115,28 +115,30 @@ def add_dline(buf, cell_width, position, thickness, cell_height):
|
||||
|
||||
|
||||
def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
xfactor = 2.0 * pi / cell_width
|
||||
yfactor = max(thickness, 2)
|
||||
max_x, max_y = cell_width - 1, cell_height - 1
|
||||
xfactor = 2.0 * pi / max_x
|
||||
half_height = max(thickness // 2, 1)
|
||||
|
||||
def clamp_y(y):
|
||||
return max(0, min(int(y), cell_height - 1))
|
||||
|
||||
def clamp_x(x):
|
||||
return max(0, min(int(x), cell_width - 1))
|
||||
|
||||
def add_intensity(x, y, distance):
|
||||
def add_intensity(x, y, val):
|
||||
y += position
|
||||
y = min(y, max_y)
|
||||
idx = cell_width * y + x
|
||||
buf[idx] = min(
|
||||
255, buf[idx] + int(255 * (1 - distance))
|
||||
)
|
||||
buf[idx] = min(255, buf[idx] + val)
|
||||
|
||||
for x_exact in range(cell_width):
|
||||
y_exact = yfactor * cos(x_exact * xfactor) + position
|
||||
y = clamp_y(ceil(y_exact))
|
||||
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
|
||||
for x in {x_before, x_exact, x_after}:
|
||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||
add_intensity(x, y, dist)
|
||||
# Ensure all space at bottom of cell is used
|
||||
if position + half_height < max_y:
|
||||
position += max_y - (position + half_height)
|
||||
if position + half_height > max_y:
|
||||
position -= position + half_height - max_y
|
||||
|
||||
# Use the Wu antialias algorithm to draw the curve
|
||||
# cosine waves always have slope <= 1 so are never steep
|
||||
for x in range(cell_width):
|
||||
y = half_height * cos(x * xfactor)
|
||||
y1, y2 = floor(y), ceil(y)
|
||||
i1 = int(255 * abs(y - y1))
|
||||
add_intensity(x, y1, 255 - i1)
|
||||
add_intensity(x, y2, i1)
|
||||
|
||||
|
||||
def render_special(
|
||||
|
||||
127
kitty/freetype.c
127
kitty/freetype.c
@@ -16,6 +16,10 @@
|
||||
#define HARFBUZZ_HAS_CHANGE_FONT
|
||||
#endif
|
||||
|
||||
#if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 7
|
||||
#define FT_Bitmap_Init FT_Bitmap_New
|
||||
#endif
|
||||
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_BITMAP_H
|
||||
typedef struct {
|
||||
@@ -232,27 +236,6 @@ load_glyph(Face *self, int glyph_index, int load_type) {
|
||||
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
|
||||
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
||||
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
|
||||
|
||||
// Embedded bitmap glyph?
|
||||
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && load_type != FT_LOAD_DEFAULT) {
|
||||
FT_Bitmap bitmap;
|
||||
FT_Bitmap_New(&bitmap);
|
||||
|
||||
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
|
||||
error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
|
||||
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
|
||||
|
||||
// Normalize gray levels to the range [0..255]
|
||||
bitmap.num_grays = 256;
|
||||
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
|
||||
for (unsigned int i = 0; i < bitmap.rows; ++i) {
|
||||
// We only have 2 levels
|
||||
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
|
||||
}
|
||||
error = FT_Bitmap_Copy(library, &bitmap, &self->face->glyph->bitmap);
|
||||
if (error) { set_freetype_error("Failed to copy bitmap, with error:", error); return false; }
|
||||
FT_Bitmap_Done(library, &bitmap);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -294,6 +277,20 @@ is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
#undef M
|
||||
}
|
||||
|
||||
int
|
||||
get_glyph_width(PyObject *s, glyph_index g) {
|
||||
Face *self = (Face*)s;
|
||||
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
#define M self->face->glyph->metrics
|
||||
#define B self->face->glyph->bitmap
|
||||
/* printf("glyph: %u bitmap.width: %d bitmap.rows: %d horiAdvance: %ld horiBearingX: %ld horiBearingY: %ld vertBearingX: %ld vertBearingY: %ld vertAdvance: %ld width: %ld height: %ld\n", */
|
||||
/* g, B.width, B.rows, M.horiAdvance, M.horiBearingX, M.horiBearingY, M.vertBearingX, M.vertBearingY, M.vertAdvance, M.width, M.height); */
|
||||
return bitmap->width;
|
||||
#undef M
|
||||
#undef B
|
||||
}
|
||||
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
|
||||
|
||||
@@ -305,8 +302,17 @@ typedef struct {
|
||||
FT_Pixel_Mode pixel_mode;
|
||||
bool needs_free;
|
||||
unsigned int factor, right_edge;
|
||||
int bitmap_left, bitmap_top;
|
||||
} ProcessedBitmap;
|
||||
|
||||
static inline void
|
||||
free_processed_bitmap(ProcessedBitmap *bm) {
|
||||
if (bm->needs_free) {
|
||||
bm->needs_free = false;
|
||||
free(bm->buf); bm->buf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
bool column_has_text = false;
|
||||
@@ -324,17 +330,48 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
ans->width -= extra;
|
||||
}
|
||||
|
||||
static inline void
|
||||
populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) {
|
||||
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
|
||||
ans->rows = bitmap->rows;
|
||||
if (copy_buf) {
|
||||
ans->buf = calloc(ans->rows, ans->stride);
|
||||
if (!ans->buf) fatal("Out of memory");
|
||||
ans->needs_free = true;
|
||||
memcpy(ans->buf, bitmap->buffer, ans->rows * ans->stride);
|
||||
} else ans->buf = bitmap->buffer;
|
||||
ans->start_x = 0; ans->width = bitmap->width;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) {
|
||||
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
|
||||
unsigned int max_width = cell_width * num_cells;
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
ans->buf = bitmap->buffer;
|
||||
ans->start_x = 0; ans->width = bitmap->width;
|
||||
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
|
||||
ans->rows = bitmap->rows;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
|
||||
// Embedded bitmap glyph?
|
||||
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
|
||||
FT_Bitmap bitmap;
|
||||
FT_Bitmap_Init(&bitmap);
|
||||
|
||||
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
|
||||
int error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
|
||||
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
|
||||
|
||||
// Normalize gray levels to the range [0..255]
|
||||
bitmap.num_grays = 256;
|
||||
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
|
||||
for (unsigned int i = 0; i < bitmap.rows; ++i) {
|
||||
// We only have 2 levels
|
||||
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
|
||||
}
|
||||
populate_processed_bitmap(self->face->glyph, &bitmap, ans, true);
|
||||
FT_Bitmap_Done(library, &bitmap);
|
||||
} else {
|
||||
populate_processed_bitmap(self->face->glyph, &self->face->glyph->bitmap, ans, false);
|
||||
}
|
||||
|
||||
if (ans->width > max_width) {
|
||||
size_t extra = ans->width - max_width;
|
||||
if (italic && extra < cell_width / 2) {
|
||||
@@ -345,8 +382,9 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
// bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352
|
||||
} else if (rescale && self->is_scalable && extra > 1) {
|
||||
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
|
||||
float ar = (float)max_width / (float)bitmap->width;
|
||||
float ar = (float)max_width / (float)ans->width;
|
||||
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0, fg->cell_height)) {
|
||||
free_processed_bitmap(ans);
|
||||
if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false, fg)) return false;
|
||||
if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0, fg->cell_height)) return false;
|
||||
} else return false;
|
||||
@@ -422,6 +460,8 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
|
||||
ans->rows = bitmap->rows;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
if (ans->width > num_cells * cell_width + 2) downsample_bitmap(ans, num_cells * cell_width, cell_height);
|
||||
ans->bitmap_top = (float)self->face->glyph->bitmap_top / ans->factor;
|
||||
ans->bitmap_left = (float)self->face->glyph->bitmap_left / ans->factor;
|
||||
detect_right_edge(ans);
|
||||
return true;
|
||||
}
|
||||
@@ -444,16 +484,14 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
|
||||
}
|
||||
|
||||
static inline void
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) {
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline) {
|
||||
// We want the glyph to be positioned inside the cell based on the bearingX
|
||||
// and bearingY values, making sure that it does not overflow the cell.
|
||||
|
||||
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }, dest = { .bottom = cell_height, .right = cell_width };
|
||||
|
||||
// Calculate column bounds
|
||||
float bearing_x = (float)metrics->horiBearingX / 64.f;
|
||||
bearing_x /= bm->factor;
|
||||
int32_t xoff = (ssize_t)(x_offset + bearing_x);
|
||||
int32_t xoff = (ssize_t)(x_offset + bm->bitmap_left);
|
||||
uint32_t extra;
|
||||
if (xoff < 0) src.left += -xoff;
|
||||
else dest.left = xoff;
|
||||
@@ -464,9 +502,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
}
|
||||
|
||||
// Calculate row bounds
|
||||
float bearing_y = (float)metrics->horiBearingY / 64.f;
|
||||
bearing_y /= bm->factor;
|
||||
int32_t yoff = (ssize_t)(y_offset + bearing_y);
|
||||
int32_t yoff = (ssize_t)(y_offset + bm->bitmap_top);
|
||||
if ((yoff > 0 && (size_t)yoff > baseline)) {
|
||||
dest.top = 0;
|
||||
} else {
|
||||
@@ -483,7 +519,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
|
||||
|
||||
bool
|
||||
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
|
||||
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
Face *self = (Face*)f;
|
||||
bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color;
|
||||
float x = 0.f, y = 0.f, x_offset = 0.f;
|
||||
@@ -502,17 +538,20 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
}
|
||||
x_offset = x + (float)positions[i].x_offset / 64.0f;
|
||||
y = (float)positions[i].y_offset / 64.0f;
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline);
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
|
||||
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline);
|
||||
}
|
||||
x += (float)positions[i].x_advance / 64.0f;
|
||||
if (bm.needs_free) free(bm.buf);
|
||||
free_processed_bitmap(&bm);
|
||||
}
|
||||
|
||||
// center the glyphs in the canvas
|
||||
unsigned int right_edge = (unsigned int)x, delta;
|
||||
// x_advance is wrong for colored bitmaps that have been downsampled
|
||||
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
|
||||
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
|
||||
right_shift_canvas(canvas, canvas_width, cell_height, delta);
|
||||
if (center_glyph && num_glyphs) {
|
||||
unsigned int right_edge = (unsigned int)x, delta;
|
||||
// x_advance is wrong for colored bitmaps that have been downsampled
|
||||
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
|
||||
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
|
||||
right_shift_canvas(canvas, canvas_width, cell_height, delta);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
5606
kitty/gl-wrapper.c
generated
5606
kitty/gl-wrapper.c
generated
File diff suppressed because it is too large
Load Diff
87
kitty/gl-wrapper.h
generated
87
kitty/gl-wrapper.h
generated
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
OpenGL loader generated by glad 0.1.24a0 on Wed Jun 20 12:32:33 2018.
|
||||
OpenGL loader generated by glad 0.1.28 on Tue Dec 4 16:30:01 2018.
|
||||
Language/Generator: C/C++ Debug
|
||||
Specification: gl
|
||||
APIs: gl=3.3
|
||||
@@ -13,10 +13,11 @@
|
||||
Loader: True
|
||||
Local files: False
|
||||
Omit khrplatform: False
|
||||
Reproducible: False
|
||||
Commandline:
|
||||
--profile="core" --api="gl=3.3" --generator="c-debug" --spec="gl" --extensions="GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug"
|
||||
Online:
|
||||
http://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
|
||||
https://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
|
||||
*/
|
||||
#ifndef __glad_h_
|
||||
#define __glad_h_
|
||||
@@ -39,6 +40,9 @@
|
||||
#ifndef APIENTRYP
|
||||
#define APIENTRYP APIENTRY *
|
||||
#endif
|
||||
#ifndef GLAPIENTRY
|
||||
#define GLAPIENTRY APIENTRY
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -79,59 +83,21 @@ GLAPI int gladLoadGLLoader(GLADloadproc);
|
||||
typedef void (* GLADcallback)(const char *name, void *funcptr, int len_args, ...);
|
||||
GLAPI void glad_set_pre_callback(GLADcallback cb);
|
||||
GLAPI void glad_set_post_callback(GLADcallback cb);
|
||||
#include <stddef.h>
|
||||
#include "khrplatform.h"
|
||||
#ifndef GLEXT_64_TYPES_DEFINED
|
||||
/* This code block is duplicated in glxext.h, so must be protected */
|
||||
#define GLEXT_64_TYPES_DEFINED
|
||||
/* Define int32_t, int64_t, and uint64_t types for UST/MSC */
|
||||
/* (as used in the GL_EXT_timer_query extension). */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||
#include <inttypes.h>
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
#include <inttypes.h>
|
||||
#if defined(__STDC__)
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int int64_t;
|
||||
typedef unsigned long int uint64_t;
|
||||
#else
|
||||
typedef long long int int64_t;
|
||||
typedef unsigned long long int uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#endif /* __STDC__ */
|
||||
#elif defined( __VMS ) || defined(__sgi)
|
||||
#include <inttypes.h>
|
||||
#elif defined(__SCO__) || defined(__USLC__)
|
||||
#include <stdint.h>
|
||||
#elif defined(__UNIXOS2__) || defined(__SOL64__)
|
||||
typedef long int int32_t;
|
||||
typedef long long int int64_t;
|
||||
typedef unsigned long long int uint64_t;
|
||||
#elif defined(_WIN32) && defined(__GNUC__)
|
||||
#include <stdint.h>
|
||||
#elif defined(_WIN32)
|
||||
typedef __int32 int32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
/* Fallback if nothing above works */
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
#endif
|
||||
typedef unsigned int GLenum;
|
||||
typedef unsigned char GLboolean;
|
||||
typedef unsigned int GLbitfield;
|
||||
typedef void GLvoid;
|
||||
typedef signed char GLbyte;
|
||||
typedef short GLshort;
|
||||
typedef khronos_int8_t GLbyte;
|
||||
typedef khronos_uint8_t GLubyte;
|
||||
typedef khronos_int16_t GLshort;
|
||||
typedef khronos_uint16_t GLushort;
|
||||
typedef int GLint;
|
||||
typedef int GLclampx;
|
||||
typedef unsigned char GLubyte;
|
||||
typedef unsigned short GLushort;
|
||||
typedef unsigned int GLuint;
|
||||
typedef khronos_int32_t GLclampx;
|
||||
typedef int GLsizei;
|
||||
typedef float GLfloat;
|
||||
typedef float GLclampf;
|
||||
typedef khronos_float_t GLfloat;
|
||||
typedef khronos_float_t GLclampf;
|
||||
typedef double GLdouble;
|
||||
typedef double GLclampd;
|
||||
typedef void *GLeglClientBufferEXT;
|
||||
@@ -143,25 +109,17 @@ typedef void *GLhandleARB;
|
||||
#else
|
||||
typedef unsigned int GLhandleARB;
|
||||
#endif
|
||||
typedef unsigned short GLhalfARB;
|
||||
typedef unsigned short GLhalf;
|
||||
typedef GLint GLfixed;
|
||||
typedef khronos_uint16_t GLhalf;
|
||||
typedef khronos_uint16_t GLhalfARB;
|
||||
typedef khronos_int32_t GLfixed;
|
||||
typedef khronos_intptr_t GLintptr;
|
||||
typedef khronos_intptr_t GLintptrARB;
|
||||
typedef khronos_ssize_t GLsizeiptr;
|
||||
typedef int64_t GLint64;
|
||||
typedef uint64_t GLuint64;
|
||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
||||
typedef long GLintptrARB;
|
||||
#else
|
||||
typedef ptrdiff_t GLintptrARB;
|
||||
#endif
|
||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
||||
typedef long GLsizeiptrARB;
|
||||
#else
|
||||
typedef ptrdiff_t GLsizeiptrARB;
|
||||
#endif
|
||||
typedef int64_t GLint64EXT;
|
||||
typedef uint64_t GLuint64EXT;
|
||||
typedef khronos_ssize_t GLsizeiptrARB;
|
||||
typedef khronos_int64_t GLint64;
|
||||
typedef khronos_int64_t GLint64EXT;
|
||||
typedef khronos_uint64_t GLuint64;
|
||||
typedef khronos_uint64_t GLuint64EXT;
|
||||
typedef struct __GLsync *GLsync;
|
||||
struct _cl_context;
|
||||
struct _cl_event;
|
||||
@@ -814,7 +772,6 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
|
||||
#define GL_MAX_SAMPLES 0x8D57
|
||||
#define GL_INDEX 0x8222
|
||||
#define GL_FRAMEBUFFER_SRGB 0x8DB9
|
||||
#define GL_HALF_FLOAT 0x140B
|
||||
#define GL_MAP_READ_BIT 0x0001
|
||||
|
||||
11
kitty/glfw-wrapper.c
generated
11
kitty/glfw-wrapper.c
generated
@@ -131,6 +131,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwGetWindowContentScale_impl) = dlsym(handle, "glfwGetWindowContentScale");
|
||||
if (glfwGetWindowContentScale_impl == NULL) fail("Failed to load glfw function glfwGetWindowContentScale with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetDoubleClickInterval_impl) = dlsym(handle, "glfwGetDoubleClickInterval");
|
||||
if (glfwGetDoubleClickInterval_impl == NULL) fail("Failed to load glfw function glfwGetDoubleClickInterval with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetWindowOpacity_impl) = dlsym(handle, "glfwGetWindowOpacity");
|
||||
if (glfwGetWindowOpacity_impl == NULL) fail("Failed to load glfw function glfwGetWindowOpacity with error: %s", dlerror());
|
||||
|
||||
@@ -358,6 +361,8 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow");
|
||||
|
||||
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
|
||||
|
||||
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
|
||||
|
||||
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||
@@ -372,12 +377,14 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window");
|
||||
|
||||
*(void **) (&glfwSetX11SelectionString_impl) = dlsym(handle, "glfwSetX11SelectionString");
|
||||
*(void **) (&glfwSetPrimarySelectionString_impl) = dlsym(handle, "glfwSetPrimarySelectionString");
|
||||
|
||||
*(void **) (&glfwGetX11SelectionString_impl) = dlsym(handle, "glfwGetX11SelectionString");
|
||||
*(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString");
|
||||
|
||||
*(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode");
|
||||
|
||||
*(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
26
kitty/glfw-wrapper.h
generated
26
kitty/glfw-wrapper.h
generated
@@ -1387,7 +1387,7 @@ typedef struct GLFWgamepadstate
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
|
||||
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||
typedef int (*glfwInit_func)();
|
||||
glfwInit_func glfwInit_impl;
|
||||
#define glfwInit glfwInit_impl
|
||||
@@ -1544,6 +1544,10 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*);
|
||||
glfwGetWindowContentScale_func glfwGetWindowContentScale_impl;
|
||||
#define glfwGetWindowContentScale glfwGetWindowContentScale_impl
|
||||
|
||||
typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
|
||||
glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl;
|
||||
#define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl
|
||||
|
||||
typedef float (*glfwGetWindowOpacity_func)(GLFWwindow*);
|
||||
glfwGetWindowOpacity_func glfwGetWindowOpacity_impl;
|
||||
#define glfwGetWindowOpacity glfwGetWindowOpacity_impl
|
||||
@@ -1848,6 +1852,10 @@ typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*);
|
||||
glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
|
||||
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
|
||||
|
||||
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
|
||||
glfwGetNSGLContext_func glfwGetNSGLContext_impl;
|
||||
#define glfwGetNSGLContext glfwGetNSGLContext_impl
|
||||
|
||||
typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
|
||||
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
|
||||
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
|
||||
@@ -1876,16 +1884,20 @@ typedef int32_t (*glfwGetX11Window_func)(GLFWwindow*);
|
||||
glfwGetX11Window_func glfwGetX11Window_impl;
|
||||
#define glfwGetX11Window glfwGetX11Window_impl
|
||||
|
||||
typedef void (*glfwSetX11SelectionString_func)(const char*);
|
||||
glfwSetX11SelectionString_func glfwSetX11SelectionString_impl;
|
||||
#define glfwSetX11SelectionString glfwSetX11SelectionString_impl
|
||||
typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*);
|
||||
glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl;
|
||||
#define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl
|
||||
|
||||
typedef const char* (*glfwGetX11SelectionString_func)();
|
||||
glfwGetX11SelectionString_func glfwGetX11SelectionString_impl;
|
||||
#define glfwGetX11SelectionString glfwGetX11SelectionString_impl
|
||||
typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*);
|
||||
glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl;
|
||||
#define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl
|
||||
|
||||
typedef int (*glfwGetXKBScancode_func)(const char*, int);
|
||||
glfwGetXKBScancode_func glfwGetXKBScancode_impl;
|
||||
#define glfwGetXKBScancode glfwGetXKBScancode_impl
|
||||
|
||||
typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc);
|
||||
glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;
|
||||
#define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
|
||||
74
kitty/glfw.c
74
kitty/glfw.c
@@ -15,6 +15,7 @@ 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);
|
||||
|
||||
|
||||
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
|
||||
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
|
||||
#endif
|
||||
@@ -26,9 +27,13 @@ static void set_os_window_dpi(OSWindow *w);
|
||||
|
||||
void
|
||||
update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
int w, h;
|
||||
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
|
||||
int w, h, fw, fh;
|
||||
glfwGetFramebufferSize(window->handle, &fw, &fh);
|
||||
glfwGetWindowSize(window->handle, &w, &h);
|
||||
if (fw == window->viewport_width && fh == window->viewport_height && w == window->window_width && h == window->window_height) {
|
||||
return; // no change, ignore
|
||||
}
|
||||
window->viewport_width = fw; window->viewport_height = fh;
|
||||
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
|
||||
window->viewport_x_ratio = (double)window->viewport_width / (double)w;
|
||||
window->viewport_y_ratio = (double)window->viewport_height / (double)h;
|
||||
@@ -44,6 +49,11 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
window->window_height = MAX(h, 100);
|
||||
if (notify_boss) {
|
||||
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
|
||||
if (dpi_changed && global_state.is_wayland) {
|
||||
// Fake resize event needed on weston to ensure surface is
|
||||
// positioned correctly after DPI change
|
||||
glfwSetWindowSize(window->handle, window->window_width, window->window_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +310,7 @@ make_os_window_context_current(OSWindow *w) {
|
||||
}
|
||||
|
||||
|
||||
#ifndef __APPLE__
|
||||
static GLFWmonitor*
|
||||
current_monitor(GLFWwindow *window) {
|
||||
// Find the monitor that has the maximum overlap with this window
|
||||
@@ -334,14 +345,15 @@ current_monitor(GLFWwindow *window) {
|
||||
|
||||
return bestmonitor;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
||||
GLFWmonitor *monitor = NULL;
|
||||
if (w) monitor = current_monitor(w);
|
||||
if (monitor == NULL) { PyErr_Print(); monitor = glfwGetPrimaryMonitor(); }
|
||||
float xscale = 1, yscale = 1;
|
||||
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
if (w) glfwGetWindowContentScale(w, &xscale, &yscale);
|
||||
else glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale);
|
||||
if (!xscale) xscale = 1.0;
|
||||
if (!yscale) yscale = 1.0;
|
||||
#ifdef __APPLE__
|
||||
double factor = 72.0;
|
||||
#else
|
||||
@@ -463,9 +475,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHint(GLFW_DEPTH_BITS, 0);
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 0);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_hide_titlebar)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
|
||||
glfwSetApplicationShouldHandleReopen(on_application_reopen);
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -474,9 +486,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);
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||
if (OPT(x11_hide_window_decorations)) {
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
}
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
if (global_state.num_os_windows >= MAX_CHILDREN) {
|
||||
@@ -541,6 +551,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
}}
|
||||
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
|
||||
#undef CC
|
||||
if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window);
|
||||
is_first_window = false;
|
||||
}
|
||||
OSWindow *w = add_os_window();
|
||||
@@ -555,7 +566,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
w->fonts_data = fonts_data;
|
||||
w->shown_once = true;
|
||||
push_focus_history(w);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
||||
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
|
||||
@@ -806,9 +817,15 @@ hide_mouse(OSWindow *w) {
|
||||
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
}
|
||||
|
||||
bool
|
||||
is_mouse_hidden(OSWindow *w) {
|
||||
return w->handle && glfwGetInputMode(w->handle, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
swap_window_buffers(OSWindow *w) {
|
||||
glfwSwapBuffers(w->handle);
|
||||
swap_window_buffers(OSWindow *os_window) {
|
||||
glfwSwapBuffers(os_window->handle);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -878,9 +895,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
|
||||
static PyObject*
|
||||
get_primary_selection(PYNOARG) {
|
||||
if (glfwGetX11SelectionString) {
|
||||
return Py_BuildValue("y", glfwGetX11SelectionString());
|
||||
} else log_error("Failed to load glfwGetX11SelectionString");
|
||||
if (glfwGetPrimarySelectionString) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle));
|
||||
} else log_error("Failed to load glfwGetPrimarySelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -888,8 +906,11 @@ static PyObject*
|
||||
set_primary_selection(PyObject UNUSED *self, PyObject *args) {
|
||||
char *text;
|
||||
if (!PyArg_ParseTuple(args, "s", &text)) return NULL;
|
||||
if (glfwSetX11SelectionString) glfwSetX11SelectionString(text);
|
||||
else log_error("Failed to load glfwSetX11SelectionString");
|
||||
if (glfwSetPrimarySelectionString) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) glfwSetPrimarySelectionString(w->handle, text);
|
||||
}
|
||||
else log_error("Failed to load glfwSetPrimarySelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -972,6 +993,23 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
|
||||
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
wayland_frame_request_callback(id_type os_window_id) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
if (global_state.os_windows[i].id == os_window_id) {
|
||||
global_state.os_windows[i].wayland_render_state = RENDER_FRAME_READY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wayland_request_frame_render(OSWindow *w) {
|
||||
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
|
||||
w->wayland_render_state = RENDER_FRAME_REQUESTED;
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
|
||||
169
kitty/history.c
169
kitty/history.c
@@ -54,12 +54,35 @@ attrptr(HistoryBuf *self, index_type y) {
|
||||
seg_ptr(line_attrs, 1);
|
||||
}
|
||||
|
||||
static inline PagerHistoryBuf*
|
||||
alloc_pagerhist(unsigned int pagerhist_sz) {
|
||||
PagerHistoryBuf *ph;
|
||||
if (!pagerhist_sz) return NULL;
|
||||
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
|
||||
ph->maxsz = pagerhist_sz / sizeof(Py_UCS4);
|
||||
ph->bufsize = 1024*1024 / sizeof(Py_UCS4);
|
||||
ph->buffer = PyMem_RawMalloc(1024*1024);
|
||||
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
||||
return ph;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||
if (ph->bufsize >= ph->maxsz) return false;
|
||||
size_t newsz = ph->bufsize + MAX(1024 * 1024, minsz);
|
||||
void *newbuf = PyMem_Realloc(ph->buffer, newsz * sizeof(Py_UCS4));
|
||||
if (!newbuf) return false;
|
||||
ph->buffer = newbuf;
|
||||
ph->bufsize = newsz;
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
HistoryBuf *self;
|
||||
unsigned int xnum = 1, ynum = 1;
|
||||
unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL;
|
||||
|
||||
if (xnum == 0 || ynum == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
|
||||
@@ -74,6 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
add_segment(self);
|
||||
self->line = alloc_line();
|
||||
self->line->xnum = xnum;
|
||||
self->pagerhist = alloc_pagerhist(pagerhist_sz);
|
||||
}
|
||||
|
||||
return (PyObject*)self;
|
||||
@@ -88,6 +112,8 @@ dealloc(HistoryBuf* self) {
|
||||
PyMem_Free(self->segments[i].line_attrs);
|
||||
}
|
||||
PyMem_Free(self->segments);
|
||||
if (self->pagerhist) PyMem_Free(self->pagerhist->buffer);
|
||||
PyMem_Free(self->pagerhist);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
@@ -132,12 +158,50 @@ historybuf_clear(HistoryBuf *self) {
|
||||
self->start_of_data = 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pagerhist_push(HistoryBuf *self) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
if (!ph) return;
|
||||
bool truncated;
|
||||
Line l = {.xnum=self->xnum};
|
||||
init_line(self, self->start_of_data, &l);
|
||||
#define EXPAND_IF_FULL(sz) { \
|
||||
if (ph->bufsize - ph->end < sz && !pagerhist_extend(ph, sz)) { \
|
||||
ph->bufend = ph->end; ph->end = 0; \
|
||||
} \
|
||||
}
|
||||
size_t sz = MAX(1024, ph->bufsize - ph->end);
|
||||
sz = MAX(sz, self->xnum + self->xnum);
|
||||
EXPAND_IF_FULL(sz);
|
||||
if (ph->start != ph->end && !l.continued) {
|
||||
ph->buffer[ph->end++] = '\n';
|
||||
}
|
||||
while(sz < ph->maxsz - 2) {
|
||||
size_t num = line_as_ansi(&l, ph->buffer + ph->end, ph->bufsize - ph->end - 2, &truncated);
|
||||
if (!truncated) {
|
||||
ph->end += num;
|
||||
ph->buffer[ph->end++] = '\r';
|
||||
if (ph->bufend) {
|
||||
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// check if sz is too large too fit in buffer
|
||||
if (ph->bufsize > ph->maxsz && !ph->end) break;
|
||||
sz *= 2;
|
||||
EXPAND_IF_FULL(sz);
|
||||
}
|
||||
#undef EXPAND_IF_FULL
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
historybuf_push(HistoryBuf *self) {
|
||||
index_type idx = (self->start_of_data + self->count) % self->ynum;
|
||||
init_line(self, idx, self->line);
|
||||
if (self->count == self->ynum) self->start_of_data = (self->start_of_data + 1) % self->ynum;
|
||||
else self->count++;
|
||||
if (self->count == self->ynum) {
|
||||
pagerhist_push(self);
|
||||
self->start_of_data = (self->start_of_data + 1) % self->ynum;
|
||||
} else self->count++;
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -189,12 +253,13 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
|
||||
static Py_UCS4 t[5120];
|
||||
Line l = {.xnum=self->xnum};
|
||||
bool truncated;
|
||||
for(unsigned int i = 0; i < self->count; i++) {
|
||||
init_line(self, i, &l);
|
||||
if (i < self->count - 1) {
|
||||
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
|
||||
} else l.continued = false;
|
||||
index_type num = line_as_ansi(&l, t, 5120);
|
||||
index_type num = line_as_ansi(&l, t, 5120, &truncated);
|
||||
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
@@ -209,6 +274,93 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
static inline Line*
|
||||
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
|
||||
|
||||
static void
|
||||
pagerhist_rewrap(PagerHistoryBuf *ph, index_type xnum) {
|
||||
Py_UCS4 *buf = PyMem_RawMalloc(ph->bufsize * sizeof(Py_UCS4));
|
||||
if (!buf) return;
|
||||
index_type s = ph->start, i = s, dest = 0, dest_bufend = 0, x = 0;
|
||||
index_type end = ph->bufend ? ph->bufend : ph->end;
|
||||
index_type lastmod_s = 0, lastmod_len = 0;
|
||||
#define CPY(_s, _l) { if (dest + (_l) >= ph->bufsize - 1) { dest_bufend = dest; dest = 0; } \
|
||||
memcpy(buf + dest, ph->buffer + (_s), (_l) * sizeof(Py_UCS4)); dest += (_l); }
|
||||
while (i < end) {
|
||||
switch (ph->buffer[i]) {
|
||||
case '\n':
|
||||
CPY(s, i - s + 1);
|
||||
x = 0; s = i + 1; lastmod_len = 0;
|
||||
break;
|
||||
case '\r':
|
||||
CPY(s, i - s);
|
||||
if (!memcmp(ph->buffer + lastmod_s, ph->buffer + i + 1, lastmod_len * sizeof(Py_UCS4)))
|
||||
i += lastmod_len;
|
||||
s = i + 1;
|
||||
break;
|
||||
case '\x1b':
|
||||
if (ph->buffer[i+1] != '[') break;
|
||||
lastmod_s = i;
|
||||
while (ph->buffer[++i] != 'm');
|
||||
lastmod_len = i - lastmod_s + 1;
|
||||
break;
|
||||
default:
|
||||
x++; break;
|
||||
}
|
||||
i++;
|
||||
if (ph->bufend && i == ph->bufend) {
|
||||
if (s != i) CPY(s, i - s);
|
||||
end = ph->end; i = s = 0;
|
||||
}
|
||||
if (x == xnum) {
|
||||
CPY(s, i - s); buf[dest++] = '\r'; s = i; x = 0;
|
||||
if (!(ph->buffer[i] == '\x1b' && ph->buffer[i+1] == '[') && lastmod_len)
|
||||
CPY(lastmod_s, lastmod_len);
|
||||
}
|
||||
}
|
||||
#undef CPY
|
||||
PyMem_Free(ph->buffer);
|
||||
ph->buffer = buf;
|
||||
ph->end = dest; ph->bufend = dest_bufend;
|
||||
ph->start = dest_bufend ? dest + 1 : 0;
|
||||
ph->rewrap_needed = false;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pagerhist_as_text(HistoryBuf *self, PyObject *callback) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
PyObject *ret = NULL, *t = NULL;
|
||||
Py_UCS4 *buf = NULL;
|
||||
index_type num;
|
||||
if (!ph) Py_RETURN_NONE;
|
||||
|
||||
if (ph->rewrap_needed) pagerhist_rewrap(ph, self->xnum);
|
||||
|
||||
#define CALLBACK { \
|
||||
if (t == NULL) goto end; \
|
||||
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \
|
||||
Py_DECREF(t); \
|
||||
if (ret == NULL) goto end; \
|
||||
Py_DECREF(ret); \
|
||||
}
|
||||
|
||||
num = (ph->bufend ? ph->bufend : ph->end) - ph->start;
|
||||
buf = ph->buffer + ph->start;
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
|
||||
CALLBACK;
|
||||
if (ph->bufend) {
|
||||
num = ph->end; buf = ph->buffer;
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
|
||||
CALLBACK;
|
||||
}
|
||||
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
|
||||
if (!l.continued) {
|
||||
t = PyUnicode_FromString("\n");
|
||||
CALLBACK;
|
||||
}
|
||||
#undef CALLBACK
|
||||
end:
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_text(HistoryBuf *self, PyObject *args) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
@@ -238,6 +390,7 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
|
||||
static PyMethodDef methods[] = {
|
||||
METHOD(line, METH_O)
|
||||
METHOD(as_ansi, METH_O)
|
||||
METHODB(pagerhist_as_text, METH_O),
|
||||
METHODB(as_text, METH_VARARGS),
|
||||
METHOD(dirty_lines, METH_NOARGS)
|
||||
METHOD(push, METH_VARARGS)
|
||||
@@ -267,8 +420,8 @@ PyTypeObject HistoryBuf_Type = {
|
||||
|
||||
INIT_TYPE(HistoryBuf)
|
||||
|
||||
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
|
||||
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("II", lines, columns), NULL);
|
||||
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz) {
|
||||
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("III", lines, columns, pagerhist_sz), NULL);
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -298,6 +451,8 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
|
||||
other->count = self->count; other->start_of_data = self->start_of_data;
|
||||
return;
|
||||
}
|
||||
if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->end != other->pagerhist->start)
|
||||
other->pagerhist->rewrap_needed = true;
|
||||
other->count = 0; other->start_of_data = 0;
|
||||
index_type x = 0, y = 0;
|
||||
if (self->count > 0) {
|
||||
|
||||
480
kitty/keys.h
generated
480
kitty/keys.h
generated
File diff suppressed because it is too large
Load Diff
@@ -17,11 +17,13 @@ def modify_key_bytes(keybytes, amt):
|
||||
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
|
||||
if ans[1] == ord('O'):
|
||||
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
|
||||
raise ValueError('Unknown key type')
|
||||
raise ValueError('Unknown key type in key: {!r}'.format(keybytes))
|
||||
|
||||
|
||||
def modify_complex_key(name, amt):
|
||||
return modify_key_bytes(key_as_bytes(name), amt)
|
||||
if not isinstance(name, bytes):
|
||||
name = key_as_bytes(name)
|
||||
return modify_key_bytes(name, amt)
|
||||
|
||||
|
||||
control_codes = {
|
||||
@@ -39,6 +41,7 @@ shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt')
|
||||
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
|
||||
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
|
||||
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
|
||||
ctrl_alt_shift_mod = ctrl_alt_mod | defines.GLFW_MOD_SHIFT
|
||||
SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
|
||||
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
|
||||
@@ -54,6 +57,32 @@ SHIFTED_KEYS = {
|
||||
control_alt_codes = {
|
||||
defines.GLFW_KEY_SPACE: b'\x1b\0',
|
||||
}
|
||||
control_alt_shift_codes = {}
|
||||
ASCII_C0_SHIFTED = {
|
||||
# ^@
|
||||
'2': b'\x00',
|
||||
# ^^
|
||||
'6': b'\x1e',
|
||||
# ^_
|
||||
'MINUS': b'\x1f',
|
||||
# ^?
|
||||
'SLASH': b'\x7f',
|
||||
}
|
||||
control_shift_keys = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
|
||||
|
||||
|
||||
def create_modifier_variants(keycode, terminfo_name_or_bytes, add_shifted_key=True):
|
||||
kn = terminfo_name_or_bytes
|
||||
smkx_key_map[keycode] = kn if isinstance(kn, bytes) else key_as_bytes(kn)
|
||||
if add_shifted_key:
|
||||
SHIFTED_KEYS[keycode] = modify_complex_key(kn, 2)
|
||||
alt_codes[keycode] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[keycode] = modify_complex_key(kn, 4)
|
||||
control_codes[keycode] = modify_complex_key(kn, 5)
|
||||
control_shift_keys[keycode] = modify_complex_key(kn, 6)
|
||||
control_alt_codes[keycode] = modify_complex_key(kn, 7)
|
||||
control_alt_shift_codes[keycode] = modify_complex_key(kn, 8)
|
||||
|
||||
|
||||
for kf, kn in {
|
||||
defines.GLFW_KEY_UP: 'kcuu1',
|
||||
@@ -67,24 +96,16 @@ for kf, kn in {
|
||||
defines.GLFW_KEY_PAGE_UP: 'kpp',
|
||||
defines.GLFW_KEY_PAGE_DOWN: 'knp',
|
||||
}.items():
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
create_modifier_variants(kf, kn, add_shifted_key=False)
|
||||
for f in range(1, 13):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
create_modifier_variants(kf, kn)
|
||||
for f in range(13, 26):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
create_modifier_variants(defines.GLFW_KEY_MENU, b'\x1b[29~')
|
||||
f = {k: k for k in '0123456789'}
|
||||
f.update({
|
||||
'COMMA': ',',
|
||||
@@ -215,17 +236,6 @@ SHIFTED_PRINTABLE.update(pmap(
|
||||
"{|}~"
|
||||
))
|
||||
|
||||
ASCII_C0_SHIFTED = {
|
||||
# ^@
|
||||
'2': b'\x00',
|
||||
# ^^
|
||||
'6': b'\x1e',
|
||||
# ^_
|
||||
'MINUS': b'\x1f',
|
||||
# ^?
|
||||
'SLASH': b'\x7f',
|
||||
}
|
||||
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
|
||||
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
|
||||
all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes)
|
||||
|
||||
@@ -237,8 +247,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
|
||||
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
|
||||
# Map Ctrl-key to ascii control code
|
||||
data.extend(control_codes[key])
|
||||
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
|
||||
data.extend(CTRL_SHIFT_KEYS[key])
|
||||
elif mods == ctrl_shift_mod and key in control_shift_keys:
|
||||
data.extend(control_shift_keys[key])
|
||||
elif mods in alt_mods:
|
||||
if key in alt_codes:
|
||||
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
|
||||
@@ -251,6 +261,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
|
||||
data.append(0x1b), data.extend(control_codes[key])
|
||||
else:
|
||||
data.extend(control_alt_codes[key])
|
||||
elif mods == ctrl_alt_shift_mod and key in control_alt_shift_codes:
|
||||
data.extend(control_alt_shift_codes[key])
|
||||
else:
|
||||
key_map = cursor_key_mode_map[smkx]
|
||||
x = key_map.get(key)
|
||||
|
||||
@@ -721,7 +721,7 @@ class Grid(Layout): # {{{
|
||||
def neighbors_for_window(self, window, windows):
|
||||
n = len(windows)
|
||||
if n < 4:
|
||||
return Tall.neighbors_for_window(window, windows)
|
||||
return Tall.neighbors_for_window(self, window, windows)
|
||||
try:
|
||||
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
|
||||
except Exception:
|
||||
|
||||
@@ -397,16 +397,17 @@ as_ansi(LineBuf *self, PyObject *callback) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
// remove trailing empty lines
|
||||
index_type ylimit = self->ynum - 1;
|
||||
bool truncated;
|
||||
do {
|
||||
init_line(self, (&l), self->line_map[ylimit]);
|
||||
if (line_as_ansi(&l, t, 5120) != 0) break;
|
||||
if (line_as_ansi(&l, t, 5120, &truncated) != 0) break;
|
||||
ylimit--;
|
||||
} while(ylimit > 0);
|
||||
|
||||
for(index_type i = 0; i <= ylimit; i++) {
|
||||
l.continued = ((i < self->ynum - 1) ? self->line_attrs[i+1] : self->line_attrs[i]) & CONTINUED_MASK;
|
||||
init_line(self, (&l), self->line_map[i]);
|
||||
index_type num = line_as_ansi(&l, t, 5120);
|
||||
index_type num = line_as_ansi(&l, t, 5120, &truncated);
|
||||
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
|
||||
117
kitty/line.c
117
kitty/line.c
@@ -50,18 +50,25 @@ cell_text(CPUCell *cell) {
|
||||
|
||||
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
|
||||
static size_t url_prefix_lengths[sizeof(url_prefixes)/sizeof(url_prefixes[0])] = {0};
|
||||
typedef enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH} URL_PARSER_STATE;
|
||||
|
||||
static inline index_type
|
||||
find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
// Find :// at or before x
|
||||
index_type pos = x;
|
||||
URL_PARSER_STATE state = ANY;
|
||||
enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH};
|
||||
enum URL_PARSER_STATES state = ANY;
|
||||
limit = MAX(2, limit);
|
||||
if (pos < limit) return 0;
|
||||
do {
|
||||
char_type ch = self->cpu_cells[pos].ch;
|
||||
if (!is_url_char(ch)) return false;
|
||||
if (pos == x) {
|
||||
if (ch == ':') {
|
||||
if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH;
|
||||
} else if (ch == '/') {
|
||||
if (pos + 1 < self->xnum && self->cpu_cells[pos+1].ch == '/') state = FIRST_SLASH;
|
||||
}
|
||||
}
|
||||
switch(state) {
|
||||
case ANY:
|
||||
if (ch == '/') state = FIRST_SLASH;
|
||||
@@ -227,50 +234,53 @@ write_sgr(const char *val, Py_UCS4 *buf, index_type buflen, index_type *i) {
|
||||
}
|
||||
|
||||
index_type
|
||||
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
|
||||
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) return i; }
|
||||
#define WRITE_CH(val) if (i > buflen - 1) return i; buf[i++] = val;
|
||||
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated) {
|
||||
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) { *truncated = true; return i; } }
|
||||
#define WRITE_CH(val) if (i > buflen - 1) { *truncated = true; return i; } buf[i++] = val;
|
||||
|
||||
index_type limit = xlimit_for_line(self), i=0;
|
||||
*truncated = false;
|
||||
if (limit == 0) return 0;
|
||||
char_type previous_width = 0;
|
||||
|
||||
WRITE_SGR("0");
|
||||
Cursor c1 = {{0}}, c2 = {{0}};
|
||||
Cursor *cursor = &c1, *prev_cursor = &c2, *t;
|
||||
GPUCell blank_cell = { 0 };
|
||||
GPUCell *cell, *prev_cell = &blank_cell;
|
||||
|
||||
for (index_type pos=0; pos < limit; pos++) {
|
||||
char_type attrs = self->gpu_cells[pos].attrs, ch = self->cpu_cells[pos].ch;
|
||||
char_type ch = self->cpu_cells[pos].ch;
|
||||
if (ch == 0) {
|
||||
if (previous_width == 2) { previous_width = 0; continue; }
|
||||
ch = ' ';
|
||||
}
|
||||
ATTRS_TO_CURSOR(attrs, cursor);
|
||||
cursor->fg = self->gpu_cells[pos].fg; cursor->bg = self->gpu_cells[pos].bg;
|
||||
cursor->decoration_fg = self->gpu_cells[pos].decoration_fg & COL_MASK;
|
||||
|
||||
const char *sgr = cursor_as_sgr(cursor, prev_cursor);
|
||||
t = prev_cursor; prev_cursor = cursor; cursor = t;
|
||||
if (*sgr) WRITE_SGR(sgr);
|
||||
cell = &self->gpu_cells[pos];
|
||||
|
||||
#define CMP_ATTRS (cell->attrs & ATTRS_MASK_WITHOUT_WIDTH) != (prev_cell->attrs & ATTRS_MASK_WITHOUT_WIDTH)
|
||||
#define CMP(x) cell->x != prev_cell->x
|
||||
if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) {
|
||||
const char *sgr = cell_as_sgr(cell, prev_cell);
|
||||
if (*sgr) WRITE_SGR(sgr);
|
||||
}
|
||||
prev_cell = cell;
|
||||
WRITE_CH(ch);
|
||||
for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) {
|
||||
WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c]));
|
||||
}
|
||||
previous_width = attrs & WIDTH_MASK;
|
||||
previous_width = cell->attrs & WIDTH_MASK;
|
||||
}
|
||||
return i;
|
||||
#undef CHECK_BOOL
|
||||
#undef CHECK_COLOR
|
||||
#undef CMP_ATTRS
|
||||
#undef CMP
|
||||
#undef WRITE_SGR
|
||||
#undef WRITE_CH
|
||||
#undef WRITE_COLOR
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_ansi(Line* self, PyObject *a UNUSED) {
|
||||
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
|
||||
static Py_UCS4 t[5120] = {0};
|
||||
index_type num = line_as_ansi(self, t, 5120);
|
||||
bool truncated;
|
||||
index_type num = line_as_ansi(self, t, 5120, &truncated);
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
return ans;
|
||||
}
|
||||
@@ -527,6 +537,73 @@ set_attribute(Line *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline int
|
||||
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
|
||||
switch(val & 0xff) {
|
||||
case 1:
|
||||
val >>= 8;
|
||||
if (val < 16 && simple_code) {
|
||||
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
|
||||
}
|
||||
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
|
||||
case 2:
|
||||
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
|
||||
default:
|
||||
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
decoration_as_sgr(uint8_t decoration) {
|
||||
switch(decoration) {
|
||||
case 1: return "4;";
|
||||
case 2: return "4:2;";
|
||||
case 3: return "4:3;";
|
||||
default: return "24;";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
cell_as_sgr(GPUCell *cell, GPUCell *prev) {
|
||||
static char buf[128];
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
#define P(s) { size_t len = strlen(s); if (SZ > len) { memcpy(p, s, len); p += len; } }
|
||||
char *p = buf;
|
||||
#define CMP(attr) (attr(cell) != attr(prev))
|
||||
#define BOLD(cell) (cell->attrs & (1 << BOLD_SHIFT))
|
||||
#define DIM(cell) (cell->attrs & (1 << DIM_SHIFT))
|
||||
#define ITALIC(cell) (cell->attrs & (1 << ITALIC_SHIFT))
|
||||
#define REVERSE(cell) (cell->attrs & (1 << REVERSE_SHIFT))
|
||||
#define STRIKETHROUGH(cell) (cell->attrs & (1 << STRIKE_SHIFT))
|
||||
#define DECORATION(cell) (cell->attrs & (DECORATION_MASK << DECORATION_SHIFT))
|
||||
bool intensity_differs = CMP(BOLD) || CMP(DIM);
|
||||
if (intensity_differs) {
|
||||
if (!BOLD(cell) && !DIM(cell)) { P("22;"); }
|
||||
else { if (BOLD(cell)) P("1;"); if (DIM(cell)) P("2;"); }
|
||||
}
|
||||
if (CMP(ITALIC)) P(ITALIC(cell) ? "3;" : "23;");
|
||||
if (CMP(REVERSE)) P(REVERSE(cell) ? "7;" : "27;");
|
||||
if (CMP(STRIKETHROUGH)) P(STRIKETHROUGH(cell) ? "9;" : "29;");
|
||||
if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38);
|
||||
if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48);
|
||||
if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE);
|
||||
if (CMP(DECORATION)) P(decoration_as_sgr((cell->attrs >> DECORATION_SHIFT) & DECORATION_MASK));
|
||||
#undef CMP
|
||||
#undef BOLD
|
||||
#undef DIM
|
||||
#undef ITALIC
|
||||
#undef REVERSE
|
||||
#undef STRIKETHROUGH
|
||||
#undef DECORATION
|
||||
#undef P
|
||||
#undef SZ
|
||||
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
|
||||
*p = 0; // ensure string is null-terminated
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
static Py_ssize_t
|
||||
__len__(PyObject *self) {
|
||||
return (Py_ssize_t)(((Line*)self)->xnum);
|
||||
|
||||
@@ -37,6 +37,7 @@ xlimit_for_line(Line *line) {
|
||||
index_type xlimit = line->xnum;
|
||||
if (BLANK_CHAR == 0) {
|
||||
while (xlimit > 0 && (line->cpu_cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--;
|
||||
if (xlimit < line->xnum && (line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs & WIDTH_MASK) == 2) xlimit++;
|
||||
}
|
||||
return xlimit;
|
||||
}
|
||||
@@ -60,7 +61,7 @@ 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, char_type);
|
||||
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
|
||||
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool*);
|
||||
unsigned int line_length(Line *self);
|
||||
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
|
||||
size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type);
|
||||
@@ -110,7 +111,8 @@ void historybuf_clear(HistoryBuf *self);
|
||||
Py_CLEAR(ret); \
|
||||
} \
|
||||
if (as_ansi) { \
|
||||
index_type num = line_as_ansi(line, buf, columns * 100 - 2); \
|
||||
bool truncated; \
|
||||
index_type num = line_as_ansi(line, buf, columns * 100 - 2, &truncated); \
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); \
|
||||
} else { \
|
||||
t = line_as_unicode(line); \
|
||||
|
||||
@@ -85,7 +85,7 @@ encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_proto
|
||||
|
||||
static int
|
||||
encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
|
||||
unsigned int x = w->mouse_cell_x + 1, y = w->mouse_cell_y + 1; // 1 based indexing
|
||||
unsigned int x = w->mouse_pos.cell_x + 1, y = w->mouse_pos.cell_y + 1; // 1 based indexing
|
||||
Screen *screen = w->render_data.screen;
|
||||
return encode_mouse_event_impl(x, y, screen->modes.mouse_tracking_protocol, button, action, mods);
|
||||
|
||||
@@ -142,6 +142,7 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
|
||||
mouse_x = MIN(MAX(mouse_x, left), right);
|
||||
mouse_y = MIN(MAX(mouse_y, top), bottom);
|
||||
}
|
||||
w->mouse_pos.x = mouse_x - left; w->mouse_pos.y = mouse_y - top;
|
||||
if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
|
||||
if (mouse_x >= g->right) qx = screen->columns - 1;
|
||||
else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / os_window->fonts_data->cell_width);
|
||||
@@ -164,14 +165,14 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
||||
global_state.active_drag_in_window = 0;
|
||||
w->last_drag_scroll_at = 0;
|
||||
if (screen->selection.in_progress)
|
||||
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
|
||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, true);
|
||||
}
|
||||
else {
|
||||
global_state.active_drag_in_window = w->id;
|
||||
screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
|
||||
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
|
||||
}
|
||||
} else if (screen->selection.in_progress) {
|
||||
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
|
||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +202,7 @@ static inline void
|
||||
extend_selection(Window *w) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (screen_has_selection(screen)) {
|
||||
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
|
||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +277,8 @@ HANDLER(handle_move_event) {
|
||||
if (!cell_for_pos(w, &x, &y, global_state.callback_os_window)) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
detect_url(screen, x, y);
|
||||
bool mouse_cell_changed = x != w->mouse_cell_x || y != w->mouse_cell_y;
|
||||
w->mouse_cell_x = x; w->mouse_cell_y = y;
|
||||
bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y;
|
||||
w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
|
||||
bool handle_in_kitty = (
|
||||
(screen->modes.mouse_tracking_mode == ANY_MODE ||
|
||||
(screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)) &&
|
||||
@@ -304,14 +305,14 @@ multi_click(Window *w, unsigned int count) {
|
||||
index_type start, end;
|
||||
bool found_selection = false;
|
||||
SelectionExtendMode mode = EXTEND_CELL;
|
||||
unsigned int y1 = w->mouse_cell_y, y2 = w->mouse_cell_y;
|
||||
unsigned int y1 = w->mouse_pos.cell_y, y2 = w->mouse_pos.cell_y;
|
||||
switch(count) {
|
||||
case 2:
|
||||
found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, &y1, &y2, &start, &end);
|
||||
found_selection = screen_selection_range_for_word(screen, w->mouse_pos.cell_x, &y1, &y2, &start, &end);
|
||||
mode = EXTEND_WORD;
|
||||
break;
|
||||
case 3:
|
||||
found_selection = screen_selection_range_for_line(screen, w->mouse_cell_y, &start, &end);
|
||||
found_selection = screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end);
|
||||
mode = EXTEND_LINE;
|
||||
break;
|
||||
default:
|
||||
@@ -323,19 +324,39 @@ multi_click(Window *w, unsigned int count) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline double
|
||||
distance(double x1, double y1, double x2, double y2) {
|
||||
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||||
}
|
||||
|
||||
HANDLER(add_click) {
|
||||
ClickQueue *q = &w->click_queue;
|
||||
if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; }
|
||||
double now = monotonic();
|
||||
#define N(n) (q->clicks[q->length - n])
|
||||
N(0).at = now; N(0).button = button; N(0).modifiers = modifiers;
|
||||
N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; N(0).x = w->mouse_pos.x; N(0).y = w->mouse_pos.y;
|
||||
q->length++;
|
||||
double multi_click_allowed_radius = 1.2 * global_state.callback_os_window->fonts_data->cell_height;
|
||||
// Now dispatch the multi-click if any
|
||||
if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) {
|
||||
multi_click(w, 3);
|
||||
q->length = 0;
|
||||
} else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) {
|
||||
multi_click(w, 2);
|
||||
if (q->length > 2) {
|
||||
// possible triple-click
|
||||
if (
|
||||
N(1).at - N(3).at <= 2 * OPT(click_interval) &&
|
||||
distance(N(1).x, N(1).y, N(3).x, N(3).y) <= multi_click_allowed_radius
|
||||
) {
|
||||
multi_click(w, 3);
|
||||
q->length = 0;
|
||||
}
|
||||
}
|
||||
if (q->length > 1) {
|
||||
// possible double-click
|
||||
if (
|
||||
N(1).at - N(2).at <= OPT(click_interval) &&
|
||||
distance(N(1).x, N(1).y, N(2).x, N(2).y) <= multi_click_allowed_radius
|
||||
) {
|
||||
multi_click(w, 2);
|
||||
}
|
||||
|
||||
}
|
||||
#undef N
|
||||
}
|
||||
@@ -343,6 +364,7 @@ HANDLER(add_click) {
|
||||
static inline void
|
||||
open_url(Window *w) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y);
|
||||
screen_open_url(screen);
|
||||
}
|
||||
|
||||
@@ -549,6 +571,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
int s;
|
||||
bool is_high_resolution = flags & 1;
|
||||
if (is_high_resolution) {
|
||||
yoffset *= OPT(touch_scroll_multiplier);
|
||||
if (yoffset * global_state.callback_os_window->pending_scroll_pixels < 0) {
|
||||
global_state.callback_os_window->pending_scroll_pixels = 0; // change of direction
|
||||
}
|
||||
|
||||
@@ -599,6 +599,13 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
} \
|
||||
break;
|
||||
|
||||
#define NO_MODIFIERS(modifier, special, special_msg) { \
|
||||
if (start_modifier || end_modifier) { \
|
||||
if (special && modifier == special) { REPORT_ERROR(special_msg); } \
|
||||
else { REPORT_ERROR("CSI code 0x%x has unsupported start modifier: 0x%x or end modifier: 0x%x", code, start_modifier, end_modifier);} \
|
||||
break; \
|
||||
}}
|
||||
|
||||
char start_modifier = 0, end_modifier = 0;
|
||||
uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos];
|
||||
unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2;
|
||||
@@ -641,8 +648,10 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
if (i > start) params[num_params++] = utoi(buf + start, i - start);
|
||||
switch(code) {
|
||||
case ICH:
|
||||
NO_MODIFIERS(end_modifier, ' ', "Shift left escape code not implemented");
|
||||
CALL_CSI_HANDLER1(screen_insert_characters, 1);
|
||||
case CUU:
|
||||
NO_MODIFIERS(end_modifier, ' ', "Shift right escape code not implemented");
|
||||
CALL_CSI_HANDLER1(screen_cursor_up2, 1);
|
||||
case CUD:
|
||||
case VPR:
|
||||
@@ -758,6 +767,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
case DECSCUSR:
|
||||
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
||||
case SU:
|
||||
NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented");
|
||||
CALL_CSI_HANDLER1(screen_scroll, 1);
|
||||
case SD:
|
||||
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef init_dest_line
|
||||
#define init_dest_line(dest_y) init_line(dest, dest->line, dest->line_map[dest_y]);
|
||||
#define init_dest_line(dest_y) init_line(dest, dest->line, dest->line_map[dest_y]); dest->line->continued = dest->line_attrs[dest_y];
|
||||
#endif
|
||||
|
||||
#ifndef first_dest_line
|
||||
|
||||
@@ -107,7 +107,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self->color_profile = alloc_color_profile();
|
||||
self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns);
|
||||
self->linebuf = self->main_linebuf;
|
||||
self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns);
|
||||
self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns, OPT(scrollback_pager_history_size));
|
||||
self->main_grman = grman_alloc();
|
||||
self->alt_grman = grman_alloc();
|
||||
self->grman = self->main_grman;
|
||||
@@ -165,8 +165,9 @@ screen_dirty_sprite_positions(Screen *self) {
|
||||
|
||||
static inline HistoryBuf*
|
||||
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns) {
|
||||
HistoryBuf *ans = alloc_historybuf(lines, columns);
|
||||
HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
|
||||
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
|
||||
ans->pagerhist = old->pagerhist; old->pagerhist = NULL;
|
||||
historybuf_rewrap(old, ans);
|
||||
return ans;
|
||||
}
|
||||
@@ -365,6 +366,7 @@ draw_combining_char(Screen *self, char_type ch) {
|
||||
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
|
||||
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
|
||||
if ((gpu_cell->attrs & WIDTH_MASK) != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
|
||||
if (self->cursor->x <= self->columns - 1) line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, true);
|
||||
gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 2;
|
||||
if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos);
|
||||
else self->cursor->x++;
|
||||
@@ -974,9 +976,9 @@ screen_restore_cursor(Screen *self) {
|
||||
|
||||
void
|
||||
screen_restore_modes(Screen *self) {
|
||||
ScreenModes *m;
|
||||
const ScreenModes *m;
|
||||
buffer_pop(&self->modes_savepoints, m);
|
||||
if (m == NULL) *m = empty_modes;
|
||||
if (m == NULL) m = &empty_modes;
|
||||
#define S(name) set_mode_from_const(self, name, m->m##name)
|
||||
S(DECTCEM); S(DECSCNM); S(DECSCNM); S(DECOM); S(DECAWM); S(DECARM); S(DECCKM);
|
||||
S(BRACKETED_PASTE); S(FOCUS_TRACKING); S(EXTENDED_KEYBOARD);
|
||||
@@ -1126,6 +1128,15 @@ screen_insert_lines(Screen *self, unsigned int count) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
screen_scroll_until_cursor(Screen *self) {
|
||||
unsigned int num_lines_to_scroll = MIN(self->margin_bottom, self->cursor->y + 1);
|
||||
index_type y = self->cursor->y;
|
||||
self->cursor->y = self->margin_bottom;
|
||||
while (num_lines_to_scroll--) screen_index(self);
|
||||
self->cursor->y = y;
|
||||
}
|
||||
|
||||
void
|
||||
screen_delete_lines(Screen *self, unsigned int count) {
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
@@ -1405,7 +1416,6 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
|
||||
static char buf[128];
|
||||
int shape = 0;
|
||||
const char *query;
|
||||
Cursor blank_cursor = {{0}};
|
||||
switch(c) {
|
||||
case '+':
|
||||
CALLBACK("request_capabilities", "O", q);
|
||||
@@ -1429,7 +1439,7 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
|
||||
shape = snprintf(buf, sizeof(buf), "1$r%d q", shape);
|
||||
} else if (strcmp("m", query) == 0) {
|
||||
// SGR
|
||||
shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor, &blank_cursor));
|
||||
shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor));
|
||||
} else if (strcmp("r", query) == 0) {
|
||||
shape = snprintf(buf, sizeof(buf), "1$r%u;%ur", self->margin_top + 1, self->margin_bottom + 1);
|
||||
} else {
|
||||
@@ -1863,6 +1873,7 @@ is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) {
|
||||
WRAP1E(cursor_back, 1, -1)
|
||||
WRAP1B(erase_in_line, 0)
|
||||
WRAP1B(erase_in_display, 0)
|
||||
WRAP0(scroll_until_cursor)
|
||||
|
||||
#define MODE_GETSET(name, uname) \
|
||||
static PyObject* name##_get(Screen *self, void UNUSED *closure) { PyObject *ans = self->modes.m##uname ? Py_True : Py_False; Py_INCREF(ans); return ans; } \
|
||||
@@ -2148,7 +2159,7 @@ COUNT_WRAP(cursor_forward)
|
||||
|
||||
static PyObject*
|
||||
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
|
||||
return PyLong_FromUnsignedLong(wcwidth_std(PyLong_AsLong(chr)));
|
||||
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
|
||||
}
|
||||
|
||||
|
||||
@@ -2169,6 +2180,7 @@ static PyMethodDef methods[] = {
|
||||
MND(cursor_back, METH_VARARGS)
|
||||
MND(erase_in_line, METH_VARARGS)
|
||||
MND(erase_in_display, METH_VARARGS)
|
||||
MND(scroll_until_cursor, METH_NOARGS)
|
||||
METHOD(current_char_width, METH_NOARGS)
|
||||
MND(insert_lines, METH_VARARGS)
|
||||
MND(delete_lines, METH_VARARGS)
|
||||
|
||||
@@ -437,11 +437,6 @@ blank_os_window(OSWindow *os_window) {
|
||||
bool
|
||||
send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) {
|
||||
bool changed = false;
|
||||
if (os_window->clear_count < 2) {
|
||||
os_window->clear_count++;
|
||||
blank_os_window(os_window);
|
||||
changed = true;
|
||||
}
|
||||
if (os_window->fonts_data) {
|
||||
if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true;
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t t
|
||||
|
||||
void
|
||||
os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
|
||||
if (os_window->num_tabs > 1) {
|
||||
if (!global_state.tab_bar_hidden && os_window->num_tabs > 1) {
|
||||
switch(OPT(tab_bar_edge)) {
|
||||
case TOP_EDGE:
|
||||
central->left = 0; central->top = os_window->fonts_data->cell_height; central->right = os_window->viewport_width - 1;
|
||||
@@ -354,9 +354,10 @@ PYWRAP1(set_options) {
|
||||
global_state.debug_gl = debug_gl ? true : false;
|
||||
global_state.debug_font_fallback = debug_font_fallback ? true : false;
|
||||
#define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;
|
||||
#define S(name, convert) { GA(name); global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
|
||||
GA(kitty_mod);
|
||||
kitty_mod = PyLong_AsLong(ret); Py_CLEAR(ret); if (PyErr_Occurred()) return NULL;
|
||||
#define SS(name, dest, convert) { GA(name); dest = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
|
||||
#define S(name, convert) SS(name, global_state.opts.name, convert)
|
||||
SS(kitty_mod, kitty_mod, PyLong_AsLong);
|
||||
S(hide_window_decorations, PyObject_IsTrue);
|
||||
S(visual_bell_duration, PyFloat_AsDouble);
|
||||
S(enable_audio_bell, PyObject_IsTrue);
|
||||
S(focus_follows_mouse, PyObject_IsTrue);
|
||||
@@ -367,11 +368,13 @@ PYWRAP1(set_options) {
|
||||
S(dynamic_background_opacity, PyObject_IsTrue);
|
||||
S(inactive_text_alpha, PyFloat_AsDouble);
|
||||
S(window_padding_width, PyFloat_AsDouble);
|
||||
S(scrollback_pager_history_size, PyLong_AsUnsignedLong);
|
||||
S(cursor_shape, PyLong_AsLong);
|
||||
S(url_style, PyLong_AsUnsignedLong);
|
||||
S(tab_bar_edge, PyLong_AsLong);
|
||||
S(mouse_hide_wait, PyFloat_AsDouble);
|
||||
S(wheel_scroll_multiplier, PyFloat_AsDouble);
|
||||
S(touch_scroll_multiplier, PyFloat_AsDouble);
|
||||
S(open_url_modifiers, convert_mods);
|
||||
S(rectangle_select_modifiers, convert_mods);
|
||||
S(click_interval, PyFloat_AsDouble);
|
||||
@@ -387,13 +390,16 @@ PYWRAP1(set_options) {
|
||||
S(window_alert_on_bell, PyObject_IsTrue);
|
||||
S(macos_option_as_alt, PyObject_IsTrue);
|
||||
S(macos_traditional_fullscreen, PyObject_IsTrue);
|
||||
S(macos_hide_titlebar, PyObject_IsTrue);
|
||||
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
|
||||
S(macos_window_resizable, PyObject_IsTrue);
|
||||
S(x11_hide_window_decorations, PyObject_IsTrue);
|
||||
S(macos_hide_from_tasks, PyObject_IsTrue);
|
||||
S(macos_thicken_font, PyFloat_AsDouble);
|
||||
|
||||
GA(tab_bar_style);
|
||||
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
||||
Py_CLEAR(ret);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
|
||||
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
|
||||
if (chars == NULL) return NULL;
|
||||
for (size_t i = 0; i < MIN((size_t)PyUnicode_GET_LENGTH(chars), sizeof(OPT(select_by_word_characters))/sizeof(OPT(select_by_word_characters[0]))); i++) {
|
||||
@@ -422,6 +428,7 @@ PYWRAP1(set_options) {
|
||||
read_adjust(adjust_column_width);
|
||||
#undef read_adjust
|
||||
#undef S
|
||||
#undef SS
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,18 @@
|
||||
typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge;
|
||||
|
||||
typedef struct {
|
||||
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier;
|
||||
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier, touch_scroll_multiplier;
|
||||
bool enable_audio_bell;
|
||||
CursorShape cursor_shape;
|
||||
unsigned int open_url_modifiers;
|
||||
unsigned int rectangle_select_modifiers;
|
||||
unsigned int url_style;
|
||||
unsigned int scrollback_pager_history_size;
|
||||
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
|
||||
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
|
||||
double repaint_delay, input_delay;
|
||||
bool focus_follows_mouse;
|
||||
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
|
||||
bool focus_follows_mouse, hide_window_decorations;
|
||||
bool macos_option_as_alt, macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
|
||||
float macos_thicken_font;
|
||||
int adjust_line_height_px, adjust_column_width_px;
|
||||
float adjust_line_height_frac, adjust_column_width_frac;
|
||||
@@ -51,6 +52,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
double at;
|
||||
int button, modifiers;
|
||||
double x, y;
|
||||
} Click;
|
||||
|
||||
#define CLICK_QUEUE_SZ 3
|
||||
@@ -66,7 +68,10 @@ typedef struct {
|
||||
CursorShape last_cursor_shape;
|
||||
PyObject *title;
|
||||
ScreenRenderData render_data;
|
||||
unsigned int mouse_cell_x, mouse_cell_y;
|
||||
struct {
|
||||
unsigned int cell_x, cell_y;
|
||||
double x, y;
|
||||
} mouse_pos;
|
||||
WindowGeometry geometry;
|
||||
ClickQueue click_queue;
|
||||
double last_drag_scroll_at;
|
||||
@@ -97,6 +102,7 @@ typedef struct {
|
||||
bool is_set;
|
||||
} OSWindowGeometry;
|
||||
|
||||
enum WAYLAND_RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY };
|
||||
|
||||
typedef struct {
|
||||
void *handle;
|
||||
@@ -126,6 +132,7 @@ typedef struct {
|
||||
FONTS_DATA_HANDLE fonts_data;
|
||||
id_type temp_font_group_id;
|
||||
double pending_scroll_pixels;
|
||||
enum WAYLAND_RENDER_STATE wayland_render_state;
|
||||
} OSWindow;
|
||||
|
||||
|
||||
@@ -142,6 +149,7 @@ typedef struct {
|
||||
bool debug_gl, debug_font_fallback;
|
||||
bool has_pending_resizes;
|
||||
bool in_sequence_mode;
|
||||
bool tab_bar_hidden;
|
||||
double font_sz_in_pts;
|
||||
struct { double x, y; } default_dpi;
|
||||
id_type active_drag_in_window;
|
||||
@@ -171,6 +179,7 @@ void event_loop_wait(double timeout);
|
||||
void swap_window_buffers(OSWindow *w);
|
||||
void make_window_context_current(OSWindow *w);
|
||||
void hide_mouse(OSWindow *w);
|
||||
bool is_mouse_hidden(OSWindow *w);
|
||||
void destroy_os_window(OSWindow *w);
|
||||
void focus_os_window(OSWindow *w, bool also_raise);
|
||||
void set_os_window_title(OSWindow *w, const char *title);
|
||||
@@ -201,3 +210,4 @@ typedef enum {
|
||||
void set_cocoa_pending_action(CocoaPendingAction action);
|
||||
bool application_quit_requested();
|
||||
#endif
|
||||
void wayland_request_frame_render(OSWindow *w);
|
||||
|
||||
@@ -27,6 +27,16 @@ def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None,
|
||||
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env)
|
||||
|
||||
|
||||
def add_active_id_to_history(items, item_id, maxlen=64):
|
||||
try:
|
||||
items.remove(item_id)
|
||||
except ValueError:
|
||||
pass
|
||||
items.append(item_id)
|
||||
if len(items) > maxlen:
|
||||
items.popleft()
|
||||
|
||||
|
||||
class Tab: # {{{
|
||||
|
||||
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
|
||||
@@ -34,6 +44,7 @@ class Tab: # {{{
|
||||
self.tab_manager_ref = weakref.ref(tab_manager)
|
||||
self.os_window_id = tab_manager.os_window_id
|
||||
self.id = add_tab(self.os_window_id)
|
||||
self.active_window_history = deque()
|
||||
if not self.id:
|
||||
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
|
||||
self.opts, self.args = tab_manager.opts, tab_manager.args
|
||||
@@ -84,6 +95,9 @@ class Tab: # {{{
|
||||
old_active_window = self.windows[self._active_window_idx]
|
||||
except Exception:
|
||||
old_active_window = None
|
||||
else:
|
||||
wid = old_active_window.id if old_active_window.overlay_for is None else old_active_window.overlay_for
|
||||
add_active_id_to_history(self.active_window_history, wid)
|
||||
self._active_window_idx = max(0, min(val, len(self.windows) - 1))
|
||||
try:
|
||||
new_active_window = self.windows[self._active_window_idx]
|
||||
@@ -255,7 +269,19 @@ class Tab: # {{{
|
||||
|
||||
def nth_window(self, num=0):
|
||||
if self.windows:
|
||||
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
|
||||
if num < 0:
|
||||
try:
|
||||
old_window_id = self.active_window_history[num]
|
||||
except IndexError:
|
||||
return
|
||||
for idx, w in enumerate(self.windows):
|
||||
if w.id == old_window_id:
|
||||
self.active_window_idx = self.current_layout.set_active_window(self.windows, idx)
|
||||
break
|
||||
else:
|
||||
return
|
||||
else:
|
||||
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
|
||||
self.relayout_borders()
|
||||
glfw_post_empty_event()
|
||||
|
||||
@@ -330,7 +356,9 @@ class TabManager: # {{{
|
||||
|
||||
def __init__(self, os_window_id, opts, args, startup_session):
|
||||
self.os_window_id = os_window_id
|
||||
self.last_active_tab_id = None
|
||||
self.opts, self.args = opts, args
|
||||
self.tab_bar_hidden = self.opts.tab_bar_style == 'hidden'
|
||||
self.tabs = []
|
||||
self.active_tab_history = deque()
|
||||
self.tab_bar = TabBar(self.os_window_id, opts)
|
||||
@@ -348,11 +376,10 @@ class TabManager: # {{{
|
||||
def active_tab_idx(self, val):
|
||||
try:
|
||||
old_active_tab = self.tabs[self._active_tab_idx]
|
||||
self.active_tab_history.append(old_active_tab.id)
|
||||
if len(self.active_tab_history) > 64:
|
||||
self.active_tab_history.popleft()
|
||||
except Exception:
|
||||
old_active_tab = None
|
||||
else:
|
||||
add_active_id_to_history(self.active_tab_history, old_active_tab.id)
|
||||
self._active_tab_idx = max(0, min(val, len(self.tabs) - 1))
|
||||
try:
|
||||
new_active_tab = self.tabs[self._active_tab_idx]
|
||||
@@ -369,7 +396,8 @@ class TabManager: # {{{
|
||||
w.focus_changed(True)
|
||||
|
||||
def refresh_sprite_positions(self):
|
||||
self.tab_bar.screen.refresh_sprite_positions()
|
||||
if not self.tab_bar_hidden:
|
||||
self.tab_bar.screen.refresh_sprite_positions()
|
||||
|
||||
def _add_tab(self, tab):
|
||||
before = len(self.tabs)
|
||||
@@ -389,12 +417,13 @@ class TabManager: # {{{
|
||||
set_active_tab(self.os_window_id, idx)
|
||||
|
||||
def tabbar_visibility_changed(self):
|
||||
self.tab_bar.layout()
|
||||
self.resize(only_tabs=True)
|
||||
glfw_post_empty_event()
|
||||
if not self.tab_bar_hidden:
|
||||
self.tab_bar.layout()
|
||||
self.resize(only_tabs=True)
|
||||
glfw_post_empty_event()
|
||||
|
||||
def mark_tab_bar_dirty(self):
|
||||
if len(self.tabs) > 1:
|
||||
if len(self.tabs) > 1 and not self.tab_bar_hidden:
|
||||
mark_tab_bar_dirty(self.os_window_id)
|
||||
|
||||
def update_tab_bar_data(self):
|
||||
@@ -402,8 +431,9 @@ class TabManager: # {{{
|
||||
|
||||
def resize(self, only_tabs=False):
|
||||
if not only_tabs:
|
||||
self.tab_bar.layout()
|
||||
self.mark_tab_bar_dirty()
|
||||
if not self.tab_bar_hidden:
|
||||
self.tab_bar.layout()
|
||||
self.mark_tab_bar_dirty()
|
||||
for tab in self.tabs:
|
||||
tab.relayout()
|
||||
|
||||
@@ -426,6 +456,15 @@ class TabManager: # {{{
|
||||
def goto_tab(self, tab_num):
|
||||
if tab_num < len(self.tabs) and 0 <= tab_num:
|
||||
self.set_active_tab_idx(tab_num)
|
||||
elif tab_num < 0:
|
||||
try:
|
||||
old_active_tab_id = self.active_tab_history[tab_num]
|
||||
except IndexError:
|
||||
return
|
||||
for idx, tab in enumerate(self.tabs):
|
||||
if tab.id == old_active_tab_id:
|
||||
self.set_active_tab_idx(idx)
|
||||
break
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.tabs)
|
||||
@@ -439,6 +478,7 @@ class TabManager: # {{{
|
||||
'id': tab.id,
|
||||
'is_focused': tab is active_tab,
|
||||
'title': tab.name or tab.title,
|
||||
'layout': tab.current_layout.name,
|
||||
'windows': list(tab.list_windows(active_window)),
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ bool_capabilities = {
|
||||
# described at:
|
||||
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
|
||||
'Su',
|
||||
# Indicates support for full keyboard mode (non-standard) as
|
||||
# described at:
|
||||
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
|
||||
'fullkbd',
|
||||
|
||||
# The following are entries that we don't use
|
||||
# # background color erase
|
||||
@@ -223,8 +227,8 @@ string_capabilities = {
|
||||
'rmso': r'\E[27m',
|
||||
# Exit underline mode
|
||||
'rmul': r'\E[24m',
|
||||
# Reset string1
|
||||
'rs1': r'\Ec',
|
||||
# Reset string1 (empty OSC sequence to exit OSC/OTH modes, and regular reset)
|
||||
'rs1': r'\E]\E\\\Ec',
|
||||
# Save cursor
|
||||
'sc': r'\E7',
|
||||
# Set background color
|
||||
@@ -284,10 +288,6 @@ string_capabilities = {
|
||||
'setrgbb': r'\E[48:2:%p1%d:%p2%d:%p3%dm',
|
||||
|
||||
# The following are entries that we don't use
|
||||
# # display status line
|
||||
# 'dsl': r'\E]2;\007',
|
||||
# # return from status line
|
||||
# 'fsl': r'^G',
|
||||
# # turn on blank mode, (characters invisible)
|
||||
# 'invis': r'\E[8m',
|
||||
# # init2 string
|
||||
@@ -436,7 +436,7 @@ termcap_aliases.update({
|
||||
queryable_capabilities = numeric_capabilities.copy()
|
||||
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())
|
||||
no_termcap_for = frozenset('Su Tc setrgbf setrgbb fullkbd'.split())
|
||||
if extra - no_termcap_for:
|
||||
raise Exception('Termcap aliases not complete, missing: {}'.format(extra - no_termcap_for))
|
||||
del extra
|
||||
|
||||
@@ -61,6 +61,9 @@ def color_from_int(val):
|
||||
|
||||
def parse_color_set(raw):
|
||||
parts = raw.split(';')
|
||||
lp = len(parts)
|
||||
if lp % 2 != 0:
|
||||
return
|
||||
for c, spec in [parts[i:i + 2] for i in range(0, len(parts), 2)]:
|
||||
try:
|
||||
c = int(c)
|
||||
@@ -419,7 +422,7 @@ def get_editor():
|
||||
import shlex
|
||||
ans = os.environ.get('EDITOR')
|
||||
if not ans or not exe_exists(shlex.split(ans)[0]):
|
||||
for q in ('vim', 'vi', 'emacs', 'micro', 'nano'):
|
||||
for q in ('vim', 'nvim', 'vi', 'emacs', 'micro', 'nano', 'vis'):
|
||||
r = exe_exists(q)
|
||||
if r:
|
||||
ans = r
|
||||
|
||||
@@ -8,6 +8,7 @@ import sys
|
||||
import weakref
|
||||
from collections import deque
|
||||
from enum import IntEnum
|
||||
from itertools import chain
|
||||
|
||||
from .child import cwd_of_process
|
||||
from .config import build_ansi_color_table
|
||||
@@ -98,6 +99,24 @@ def setup_colors(screen, opts):
|
||||
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
|
||||
|
||||
|
||||
def text_sanitizer(as_ansi, add_wrap_markers):
|
||||
import re
|
||||
pat = re.compile(r'\033\[.+?m')
|
||||
|
||||
def remove_wrap_markers(line):
|
||||
return line.replace('\r', '')
|
||||
|
||||
def remove_sgr(line):
|
||||
return pat.sub('', line)
|
||||
|
||||
def remove_both(line):
|
||||
return pat.sub('', line.replace('\r', ''))
|
||||
|
||||
if as_ansi:
|
||||
return remove_both if add_wrap_markers else remove_sgr
|
||||
return remove_wrap_markers
|
||||
|
||||
|
||||
class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args, override_title=None):
|
||||
@@ -248,7 +267,7 @@ class Window:
|
||||
|
||||
# screen callbacks {{{
|
||||
def use_utf8(self, on):
|
||||
get_boss().child_monitor.set_iutf8(self.window_id, on)
|
||||
get_boss().child_monitor.set_iutf8(self.id, on)
|
||||
|
||||
def focus_changed(self, focused):
|
||||
if focused:
|
||||
@@ -376,6 +395,8 @@ class Window:
|
||||
|
||||
def clipboard_control(self, data):
|
||||
where, text = data.partition(';')[::2]
|
||||
if not where:
|
||||
where = 's0'
|
||||
if text == '?':
|
||||
response = None
|
||||
if 's' in where or 'c' in where:
|
||||
@@ -438,7 +459,7 @@ class Window:
|
||||
|
||||
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False):
|
||||
lines = []
|
||||
add_history = add_history and not self.screen.is_using_alternate_linebuf() and not alternate_screen
|
||||
add_history = add_history and not (self.screen.is_using_alternate_linebuf() ^ alternate_screen)
|
||||
if alternate_screen:
|
||||
f = self.screen.as_text_alternate
|
||||
else:
|
||||
@@ -446,8 +467,12 @@ class Window:
|
||||
f(lines.append, as_ansi, add_wrap_markers)
|
||||
if add_history:
|
||||
h = []
|
||||
self.screen.historybuf.pagerhist_as_text(h.append)
|
||||
if not as_ansi or not add_wrap_markers:
|
||||
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
|
||||
h = list(map(sanitizer, h))
|
||||
self.screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
|
||||
lines = h + lines
|
||||
lines = chain(h, lines)
|
||||
return ''.join(lines)
|
||||
|
||||
@property
|
||||
@@ -458,15 +483,26 @@ class Window:
|
||||
if pid is not None:
|
||||
return cwd_of_process(pid) or None
|
||||
|
||||
def pipe_data(self, text, has_wrap_markers=False):
|
||||
text = text or ''
|
||||
if has_wrap_markers:
|
||||
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
||||
lines = text.count('\n')
|
||||
input_line_number = (lines - (self.screen.lines - 1) - self.screen.scrolled_by)
|
||||
return {
|
||||
'input_line_number': input_line_number, 'scrolled_by': self.screen.scrolled_by,
|
||||
'cursor_x': self.screen.cursor.x + 1, 'cursor_y': self.screen.cursor.y + 1,
|
||||
'lines': self.screen.lines, 'columns': self.screen.columns,
|
||||
'text': text
|
||||
}
|
||||
|
||||
# actions {{{
|
||||
|
||||
def show_scrollback(self):
|
||||
data = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
|
||||
data = data.replace('\r\n', '\n').replace('\r', '\n')
|
||||
lines = data.count('\n')
|
||||
input_line_number = (lines - (self.screen.lines - 1) - self.screen.scrolled_by)
|
||||
cmd = [x.replace('INPUT_LINE_NUMBER', str(input_line_number)) for x in self.opts.scrollback_pager]
|
||||
get_boss().display_scrollback(self, data, cmd)
|
||||
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
|
||||
data = self.pipe_data(text, has_wrap_markers=True)
|
||||
cmd = [x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) for x in self.opts.scrollback_pager]
|
||||
get_boss().display_scrollback(self, data['text'], cmd)
|
||||
|
||||
def paste(self, text):
|
||||
if text and not self.destroyed:
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from kitty.config import Options, defaults, merge_configs
|
||||
from kitty.fast_data_types import set_options
|
||||
from kitty.fast_data_types import LineBuf, Cursor, Screen, HistoryBuf
|
||||
|
||||
|
||||
@@ -72,7 +74,11 @@ class BaseTest(TestCase):
|
||||
ae = TestCase.assertEqual
|
||||
maxDiff = 2000
|
||||
|
||||
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20):
|
||||
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None):
|
||||
if options is None:
|
||||
options = {'scrollback_pager_history_size': 1024}
|
||||
options = Options(merge_configs(defaults._asdict(), options))
|
||||
set_options(options)
|
||||
c = Callbacks()
|
||||
return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)
|
||||
|
||||
|
||||
64
kitty_tests/bench_scrollback.py
Executable file
64
kitty_tests/bench_scrollback.py
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from time import clock_gettime, CLOCK_MONOTONIC, sleep
|
||||
from argparse import ArgumentParser
|
||||
from random import Random
|
||||
from string import printable
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description='Generate text')
|
||||
parser.add_argument('--freq', default=10000, type=int, help='Number of lines to try to write per second. Will warn if not attained.')
|
||||
parser.add_argument('--color', action='store_true', help='Add color to the output')
|
||||
parser.add_argument('--unicode', action='store_true', help='Mix in some unicode characters')
|
||||
parser.add_argument('--length', default=50, type=int, help='Average line length')
|
||||
parser.add_argument('--lengthvar', default=0.3, type=float, help='Variation for line length, in ratio of line length')
|
||||
parser.add_argument('--emptylines', default=0.1, type=float, help='ratio of empty lines')
|
||||
parser.add_argument('--linesperwrite', default=1, type=int, help='number of lines to repeat/write at a time')
|
||||
parser.add_argument('--patterns', default=1000, type=int, help='number of different pattern to alternate')
|
||||
parser.add_argument('--seed', default=sys.argv[0], type=str, help='seed to get different output')
|
||||
args = parser.parse_args()
|
||||
|
||||
rng = Random()
|
||||
rng.seed(args.seed)
|
||||
|
||||
characters = [c for c in printable if c not in '\r\n\x0b\x0c']
|
||||
if args.color:
|
||||
characters += ['\x1b[91m', '\x1b[0m', '\x1b[1;32m', '\x1b[22m', '\x1b[35m']
|
||||
|
||||
if args.unicode:
|
||||
characters += [u'日', u'本', u'💜', u'☃', u'🎩', u'🍀', u'、']
|
||||
|
||||
patterns = []
|
||||
for _ in range(0, args.patterns):
|
||||
s = ""
|
||||
for _ in range(0, args.linesperwrite):
|
||||
cnt = int(rng.gauss(args.length, args.length * args.lengthvar))
|
||||
if cnt < 0 or rng.random() < args.emptylines:
|
||||
cnt = 0
|
||||
s += "".join(rng.choices(characters, k=cnt)) + '\n'
|
||||
patterns += [s]
|
||||
|
||||
time_per_print = args.linesperwrite / args.freq
|
||||
t1 = clock_gettime(CLOCK_MONOTONIC)
|
||||
cnt = 0
|
||||
while True:
|
||||
sys.stdout.write(patterns[rng.randrange(0, args.patterns)])
|
||||
sys.stdout.flush()
|
||||
cnt += 1
|
||||
t2 = clock_gettime(CLOCK_MONOTONIC)
|
||||
if t2 - t1 < cnt * time_per_print:
|
||||
sleep(cnt * time_per_print - (t2 - t1))
|
||||
t1 = t2
|
||||
cnt = 0
|
||||
elif cnt >= 100:
|
||||
print("Cannot print fast enough, printed %d lines in %f seconds instead of %f seconds target" %
|
||||
(cnt * args.linesperwrite, t2 - t1, cnt * time_per_print))
|
||||
break
|
||||
else:
|
||||
cnt += 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -242,6 +242,9 @@ class TestDataTypes(BaseTest):
|
||||
self.ae(l0.url_end_at(0), len(l0) - 1)
|
||||
l2 = create("http://-abcd] ")
|
||||
self.ae(l2.url_end_at(0), len(l2) - 3)
|
||||
l3 = create("http://ab.de ")
|
||||
self.ae(l3.url_start_at(4), 0)
|
||||
self.ae(l3.url_start_at(5), 0)
|
||||
|
||||
def lspace_test(n, scheme='http'):
|
||||
lf = create(' ' * n + scheme + '://acme.com')
|
||||
@@ -447,10 +450,10 @@ class TestDataTypes(BaseTest):
|
||||
def test_ansi_repr(self):
|
||||
lb = filled_line_buf()
|
||||
l0 = lb.line(0)
|
||||
self.ae(l0.as_ansi(), '\x1b[0m00000')
|
||||
self.ae(l0.as_ansi(), '00000')
|
||||
a = []
|
||||
lb.as_ansi(a.append)
|
||||
self.ae(a, ['\x1b[0m' + str(lb.line(i)) + '\n' for i in range(lb.ynum)])
|
||||
self.ae(a, [str(lb.line(i)) + '\n' for i in range(lb.ynum)])
|
||||
l2 = lb.line(0)
|
||||
c = C()
|
||||
c.bold = c.italic = c.reverse = c.strikethrough = c.dim = True
|
||||
@@ -458,15 +461,15 @@ class TestDataTypes(BaseTest):
|
||||
c.bg = (1 << 24) | (2 << 16) | (3 << 8) | 2
|
||||
c.decoration_fg = (5 << 8) | 1
|
||||
l2.set_text('1', 0, 1, c)
|
||||
self.ae(l2.as_ansi(), '\x1b[0m\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1'
|
||||
self.ae(l2.as_ansi(), '\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1'
|
||||
'\x1b[22;23;27;29;39;49;59m' '0000')
|
||||
lb = filled_line_buf()
|
||||
for i in range(lb.ynum):
|
||||
lb.set_continued(i, True)
|
||||
a = []
|
||||
lb.as_ansi(a.append)
|
||||
self.ae(a, ['\x1b[0m' + str(lb.line(i)) for i in range(lb.ynum)])
|
||||
self.ae(a, [str(lb.line(i)) for i in range(lb.ynum)])
|
||||
hb = filled_history_buf(5, 5)
|
||||
a = []
|
||||
hb.as_ansi(a.append)
|
||||
self.ae(a, ['\x1b[0m' + str(hb.line(i)) + '\n' for i in range(hb.count - 1, -1, -1)])
|
||||
self.ae(a, [str(hb.line(i)) + '\n' for i in range(hb.count - 1, -1, -1)])
|
||||
|
||||
@@ -98,7 +98,7 @@ class TestParser(BaseTest):
|
||||
pb('x\033[2;7@y', 'x', ('screen_insert_characters', 2), 'y')
|
||||
pb('x\033[@y', 'x', ('screen_insert_characters', 1), 'y')
|
||||
pb('x\033[345@y', 'x', ('screen_insert_characters', 345), 'y')
|
||||
pb('x\033[345;@y', 'x', ('screen_insert_characters', 345), 'y')
|
||||
pb('x\033[345;@y', 'x', ('CSI code 0x40 has unsupported start modifier: 0x0 or end modifier: 0x3b',), 'y')
|
||||
pb('\033[H', ('screen_cursor_position', 1, 1))
|
||||
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 0)
|
||||
pb('\033[4H', ('screen_cursor_position', 4, 1))
|
||||
@@ -171,6 +171,11 @@ class TestParser(BaseTest):
|
||||
self.assertTrue(s.cursor.blink)
|
||||
self.ae(s.cursor.shape, CURSOR_BLOCK)
|
||||
|
||||
s.reset()
|
||||
pb('\033[3 @', ('Shift left escape code not implemented',))
|
||||
pb('\033[3 A', ('Shift right escape code not implemented',))
|
||||
pb('\033[3;4 S', ('Select presentation directions escape code not implemented',))
|
||||
|
||||
def test_osc_codes(self):
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
|
||||
@@ -441,4 +441,4 @@ class TestScreen(BaseTest):
|
||||
return ''.join(d)
|
||||
|
||||
self.ae(as_text(), 'ababababab\nc\n\n')
|
||||
self.ae(as_text(True), '\x1b[0mababa\x1b[0mbabab\n\x1b[0mc\n\n')
|
||||
self.ae(as_text(True), 'ababababab\nc\n\n')
|
||||
|
||||
@@ -20,3 +20,4 @@ class TestUnicodeInput(BaseTest):
|
||||
self.ae(matches('horiz', 'ell'), {0x2026, 0x22ef, 0x2b2c, 0x2b2d, 0xfe19})
|
||||
self.ae(matches('horizontal', 'ell'), {0x2026, 0x22ef, 0x2b2c, 0x2b2d, 0xfe19})
|
||||
self.assertFalse(matches('sfgsfgsfgfgsdg'))
|
||||
self.assertIn(0x1f41d, matches('bee'))
|
||||
|
||||
27
setup.py
27
setup.py
@@ -13,7 +13,6 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -141,11 +140,13 @@ def get_sanitize_args(cc, ccver):
|
||||
|
||||
|
||||
def test_compile(cc, *cflags, src=None):
|
||||
with tempfile.NamedTemporaryFile(suffix='.c') as f:
|
||||
src = src or 'int main(void) { return 0; }'
|
||||
f.write(src.encode('utf-8'))
|
||||
f.flush()
|
||||
return subprocess.Popen([cc] + list(cflags) + [f.name, '-o', os.devnull]).wait() == 0
|
||||
src = src or 'int main(void) { return 0; }'
|
||||
p = subprocess.Popen([cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'], stdin=subprocess.PIPE)
|
||||
try:
|
||||
p.stdin.write(src.encode('utf-8')), p.stdin.close()
|
||||
except BrokenPipeError:
|
||||
return False
|
||||
return p.wait() == 0
|
||||
|
||||
|
||||
def first_successful_compile(cc, *cflags, src=None):
|
||||
@@ -423,7 +424,7 @@ def compile_glfw(incremental, compilation_database, all_keys):
|
||||
modules = 'cocoa' if is_macos else 'x11 wayland'
|
||||
for module in modules.split():
|
||||
try:
|
||||
genv = glfw.init_env(env, pkg_config, at_least_version, module)
|
||||
genv = glfw.init_env(env, pkg_config, at_least_version, test_compile, module)
|
||||
except SystemExit as err:
|
||||
if module != 'wayland':
|
||||
raise
|
||||
@@ -575,6 +576,7 @@ make && make docs
|
||||
|
||||
def compile_python(base_path):
|
||||
import compileall
|
||||
import py_compile
|
||||
try:
|
||||
from multiprocessing import cpu_count
|
||||
num_workers = max(1, cpu_count())
|
||||
@@ -584,7 +586,10 @@ def compile_python(base_path):
|
||||
for f in files:
|
||||
if f.rpartition('.')[-1] in ('pyc', 'pyo'):
|
||||
os.remove(os.path.join(root, f))
|
||||
compileall.compile_dir(base_path, ddir='', force=True, optimize=1, quiet=1, workers=num_workers)
|
||||
kwargs = dict(ddir='', force=True, optimize=1, quiet=1, workers=num_workers)
|
||||
if hasattr(py_compile, 'PycInvalidationMode'):
|
||||
kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.UNCHECKED_HASH
|
||||
compileall.compile_dir(base_path, **kwargs)
|
||||
|
||||
|
||||
def package(args, for_bundle=False, sh_launcher=False):
|
||||
@@ -598,7 +603,11 @@ def package(args, for_bundle=False, sh_launcher=False):
|
||||
for x in (libdir, os.path.join(ddir, 'share')):
|
||||
odir = os.path.join(x, 'terminfo')
|
||||
safe_makedirs(odir)
|
||||
subprocess.check_call(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'])
|
||||
proc = subprocess.run(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
|
||||
regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$'
|
||||
for error in proc.stderr.decode('utf-8').splitlines():
|
||||
if not re.match(regex, error):
|
||||
print(error, file=sys.stderr)
|
||||
if not glob.glob(os.path.join(odir, '*/xterm-kitty')):
|
||||
raise SystemExit('tic failed to output the compiled kitty terminfo file')
|
||||
shutil.copy2('__main__.py', libdir)
|
||||
|
||||
Binary file not shown.
@@ -3,6 +3,7 @@ xterm-kitty|KovIdTTY,
|
||||
Tc,
|
||||
am,
|
||||
ccc,
|
||||
fullkbd,
|
||||
hs,
|
||||
km,
|
||||
mc5i,
|
||||
@@ -120,7 +121,7 @@ xterm-kitty|KovIdTTY,
|
||||
rmkx=\E[?1l,
|
||||
rmso=\E[27m,
|
||||
rmul=\E[24m,
|
||||
rs1=\Ec,
|
||||
rs1=\E]\E\\\Ec,
|
||||
sc=\E7,
|
||||
setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
|
||||
setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user