Compare commits
293 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
361b23e3f1 | ||
|
|
8feccdf63f | ||
|
|
ed905fcf6d | ||
|
|
50a6abaac1 | ||
|
|
718f7e77a1 | ||
|
|
c4aeb1adba | ||
|
|
d9c69f0a54 | ||
|
|
5afdbbe6f1 | ||
|
|
4273cb35bf | ||
|
|
89be7a031d | ||
|
|
06c5127856 | ||
|
|
fd298256d7 | ||
|
|
e0246044b7 | ||
|
|
2f677990b2 | ||
|
|
da2e4c8503 | ||
|
|
a1eb236616 | ||
|
|
11186735c2 | ||
|
|
a038f62a5b | ||
|
|
f45acc055e | ||
|
|
43a25c49c4 | ||
|
|
e47e2b2560 | ||
|
|
7652065134 | ||
|
|
d0da418e49 | ||
|
|
77e72cfd9a | ||
|
|
7b2ecb0498 | ||
|
|
318c5bca67 | ||
|
|
d52da934ca | ||
|
|
21951e3668 | ||
|
|
64bef38813 | ||
|
|
fe67e3dde7 | ||
|
|
fc5e74e457 | ||
|
|
fdbd8df7b2 | ||
|
|
246972b59f | ||
|
|
6057296499 | ||
|
|
b4b2207b93 | ||
|
|
b881364c50 | ||
|
|
76059882c4 | ||
|
|
be71f5f1f4 | ||
|
|
021dfcad39 | ||
|
|
4e7498b854 | ||
|
|
494035c94b | ||
|
|
9c4230ad9c | ||
|
|
056c2c12bb | ||
|
|
a97c440872 | ||
|
|
653d823175 | ||
|
|
c2a2dbc88c | ||
|
|
77492d31cf | ||
|
|
aaa609109e | ||
|
|
2367fad90b | ||
|
|
aaa521bd24 | ||
|
|
d22686da10 | ||
|
|
48b93ab28f | ||
|
|
703a1b83a8 | ||
|
|
c9441bd3e2 | ||
|
|
9de6d619ee | ||
|
|
9aae31ef2f | ||
|
|
dca04c549b | ||
|
|
69e76d6eeb | ||
|
|
b4a93c67c9 | ||
|
|
f098e6c973 | ||
|
|
f8ebba3954 | ||
|
|
37ee693de2 | ||
|
|
f77ad2b41d | ||
|
|
e99eb2e72b | ||
|
|
86b89072f3 | ||
|
|
022bee0560 | ||
|
|
204ea44946 | ||
|
|
3f2489b306 | ||
|
|
291a2d6295 | ||
|
|
b244d13e8a | ||
|
|
b33324d07c | ||
|
|
ae2a641db5 | ||
|
|
1cac757365 | ||
|
|
992de77040 | ||
|
|
f6b5e54890 | ||
|
|
d20c65ef80 | ||
|
|
4a8562a85f | ||
|
|
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,167 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
0.13.2 [2019-01-04]
|
||||
------------------------------
|
||||
|
||||
- Add a new option :opt:`tab_title_template` to control how tab titles
|
||||
are formatted. In particular the template can be used to display
|
||||
the tab number next to the title (:iss:`1223`)
|
||||
|
||||
- Report the current foreground processes as well as the original child process,
|
||||
when using `kitty @ ls`
|
||||
|
||||
- Use the current working directory of the foreground process for the
|
||||
`*_with_cwd` actions that open a new window with the current working
|
||||
directory.
|
||||
|
||||
- Add a new ``copy_or_interrupt`` action that can be mapped to kbd:`ctrl+c`. It
|
||||
will copy if there is a selection and interrupt otherwise (:iss:`1286`)
|
||||
|
||||
- Fix setting :opt:`background_opacity` causing window margins/padding to be slightly
|
||||
different shade from background (:iss:`1221`)
|
||||
|
||||
- Handle keyboards with a "+" key (:iss:`1224`)
|
||||
|
||||
- Fix Private use Unicode area characters followed by spaces at the end of text
|
||||
not being rendered correctly (:iss:`1210`)
|
||||
|
||||
- macOS: Add an entry to the dock menu to open a new OS window (:iss:`1242`)
|
||||
|
||||
- macOS: Fix scrolling very slowly with wheel mice not working (:iss:`1238`)
|
||||
|
||||
- Fix changing :opt:`cursor_text_color` via remote control not working
|
||||
(:iss:`1229`)
|
||||
|
||||
- Add an action to resize windows that can be mapped to shortcuts in :file:`kitty.conf`
|
||||
(:pull:`1245`)
|
||||
|
||||
- Fix using the ``new_tab !neighbor`` action changing the order of the
|
||||
non-neighboring tabs (:iss:`1256`)
|
||||
|
||||
- macOS: Fix momentum scrolling continuing when changing the active window/tab
|
||||
(:iss:`1267`)
|
||||
|
||||
|
||||
0.13.1 [2018-12-06]
|
||||
------------------------------
|
||||
|
||||
- Fix passing input via the pipe action to a program without a window not
|
||||
working.
|
||||
|
||||
- Linux: Fix a regression in the previous release that caused automatic
|
||||
selection of bold/italic fonts when using aliases such as "monospace" to not
|
||||
work (:iss:`1209`)
|
||||
|
||||
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||
be properly unwrapped (:iss:`1206`)
|
||||
|
||||
0.13.0 [2018-12-05]
|
||||
------------------------------
|
||||
|
||||
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
|
||||
extended scrollback to use when viewing the scrollback buffer in a pager
|
||||
(:iss:`970`)
|
||||
|
||||
- Modify the kittens sub-system to allow creating custom kittens without any
|
||||
user interface. This is useful for creating more complex actions that can
|
||||
be bound to key presses in :file:`kitty.conf`. See
|
||||
doc:`kittens/custom`. (:iss:`870`)
|
||||
|
||||
- Add a new ``nth_window`` action that can be used to go to the nth window and
|
||||
also previously active windows, using negative numbers. Similarly,
|
||||
``goto_tab`` now accepts negative numbers to go to previously active tabs
|
||||
(:iss:`1040`)
|
||||
|
||||
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
|
||||
``hidden``. (:iss:`1014`)
|
||||
|
||||
- Allow private use unicode characters to stretch over more than a single
|
||||
neighboring space (:pull:`1036`)
|
||||
|
||||
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
|
||||
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
|
||||
|
||||
- icat kitten: Implement reading image data from STDIN, if STDIN is not
|
||||
connected to a terminal (:iss:`1130`)
|
||||
|
||||
- hints kitten: Insert trailing spaces after matches when using the
|
||||
``--multiple`` option. Also add a separate ``--add-trailing-space``
|
||||
option to control this behavior (:pull:`1132`)
|
||||
|
||||
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
|
||||
than the underlying window's cwd (:iss:`1045`)
|
||||
|
||||
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
|
||||
|
||||
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
|
||||
|
||||
- macOS: Fix IME input for East Asian languages (:iss:`910`)
|
||||
|
||||
- macOS: Fix rendering frames-per-second very low when processing
|
||||
large amounts of input in small chunks (:pull:`1082`)
|
||||
|
||||
- macOS: Fix incorrect text sizes calculated when using an external display
|
||||
that is set to mirror the main display (:iss:`1056`)
|
||||
|
||||
- macOS: Use the system default double click interval (:pull:`1090`)
|
||||
|
||||
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
|
||||
|
||||
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
|
||||
small sizes (:iss:`1173`)
|
||||
|
||||
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
|
||||
respected (:iss:`1085`)
|
||||
|
||||
- Linux: Fix a crash when using the GNU Unifont as a fallback font
|
||||
(:iss:`1087`)
|
||||
|
||||
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
|
||||
|
||||
- Wayland: Add support for the primary selection protocol
|
||||
implemented by some compositors (:pull:`1095`)
|
||||
|
||||
- Fix expansion of env vars not working in the :opt:`env` directive
|
||||
(:iss:`1075`)
|
||||
|
||||
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
|
||||
blink or key press (:iss:`1073`)
|
||||
|
||||
- Fix the ``set_background_opacity`` action not working correctly
|
||||
(:pull:`1147`)
|
||||
|
||||
- Fix second cell of emoji created using variation selectors not having
|
||||
the same attributes as the first cell (:iss:`1109`)
|
||||
|
||||
- Fix focusing neighboring windows in the grid layout with less than 4 windows
|
||||
not working (:iss:`1115`)
|
||||
|
||||
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
|
||||
modes (:iss:`1114`)
|
||||
|
||||
- Add a terminfo entry for full keyboard mode.
|
||||
|
||||
- Fix incorrect text-antialiasing when using very low background opacity
|
||||
(:iss:`1005`)
|
||||
|
||||
- When double or triple clicking ignore clicks if they are "far" from each
|
||||
other (:iss:`1093`)
|
||||
|
||||
- Follow xterm's behavior for the menu key (:iss:`597`)
|
||||
|
||||
- Fix hover detection of URLs not working when hovering over the first colon
|
||||
and slash characters in short URLs (:iss:`1201`)
|
||||
|
||||
0.12.3 [2018-09-29]
|
||||
------------------------------
|
||||
|
||||
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
|
||||
moved or resized at least once (:iss:`887`)
|
||||
|
||||
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
|
||||
|
||||
|
||||
0.12.2 [2018-09-24]
|
||||
------------------------------
|
||||
|
||||
@@ -133,6 +294,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 +506,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
|
||||
|
||||
54
docs/faq.rst
54
docs/faq.rst
@@ -6,7 +6,7 @@ Frequently Asked Questions
|
||||
.. contents::
|
||||
|
||||
Some special symbols are rendered small/truncated in kitty?
|
||||
-----------------------------------------------------------------
|
||||
-----------------------------------------------------------
|
||||
|
||||
The number of cells a unicode character takes up are controlled by the unicode
|
||||
standard. All characters are rendered in a single cell unless the unicode
|
||||
@@ -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?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
@@ -66,15 +48,43 @@ terminfo files to the server::
|
||||
kitty +kitten ssh myserver
|
||||
|
||||
If for some reason that does not work (typically because the server is using a
|
||||
a non POSIX compliant shell), you can use the following one-liner instead (it
|
||||
non POSIX compliant shell), you can use the following one-liner instead (it
|
||||
is slower as it needs to ssh into the server twice, but will work with most
|
||||
servers)::
|
||||
|
||||
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
|
||||
If you are behind a proxy (like Balabit) that prevents this, you must redirect the
|
||||
1st command to a file, copy that to the server and run ``tic`` manually. If you
|
||||
connect to a server, embedded or Android system that doesn't have ``tic``, copy over
|
||||
your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`.
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically when connecting to a server, so that all
|
||||
terminals work transparently.
|
||||
have ssh do this automatically, if possible, when connecting to a server, so that
|
||||
all terminals work transparently.
|
||||
|
||||
|
||||
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
Make sure the TERM environment variable, is ``xterm-kitty``. And either the
|
||||
TERMINFO environment variable points to a directory containing :file:`x/xterm-kitty`
|
||||
or that file is under :file:`~/.terminfo/x/`.
|
||||
|
||||
Note that ``sudo`` might remove TERMINFO. Then setting it at the shell prompt can
|
||||
be too late, because command line editing may not be reinitialized. In that case
|
||||
you can either ask ``sudo`` to set it or if that is not supported, insert an ``env``
|
||||
command before starting the shell, or, if not possible, after sudo start another
|
||||
Shell providing the right terminfo path::
|
||||
|
||||
sudo … TERMINFO=$HOME/.terminfo bash -i
|
||||
sudo … env TERMINFO=$HOME/.terminfo bash -i
|
||||
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
|
||||
|
||||
If you have double width characters in your prompt, you may also need to
|
||||
explicitly set a UTF-8 locale, like::
|
||||
|
||||
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
|
||||
|
||||
How do I change the colors in a running kitty instance?
|
||||
|
||||
@@ -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,7 +201,11 @@ Value of `t` Meaning
|
||||
================== ============
|
||||
``d`` Direct (the data is transmitted within the escape code itself)
|
||||
``f`` A simple file
|
||||
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data
|
||||
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data. For security reasons
|
||||
the terminal emulator should only delete the file if it
|
||||
is in a known temporary directory, such as :file:`/tmp`,
|
||||
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
|
||||
specific temporary directories.
|
||||
``s`` A `POSIX shared memory object <http://man7.org/linux/man-pages/man7/shm_overview.7.html>`_.
|
||||
The terminal emulator will delete it after reading the pixel data
|
||||
================== ============
|
||||
|
||||
@@ -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
|
||||
----------------------------------
|
||||
@@ -216,6 +223,19 @@ example, in the Tall layout you can make the first window wider/narrower, but
|
||||
not taller/shorter. Note that what you are resizing is actually not a window,
|
||||
but a row/column in the layout, all windows in that row/column will be resized.
|
||||
|
||||
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||
action, for example::
|
||||
|
||||
map ctrl+left resize_window narrower
|
||||
map ctrl+right resize_window wider
|
||||
map ctrl+up resize_window taller
|
||||
map ctrl+down resize_window shorter 3
|
||||
|
||||
The ``resize_window`` action has a second, optional argument to control
|
||||
the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
@@ -370,6 +390,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
|
||||
---------------------------------
|
||||
|
||||
@@ -68,7 +68,14 @@ function, telling kitty what kind of input your kitten would like. For example:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
def handle_result(...):
|
||||
# in main, STDIN is for the kitten process and will contain
|
||||
# the contents of the screen
|
||||
def main(args):
|
||||
return sys.stdin.read()
|
||||
|
||||
# in handle_result, STDIN is for the kitty process itself, rather
|
||||
# than the kitten process and should not be read from.
|
||||
def handle_result(args, stdin_data, target_window_id, boss):
|
||||
pass
|
||||
|
||||
handle_result.type_of_input = 'text'
|
||||
@@ -78,3 +85,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:
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include <sys/param.h> // For MAXPATHLEN
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
||||
#define NSEventMaskKeyUp NSKeyUpMask
|
||||
#define NSEventMaskKeyDown NSKeyDownMask
|
||||
#define NSEventModifierFlagCommand NSCommandKeyMask
|
||||
#endif
|
||||
|
||||
// Change to our application bundle's resources directory, if present
|
||||
//
|
||||
@@ -274,17 +278,21 @@ static GLFWbool initializeTIS(void)
|
||||
return updateUnicodeDataNS();
|
||||
}
|
||||
|
||||
@interface GLFWLayoutListener : NSObject
|
||||
@interface GLFWHelper : NSObject
|
||||
@end
|
||||
|
||||
@implementation GLFWLayoutListener
|
||||
@implementation GLFWHelper
|
||||
|
||||
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
|
||||
{
|
||||
updateUnicodeDataNS();
|
||||
}
|
||||
|
||||
@end
|
||||
- (void)doNothing:(id)object
|
||||
{
|
||||
}
|
||||
|
||||
@end // GLFWHelper
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -294,13 +302,53 @@ static GLFWbool initializeTIS(void)
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
|
||||
_glfw.ns.helper = [[GLFWHelper alloc] init];
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
||||
toTarget:_glfw.ns.helper
|
||||
withObject:nil];
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||
{
|
||||
NSEventModifierFlags modifierFlags = [event modifierFlags];
|
||||
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[NSApp keyWindow].contentView keyDown:event];
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||
{
|
||||
NSEventModifierFlags modifierFlags = [event modifierFlags];
|
||||
if ([event modifierFlags] & NSEventModifierFlagCommand) {
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
[[NSApp keyWindow] sendEvent:event];
|
||||
}
|
||||
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[NSApp keyWindow].contentView keyUp:event];
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
_glfw.ns.keyUpMonitor =
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
|
||||
handler:keyup_block];
|
||||
_glfw.ns.keyDownMonitor =
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
||||
handler:keydown_block];
|
||||
if (_glfw.hints.init.ns.chdir)
|
||||
changeToResourcesDirectory();
|
||||
|
||||
_glfw.ns.listener = [[GLFWLayoutListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:_glfw.ns.listener
|
||||
addObserver:_glfw.ns.helper
|
||||
selector:@selector(selectedKeyboardInputSourceChanged:)
|
||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||
object:nil];
|
||||
@@ -345,17 +393,21 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.ns.delegate = nil;
|
||||
}
|
||||
|
||||
if (_glfw.ns.listener)
|
||||
if (_glfw.ns.helper)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:_glfw.ns.listener
|
||||
removeObserver:_glfw.ns.helper
|
||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:_glfw.ns.listener];
|
||||
[_glfw.ns.listener release];
|
||||
_glfw.ns.listener = nil;
|
||||
removeObserver:_glfw.ns.helper];
|
||||
[_glfw.ns.helper release];
|
||||
_glfw.ns.helper = nil;
|
||||
}
|
||||
if (_glfw.ns.keyUpMonitor)
|
||||
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
|
||||
if (_glfw.ns.keyDownMonitor)
|
||||
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
|
||||
|
||||
free(_glfw.ns.clipboardString);
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
|
||||
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
|
||||
@@ -489,6 +489,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
}
|
||||
|
||||
free(values);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
@@ -523,4 +524,3 @@ GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
|
||||
return monitor->ns.displayID;
|
||||
}
|
||||
|
||||
|
||||
8
glfw/cocoa_platform.h
vendored
8
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
|
||||
|
||||
@@ -120,7 +118,9 @@ typedef struct _GLFWlibraryNS
|
||||
TISInputSourceRef inputSource;
|
||||
IOHIDManagerRef hidManager;
|
||||
id unicodeData;
|
||||
id listener;
|
||||
id helper;
|
||||
id keyUpMonitor;
|
||||
id keyDownMonitor;
|
||||
|
||||
char keyName[64];
|
||||
char text[256];
|
||||
|
||||
@@ -47,9 +47,13 @@
|
||||
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
|
||||
#define NSEventMaskAny NSAnyEventMask
|
||||
#define NSEventTypeApplicationDefined NSApplicationDefined
|
||||
#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
|
||||
//
|
||||
@@ -307,6 +311,7 @@ static int translateKey(unsigned int key, GLFWbool apply_keymap)
|
||||
K(';', SEMICOLON);
|
||||
K('[', LEFT_BRACKET);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('+', PLUS);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('\\', BACKSLASH);
|
||||
#undef K
|
||||
@@ -339,6 +344,8 @@ static NSUInteger translateKeyToModifierFlag(int key)
|
||||
case GLFW_KEY_LEFT_SUPER:
|
||||
case GLFW_KEY_RIGHT_SUPER:
|
||||
return NSEventModifierFlagCommand;
|
||||
case GLFW_KEY_CAPS_LOCK:
|
||||
return NSEventModifierFlagCapsLock;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -450,6 +457,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 +562,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 +599,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)
|
||||
@@ -600,6 +627,11 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event
|
||||
{
|
||||
return NO; // changed by Kovid, to follow cocoa platform conventions
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
_glfwInputMouseClick(window,
|
||||
@@ -828,28 +860,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,52 +915,64 @@ 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);
|
||||
switch([event momentumPhase]) {
|
||||
case NSEventPhaseBegan:
|
||||
flags |= (1 << 1); break;
|
||||
case NSEventPhaseStationary:
|
||||
flags |= (2 << 1); break;
|
||||
case NSEventPhaseChanged:
|
||||
flags |= (3 << 1); break;
|
||||
case NSEventPhaseEnded:
|
||||
flags |= (4 << 1); break;
|
||||
case NSEventPhaseCancelled:
|
||||
flags |= (5 << 1); break;
|
||||
case NSEventPhaseMayBegin:
|
||||
flags |= (6 << 1); break;
|
||||
case NSEventPhaseNone:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
}
|
||||
|
||||
- (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 +980,6 @@ is_ascii_control_char(char x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [markedText length] > 0;
|
||||
@@ -1059,67 +1092,6 @@ is_ascii_control_char(char x) {
|
||||
@end
|
||||
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// GLFW application class
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
@interface GLFWApplication : NSApplication
|
||||
{
|
||||
NSArray* nibObjects;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GLFWApplication
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
NSEventType etype = [event type];
|
||||
NSEventModifierFlags flags;
|
||||
switch(etype) {
|
||||
case NSEventTypeKeyUp:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (flags & NSEventModifierFlagCommand) {
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
[[self keyWindow] sendEvent:event];
|
||||
return;
|
||||
}
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyUp:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NSEventTypeKeyDown:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyDown:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
|
||||
// No-op thread entry point
|
||||
//
|
||||
- (void)doNothing:(id)object
|
||||
{
|
||||
}
|
||||
|
||||
- (void)loadMainMenu
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Set up the menu bar (manually)
|
||||
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
|
||||
// could go away at any moment, lots of stuff that really should be
|
||||
@@ -1229,32 +1201,9 @@ static void createMenuBar(void)
|
||||
//
|
||||
static GLFWbool initializeAppKit(void)
|
||||
{
|
||||
if (NSApp)
|
||||
if (_glfw.ns.delegate)
|
||||
return GLFW_TRUE;
|
||||
|
||||
// Implicitly create shared NSApplication instance
|
||||
[GLFWApplication sharedApplication];
|
||||
|
||||
// Make Cocoa enter multi-threaded mode
|
||||
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
||||
toTarget:NSApp
|
||||
withObject:nil];
|
||||
|
||||
if (_glfw.hints.init.ns.menubar)
|
||||
{
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Menu bar setup must go between sharedApplication above and
|
||||
// finishLaunching below, in order to properly emulate the behavior
|
||||
// of NSApplicationMain
|
||||
|
||||
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
|
||||
[NSApp loadMainMenu];
|
||||
else
|
||||
createMenuBar();
|
||||
}
|
||||
|
||||
// There can only be one application delegate, but we allocate it the
|
||||
// first time a window is created to keep all window code in this file
|
||||
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
|
||||
@@ -1264,15 +1213,29 @@ static GLFWbool initializeAppKit(void)
|
||||
"Cocoa: Failed to create application delegate");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
[NSApp setDelegate:_glfw.ns.delegate];
|
||||
|
||||
if (_glfw.hints.init.ns.menubar)
|
||||
{
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Menu bar setup must go between sharedApplication above and
|
||||
// finishLaunching below, in order to properly emulate the behavior
|
||||
// of NSApplicationMain
|
||||
|
||||
// disabled by Kovid
|
||||
/* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */
|
||||
/* [NSApp loadMainMenu]; */
|
||||
/* else */
|
||||
createMenuBar();
|
||||
}
|
||||
|
||||
[NSApp run];
|
||||
|
||||
// Press and Hold prevents some keys from emitting repeated characters
|
||||
NSDictionary* defaults =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],
|
||||
@"ApplePressAndHoldEnabled",
|
||||
nil];
|
||||
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1569,6 +1532,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];
|
||||
@@ -1779,9 +1747,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -1791,7 +1756,7 @@ void _glfwPlatformPollEvents(void)
|
||||
if (event == nil)
|
||||
break;
|
||||
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
}
|
||||
|
||||
[_glfw.ns.autoreleasePool drain];
|
||||
@@ -1807,7 +1772,7 @@ void _glfwPlatformWaitEvents(void)
|
||||
untilDate:[NSDate distantFuture]
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
@@ -1819,8 +1784,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();
|
||||
}
|
||||
@@ -1923,8 +1887,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
NSImage* native;
|
||||
NSBitmapImageRep* rep;
|
||||
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||
if (native == nil)
|
||||
return GLFW_FALSE;
|
||||
@@ -1940,7 +1902,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||
bytesPerRow:src->width * 4
|
||||
bitsPerPixel:32];
|
||||
if (rep == nil)
|
||||
@@ -1961,8 +1923,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
cursor->ns.object = [NSCursor arrowCursor];
|
||||
@@ -2002,26 +1962,24 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
|
||||
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard declareTypes:types owner:nil];
|
||||
[pasteboard declareTypes:@[NSPasteboardTypeString] 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,
|
||||
@@ -2205,6 +2163,7 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
|
||||
K(';', SEMICOLON);
|
||||
K('[', LEFT_BRACKET);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('+', PLUS);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('\\', BACKSLASH);
|
||||
|
||||
|
||||
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__':
|
||||
|
||||
45
glfw/glfw3.h
vendored
45
glfw/glfw3.h
vendored
@@ -407,6 +407,7 @@ extern "C" {
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
||||
#define GLFW_KEY_PLUS 163
|
||||
|
||||
/* Function keys */
|
||||
#define GLFW_KEY_ESCAPE 256
|
||||
@@ -1379,7 +1380,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] xoffset The scroll offset along the x-axis.
|
||||
* @param[in] yoffset The scroll offset along the y-axis.
|
||||
* @param[in] flags A bit-mask providing extra data about the event. flags & 1 will be true if and only if the offset values are "high-precision". Typically pixel values. Otherwise the offset values are number of lines.
|
||||
* @param[in] flags A bit-mask providing extra data about the event.
|
||||
* flags & 1 will be true if and only if the offset values are "high-precision".
|
||||
* Typically pixel values. Otherwise the offset values are number of lines.
|
||||
* (flags >> 1) & 7 will have value 1 for the start of momentum scrolling,
|
||||
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
@@ -2121,9 +2128,9 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor);
|
||||
|
||||
/*! @brief Generates a gamma ramp and sets it for the specified monitor.
|
||||
*
|
||||
* This function generates a 256-element gamma ramp from the specified exponent
|
||||
* and then calls @ref glfwSetGammaRamp with it. The value must be a finite
|
||||
* number greater than zero.
|
||||
* This function generates an appropriately sized gamma ramp from the
|
||||
* specified exponent and then calls @ref glfwSetGammaRamp with it. The value
|
||||
* must be a finite number greater than zero.
|
||||
*
|
||||
* The software controlled gamma ramp is applied _in addition_ to the hardware
|
||||
* gamma correction, which today is usually an approximation of sRGB gamma.
|
||||
@@ -2202,8 +2209,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor);
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark Gamma ramp sizes other than 256 are not supported by all platforms
|
||||
* or graphics hardware.
|
||||
* @remark The size of the specified gamma ramp should match the size of the
|
||||
* current ramp for that monitor.
|
||||
*
|
||||
* @remark @win32 The gamma ramp size must be 256.
|
||||
*
|
||||
@@ -2930,6 +2937,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.
|
||||
|
||||
17
glfw/input.c
vendored
17
glfw/input.c
vendored
@@ -487,6 +487,7 @@ const char* _glfwGetKeyName(int key)
|
||||
case GLFW_KEY_SLASH: return "SLASH";
|
||||
case GLFW_KEY_WORLD_1: return "WORLD 1";
|
||||
case GLFW_KEY_WORLD_2: return "WORLD 2";
|
||||
case GLFW_KEY_PLUS: return "PLUS";
|
||||
|
||||
// Function keys
|
||||
case GLFW_KEY_ESCAPE: return "ESCAPE";
|
||||
@@ -1375,6 +1376,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);
|
||||
|
||||
12
glfw/internal.h
vendored
12
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;
|
||||
|
||||
@@ -624,11 +619,15 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
|
||||
float* xscale, float* yscale);
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
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
glfw/monitor.c
vendored
27
glfw/monitor.c
vendored
@@ -427,11 +427,11 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle)
|
||||
|
||||
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
{
|
||||
int i;
|
||||
unsigned short values[256];
|
||||
unsigned int i;
|
||||
unsigned short* values;
|
||||
GLFWgammaramp ramp;
|
||||
const GLFWgammaramp* original;
|
||||
assert(handle != NULL);
|
||||
assert(gamma == gamma);
|
||||
assert(gamma >= 0.f);
|
||||
assert(gamma <= FLT_MAX);
|
||||
|
||||
@@ -442,13 +442,18 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
|
||||
return;
|
||||
}
|
||||
original = glfwGetGammaRamp(handle);
|
||||
if (!original)
|
||||
return;
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
values = calloc(original->size, sizeof(unsigned short));
|
||||
|
||||
for (i = 0; i < original->size; i++)
|
||||
{
|
||||
float value;
|
||||
|
||||
// Calculate intensity
|
||||
value = i / 255.f;
|
||||
value = i / (float) (original->size - 1);
|
||||
// Apply gamma curve
|
||||
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
||||
|
||||
@@ -462,9 +467,10 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
ramp.red = values;
|
||||
ramp.green = values;
|
||||
ramp.blue = values;
|
||||
ramp.size = 256;
|
||||
ramp.size = original->size;
|
||||
|
||||
glfwSetGammaRamp(handle, &ramp);
|
||||
free(values);
|
||||
}
|
||||
|
||||
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
||||
@@ -475,7 +481,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
_glfwFreeGammaArrays(&monitor->currentRamp);
|
||||
_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp);
|
||||
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp))
|
||||
return NULL;
|
||||
|
||||
return &monitor->currentRamp;
|
||||
}
|
||||
@@ -501,8 +508,10 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp)
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
if (!monitor->originalRamp.size)
|
||||
_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp);
|
||||
{
|
||||
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp))
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwPlatformSetGammaRamp(monitor, ramp);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
4
glfw/null_monitor.c
vendored
4
glfw/null_monitor.c
vendored
@@ -58,11 +58,11 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
3
glfw/osmesa_context.c
vendored
3
glfw/osmesa_context.c
vendored
@@ -240,7 +240,7 @@ GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
|
||||
if (ctxconfig->forward)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"OSMesa: Foward-compatible contexts not supported");
|
||||
"OSMesa: Forward-compatible contexts not supported");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -367,4 +367,3 @@ GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
|
||||
|
||||
return window->context.osmesa.handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_monitor.c
vendored
5
glfw/win32_monitor.c
vendored
@@ -455,7 +455,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
&mode->blueBits);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
HDC dc;
|
||||
WORD values[768];
|
||||
@@ -469,6 +469,8 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
memcpy(ramp->red, values + 0, 256 * sizeof(unsigned short));
|
||||
memcpy(ramp->green, values + 256, 256 * sizeof(unsigned short));
|
||||
memcpy(ramp->blue, values + 512, 256 * sizeof(unsigned short));
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
@@ -510,4 +512,3 @@ GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return monitor->win32.publicDisplayName;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
58
glfw/wl_monitor.c
vendored
58
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -180,18 +180,17 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
*mode = monitor->modes[monitor->wl.currentMode];
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Gamma ramp getting not supported yet");
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Gamma ramp setting not supported yet");
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
}
|
||||
|
||||
|
||||
@@ -205,4 +204,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);
|
||||
}
|
||||
}
|
||||
|
||||
14
glfw/x11_monitor.c
vendored
14
glfw/x11_monitor.c
vendored
@@ -433,7 +433,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
||||
{
|
||||
@@ -449,6 +449,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
|
||||
|
||||
XRRFreeGamma(gamma);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else if (_glfw.x11.vidmode.available)
|
||||
{
|
||||
@@ -460,6 +461,13 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
XF86VidModeGetGammaRamp(_glfw.x11.display,
|
||||
_glfw.x11.screen,
|
||||
ramp->size, ramp->red, ramp->green, ramp->blue);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +500,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
(unsigned short*) ramp->green,
|
||||
(unsigned short*) ramp->blue);
|
||||
}
|
||||
else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
2
glfw/xkb_glfw.c
vendored
2
glfw/xkb_glfw.c
vendored
@@ -46,6 +46,7 @@
|
||||
S(bracketleft, LEFT_BRACKET); \
|
||||
S(backslash, BACKSLASH); \
|
||||
S(bracketright, RIGHT_BRACKET); \
|
||||
S(plus, PLUS); \
|
||||
S(grave, GRAVE_ACCENT); \
|
||||
S(Escape, ESCAPE); \
|
||||
S(Return, ENTER); \
|
||||
@@ -255,6 +256,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
133
kitty/boss.py
133
kitty/boss.py
@@ -10,6 +10,7 @@ from functools import partial
|
||||
from gettext import gettext as _
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from .child import cached_process_data
|
||||
from .cli import create_opts, parse_args
|
||||
from .conf.utils import to_cmdline
|
||||
from .config import initial_window_size_func, prepare_config_file_for_editing
|
||||
@@ -32,9 +33,9 @@ from .rgb import Color, color_from_int
|
||||
from .session import create_session
|
||||
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
||||
from .utils import (
|
||||
get_editor, get_primary_selection, log_error, open_url, parse_address_spec,
|
||||
remove_socket_file, safe_print, set_primary_selection, single_instance,
|
||||
startup_notification_handler
|
||||
get_editor, get_primary_selection, is_path_in_temp_dir, log_error,
|
||||
open_url, parse_address_spec, remove_socket_file, safe_print,
|
||||
set_primary_selection, single_instance, startup_notification_handler
|
||||
)
|
||||
|
||||
|
||||
@@ -48,21 +49,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: # {{{
|
||||
@@ -98,6 +107,7 @@ class Boss:
|
||||
set_draw_minimal_borders(opts)
|
||||
self.window_id_map = WeakValueDictionary()
|
||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||
self.startup_cursor_text_color = opts.cursor_text_color
|
||||
self.pending_sequences = None
|
||||
self.cached_values = cached_values
|
||||
self.os_window_map = {}
|
||||
@@ -141,14 +151,15 @@ class Boss:
|
||||
return os_window_id
|
||||
|
||||
def list_os_windows(self):
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
with cached_process_data():
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
|
||||
@property
|
||||
def all_tab_managers(self):
|
||||
@@ -236,9 +247,16 @@ 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
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
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_for_cwd if w is not None else None
|
||||
self._new_os_window(args, cwd_from)
|
||||
|
||||
def add_child(self, window):
|
||||
@@ -362,6 +380,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 +466,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 +562,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 +577,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 +656,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 +821,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,12 +850,12 @@ 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)
|
||||
window = self.active_window
|
||||
cwd_from = window.child.pid if window else None
|
||||
cwd_from = window.child.pid_for_cwd if window else None
|
||||
|
||||
def create_window():
|
||||
return self.special_window_for_cmd(
|
||||
@@ -830,7 +873,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,8 +922,8 @@ class Boss:
|
||||
self._create_tab(args)
|
||||
|
||||
def new_tab_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._create_tab(args, cwd_from=cwd_from)
|
||||
|
||||
def _new_window(self, args, cwd_from=None):
|
||||
@@ -890,10 +938,10 @@ 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
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._new_window(args, cwd_from=cwd_from)
|
||||
|
||||
def move_tab_forward(self):
|
||||
@@ -906,11 +954,20 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.move_tab(-1)
|
||||
|
||||
def patch_colors(self, spec, configured=False):
|
||||
def patch_colors(self, spec, cursor_text_color, configured=False):
|
||||
if configured:
|
||||
for k, v in spec.items():
|
||||
if hasattr(self.opts, k):
|
||||
setattr(self.opts, k, color_from_int(v))
|
||||
if cursor_text_color is not False:
|
||||
self.opts.cursor_text_color = cursor_text_color
|
||||
for tm in self.all_tab_managers:
|
||||
tm.tab_bar.patch_colors(spec)
|
||||
patch_global_colors(spec, configured)
|
||||
|
||||
def safe_delete_temp_file(self, path):
|
||||
if is_path_in_temp_dir(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
@@ -101,12 +101,12 @@ void main() {
|
||||
#else
|
||||
// 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 = alpha_blend_premul(fg.rgb, fg.a, background, bg_alpha);
|
||||
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
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
||||
#define STRIKE_SHIFT {STRIKE_SHIFT}
|
||||
#define DIM_SHIFT {DIM_SHIFT}
|
||||
#define CURSOR_TEXT_COLOR {CURSOR_TEXT_COLOR}
|
||||
|
||||
// Inputs {{{
|
||||
layout(std140) uniform CellRenderData {
|
||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
|
||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
|
||||
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
|
||||
|
||||
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||
float cursor_x, cursor_y, cursor_w;
|
||||
@@ -182,8 +181,9 @@ void main() {
|
||||
|
||||
// Cursor
|
||||
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
|
||||
foreground = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, foreground);
|
||||
decoration_fg = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, decoration_fg);
|
||||
vec3 final_cursor_text_color = mix(color_to_vec(cursor_text_color), bg, cursor_text_uses_bg);
|
||||
foreground = choose_color(cell_has_block_cursor, final_cursor_text_color, foreground);
|
||||
decoration_fg = choose_color(cell_has_block_cursor, final_cursor_text_color, decoration_fg);
|
||||
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
@@ -372,7 +372,7 @@ parse_input(ChildMonitor *self) {
|
||||
}
|
||||
|
||||
if (UNLIKELY(kill_signal_received)) {
|
||||
global_state.close_all_windows = true;
|
||||
global_state.terminate = true;
|
||||
} else {
|
||||
count = self->count;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -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) {
|
||||
@@ -727,7 +737,6 @@ process_pending_resizes(double now) {
|
||||
static inline void
|
||||
close_all_windows() {
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||
global_state.close_all_windows = false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -783,7 +792,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
}
|
||||
#endif
|
||||
parse_input(self);
|
||||
if (global_state.close_all_windows) close_all_windows();
|
||||
if (global_state.terminate) {
|
||||
global_state.terminate = false;
|
||||
close_all_windows();
|
||||
#ifdef __APPLE__
|
||||
request_application_quit();
|
||||
#endif
|
||||
}
|
||||
has_open_windows = process_pending_closes(self);
|
||||
}
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
@@ -864,7 +879,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 +996,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 +1016,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 +1067,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);
|
||||
|
||||
@@ -115,6 +115,9 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
environ = env;
|
||||
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
||||
// which does not reset signal handlers on its own
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
execvp(exe, argv);
|
||||
// Report the failure and exec a shell instead, so that we are not left
|
||||
// with a forked but not exec'ed process
|
||||
|
||||
@@ -4,17 +4,28 @@
|
||||
|
||||
import fcntl
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import is_macos, shell_path, terminfo_dir
|
||||
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process
|
||||
from kitty.fast_data_types import (
|
||||
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process,
|
||||
process_group_map as _process_group_map
|
||||
)
|
||||
|
||||
def cwd_of_process(pid):
|
||||
return os.path.realpath(_cwd(pid))
|
||||
|
||||
def process_group_map():
|
||||
ans = defaultdict(list)
|
||||
for pid, pgid in _process_group_map():
|
||||
ans[pgid].append(pid)
|
||||
return ans
|
||||
|
||||
else:
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
@@ -27,6 +38,46 @@ else:
|
||||
def _environ_of_process(pid):
|
||||
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
|
||||
|
||||
def process_group_map():
|
||||
ans = defaultdict(list)
|
||||
for x in os.listdir('/proc'):
|
||||
try:
|
||||
pid = int(x)
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
raw = open('/proc/' + x + '/stat', 'rb').read().decode('utf-8')
|
||||
except EnvironmentError:
|
||||
continue
|
||||
try:
|
||||
q = int(raw.split(' ', 5)[4])
|
||||
except Exception:
|
||||
continue
|
||||
ans[q].append(pid)
|
||||
return ans
|
||||
|
||||
|
||||
def processes_in_group(grp):
|
||||
gmap = getattr(process_group_map, 'cached_map', None)
|
||||
if gmap is None:
|
||||
try:
|
||||
gmap = process_group_map()
|
||||
except Exception:
|
||||
gmap = {}
|
||||
return gmap.get(grp, [])
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cached_process_data():
|
||||
try:
|
||||
process_group_map.cached_map = process_group_map()
|
||||
except Exception:
|
||||
process_group_map.cached_map = {}
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
process_group_map.cached_map = None
|
||||
|
||||
|
||||
def parse_environ_block(data):
|
||||
"""Parse a C environ block of environment variables into a dictionary."""
|
||||
@@ -142,6 +193,28 @@ class Child:
|
||||
os.close(self.terminal_ready_fd)
|
||||
self.terminal_ready_fd = -1
|
||||
|
||||
@property
|
||||
def foreground_processes(self):
|
||||
try:
|
||||
pgrp = os.tcgetpgrp(self.child_fd)
|
||||
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||
|
||||
def process_desc(pid):
|
||||
ans = {'pid': pid}
|
||||
try:
|
||||
ans['cmdline'] = cmdline_of_process(pid)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ans['cwd'] = cwd_of_process(pid) or None
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
return list(map(process_desc, foreground_processes))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
try:
|
||||
@@ -162,3 +235,21 @@ class Child:
|
||||
return cwd_of_process(self.pid)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@property
|
||||
def pid_for_cwd(self):
|
||||
try:
|
||||
pgrp = os.tcgetpgrp(self.child_fd)
|
||||
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||
if len(foreground_processes) == 1:
|
||||
return foreground_processes[0]
|
||||
except Exception:
|
||||
pass
|
||||
return self.pid
|
||||
|
||||
@property
|
||||
def foreground_cwd(self):
|
||||
try:
|
||||
return cwd_of_process(self.pid_for_cwd) or None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -126,7 +126,7 @@ def set_font_size(boss, window, payload):
|
||||
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --match` option'
|
||||
' the text will be sent to all matched windows. By default, text is sent to'
|
||||
' only the currently active window.',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
||||
--stdin
|
||||
type=bool-set
|
||||
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
||||
@@ -142,7 +142,7 @@ are sent as is, not interpreted for escapes.
|
||||
)
|
||||
def cmd_send_text(global_opts, opts, args):
|
||||
limit = 1024
|
||||
ret = {'match': opts.match, 'is_binary': False}
|
||||
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
|
||||
|
||||
def pipe():
|
||||
ret['is_binary'] = True
|
||||
@@ -209,6 +209,13 @@ def send_text(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if payload['match_tab']:
|
||||
windows = []
|
||||
tabs = tuple(boss.match_tabs(payload['match_tab']))
|
||||
if not tabs:
|
||||
raise MatchError(payload['match_tab'], 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
|
||||
for window in windows:
|
||||
if window is not None:
|
||||
@@ -684,7 +691,7 @@ this option, any color arguments are ignored and --configured and --all are impl
|
||||
)
|
||||
def cmd_set_colors(global_opts, opts, args):
|
||||
from .rgb import color_as_int, Color
|
||||
colors = {}
|
||||
colors, cursor_text_color = {}, False
|
||||
if not opts.reset:
|
||||
for spec in args:
|
||||
if '=' in spec:
|
||||
@@ -692,15 +699,17 @@ def cmd_set_colors(global_opts, opts, args):
|
||||
else:
|
||||
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||
colors.update(parse_config(f))
|
||||
cursor_text_color = colors.pop('cursor_text_color', False)
|
||||
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||
return {
|
||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
|
||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
|
||||
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
|
||||
}
|
||||
|
||||
|
||||
def set_colors(boss, window, payload):
|
||||
from .rgb import color_as_int
|
||||
from .rgb import color_as_int, Color
|
||||
if payload['all']:
|
||||
windows = tuple(boss.all_windows)
|
||||
else:
|
||||
@@ -717,10 +726,14 @@ def set_colors(boss, window, payload):
|
||||
windows += tuple(tab)
|
||||
if payload['reset']:
|
||||
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
||||
payload['cursor_text_color'] = boss.startup_cursor_text_color
|
||||
profiles = tuple(w.screen.color_profile for w in windows)
|
||||
from .fast_data_types import patch_color_profiles
|
||||
patch_color_profiles(payload['colors'], profiles, payload['configured'])
|
||||
boss.patch_colors(payload['colors'], payload['configured'])
|
||||
cursor_text_color = payload.get('cursor_text_color', False)
|
||||
if isinstance(cursor_text_color, (tuple, list, Color)):
|
||||
cursor_text_color = color_as_int(Color(*cursor_text_color))
|
||||
patch_color_profiles(payload['colors'], cursor_text_color, profiles, payload['configured'])
|
||||
boss.patch_colors(payload['colors'], cursor_text_color, payload['configured'])
|
||||
default_bg_changed = 'background' in payload['colors']
|
||||
for w in windows:
|
||||
if default_bg_changed:
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
@@ -99,6 +100,21 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
// Implementation of applicationDockMenu: for the app delegate
|
||||
static NSMenu *dockMenu = nil;
|
||||
static NSMenu *
|
||||
get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
||||
if (!dockMenu) {
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
dockMenu = [[NSMenu alloc] init];
|
||||
NSMenuItem *newWindowItem = [dockMenu addItemWithTitle:@"New OS window"
|
||||
action:@selector(new_os_window:)
|
||||
keyEquivalent:@""];
|
||||
[newWindowItem setTarget:global_menu_target];
|
||||
}
|
||||
return dockMenu;
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_create_global_menu(void) {
|
||||
NSString* app_name = find_app_name();
|
||||
@@ -183,6 +199,12 @@ cocoa_create_global_menu(void) {
|
||||
}
|
||||
|
||||
[bar release];
|
||||
|
||||
class_addMethod(
|
||||
object_getClass([NSApp delegate]),
|
||||
@selector(applicationDockMenu:),
|
||||
(IMP)get_dock_menu,
|
||||
"@@:@");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -292,6 +314,12 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup() {
|
||||
if (dockMenu) [dockMenu release];
|
||||
dockMenu = nil;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||
@@ -301,5 +329,9 @@ static PyMethodDef module_methods[] = {
|
||||
bool
|
||||
init_cocoa(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
if (Py_AtExit(cleanup) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
|
||||
static PyObject*
|
||||
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
|
||||
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
|
||||
if (!PyArg_ParseTuple(args, "O!OO!p", &PyDict_Type, &spec, &cursor_text_color, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||
char key[32] = {0};
|
||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
||||
@@ -131,6 +131,22 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
||||
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
||||
#undef S
|
||||
if (cursor_text_color != Py_False) {
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
||||
self->overridden.cursor_text_color = 0x111111;
|
||||
self->overridden.cursor_text_uses_bg = 3;
|
||||
if (cursor_text_color != Py_None) {
|
||||
self->overridden.cursor_text_color = (PyLong_AsUnsignedLong(cursor_text_color) << 8) | 2;
|
||||
self->overridden.cursor_text_uses_bg = 1;
|
||||
}
|
||||
if (change_configured) {
|
||||
self->configured.cursor_text_color = self->overridden.cursor_text_color;
|
||||
self->configured.cursor_text_uses_bg = self->overridden.cursor_text_uses_bg;
|
||||
}
|
||||
self->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -149,6 +165,14 @@ colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
||||
}
|
||||
}
|
||||
|
||||
float
|
||||
cursor_text_as_bg(ColorProfile *self) {
|
||||
if (self->overridden.cursor_text_uses_bg & 1) {
|
||||
return self->overridden.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||
}
|
||||
return self->configured.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
||||
@@ -174,7 +198,7 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
||||
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
|
||||
}}
|
||||
D(default_fg, foreground); D(default_bg, background);
|
||||
D(cursor_color, cursor); D(highlight_fg, selection_foreground);
|
||||
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
|
||||
D(highlight_bg, selection_background);
|
||||
|
||||
#undef D
|
||||
@@ -236,7 +260,11 @@ set_color(ColorProfile *self, PyObject *args) {
|
||||
static PyObject*
|
||||
set_configured_colors(ColorProfile *self, PyObject *args) {
|
||||
#define set_configured_colors_doc "Set the configured colors"
|
||||
if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "II|IIIII",
|
||||
&(self->configured.default_fg), &(self->configured.default_bg),
|
||||
&(self->configured.cursor_color), &(self->configured.cursor_text_color), &(self->configured.cursor_text_uses_bg),
|
||||
&(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
||||
self->dirty = true;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -286,6 +314,7 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
CGETSET(default_fg)
|
||||
CGETSET(default_bg)
|
||||
CGETSET(cursor_color)
|
||||
CGETSET(cursor_text_color)
|
||||
CGETSET(highlight_fg)
|
||||
CGETSET(highlight_bg)
|
||||
|
||||
@@ -293,6 +322,7 @@ static PyGetSetDef getsetters[] = {
|
||||
GETSET(default_fg)
|
||||
GETSET(default_bg)
|
||||
GETSET(cursor_color)
|
||||
GETSET(cursor_text_color)
|
||||
GETSET(highlight_fg)
|
||||
GETSET(highlight_bg)
|
||||
{NULL} /* Sentinel */
|
||||
|
||||
@@ -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 = {
|
||||
@@ -115,7 +115,7 @@ def float_parse(func, rest):
|
||||
|
||||
@func_with_args('change_font_size')
|
||||
def parse_change_font_size(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('Invalid change_font_size specification: {}, treating it as default'.format(rest))
|
||||
args = [True, None, 0]
|
||||
@@ -125,13 +125,13 @@ def parse_change_font_size(func, rest):
|
||||
if amt[0] in '+-':
|
||||
args[1] = amt[0]
|
||||
amt = amt[1:]
|
||||
args[2] = float(amt)
|
||||
args[2] = float(amt.strip())
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('clear_terminal')
|
||||
def clear_terminal(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('clear_terminal needs two arguments, using defaults')
|
||||
args = ['reset', 'active']
|
||||
@@ -150,6 +150,27 @@ def neighboring_window(func, rest):
|
||||
return func, [rest]
|
||||
|
||||
|
||||
@func_with_args('resize_window')
|
||||
def resize_window(func, rest):
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) > 2:
|
||||
log_error('resize_window needs one or two arguments, using defaults')
|
||||
args = ['wider', 1]
|
||||
else:
|
||||
quality = vals[0].lower()
|
||||
if quality not in ('taller', 'shorter', 'wider', 'narrower'):
|
||||
log_error('Invalid quality specification: {}'.format(quality))
|
||||
quality = 'wider'
|
||||
increment = 1
|
||||
if len(vals) == 2:
|
||||
try:
|
||||
increment = int(vals[1])
|
||||
except Exception:
|
||||
log_error('Invalid increment specification: {}'.format(vals[1]))
|
||||
args = [quality, increment]
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('move_window')
|
||||
def move_window(func, rest):
|
||||
rest = rest.lower()
|
||||
@@ -173,8 +194,18 @@ 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)
|
||||
parts = action.strip().split(' ', 1)
|
||||
func = parts[0]
|
||||
if len(parts) == 1:
|
||||
return KeyAction(func, ())
|
||||
@@ -185,7 +216,8 @@ def parse_key_action(action):
|
||||
func, args = parser(func, rest)
|
||||
except Exception:
|
||||
log_error('Ignoring invalid key action: {}'.format(action))
|
||||
return KeyAction(func, args)
|
||||
else:
|
||||
return KeyAction(func, args)
|
||||
|
||||
|
||||
all_key_actions = set()
|
||||
@@ -302,6 +334,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 +367,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 +387,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 +592,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
|
||||
@@ -168,14 +169,14 @@ Similarly, to switch back to the previous layout::
|
||||
''')],
|
||||
'shortcuts.fonts': [
|
||||
_('Font sizes'), _('''\
|
||||
You can change the font size for all top-level kitty windows at a time
|
||||
You can change the font size for all top-level kitty OS windows at a time
|
||||
or only the current one.
|
||||
'''), _('''\
|
||||
To setup shortcuts for specific font sizes::
|
||||
|
||||
map kitty_mod+f6 change_font_size all 10.0
|
||||
|
||||
To setup shortcuts to change only the current window's font size::
|
||||
To setup shortcuts to change only the current OS window's font size::
|
||||
|
||||
map kitty_mod+f6 change_font_size current 10.0
|
||||
''')],
|
||||
@@ -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.
|
||||
'''))
|
||||
@@ -580,6 +608,13 @@ entries to this list.
|
||||
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
|
||||
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
||||
|
||||
o('tab_title_template', '{title}', long_text=_('''
|
||||
A template to render the tab title. The default just renders
|
||||
the title. If you wish to include the tab-index as well,
|
||||
use something like: :code:`{index}: {title}`. Useful
|
||||
if you have shortcuts mapped for :code:`goto_tab N`.
|
||||
'''))
|
||||
|
||||
o('active_tab_foreground', '#000', option_type=to_color, long_text=_('''
|
||||
Tab bar colors and styles'''))
|
||||
o('active_tab_background', '#eee', option_type=to_color)
|
||||
@@ -690,7 +725,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 +759,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 +790,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
|
||||
@@ -818,7 +848,9 @@ g('shortcuts.clipboard') # {{{
|
||||
if is_macos:
|
||||
k('copy_to_clipboard', 'cmd+c', 'copy_to_clipboard', _('Copy to clipboard'), add_to_docs=False)
|
||||
k('paste_from_clipboard', 'cmd+v', 'paste_from_clipboard', _('Paste from clipboard'), add_to_docs=False)
|
||||
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'))
|
||||
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'), long_text=_('''
|
||||
There is also a :code:`copy_or_interrupt` action that can be optionally mapped to :kbd:`Ctrl+c`.
|
||||
It will copy only if there is a selection and send an interrupt otherwise.'''))
|
||||
k('paste_from_clipboard', 'kitty_mod+v', 'paste_from_clipboard', _('Paste from clipboard'))
|
||||
k('paste_from_selection', 'kitty_mod+s', 'paste_from_selection', _('Paste from selection'))
|
||||
k('paste_from_selection', 'shift+insert', 'paste_from_selection', _('Paste from selection'))
|
||||
@@ -854,18 +886,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 +996,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, 2)
|
||||
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 *
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#define NS_TO_US (1000)
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libproc.h>
|
||||
#include <mach/mach_time.h>
|
||||
static mach_timebase_info_data_t timebase = {0};
|
||||
|
||||
@@ -54,6 +55,25 @@ user_cache_dir() {
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
process_group_map() {
|
||||
int num_of_processes = proc_listallpids(NULL, 0);
|
||||
size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024);
|
||||
pid_t *buf = malloc(bufsize);
|
||||
if (!buf) return PyErr_NoMemory();
|
||||
num_of_processes = proc_listallpids(buf, (int)bufsize);
|
||||
PyObject *ans = PyTuple_New(num_of_processes);
|
||||
if (ans == NULL) { free(buf); return PyErr_NoMemory(); }
|
||||
for (int i = 0; i < num_of_processes; i++) {
|
||||
long pid = buf[i], pgid = getpgid(buf[i]);
|
||||
PyObject *t = Py_BuildValue("ll", pid, pgid);
|
||||
if (t == NULL) { free(buf); Py_DECREF(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, t);
|
||||
}
|
||||
free(buf);
|
||||
return ans;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <time.h>
|
||||
static inline double monotonic_() {
|
||||
@@ -177,6 +197,7 @@ static PyMethodDef module_methods[] = {
|
||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||
#ifdef __APPLE__
|
||||
METHODB(user_cache_dir, METH_NOARGS),
|
||||
METHODB(process_group_map, METH_NOARGS),
|
||||
#endif
|
||||
#ifdef WITH_PROFILER
|
||||
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
|
||||
|
||||
@@ -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;
|
||||
@@ -201,7 +210,7 @@ typedef struct {
|
||||
} CursorRenderInfo;
|
||||
|
||||
typedef struct {
|
||||
color_type default_fg, default_bg, cursor_color, highlight_fg, highlight_bg;
|
||||
color_type default_fg, default_bg, cursor_color, cursor_text_color, cursor_text_uses_bg, highlight_fg, highlight_bg;
|
||||
} DynamicColor;
|
||||
|
||||
typedef struct {
|
||||
@@ -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);
|
||||
@@ -275,6 +285,7 @@ bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
|
||||
bool set_iutf8(int, bool);
|
||||
|
||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||
float cursor_text_as_bg(ColorProfile *self);
|
||||
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
||||
void colorprofile_push_dynamic_colors(ColorProfile*);
|
||||
void colorprofile_pop_dynamic_colors(ColorProfile*);
|
||||
|
||||
@@ -31,10 +31,15 @@ static PyObject*
|
||||
init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) {
|
||||
static bool done = false;
|
||||
static const char* libname = "libstartup-notification-1.so";
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
static const char* libname2 = "libstartup-notification-1.so.0";
|
||||
static const char* libname3 = "libstartup-notification-1.so.0.0.0";
|
||||
if (!done) {
|
||||
done = true;
|
||||
|
||||
libsn_handle = dlopen(libname, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) {
|
||||
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror());
|
||||
return NULL;
|
||||
|
||||
@@ -24,11 +24,12 @@ pyspacing(int val) {
|
||||
#undef S
|
||||
}
|
||||
|
||||
|
||||
static inline PyObject*
|
||||
pattern_as_dict(FcPattern *pat) {
|
||||
PyObject *ans = PyDict_New();
|
||||
if (ans == NULL) return NULL;
|
||||
#define PS(x) PyUnicode_FromString((char*)x)
|
||||
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
|
||||
#define G(type, get, which, conv, name) { \
|
||||
type out; PyObject *p; \
|
||||
if (get(pat, which, 0, &out) == FcResultMatch) { \
|
||||
@@ -114,6 +115,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 +147,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,46 @@ 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 = 1;
|
||||
if (cell_font_idx > 0) {
|
||||
Font *font = (fg->fonts + cell_font_idx);
|
||||
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
|
||||
|
||||
int width = get_glyph_width(font->face, glyph_id);
|
||||
desired_cells = ceilf((float)width / fg->cell_width);
|
||||
}
|
||||
|
||||
int num_spaces = 0;
|
||||
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
|
||||
&& 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;
|
||||
}
|
||||
|
||||
|
||||
27
kitty/glfw-wrapper.h
generated
27
kitty/glfw-wrapper.h
generated
@@ -166,6 +166,7 @@
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
||||
#define GLFW_KEY_PLUS 163 /* non-US #2 */
|
||||
|
||||
/* Function keys */
|
||||
#define GLFW_KEY_ESCAPE 256
|
||||
@@ -1387,7 +1388,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 +1545,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 +1853,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 +1885,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);
|
||||
|
||||
111
kitty/glfw.c
111
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);
|
||||
@@ -639,6 +650,12 @@ bool
|
||||
application_quit_requested() {
|
||||
return !application_quit_canary || glfwWindowShouldClose(application_quit_canary);
|
||||
}
|
||||
|
||||
void
|
||||
request_application_quit() {
|
||||
if (application_quit_canary)
|
||||
glfwSetWindowShouldClose(application_quit_canary, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Global functions {{{
|
||||
@@ -738,6 +755,24 @@ get_clipboard_string(PYNOARG) {
|
||||
return Py_BuildValue("s", "");
|
||||
}
|
||||
|
||||
void
|
||||
ring_audio_bell(OSWindow *w) {
|
||||
static double last_bell_at = -1;
|
||||
double now = monotonic();
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
last_bell_at = now;
|
||||
if (w->handle) {
|
||||
glfwWindowBell(w->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
ring_bell(PYNOARG) {
|
||||
OSWindow *w = current_os_window();
|
||||
ring_audio_bell(w);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_content_scale_for_window(PYNOARG) {
|
||||
OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows;
|
||||
@@ -775,17 +810,6 @@ change_os_window_state(PyObject *self UNUSED, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
ring_audio_bell(OSWindow *w) {
|
||||
static double last_bell_at = -1;
|
||||
double now = monotonic();
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
last_bell_at = now;
|
||||
if (w->handle) {
|
||||
glfwWindowBell(w->handle);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
request_window_attention(id_type kitty_window_id, bool audio_bell) {
|
||||
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
|
||||
@@ -806,9 +830,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 +908,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 +919,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 +1006,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[] = {
|
||||
@@ -981,6 +1032,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(set_default_window_icon, METH_VARARGS),
|
||||
METHODB(get_clipboard_string, METH_NOARGS),
|
||||
METHODB(get_content_scale_for_window, METH_NOARGS),
|
||||
METHODB(ring_bell, METH_NOARGS),
|
||||
METHODB(set_clipboard_string, METH_VARARGS),
|
||||
METHODB(toggle_fullscreen, METH_NOARGS),
|
||||
METHODB(change_os_window_state, METH_VARARGS),
|
||||
@@ -1078,6 +1130,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_KEY_GRAVE_ACCENT);
|
||||
ADDC(GLFW_KEY_WORLD_1);
|
||||
ADDC(GLFW_KEY_WORLD_2);
|
||||
ADDC(GLFW_KEY_PLUS);
|
||||
|
||||
// --- Function keys -----------------------------------------------------------
|
||||
ADDC(GLFW_KEY_ESCAPE);
|
||||
|
||||
@@ -368,7 +368,10 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
||||
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
||||
close(fd);
|
||||
if (tt == 't') unlink(fname);
|
||||
if (tt == 't') {
|
||||
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
|
||||
else unlink(fname);
|
||||
}
|
||||
else if (tt == 's') shm_unlink(fname);
|
||||
break;
|
||||
default:
|
||||
|
||||
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) {
|
||||
|
||||
47
kitty/key_encoding.py
generated
47
kitty/key_encoding.py
generated
@@ -127,7 +127,8 @@ ENCODING = {
|
||||
'WORLD 2': 'x',
|
||||
'X': 'p',
|
||||
'Y': 'q',
|
||||
'Z': 'r'
|
||||
'Z': 'r',
|
||||
'PLUS': 'Bi'
|
||||
}
|
||||
KEY_MAP = {
|
||||
32: 'A',
|
||||
@@ -180,6 +181,7 @@ KEY_MAP = {
|
||||
96: 'v',
|
||||
161: 'w',
|
||||
162: 'x',
|
||||
163: 'Bi',
|
||||
256: 'y',
|
||||
257: 'z',
|
||||
258: '0',
|
||||
@@ -250,10 +252,8 @@ KEY_MAP = {
|
||||
346: 'Bg',
|
||||
347: 'Bh'
|
||||
}
|
||||
|
||||
# END_ENCODING }}}
|
||||
|
||||
|
||||
text_keys = string.ascii_uppercase + string.ascii_lowercase + string.digits + '`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
|
||||
|
||||
|
||||
@@ -324,28 +324,35 @@ mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
|
||||
key_rmap = {}
|
||||
g = globals()
|
||||
config_key_map = {}
|
||||
config_mod_map = {'SHIFT': SHIFT, 'ALT': ALT, 'OPTION': ALT, '⌥': ALT, '⌘': SUPER, 'CMD': SUPER, 'SUPER': SUPER, 'CTRL': CTRL, 'CONTROL': CTRL}
|
||||
config_mod_map = {
|
||||
'SHIFT': SHIFT,
|
||||
'ALT': ALT,
|
||||
'OPTION': ALT,
|
||||
'⌥': ALT,
|
||||
'⌘': SUPER,
|
||||
'CMD': SUPER,
|
||||
'SUPER': SUPER,
|
||||
'CTRL': CTRL,
|
||||
'CONTROL': CTRL
|
||||
}
|
||||
for key_name, enc in ENCODING.items():
|
||||
key_name = key_name.replace(' ', '_')
|
||||
g[key_name] = config_key_map[key_name] = key_name
|
||||
key_rmap[enc] = key_name
|
||||
config_key_map.update({
|
||||
'`': g['GRAVE_ACCENT'],
|
||||
'-': g['MINUS'],
|
||||
'=': g['EQUAL'],
|
||||
|
||||
'[': g['LEFT_BRACKET'],
|
||||
']': g['RIGHT_BRACKET'],
|
||||
'\\': g['BACKSLASH'],
|
||||
|
||||
';': g['SEMICOLON'],
|
||||
"'": g['APOSTROPHE'],
|
||||
|
||||
',': g['COMMA'],
|
||||
'.': g['PERIOD'],
|
||||
'/': g['SLASH'],
|
||||
|
||||
'ESC': g['ESCAPE'],
|
||||
'`': g['GRAVE_ACCENT'],
|
||||
'-': g['MINUS'],
|
||||
'=': g['EQUAL'],
|
||||
'[': g['LEFT_BRACKET'],
|
||||
']': g['RIGHT_BRACKET'],
|
||||
'\\': g['BACKSLASH'],
|
||||
';': g['SEMICOLON'],
|
||||
"'": g['APOSTROPHE'],
|
||||
',': g['COMMA'],
|
||||
'.': g['PERIOD'],
|
||||
'/': g['SLASH'],
|
||||
'ESC': g['ESCAPE'],
|
||||
'+': g['PLUS'],
|
||||
})
|
||||
|
||||
enter_key = KeyEvent(PRESS, 0, g['ENTER'])
|
||||
|
||||
12043
kitty/keys.h
generated
12043
kitty/keys.h
generated
File diff suppressed because it is too large
Load Diff
@@ -6,22 +6,14 @@ import string
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .key_encoding import KEY_MAP
|
||||
from .terminfo import key_as_bytes
|
||||
from .terminfo import key_as_bytes, modify_key_bytes
|
||||
from .utils import base64_encode
|
||||
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
ans = bytearray(keybytes)
|
||||
amt = str(amt).encode('ascii')
|
||||
if ans[-1] == ord('~'):
|
||||
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')
|
||||
|
||||
|
||||
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 +31,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 +47,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 +86,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 +226,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 +237,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 +251,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); \
|
||||
|
||||
@@ -83,13 +83,10 @@ def talk_to_instance(args):
|
||||
|
||||
|
||||
def load_all_shaders(semi_transparent=0):
|
||||
load_shader_programs(semi_transparent, load_all_shaders.cursor_text_color)
|
||||
load_shader_programs(semi_transparent)
|
||||
load_borders_program()
|
||||
|
||||
|
||||
load_all_shaders.cursor_text_color = None
|
||||
|
||||
|
||||
def init_glfw(debug_keyboard=False):
|
||||
glfw_module = 'cocoa' if is_macos else ('wayland' if is_wayland else 'x11')
|
||||
if not glfw_init(glfw_path(glfw_module), debug_keyboard):
|
||||
@@ -122,7 +119,6 @@ def _run_app(opts, args):
|
||||
new_os_window_trigger = get_new_os_window_trigger(opts)
|
||||
if is_macos and opts.macos_custom_beam_cursor:
|
||||
set_custom_ibeam_cursor()
|
||||
load_all_shaders.cursor_text_color = opts.cursor_text_color
|
||||
if not is_wayland and not is_macos: # no window icons on wayland
|
||||
with open(logo_data_file, 'rb') as f:
|
||||
set_default_window_icon(f.read(), 256, 256)
|
||||
|
||||
105
kitty/mouse.c
105
kitty/mouse.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -535,6 +557,8 @@ mouse_event(int button, int modifiers, int action) {
|
||||
void
|
||||
scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
bool in_tab_bar;
|
||||
static id_type window_for_momentum_scroll = 0;
|
||||
static bool main_screen_for_momentum_scroll = false;
|
||||
unsigned int window_idx = 0;
|
||||
Window *w = window_for_event(&window_idx, &in_tab_bar);
|
||||
if (!w && !in_tab_bar) {
|
||||
@@ -545,10 +569,37 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
if (t) w = t->windows + t->active_window;
|
||||
}
|
||||
if (!w) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
|
||||
enum MomentumData { NoMomentumData, MomentumPhaseBegan, MomentumPhaseStationary, MomentumPhaseActive, MomentumPhaseEnded, MomentumPhaseCancelled, MomentumPhaseMayBegin };
|
||||
enum MomentumData momentum_data = (flags >> 1) & 7;
|
||||
|
||||
switch(momentum_data) {
|
||||
case NoMomentumData:
|
||||
break;
|
||||
case MomentumPhaseBegan:
|
||||
window_for_momentum_scroll = w->id;
|
||||
main_screen_for_momentum_scroll = screen->linebuf == screen->main_linebuf;
|
||||
break;
|
||||
case MomentumPhaseStationary:
|
||||
case MomentumPhaseActive:
|
||||
if (window_for_momentum_scroll != w->id || main_screen_for_momentum_scroll != (screen->linebuf == screen->main_linebuf)) return;
|
||||
break;
|
||||
case MomentumPhaseEnded:
|
||||
case MomentumPhaseCancelled:
|
||||
window_for_momentum_scroll = 0;
|
||||
break;
|
||||
case MomentumPhaseMayBegin:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (yoffset == 0.0) return;
|
||||
|
||||
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
|
||||
}
|
||||
@@ -557,16 +608,22 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
global_state.callback_os_window->pending_scroll_pixels = pixels;
|
||||
return;
|
||||
}
|
||||
s = abs(((int)round(pixels))) / global_state.callback_os_window->fonts_data->cell_height;
|
||||
if (pixels < 0) s *= -1;
|
||||
s = (int)round(pixels) / (int)global_state.callback_os_window->fonts_data->cell_height;
|
||||
global_state.callback_os_window->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height;
|
||||
} else {
|
||||
s = (int) round(yoffset * OPT(wheel_scroll_multiplier));
|
||||
if (screen->linebuf == screen->main_linebuf || !screen->modes.mouse_tracking_mode) {
|
||||
// Only use wheel_scroll_multiplier if we are scrolling kitty scrollback or in mouse
|
||||
// tracking mode, where the application is responsible for interpreting scroll events
|
||||
yoffset *= OPT(wheel_scroll_multiplier);
|
||||
}
|
||||
s = (int) round(yoffset);
|
||||
// apparently on cocoa some mice generate really small yoffset values
|
||||
// when scrolling slowly https://github.com/kovidgoyal/kitty/issues/1238
|
||||
if (s == 0 && yoffset != 0) s = yoffset > 0 ? 1 : -1;
|
||||
global_state.callback_os_window->pending_scroll_pixels = 0;
|
||||
}
|
||||
if (s == 0) return;
|
||||
bool upwards = s > 0;
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
} else {
|
||||
@@ -574,13 +631,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
|
||||
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
if (is_high_resolution) {
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
write_escape_code_to_child(screen, CSI, mouse_event_buf);
|
||||
}
|
||||
} else {
|
||||
// Since we are sending a mouse button 4/5 event, we ignore 's'
|
||||
// and simply send one event per received scroll event
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
write_escape_code_to_child(screen, CSI, mouse_event_buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -219,9 +219,9 @@ static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=
|
||||
static inline void
|
||||
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) {
|
||||
struct CellRenderData {
|
||||
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
|
||||
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
|
||||
|
||||
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
|
||||
|
||||
GLuint xnum, ynum, cursor_fg_sprite_idx;
|
||||
GLfloat cursor_x, cursor_y, cursor_w;
|
||||
@@ -260,8 +260,10 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
|
||||
|
||||
#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name)
|
||||
rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg);
|
||||
rd->cursor_text_color = COLOR(cursor_text_color);
|
||||
#undef COLOR
|
||||
rd->cursor_color = cursor->color; rd->url_color = OPT(url_color); rd->url_style = OPT(url_style);
|
||||
rd->cursor_text_uses_bg = cursor_text_as_bg(screen->color_profile);
|
||||
|
||||
unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
|
||||
}
|
||||
@@ -437,11 +439,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;
|
||||
|
||||
|
||||
@@ -137,11 +144,12 @@ typedef struct {
|
||||
OSWindow *os_windows;
|
||||
size_t num_os_windows, capacity;
|
||||
OSWindow *callback_os_window;
|
||||
bool close_all_windows;
|
||||
bool terminate;
|
||||
bool is_wayland;
|
||||
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);
|
||||
@@ -200,4 +209,6 @@ typedef enum {
|
||||
} CocoaPendingAction;
|
||||
void set_cocoa_pending_action(CocoaPendingAction action);
|
||||
bool application_quit_requested();
|
||||
void request_application_quit();
|
||||
#endif
|
||||
void wayland_request_frame_render(OSWindow *w);
|
||||
|
||||
@@ -11,31 +11,40 @@ from .fast_data_types import (
|
||||
viewport_for_window
|
||||
)
|
||||
from .layout import Rect
|
||||
from .utils import color_as_int
|
||||
from .utils import color_as_int, log_error
|
||||
from .window import calculate_gl_geometry
|
||||
from .rgb import alpha_blend, color_from_int
|
||||
|
||||
TabBarData = namedtuple('TabBarData', 'title is_active needs_attention')
|
||||
DrawData = namedtuple('DrawData', 'leading_spaces sep trailing_spaces bell_on_tab bell_fg alpha active_bg inactive_bg default_bg')
|
||||
DrawData = namedtuple(
|
||||
'DrawData', 'leading_spaces sep trailing_spaces bell_on_tab'
|
||||
' bell_fg alpha active_bg inactive_bg default_bg title_template')
|
||||
|
||||
|
||||
def as_rgb(x):
|
||||
return (x << 8) | 2
|
||||
|
||||
|
||||
def draw_title(draw_data, screen, tab):
|
||||
def draw_title(draw_data, screen, tab, index):
|
||||
if tab.needs_attention and draw_data.bell_on_tab:
|
||||
fg = screen.cursor.fg
|
||||
screen.cursor.fg = draw_data.bell_fg
|
||||
screen.draw('🔔 ')
|
||||
screen.cursor.fg = fg
|
||||
screen.draw(tab.title)
|
||||
try:
|
||||
title = draw_data.title_template.format(title=tab.title, index=index)
|
||||
except Exception as e:
|
||||
if not hasattr(draw_title, 'template_failure_reported'):
|
||||
draw_title.template_failure_reported = True
|
||||
log_error('Invalid tab title template: "{}" with error: {}'.format(draw_data.title_template, e))
|
||||
title = tab.title
|
||||
screen.draw(title)
|
||||
|
||||
|
||||
def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length):
|
||||
def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, index):
|
||||
if draw_data.leading_spaces:
|
||||
screen.draw(' ' * draw_data.leading_spaces)
|
||||
draw_title(draw_data, screen, tab)
|
||||
draw_title(draw_data, screen, tab, index)
|
||||
if draw_data.trailing_spaces:
|
||||
screen.draw(' ' * draw_data.trailing_spaces)
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
@@ -49,17 +58,17 @@ def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length):
|
||||
return end
|
||||
|
||||
|
||||
def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length):
|
||||
def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index):
|
||||
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
|
||||
fade_colors = [as_rgb(color_as_int(alpha_blend(tab_bg, draw_data.default_bg, alpha))) for alpha in draw_data.alpha]
|
||||
for bg in fade_colors:
|
||||
screen.cursor.bg = bg
|
||||
screen.draw(' ')
|
||||
draw_title(draw_data, screen, tab)
|
||||
draw_title(draw_data, screen, tab, index)
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
if extra > 0:
|
||||
screen.cursor.x = before
|
||||
draw_title(draw_data, screen, tab)
|
||||
draw_title(draw_data, screen, tab, index)
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
if extra > 0:
|
||||
screen.cursor.x -= extra + 1
|
||||
@@ -81,6 +90,7 @@ class TabBar:
|
||||
def __init__(self, os_window_id, opts):
|
||||
self.os_window_id = os_window_id
|
||||
self.opts = opts
|
||||
draw_title.template = opts.tab_title_template
|
||||
self.num_tabs = 1
|
||||
self.margin_width = pt_to_px(self.opts.tab_bar_margin_width, self.os_window_id)
|
||||
self.cell_width, cell_height = cell_size_for_window(self.os_window_id)
|
||||
@@ -112,7 +122,7 @@ class TabBar:
|
||||
self.draw_data = DrawData(
|
||||
self.leading_spaces, self.sep, self.trailing_spaces, self.opts.bell_on_tab, self.bell_fg,
|
||||
self.opts.tab_fade, self.opts.active_tab_background, self.opts.inactive_tab_background,
|
||||
self.opts.background
|
||||
self.opts.background, self.opts.tab_title_template
|
||||
)
|
||||
self.draw_func = draw_tab_with_separator if self.opts.tab_bar_style == 'separator' else draw_tab_with_fade
|
||||
|
||||
@@ -162,12 +172,12 @@ class TabBar:
|
||||
cr = []
|
||||
last_tab = data[-1] if data else None
|
||||
|
||||
for t in data:
|
||||
for i, t in enumerate(data):
|
||||
s.cursor.bg = self.active_bg if t.is_active else 0
|
||||
s.cursor.fg = self.active_fg if t.is_active else 0
|
||||
s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style
|
||||
before = s.cursor.x
|
||||
end = self.draw_func(self.draw_data, s, t, before, max_title_length)
|
||||
end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1)
|
||||
cr.append((before, end))
|
||||
if s.cursor.x > s.columns - max_title_length and t is not last_tab:
|
||||
s.draw('…')
|
||||
|
||||
@@ -11,7 +11,7 @@ from .child import Child
|
||||
from .constants import appname, get_boss, is_macos, is_wayland
|
||||
from .fast_data_types import (
|
||||
add_tab, glfw_post_empty_event, mark_tab_bar_dirty, next_window_id,
|
||||
pt_to_px, remove_tab, remove_window, set_active_tab, swap_tabs,
|
||||
pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs,
|
||||
x11_window_id
|
||||
)
|
||||
from .layout import create_layout_object_for, evict_cached_layouts
|
||||
@@ -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]
|
||||
@@ -185,6 +199,16 @@ class Tab: # {{{
|
||||
return
|
||||
return 'Could not resize'
|
||||
|
||||
def resize_window(self, quality, increment):
|
||||
if increment < 1:
|
||||
raise ValueError(increment)
|
||||
is_horizontal = quality in ('wider', 'narrower')
|
||||
increment *= 1 if quality in ('wider', 'taller') else -1
|
||||
if self.resize_window_by(
|
||||
self.windows[self.active_window_idx].id,
|
||||
increment, is_horizontal) is not None:
|
||||
ring_bell()
|
||||
|
||||
def reset_window_sizes(self):
|
||||
if self.current_layout.remove_all_biases():
|
||||
self.relayout()
|
||||
@@ -255,7 +279,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 +366,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 +386,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 +406,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 +427,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 +441,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 +466,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 +488,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)),
|
||||
}
|
||||
|
||||
@@ -461,8 +511,10 @@ class TabManager: # {{{
|
||||
if len(self.tabs) > 1:
|
||||
idx = self.active_tab_idx
|
||||
nidx = (idx + len(self.tabs) + delta) % len(self.tabs)
|
||||
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
|
||||
swap_tabs(self.os_window_id, idx, nidx)
|
||||
step = 1 if idx < nidx else -1
|
||||
for i in range(idx, nidx, step):
|
||||
self.tabs[i], self.tabs[i + step] = self.tabs[i + step], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i + step)
|
||||
self._set_active_tab(nidx)
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
@@ -472,8 +524,9 @@ class TabManager: # {{{
|
||||
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
|
||||
self._set_active_tab(idx)
|
||||
if len(self.tabs) > 2 and as_neighbor and idx != nidx:
|
||||
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
|
||||
swap_tabs(self.os_window_id, idx, nidx)
|
||||
for i in range(idx, nidx, -1):
|
||||
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i-1)
|
||||
self._set_active_tab(nidx)
|
||||
idx = nidx
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
@@ -6,6 +6,22 @@ import re
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
if amt == 0:
|
||||
return keybytes
|
||||
ans = bytearray(keybytes)
|
||||
amt = str(amt).encode('ascii')
|
||||
if ans[-1] == ord('~'):
|
||||
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 in key: {!r}'.format(keybytes))
|
||||
|
||||
|
||||
def encode_keystring(keybytes):
|
||||
return keybytes.decode('ascii').replace('\033', r'\E')
|
||||
|
||||
|
||||
names = 'xterm-kitty', 'KovIdTTY'
|
||||
|
||||
termcap_aliases = {
|
||||
@@ -37,6 +53,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
|
||||
@@ -152,54 +172,8 @@ string_capabilities = {
|
||||
# 'invis': r'\E[8m',
|
||||
# Backspace
|
||||
'kbs': r'\177',
|
||||
# Left
|
||||
'kcub1': r'\EOD',
|
||||
# Down
|
||||
'kcud1': r'\EOB',
|
||||
# Right
|
||||
'kcuf1': r'\EOC',
|
||||
# Up
|
||||
'kcuu1': r'\EOA',
|
||||
# Function keys
|
||||
'kf1': r'\EOP',
|
||||
'kf2': r'\EOQ',
|
||||
'kf3': r'\EOR',
|
||||
'kf4': r'\EOS',
|
||||
'kf5': r'\E[15~',
|
||||
'kf6': r'\E[17~',
|
||||
'kf7': r'\E[18~',
|
||||
'kf8': r'\E[19~',
|
||||
'kf9': r'\E[20~',
|
||||
'kf10': r'\E[21~',
|
||||
'kf11': r'\E[23~',
|
||||
'kf12': r'\E[24~',
|
||||
'kf13': r'\E[1;2P',
|
||||
'kf14': r'\E[1;2Q',
|
||||
'kf15': r'\E[1;2R',
|
||||
'kf16': r'\E[1;2S',
|
||||
'kf17': r'\E[15;2~',
|
||||
'kf18': r'\E[17;2~',
|
||||
'kf19': r'\E[18;2~',
|
||||
'kf20': r'\E[19;2~',
|
||||
'kf21': r'\E[20;2~',
|
||||
'kf22': r'\E[21;2~',
|
||||
'kf23': r'\E[23;2~',
|
||||
'kf24': r'\E[24;2~',
|
||||
'kf25': r'\E[1;5P',
|
||||
# Home
|
||||
'khome': r'\EOH',
|
||||
# End
|
||||
'kend': r'\EOF',
|
||||
# Insert character key
|
||||
'kich1': r'\E[2~',
|
||||
# Delete character key
|
||||
'kdch1': r'\E[3~',
|
||||
# Mouse event has occurred
|
||||
'kmous': r'\E[M',
|
||||
# Page down
|
||||
'knp': r'\E[6~',
|
||||
# Page up
|
||||
'kpp': r'\E[5~',
|
||||
# Scroll backwards (reverse index)
|
||||
'kri': r'\E[1;2A',
|
||||
# scroll forwards (index)
|
||||
@@ -223,8 +197,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
|
||||
@@ -266,11 +240,6 @@ string_capabilities = {
|
||||
# Select alternate charset
|
||||
'smacs': r'\E(0',
|
||||
'rmacs': r'\E(B',
|
||||
# Shifted keys
|
||||
'kRIT': r'\E[1;2C',
|
||||
'kLFT': r'\E[1;2D',
|
||||
'kEND': r'\E[1;2F',
|
||||
'kHOM': r'\E[1;2H',
|
||||
# Special keys
|
||||
'khlp': r'',
|
||||
'kund': r'',
|
||||
@@ -284,10 +253,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
|
||||
@@ -298,6 +263,27 @@ string_capabilities = {
|
||||
# 'rs2': r'\E[!p\E[?3;4l\E[4l\E>',
|
||||
}
|
||||
|
||||
string_capabilities.update({
|
||||
'kf{}'.format(offset + n):
|
||||
encode_keystring(modify_key_bytes(b'\033' + value, mod))
|
||||
for offset, mod in {0: 0, 12: 2, 24: 5, 36: 6, 48: 3, 60: 4}.items()
|
||||
for n, value in zip(range(1, 13),
|
||||
b'OP OQ OR OS [15~ [17~ [18~ [19~ [20~ [21~ [23~ [24~'.split())
|
||||
if offset + n < 64
|
||||
})
|
||||
|
||||
string_capabilities.update({
|
||||
name.format(unmod=unmod, key=key):
|
||||
encode_keystring(modify_key_bytes(b'\033' + value, mod))
|
||||
for unmod, key, value in zip(
|
||||
'cuu1 cud1 cuf1 cub1 end home ich1 dch1 pp np'.split(),
|
||||
'UP DN RIT LFT END HOM IC DC PRV NXT'.split(),
|
||||
b'OA OB OC OD OF OH [2~ [3~ [5~ [6~'.split())
|
||||
for name, mod in {
|
||||
'k{unmod}': 0, 'k{key}': 2, 'k{key}3': 3, 'k{key}4': 4,
|
||||
'k{key}5': 5, 'k{key}6': 6, 'k{key}7': 7}.items()
|
||||
})
|
||||
|
||||
termcap_aliases.update({
|
||||
'ac': 'acsc',
|
||||
'bl': 'bel',
|
||||
@@ -346,31 +332,6 @@ termcap_aliases.update({
|
||||
'kd': 'kcud1',
|
||||
'kr': 'kcuf1',
|
||||
'ku': 'kcuu1',
|
||||
'k1': 'kf1',
|
||||
'k2': 'kf2',
|
||||
'k3': 'kf3',
|
||||
'k4': 'kf4',
|
||||
'k5': 'kf5',
|
||||
'k6': 'kf6',
|
||||
'k7': 'kf7',
|
||||
'k8': 'kf8',
|
||||
'k9': 'kf9',
|
||||
'k;': 'kf10',
|
||||
'F1': 'kf11',
|
||||
'F2': 'kf12',
|
||||
'F3': 'kf13',
|
||||
'F4': 'kf14',
|
||||
'F5': 'kf15',
|
||||
'F6': 'kf16',
|
||||
'F7': 'kf17',
|
||||
'F8': 'kf18',
|
||||
'F9': 'kf19',
|
||||
'FA': 'kf20',
|
||||
'FB': 'kf21',
|
||||
'FC': 'kf22',
|
||||
'FD': 'kf23',
|
||||
'FE': 'kf24',
|
||||
'FF': 'kf25',
|
||||
'kh': 'khome',
|
||||
'@7': 'kend',
|
||||
'kI': 'kich1',
|
||||
@@ -410,8 +371,12 @@ termcap_aliases.update({
|
||||
'ks': 'smkx',
|
||||
'ke': 'rmkx',
|
||||
'#2': 'kHOM',
|
||||
'#3': 'kIC',
|
||||
'#4': 'kLFT',
|
||||
'*4': 'kDC',
|
||||
'*7': 'kEND',
|
||||
'%c': 'kNXT',
|
||||
'%e': 'kPRV',
|
||||
'%i': 'kRIT',
|
||||
'%1': 'khlp',
|
||||
'&8': 'kund',
|
||||
@@ -433,10 +398,22 @@ termcap_aliases.update({
|
||||
# 'r2': 'rs2',
|
||||
})
|
||||
|
||||
termcap_aliases.update({
|
||||
tc: 'kf{}'.format(n)
|
||||
for n, tc in enumerate(
|
||||
'k1 k2 k3 k4 k5 k6 k7 k8 k9 k; F1 F2 F3 F4 F5 F6 F7 F8 F9 FA '
|
||||
'FB FC FD FE FF FG FH FI FJ FK FL FM FN FO FP FQ FR FS FT FU '
|
||||
'FV FW FX FY FZ Fa Fb Fc Fd Fe Ff Fg Fh Fi Fj Fk Fl Fm Fn Fo '
|
||||
'Fp Fq Fr'.split(), 1)})
|
||||
|
||||
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 kUP kDN'.split() + [
|
||||
'k{}{}'.format(key, mod)
|
||||
for key in 'UP DN RIT LFT END HOM IC DC PRV NXT'.split()
|
||||
for mod in range(3, 8)])
|
||||
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
|
||||
@@ -429,3 +432,20 @@ def get_editor():
|
||||
ans = shlex.split(ans)
|
||||
get_editor.ans = ans
|
||||
return ans
|
||||
|
||||
|
||||
def is_path_in_temp_dir(path):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
def abspath(x):
|
||||
if x:
|
||||
return os.path.abspath(os.path.realpath(x))
|
||||
|
||||
import tempfile
|
||||
path = abspath(path)
|
||||
candidates = frozenset(map(abspath, ('/tmp', '/dev/shm', os.environ.get('TMPDIR', None), tempfile.gettempdir())))
|
||||
for q in candidates:
|
||||
if q and path.startswith(q):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -8,8 +8,8 @@ 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
|
||||
from .constants import (
|
||||
ScreenGeometry, WindowGeometry, appname, get_boss, wakeup
|
||||
@@ -23,7 +23,7 @@ from .fast_data_types import (
|
||||
set_clipboard_string, set_titlebar_color, set_window_render_data,
|
||||
update_window_title, update_window_visibility, viewport_for_window
|
||||
)
|
||||
from .keys import keyboard_mode_name
|
||||
from .keys import keyboard_mode_name, extended_key_event, defines
|
||||
from .rgb import to_color
|
||||
from .terminfo import get_capabilities
|
||||
from .utils import (
|
||||
@@ -55,14 +55,13 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell
|
||||
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
|
||||
|
||||
|
||||
def load_shader_programs(semi_transparent=0, cursor_text_color=None):
|
||||
def load_shader_programs(semi_transparent=0):
|
||||
compile_program(BLIT_PROGRAM, *load_shaders('blit'))
|
||||
v, f = load_shaders('cell')
|
||||
|
||||
def color_as_vec3(x):
|
||||
return 'vec3({}, {}, {})'.format(x.red / 255, x.green / 255, x.blue / 255)
|
||||
|
||||
cursor_text_color = color_as_vec3(cursor_text_color) if cursor_text_color else 'bg'
|
||||
for which, p in {
|
||||
'SIMPLE': CELL_PROGRAM,
|
||||
'BACKGROUND': CELL_BG_PROGRAM,
|
||||
@@ -75,7 +74,6 @@ def load_shader_programs(semi_transparent=0, cursor_text_color=None):
|
||||
'STRIKE_SHIFT': STRIKETHROUGH,
|
||||
'DIM_SHIFT': DIM,
|
||||
'DECORATION_SHIFT': DECORATION,
|
||||
'CURSOR_TEXT_COLOR': cursor_text_color,
|
||||
}.items():
|
||||
vv = vv.replace('{{{}}}'.format(gln), str(pyn), 1)
|
||||
if semi_transparent:
|
||||
@@ -94,8 +92,31 @@ def load_shader_programs(semi_transparent=0, cursor_text_color=None):
|
||||
|
||||
def setup_colors(screen, opts):
|
||||
screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||
cursor_text_color = opts.cursor_text_color or (12, 12, 12)
|
||||
cursor_text_color_as_bg = 3 if cursor_text_color is None else 1
|
||||
screen.color_profile.set_configured_colors(*map(color_as_int, (
|
||||
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
|
||||
opts.foreground, opts.background, opts.cursor,
|
||||
cursor_text_color, (0, 0, cursor_text_color_as_bg),
|
||||
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:
|
||||
@@ -146,6 +167,7 @@ class Window:
|
||||
cwd=self.child.current_cwd or self.child.cwd,
|
||||
cmdline=self.child.cmdline,
|
||||
env=self.child.environ,
|
||||
foreground_processes=self.child.foreground_processes
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -248,7 +270,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 +398,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 +462,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,27 +470,38 @@ 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
|
||||
def cwd_of_child(self):
|
||||
# TODO: Maybe use the cwd of the leader of the foreground process
|
||||
# group in the session of the child process?
|
||||
pid = self.child.pid
|
||||
if pid is not None:
|
||||
return cwd_of_process(pid) or None
|
||||
return self.child.foreground_cwd or self.child.current_cwd
|
||||
|
||||
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:
|
||||
@@ -489,6 +524,15 @@ class Window:
|
||||
if text:
|
||||
set_clipboard_string(text)
|
||||
|
||||
def copy_or_interrupt(self):
|
||||
text = self.text_for_selection()
|
||||
if text:
|
||||
set_clipboard_string(text)
|
||||
else:
|
||||
mode = keyboard_mode_name(self.screen)
|
||||
text = extended_key_event(defines.GLFW_KEY_C, defines.GLFW_MOD_CONTROL, defines.GLFW_PRESS) if mode == 'kitty' else b'\x03'
|
||||
self.write_to_child(text)
|
||||
|
||||
def pass_selection_to_program(self, *args):
|
||||
cwd = self.cwd_of_child
|
||||
text = self.text_for_selection()
|
||||
|
||||
@@ -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()
|
||||
@@ -2,13 +2,16 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from kitty.config import build_ansi_color_table, defaults
|
||||
from kitty.fast_data_types import (
|
||||
REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf,
|
||||
parse_input_from_terminal, wcswidth, wcwidth, truncate_point_for_length
|
||||
parse_input_from_terminal, truncate_point_for_length, wcswidth, wcwidth
|
||||
)
|
||||
from kitty.rgb import to_color
|
||||
from kitty.utils import sanitize_title
|
||||
from kitty.utils import is_path_in_temp_dir, sanitize_title
|
||||
|
||||
from . import BaseTest, filled_cursor, filled_history_buf, filled_line_buf
|
||||
|
||||
@@ -242,6 +245,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')
|
||||
@@ -381,6 +387,12 @@ class TestDataTypes(BaseTest):
|
||||
tp('a\033', '_', 'x\033', '\\b', text='a b', apc='x')
|
||||
tp('a\033_', 'x', '\033', '\\', 'b', text='a b', apc='x')
|
||||
|
||||
for prefix in ('/tmp', tempfile.gettempdir()):
|
||||
for path in ('a.png', 'x/b.jpg', 'y/../c.jpg'):
|
||||
self.assertTrue(is_path_in_temp_dir(os.path.join(prefix, path)))
|
||||
for path in ('/home/xy/d.png', '/tmp/../home/x.jpg'):
|
||||
self.assertFalse(is_path_in_temp_dir(os.path.join(path)))
|
||||
|
||||
def test_color_profile(self):
|
||||
c = ColorProfile()
|
||||
c.update_ansi_color_table(build_ansi_color_table())
|
||||
@@ -447,10 +459,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 +470,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')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user