Compare commits

...

129 Commits

Author SHA1 Message Date
Kovid Goyal
81bda736d8 Skip multiprocessing spawn monkeypatch test on 32bit 2020-07-28 14:15:31 +05:30
Kovid Goyal
ec3998a9b8 version 0.18.2 2020-07-28 14:01:16 +05:30
Kovid Goyal
01be77e0bc Fix change in window size that does not change number of cells not being reported to the kernel
Fixes #2880
2020-07-25 22:49:39 +05:30
Kovid Goyal
6f40b8d0a1 Simplify Wayland cursor theme handling
Now themes are loaded once per scale and stored centrally not per
window. They are not unloaded till application shutdown. Since there
is unlikely to be more than two or three scales this is not a big waste
of resources. Since cursor lifetime is tied to theme lifetime and cursors
are stored per window, destroying a theme requires destroying all
cursors win all windows referring to that theme, which is too much work.

Should hopefully fix #2810
2020-07-25 15:36:46 +05:30
Kovid Goyal
a083aa04b2 vim also produces CSI > 4;m ignore that as well 2020-07-24 19:19:18 +05:30
Kovid Goyal
1f33348cf6 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 15:10:35 +05:30
Luflosi
a45651933c GLFW: Cocoa: Support some sim page joystick elements
From upstream: 0d355379e0.
2020-07-24 11:28:53 +02:00
Kovid Goyal
ab57fbff73 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 14:56:54 +05:30
Luflosi
6b3f774f05 GLFW: Cocoa: Fix potential leak of CFNumber object
From upstream: a2674a9034.
2020-07-24 10:59:04 +02:00
Kovid Goyal
1ef2880c6c Also make the platform window id available via kitty @ ls 2020-07-24 08:47:13 +05:30
Kovid Goyal
ace152d8a3 DRYer 2020-07-24 08:41:55 +05:30
Kovid Goyal
8d08036a9d Update changelog for previous PR 2020-07-24 08:35:01 +05:30
Kovid Goyal
b0876fae64 Merge branch 'cocoa-window-number' of https://github.com/eraserhd/kitty 2020-07-24 08:33:16 +05:30
Kovid Goyal
01633776d3 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 08:26:18 +05:30
Jason Felice
e4202228eb Update fast_data_types.pyi 2020-07-23 19:09:16 -04:00
Jason Felice
79d5049273 Provide cocoa_window_id() on non-Mac, also
So that its import in tabs.py doesn't have to be conditional.
2020-07-23 18:55:04 -04:00
Jason Felice
9120e3b790 Reorganize window id functions slightly 2020-07-23 18:47:36 -04:00
Jason Felice
974e6140b3 Extract find_os_window() 2020-07-23 18:36:32 -04:00
Jason Felice
1fd338cc6c Set Cocoa window number 2020-07-23 18:36:32 -04:00
Luflosi
bc61a81d7a GLFW: X11: Fix joystick detection before joystick init
From upstream: c01acc267a.
2020-07-23 23:57:03 +02:00
Luflosi
06b30d1cc3 GLFW: Cleanup
From upstream: f771d41292.
2020-07-23 23:56:32 +02:00
Luflosi
785973bb5e GLFW: Make joystick platform code init on demand
From upstream: 782e6b6cef.
2020-07-23 23:56:30 +02:00
Luflosi
78efefcea1 Revert "Linux: Fix slow startup on some systems caused by GLFW searching for joysticks. Since kitty does not use joysticks, disable joystick support."
This reverts commit ab960ea12d.
2020-07-23 23:56:21 +02:00
Kovid Goyal
1bb6242020 macOS: no longer render emoji 20% below the baseline. This causes some emoji to be cut-off and also look misaligned with very high cells
Fixes #2873
2020-07-23 20:38:02 +05:30
Kovid Goyal
1af426bf84 Add support for legacy DECSET codes 47, 1047 and 1048
Fixes #2871
2020-07-22 09:00:51 +05:30
Kovid Goyal
dc4762a69a Dont crash if Wayland returns a cursor image array with some null images 2020-07-21 19:33:05 +05:30
Kovid Goyal
a785b77da9 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-19 09:27:44 +05:30
Luflosi
1342767805 GLFW: Wayland: Fix scroll offsets being inverted
From upstream: f760b124ca.

This commit does not actually change any behaviour, it merely makes the code style of the two lines more similar to the new upstream code.
2020-07-18 19:32:03 +02:00
Kovid Goyal
a3fc3233cd Fix pre-edit text not always being cleared with iBus input
Fixes #2862
2020-07-18 19:31:38 +05:30
Kovid Goyal
8093d70643 Allow setting the class for new OS windows in a session file 2020-07-18 12:11:41 +05:30
Kovid Goyal
9d9ec84b54 fix talk mutex typo unlock -> lock 2020-07-18 11:47:22 +05:30
Kovid Goyal
6b26088c27 move wakeup out of locked region 2020-07-18 11:42:57 +05:30
Kovid Goyal
43af715caa free peer message queue at shutdown 2020-07-18 10:50:09 +05:30
Kovid Goyal
c6eea488b1 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-18 09:24:01 +05:30
Luflosi
b1c3999a81 GLFW: Wayland: Fix partial framebuffer size retrieval
From upstream: 5f60c22cfa.
2020-07-17 21:10:32 +02:00
Kovid Goyal
4f19403518 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-17 19:48:06 +05:30
Luflosi
4d1b537f78 GLFW: Documentation work for GLFW_MOUSE_PASSTHROUGH
From upstream: a122d91303.
2020-07-17 00:08:22 +02:00
Luflosi
dd54db47a9 GLFW: Move mouse passthrough before window showing
From upstream: dfeacee000.
2020-07-17 00:07:36 +02:00
Luflosi
44775e4644 GLFW: Remove no-op call at window creation
From upstream: 1095a43708.
2020-07-16 23:55:24 +02:00
Luflosi
805921d6a3 GLFW: Move management of shared state to shared code
From upstream: 6d2003d07a.
2020-07-16 23:54:16 +02:00
Luflosi
d7cd6edaa5 GLFW: X11: Include X Shape extension headers
From upstream: 68e4261d73.
2020-07-16 23:50:26 +02:00
Luflosi
6a472bd6da GLFW: X11: Fix disabling of GLFW_MOUSE_PASSTHROUGH
From upstream: e81d381256.
2020-07-16 23:24:48 +02:00
Luflosi
d0c6ce9fd7 GLFW: Add support for mouse input transparency
From upstream: d285a9fdeb.
2020-07-16 22:25:12 +02:00
Kovid Goyal
df48e2f9e2 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-16 23:43:07 +05:30
Luflosi
2b4b3bbf18 GLFW: Null: Fix typos in workarea retrieval
From upstream: 6c031af245.
2020-07-16 20:01:57 +02:00
Kovid Goyal
6ea5027bf6 Remote control close_tab should not ask for confirmation 2020-07-16 12:55:25 +05:30
Kovid Goyal
384ccb4fc7 Have the :opt:confirm_os_window_close option also apply when closing tabs with multiple windows
Fixes #2857
2020-07-16 12:51:19 +05:30
Kovid Goyal
ff763b099e Merge branch 'patch-1' of https://github.com/tammersaleh/kitty 2020-07-16 08:06:47 +05:30
Kovid Goyal
741ae4c04b Merge branch 'typo' of https://github.com/Luflosi/kitty 2020-07-16 08:06:20 +05:30
Tammer Saleh
d84ec941c0 Fix typo 2020-07-15 17:44:02 -07:00
Luflosi
f0b5dd9799 Fix typo 2020-07-15 19:09:35 +02:00
Kovid Goyal
cec4371b51 Rewrite the talk loop for interaction with peers
The new code is simpler and hopefully more robust. Supports
multiple requests/responses. Fixes #2852
2020-07-15 14:31:45 +05:30
Kovid Goyal
c0ff39c59d Fix extra close for loops with no signal_read_fd 2020-07-14 20:23:45 +05:30
Kovid Goyal
022634f2d9 Log an extra error message 2020-07-14 20:18:38 +05:30
Kovid Goyal
392f576d07 Instrument safe_close() to make it easy to see where an fd is closed from 2020-07-14 20:18:04 +05:30
Kovid Goyal
6d59e2125e Prevent invalid fds from spinning the talk thread forever 2020-07-14 19:09:04 +05:30
Kovid Goyal
6476a1d2e0 When a character from the Unicode Dingbat block is followed by a space, use the extra space to render a larger version of the character
Fixes #2850
2020-07-12 21:12:34 +05:30
Kovid Goyal
ca998fb4b0 Fix incorrect centering when a PUA or symbol glyph is followed by more than one space 2020-07-12 20:16:16 +05:30
Kovid Goyal
48631c4ea6 Add changelog entry for previous merge
Also clean up use of typing.cast
2020-07-12 14:47:14 +05:30
Kovid Goyal
294be2a772 Merge branch 'neighboring_window_changes' of https://github.com/juho-p/kitty 2020-07-12 14:33:21 +05:30
Kovid Goyal
533688a67a Implement support for box drawing rounded-corners characters
Fixes #2240
2020-07-12 12:35:52 +05:30
Kovid Goyal
4309e2371d Simplify code by using supersampling for diagonal lines as well
Wu antialiasing obviously performs better but since characters are
rendered only once and are very small in pixel size, performance is
irrelevant here.
2020-07-12 10:07:55 +05:30
Juho Peltonen
ea30c84240 Fix grid layout neighbors
Grid layout can have multiple neighbors in one side when either current
column or neighboring column is special.
2020-07-11 23:52:37 +03:00
Juho Peltonen
d4c2b13dda Prefer most recently activated window when selecting neighbors
This affects both activating and moving neighbor windows.
2020-07-11 23:52:33 +03:00
Juho Peltonen
1decae2938 Change splits layout neighbors to only include real neighbors 2020-07-11 21:35:29 +03:00
Kovid Goyal
145ec86d3b Forgot to add supersampling to rendering of filled D 2020-07-11 23:10:43 +05:30
Kovid Goyal
e17c96b20f Improve anti-aliasing of triangular box drawing characters, noticeable on low-resolution screens
Fixes #2844
2020-07-11 23:08:28 +05:30
Kovid Goyal
2cfcefd9eb Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-11 22:57:53 +05:30
Luflosi
356e950122 X11: Fix cursor hover query retry behavior
From upstream: 49dbcfcb8c.
2020-07-11 18:59:28 +02:00
Kovid Goyal
64dee63b73 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-11 22:26:32 +05:30
Luflosi
431d013d32 X11: Fix race in window hovered query
From upstream: ceb20c7f97.
2020-07-11 18:18:33 +02:00
Kovid Goyal
40eb8455d0 Fix `launch --location=last` not working
Fixes #2841
2020-07-09 22:29:24 +05:30
Kovid Goyal
c46b36509b macOS: Fix cmd+plus not increasing font size with keyboards where the + key is a shifted key. Fixes #2839 2020-07-09 22:14:07 +05:30
Kovid Goyal
f7ff63132d Merge branch 'remove-trailing-whitespace' of https://github.com/Luflosi/kitty 2020-07-09 07:42:02 +05:30
Luflosi
e17036a7a5 Remove trailing whitespace 2020-07-09 01:57:39 +02:00
Kovid Goyal
e678c41f56 More code scanning warning fixes 2020-07-07 10:24:57 +05:30
Kovid Goyal
ee3462592a Silence more code scanning warnings 2020-07-07 10:01:38 +05:30
Kovid Goyal
521f921424 Fix a bunch of code scanning warnings 2020-07-07 09:52:59 +05:30
Kovid Goyal
ee47a10b37 Simplify code a bit 2020-07-07 09:44:51 +05:30
Kovid Goyal
b81700144d Silence another code scanning warning 2020-07-07 09:38:33 +05:30
Kovid Goyal
9f28dd61d3 Silence a spurious code scanning warning
I hope it silences it anyway
2020-07-07 09:31:00 +05:30
Kovid Goyal
dad7d366b3 Fix out of bounds write on platforms where sizeof(bool) != 1
Found by code scanning
2020-07-07 09:25:14 +05:30
Kovid Goyal
209b78a3ec Silence a code scanning warning 2020-07-07 09:20:59 +05:30
Kovid Goyal
17f34d1f95 Use the more secure localtime_r variant
Pointed out by code scanning
2020-07-07 09:20:27 +05:30
Kovid Goyal
26699cd9ef Fix incorrect format specifier to printf
Detected by code scanning
2020-07-07 09:14:33 +05:30
Kovid Goyal
907db09a58 Only use pythonLocation if it is available 2020-07-07 08:57:50 +05:30
Kovid Goyal
d506fa0565 Use python3 in code analysis build step 2020-07-07 08:41:20 +05:30
Kovid Goyal
de1a0d48a6 Use ci script to build kitty for code scanning 2020-07-07 08:35:04 +05:30
Kovid Goyal
a20e5fefd3 ... 2020-07-07 08:27:10 +05:30
Kovid Goyal
44558560f9 Dont use autobuild for code scanning 2020-07-07 08:24:37 +05:30
Kovid Goyal
677f154f03 Create codeql-analysis.yml
Enable GitHub code scanning for vulnerabilities
2020-07-07 08:21:14 +05:30
Kovid Goyal
0adaf064e7 Fix #2832 2020-07-05 07:34:11 +05:30
Kovid Goyal
3c828feef2 Merge branch 'environmenterror-to-oserror' of https://github.com/Luflosi/kitty 2020-07-02 08:29:27 +05:30
Kovid Goyal
4d3c5e61fd Update changelog 2020-07-02 08:26:37 +05:30
Kovid Goyal
67baefb52a Merge branch 'hints-linenum-expand-home' of https://github.com/trygveaa/kitty 2020-07-02 08:23:59 +05:30
Trygve Aaberge
d509a4fa8c Expand ~ in the path argument for linenum in hints
Fixes #2822
2020-07-02 01:01:14 +02:00
Luflosi
cad1c12b7b Replace EnvironmentError with OSError
See 527ff0238a.
2020-07-01 22:39:01 +02:00
Kovid Goyal
9f751d8715 Add kitti3 to integrations.rst 2020-06-30 08:15:16 +05:30
Kovid Goyal
cf8a1dff33 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-30 08:13:27 +05:30
Luflosi
973d98457f EGL: Add support for EGL_ANGLE_platform_angle
From upstream: 0dea8a4441.
2020-06-30 00:34:32 +02:00
Kovid Goyal
ce262f810d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-29 19:13:34 +05:30
Luflosi
bd31c2b484 Add GLFW_CONTEXT_DEBUG window hint alias
From upstream: b420ca7404.
2020-06-29 15:30:38 +02:00
Kovid Goyal
ef0e610802 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-29 18:45:19 +05:30
Luflosi
3040d4c83f Re-word docs for GLFW_OPENGL_DEBUG_CONTEXT attrib
From upstream: 4e3b43383a.
2020-06-29 11:43:24 +02:00
Kovid Goyal
c9252e8639 When erasing in display upto cursor, the line containing the cursor should be marked as not continued.
Also add some tests for the action of ED on continued flags
2020-06-29 14:29:03 +05:30
Kovid Goyal
e2e701460b Fix the CSI J (Erase in display ED) escape code not removing line continued markers
Fixes #2809

Remains to write tests for this.
2020-06-29 14:17:40 +05:30
Kovid Goyal
f0c3641ebd Some more commands for replay commands 2020-06-29 13:04:10 +05:30
Kovid Goyal
025f6d7701 Add keypad mode support to replay commands
Also name the constants correctly
2020-06-29 12:46:34 +05:30
Kovid Goyal
c68516ca8d Wayland: Fix a crash when using animated mouse cursors
Fixes #2810
2020-06-29 08:03:25 +05:30
Kovid Goyal
a9e739e08d Apply single instance BO override when different from current BO not when less than 1 2020-06-27 17:32:03 +05:30
Kovid Goyal
cc15ff73b7 Respect --override of background_opacity for --single-instance
Fixes #2806
2020-06-27 09:18:41 +05:30
Kovid Goyal
fd3a4536e7 Fix imperative close request being ignored in absence of any other activity 2020-06-26 16:37:17 +05:30
Kovid Goyal
75caafb5ac Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 16:34:33 +05:30
Kovid Goyal
cf2f8165eb Only ask application to abort once 2020-06-26 16:33:25 +05:30
Luflosi
a2f80740d7 Remove EGL and OSMesa struct member macros
From upstream: bcb8558189.
2020-06-26 12:51:11 +02:00
Kovid Goyal
c7703791d1 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 16:19:04 +05:30
Luflosi
c39e9e9c61 EGL: Add support for EGL_EXT_platform_base
From upstream: aec9fae8f3.
2020-06-26 12:39:17 +02:00
Kovid Goyal
713059319c Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 15:59:34 +05:30
Luflosi
d7a7509da6 EGL: Replace native handle macros with functions
From upstream: d7f7b0e1b5.
2020-06-26 12:17:13 +02:00
Kovid Goyal
57e6e7b2c5 Use ctrl+[] for changing tabs in unicode input as ctrl+tab is taken on macOS
ctrl+tab remains in addition for backwards compat
2020-06-26 12:29:42 +05:30
Kovid Goyal
d675977f10 Better error message for xterm specific key resource CSI codes that vim has started emitting 2020-06-25 23:07:34 +05:30
Kovid Goyal
7ef911e17d Fix #2798 2020-06-25 21:43:54 +05:30
Kovid Goyal
9a384c5045 Allow passing the current selection to kittens
Fixes #2796
2020-06-25 08:46:05 +05:30
Kovid Goyal
2b3753d99c Prevent super large tab margin widths from causing a crash
Fixes #2793
2020-06-24 22:56:54 +05:30
Kovid Goyal
d173ab9f45 macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the language and country code do not form a valid locale
Fixes #1233
2020-06-24 22:09:25 +05:30
Kovid Goyal
fbd0e8e26a X11: Improve handling of multiple keyboards
Now pressing a modifier key in one keyboard and a normal key in another works. Fixes #2362

Don't rebuild keymaps on new keyboard events that only change geometry. Fixes #2787

Better handling of multiple keyboards with incompatible layouts (this is for free from the above fixes). Fixes #2726
2020-06-24 12:05:30 +05:30
Kovid Goyal
50414b333a Re-enable the spawn tesRe-enable the spawn test 2020-06-23 20:43:40 +05:30
Kovid Goyal
ee2fdb3d25 Merge branch 'typos' of https://github.com/Luflosi/kitty 2020-06-23 20:16:42 +05:30
Luflosi
2b884377d4 Fix typos 2020-06-23 16:35:13 +02:00
94 changed files with 1538 additions and 915 deletions

View File

@@ -23,7 +23,7 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Enviroment details**
**Environment details**
OS: Name and version of operating system(s)
```

View File

@@ -106,7 +106,7 @@ def main():
if is_bundle:
setup_bundle_env()
else:
if not is_macos:
if not is_macos and 'pythonLocation' in os.environ:
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
action = sys.argv[-1]
if action in ('build', 'package'):

41
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: "Code scanning - action"
on:
push:
branches: [master, ]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 22 * * 5'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
- name: Build kitty
run: python3 .github/workflows/ci.py build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,5 +1,5 @@
pkg-config
python
imagemagick
harfbuzz
python
imagemagick
harfbuzz
sphinx-doc

View File

@@ -1,7 +1,7 @@
### Reporting bugs
Please first search existing bug reports (especially closed ones) for a report
that matches your issue.
that matches your issue.
When reporting a bug, provide full details of your environment, that means, at
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal

View File

@@ -62,7 +62,12 @@ def build_c_extensions(ext_dir, args):
def run_tests(path_to_kitty, cwd_on_failure):
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
kw = {'cwd': cwd_on_failure}
if not ismacos:
# this is needed for the spawn test which starts an interpreter
# using the kitty launcher.
kw['PYTHONHOME'] = PREFIX
ret = run(PYTHON, 'test.py', **kw)
if ret != 0:
os.chdir(cwd_on_failure)
print(

View File

@@ -165,7 +165,7 @@ def create_tarfile(env, compression_level='9'):
base = OUTPUT_DIR
try:
shutil.rmtree(base)
except EnvironmentError as err:
except OSError as err:
if err.errno != errno.ENOENT:
raise
os.mkdir(base)

View File

@@ -241,7 +241,7 @@
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
}
},
},
{
"name": "wayland-protocols",

View File

@@ -4,6 +4,71 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.18.2 [2020-07-28]
--------------------
- X11: Improve handling of multiple keyboards. Now pressing a modifier key in
one keyboard and a normal key in another works (:iss:`2362`). Don't rebuild
keymaps on new keyboard events that only change geometry (:iss:`2787`).
Better handling of multiple keyboards with incompatible layouts (:iss:`2726`)
- Improve anti-aliasing of triangular box drawing characters, noticeable on
low-resolution screens (:iss:`2844`)
- Fix ``kitty @ send-text`` not working reliably when using a socket for remote
control (:iss:`2852`)
- Implement support for box drawing rounded-corners characters (:iss:`2240`)
- Allow setting the class for new OS windows in a session file
- When a character from the Unicode Dingbat block is followed by a space, use
the extra space to render a larger version of the character (:iss:`2850`)
- macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the
language and country code do not form a valid locale (:iss:`1233`)
- macOS: Fix :kbd:`cmd+plus` not changing font size (:iss:`2839`)
- Make neighboring window selection in grid and splits layouts more intelligent
(:pull:`2840`)
- Allow passing the current selection to kittens (:iss:`2796`)
- Fix pre-edit text not always being cleared with ibus input (:iss:`2862`)
- Allow setting the :opt:`background_opacity` of new OS windows created via
:option:`kitty --single-instance` using the :option:`kitty --override` command line
argument (:iss:`2806`)
- Fix the CSI J (Erase in display ED) escape code not removing line continued
markers (:iss:`2809`)
- hints kitten: In linenumber mode expand paths that starts with ~
(:iss:`2822`)
- Fix ``launch --location=last`` not working (:iss:`2841`)
- Fix incorrect centering when a PUA or symbol glyph is followed by more than one space
- Have the :opt:`confirm_os_window_close` option also apply when closing tabs
with multiple windows (:iss:`2857`)
- Add support for legacy DECSET codes 47, 1047 and 1048 (:pull:`2871`)
- macOS: no longer render emoji 20% below the baseline. This caused some emoji
to be cut-off and also look misaligned with very high cells (:iss:`2873`)
- macOS: Make the window id of OS windows available in the ``WINDOWID``
environment variable (:pull:`2877`)
- Wayland: Fix a regression in 0.18.0 that could cause crashes related to mouse
cursors in some rare circumstances (:iss:`2810`)
- Fix change in window size that does not change number of cells not being
reported to the kernel (:iss:`2880`)
0.18.1 [2020-06-23]
--------------------

View File

@@ -346,6 +346,8 @@ For example:
new_os_window
# set new window size to 80x25 cells
os_window_size 80c 25c
# set the --class for the new OS window
os_window_class mywindow
launch sh
# Make the current window the active (focused) window
focus

View File

@@ -106,3 +106,6 @@ Miscellaneous
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
reload key bindings from :file:`kitty.conf` without needing to restart
kitty
`kitti3 <https://github.com/LandingEllipse/kitti3>`_
allow using kitty as a drop-down terminal under the i3 window manager

View File

@@ -58,7 +58,8 @@ You can pass arguments to kittens by defining them in the map directive in
These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
of the kitten is set to the working directory of whatever program is
running in the active kitty window.
running in the active kitty window. The special argument ``@selection``
is replaced by the currently selected text in the active kitty window.
Passing the contents of the screen to the kitten
@@ -87,7 +88,8 @@ This will send the plain text of the active window to the kitten's
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
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``.
``history``, ``ansi-history`` or ``screen-history``. To get
the currently selected text, use ``selection``.
Using kittens to script kitty, without any terminal UI

View File

@@ -24,7 +24,7 @@ a leading period and the index for the match if you don't like to use arrow
keys.
You can switch between modes using either the function keys or by pressing
:kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`.
:kbd:`Ctrl+[` and :kbd:`Ctrl+]`.
Command Line Interface

View File

@@ -5,7 +5,7 @@ Launching programs in new windows/tabs
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
in news windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
in new windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
It is very powerful and allows sending the contents of
the current window to the launched program, as well as many other options.

View File

@@ -222,7 +222,7 @@ Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
Further, since it is invalid, it should be ignored by terminal emulators
that do not support this extension, thereby making it safe to use, simply
always send it before starting a new OSC 52 paste, even if you aren't chunking
up large pastes, that way kitty wont concatenate your paste, and it will have
up large pastes, that way kitty won't concatenate your paste, and it will have
no ill-effects in other terminal emulators.
In case you're using software that can't be easily adapted to this

View File

@@ -669,8 +669,6 @@ int _glfwPlatformInit(void)
if (!initializeTIS())
return false;
_glfwInitJoysticksNS();
_glfwPollMonitorsNS();
return true;
@@ -723,7 +721,6 @@ void _glfwPlatformTerminate(void)
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
_glfwTerminateJoysticksNS();
} // autoreleasepool
}

View File

@@ -44,7 +44,3 @@ typedef struct _GLFWjoystickNS
CFMutableArrayRef hats;
} _GLFWjoystickNS;
void _glfwInitJoysticksNS(void);
void _glfwTerminateJoysticksNS(void);

View File

@@ -233,6 +233,19 @@ static void matchCallback(void* context UNUSED,
break;
}
}
else if (page == kHIDPage_Simulation)
{
switch (usage)
{
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Steering:
target = axes;
break;
}
}
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
target = buttons;
@@ -291,12 +304,10 @@ static void removeCallback(void* context UNUSED,
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
// Initialize joystick interface
//
void _glfwInitJoysticksNS(void)
bool _glfwPlatformInitJoysticks(void)
{
CFMutableArrayRef matching;
const long usages[] =
@@ -315,7 +326,7 @@ void _glfwInitJoysticksNS(void)
if (!matching)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
return;
return false;
}
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
@@ -370,26 +381,24 @@ void _glfwInitJoysticksNS(void)
// Execute the run loop once in order to register any initially-attached
// joysticks
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
return true;
}
// Close all opened joystick handles
//
void _glfwTerminateJoysticksNS(void)
void _glfwPlatformTerminateJoysticks(void)
{
int jid;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
closeJoystick(_glfw.joysticks + jid);
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
if (_glfw.ns.hidManager)
{
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
{
if (mode & _GLFW_POLL_AXES)

View File

@@ -279,14 +279,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
CFSTR("IOFBCurrentPixelCount"),
kCFAllocatorDefault,
kNilOptions);
if (!clockRef || !countRef)
break;
uint32_t clock = 0, count = 0;
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
CFNumberGetValue(countRef, kCFNumberIntType, &count);
CFRelease(clockRef);
CFRelease(countRef);
if (clockRef)
{
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
CFRelease(clockRef);
}
if (countRef)
{
CFNumberGetValue(countRef, kCFNumberIntType, &count);
CFRelease(countRef);
}
if (clock > 0 && count > 0)
refreshRate = clock / (double) count;

View File

@@ -96,16 +96,11 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
#include "posix_thread.h"
#include "cocoa_joystick.h"
#include "nsgl_context.h"
#include "egl_context.h"
#include "osmesa_context.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns

View File

@@ -1854,6 +1854,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
[window->ns.object setLevel:NSNormalWindowLevel];
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
[window->ns.object setIgnoresMouseEvents:enabled];
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
return (float) [window->ns.object alphaValue];
@@ -2105,6 +2110,47 @@ const char* _glfwPlatformGetClipboardString(void)
return _glfw.ns.clipboardString;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
{
int type = 0;
if (_glfw.egl.ANGLE_platform_angle_opengl)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
}
if (_glfw.egl.ANGLE_platform_angle_metal)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL)
type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
}
if (type)
{
*attribs = calloc(3, sizeof(EGLint));
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
(*attribs)[1] = type;
(*attribs)[2] = EGL_NONE;
return EGL_PLATFORM_ANGLE_ANGLE;
}
}
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return EGL_DEFAULT_DISPLAY;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
return window->ns.layer;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)

68
glfw/egl_context.c vendored
View File

@@ -273,6 +273,8 @@ static void destroyContextEGL(_GLFWwindow* window)
bool _glfwInitEGL(void)
{
int i;
EGLint* attribs = NULL;
const char* extensions;
const char* sonames[] =
{
#if defined(_GLFW_EGL_LIBRARY)
@@ -351,7 +353,51 @@ bool _glfwInitEGL(void)
return false;
}
_glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (extensions && eglGetError() == EGL_SUCCESS)
_glfw.egl.EXT_client_extensions = true;
if (_glfw.egl.EXT_client_extensions)
{
_glfw.egl.EXT_platform_base =
_glfwStringInExtensionString("EGL_EXT_platform_base", extensions);
_glfw.egl.EXT_platform_x11 =
_glfwStringInExtensionString("EGL_EXT_platform_x11", extensions);
_glfw.egl.EXT_platform_wayland =
_glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions);
_glfw.egl.ANGLE_platform_angle =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions);
_glfw.egl.ANGLE_platform_angle_opengl =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions);
_glfw.egl.ANGLE_platform_angle_d3d =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions);
_glfw.egl.ANGLE_platform_angle_vulkan =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions);
_glfw.egl.ANGLE_platform_angle_metal =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions);
}
if (_glfw.egl.EXT_platform_base)
{
_glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
eglGetProcAddress("eglGetPlatformDisplayEXT");
_glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)
eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
}
_glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs);
if (_glfw.egl.platform)
{
_glfw.egl.display =
eglGetPlatformDisplayEXT(_glfw.egl.platform,
_glfwPlatformGetEGLNativeDisplay(),
attribs);
}
else
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
free(attribs);
if (_glfw.egl.display == EGL_NO_DISPLAY)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
@@ -419,6 +465,7 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
EGLint attribs[40];
EGLConfig config;
EGLContext share = NULL;
EGLNativeWindowType native;
int index = 0;
if (!_glfw.egl.display)
@@ -554,11 +601,20 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display,
config,
_GLFW_EGL_NATIVE_WINDOW,
attribs);
native = _glfwPlatformGetEGLNativeWindow(window);
// HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT
// despite reporting EGL_EXT_platform_base
if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE)
{
window->context.egl.surface =
eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs);
}
else
{
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display, config, native, attribs);
}
if (window->context.egl.surface == EGL_NO_SURFACE)
{
_glfwInputError(GLFW_PLATFORM_ERROR,

54
glfw/egl_context.h vendored
View File

@@ -25,26 +25,10 @@
//
//========================================================================
#if defined(_GLFW_USE_EGLPLATFORM_H)
#include <EGL/eglplatform.h>
#elif defined(_GLFW_WIN32)
#if defined(_GLFW_WIN32)
#define EGLAPIENTRY __stdcall
typedef HDC EGLNativeDisplayType;
typedef HWND EGLNativeWindowType;
#elif defined(_GLFW_COCOA)
#define EGLAPIENTRY
typedef void* EGLNativeDisplayType;
typedef id EGLNativeWindowType;
#elif defined(_GLFW_X11)
#define EGLAPIENTRY
typedef Display* EGLNativeDisplayType;
typedef Window EGLNativeWindowType;
#elif defined(_GLFW_WAYLAND)
#define EGLAPIENTRY
typedef struct wl_display* EGLNativeDisplayType;
typedef struct wl_egl_window* EGLNativeWindowType;
#else
#error "No supported EGL platform selected"
#define EGLAPIENTRY
#endif
#define EGL_SUCCESS 0x3000
@@ -108,6 +92,17 @@ typedef struct wl_egl_window* EGLNativeWindowType;
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
#define EGL_PLATFORM_X11_EXT 0x31d5
#define EGL_PLATFORM_WAYLAND_EXT 0x31d8
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d
#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e
#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207
#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450
#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489
#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f
typedef int EGLint;
typedef unsigned int EGLBoolean;
@@ -117,6 +112,9 @@ typedef void* EGLContext;
typedef void* EGLDisplay;
typedef void* EGLSurface;
typedef void* EGLNativeDisplayType;
typedef void* EGLNativeWindowType;
// EGL function pointer typedefs
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
@@ -153,9 +151,10 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglQueryString _glfw.egl.QueryString
#define eglGetProcAddress _glfw.egl.GetProcAddress
#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*);
#define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT
#define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT
// EGL-specific per-context data
//
@@ -173,6 +172,7 @@ typedef struct _GLFWcontextEGL
//
typedef struct _GLFWlibraryEGL
{
EGLenum platform;
EGLDisplay display;
EGLint major, minor;
bool prefix;
@@ -182,6 +182,15 @@ typedef struct _GLFWlibraryEGL
bool KHR_gl_colorspace;
bool KHR_get_all_proc_addresses;
bool KHR_context_flush_control;
bool EXT_client_extensions;
bool EXT_platform_base;
bool EXT_platform_x11;
bool EXT_platform_wayland;
bool ANGLE_platform_angle;
bool ANGLE_platform_angle_opengl;
bool ANGLE_platform_angle_d3d;
bool ANGLE_platform_angle_vulkan;
bool ANGLE_platform_angle_metal;
void* handle;
@@ -203,6 +212,9 @@ typedef struct _GLFWlibraryEGL
PFN_eglQueryString QueryString;
PFN_eglGetProcAddress GetProcAddress;
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
} _GLFWlibraryEGL;

42
glfw/glfw3.h vendored
View File

@@ -831,7 +831,7 @@ extern "C" {
* @analysis Application programmer error. Fix the offending call.
*/
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
/*! @brief The reuqested feature is not provided by the platform.
/*! @brief The requested feature is not provided by the platform.
*
* The requested feature is not provided by the platform, so GLFW is unable to
* implement it. The documentation for each function notes if it could emit
@@ -932,11 +932,19 @@ extern "C" {
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
*/
#define GLFW_FOCUS_ON_SHOW 0x0002000C
/*! @brief Mouse input transparency window hint and attribute
*
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
*/
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
/*! @brief Occlusion window attribute
*
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
*/
#define GLFW_OCCLUDED 0x0002000D
#define GLFW_OCCLUDED 0x0002000E
/*! @brief Framebuffer bit depth hint.
*
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
@@ -1054,12 +1062,17 @@ extern "C" {
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
*/
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
/*! @brief OpenGL debug context hint and attribute.
/*! @brief Debug mode context hint and attribute.
*
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
*/
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
#define GLFW_CONTEXT_DEBUG 0x00022007
/*! @brief Legacy name for compatibility.
*
* This is an alias for compatibility with earlier versions.
*/
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
/*! @brief OpenGL profile hint and attribute.
*
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
@@ -1142,6 +1155,14 @@ extern "C" {
#define GLFW_EGL_CONTEXT_API 0x00036002
#define GLFW_OSMESA_CONTEXT_API 0x00036003
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
/*! @defgroup shapes Standard cursor shapes
* @brief Standard system cursor shapes.
*
@@ -1175,8 +1196,12 @@ typedef enum {
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
*/
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
#define GLFW_DEBUG_KEYBOARD 0x00050002
#define GLFW_ENABLE_JOYSTICKS 0x00050003
/*! @brief ANGLE rendering backend init hint.
*
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
*/
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
#define GLFW_DEBUG_KEYBOARD 0x00050003
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
@@ -3723,6 +3748,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib),
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and
* [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib).
* [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib)
*
* Some of these attributes are ignored for full screen windows. The new
* value will take effect if the window is later made windowed.

9
glfw/init.c vendored
View File

@@ -53,8 +53,8 @@ static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
true, // hat buttons
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
false, // debug keyboard
true, // enable joystick
{
true, // macOS menu bar
true // macOS bundle chdir
@@ -92,6 +92,7 @@ static void terminate(void)
_glfw.mappingCount = 0;
_glfwTerminateVulkan();
_glfwPlatformTerminateJoysticks();
_glfwPlatformTerminate();
_glfw.initialized = false;
@@ -275,12 +276,12 @@ GLFWAPI void glfwInitHint(int hint, int value)
{
switch (hint)
{
case GLFW_ENABLE_JOYSTICKS:
_glfwInitHints.enableJoysticks = value;
return;
case GLFW_JOYSTICK_HAT_BUTTONS:
_glfwInitHints.hatButtons = value;
return;
case GLFW_ANGLE_PLATFORM_TYPE:
_glfwInitHints.angleType = value;
return;
case GLFW_DEBUG_KEYBOARD:
_glfwInitHints.debugKeyboard = value;
return;

47
glfw/input.c vendored
View File

@@ -44,6 +44,22 @@
#define _GLFW_JOYSTICK_BUTTON 2
#define _GLFW_JOYSTICK_HATBIT 3
// Initializes the platform joystick API if it has not been already
//
static bool initJoysticks(void)
{
if (!_glfw.joysticksInitialized)
{
if (!_glfwPlatformInitJoysticks())
{
_glfwPlatformTerminateJoysticks();
return false;
}
}
return _glfw.joysticksInitialized = true;
}
// Finds a mapping based on joystick GUID
//
static _GLFWmapping* findMapping(const char* guid)
@@ -1101,6 +1117,9 @@ GLFWAPI int glfwJoystickPresent(int jid)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;
@@ -1126,6 +1145,9 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1155,6 +1177,9 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1188,6 +1213,9 @@ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1214,6 +1242,9 @@ GLFWAPI const char* glfwGetJoystickName(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1239,6 +1270,9 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1284,6 +1318,10 @@ GLFWAPI void* glfwGetJoystickUserPointer(int jid)
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (!initJoysticks())
return NULL;
_GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun);
return cbfun;
}
@@ -1363,6 +1401,9 @@ GLFWAPI int glfwJoystickIsGamepad(int jid)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;
@@ -1388,6 +1429,9 @@ GLFWAPI const char* glfwGetGamepadName(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1420,6 +1464,9 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;

23
glfw/internal.h vendored
View File

@@ -198,6 +198,9 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
#error "No supported window creation API selected"
#endif
#include "egl_context.h"
#include "osmesa_context.h"
#define remove_i_from_array(array, i, count) { \
(count)--; \
if ((i) < (count)) { \
@@ -271,8 +274,8 @@ struct _GLFWerror
struct _GLFWinitconfig
{
bool hatButtons;
int angleType;
bool debugKeyboard;
bool enableJoysticks;
struct {
bool menubar;
bool chdir;
@@ -299,6 +302,7 @@ struct _GLFWwndconfig
bool maximized;
bool centerCursor;
bool focusOnShow;
bool mousePassthrough;
bool scaleToMonitor;
struct {
bool retina;
@@ -392,9 +396,9 @@ struct _GLFWcontext
// This is defined in the context API's context.h
_GLFW_PLATFORM_CONTEXT_STATE
// This is defined in egl_context.h
_GLFW_EGL_CONTEXT_STATE;
_GLFWcontextEGL egl;
// This is defined in osmesa_context.h
_GLFW_OSMESA_CONTEXT_STATE;
_GLFWcontextOSMesa osmesa;
};
// Window and context structure
@@ -409,6 +413,7 @@ struct _GLFWwindow
bool autoIconify;
bool floating;
bool focusOnShow;
bool mousePassthrough;
bool shouldClose;
void* userPointer;
GLFWid id;
@@ -569,6 +574,7 @@ struct _GLFWlibrary
_GLFWmonitor** monitors;
int monitorCount;
bool joysticksInitialized;
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
_GLFWmapping* mappings;
int mappingCount;
@@ -613,9 +619,9 @@ struct _GLFWlibrary
// This is defined in the platform's joystick.h
_GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
// This is defined in egl_context.h
_GLFW_EGL_LIBRARY_CONTEXT_STATE;
_GLFWlibraryEGL egl;
// This is defined in osmesa_context.h
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
_GLFWlibraryOSMesa osmesa;
};
// Global state shared between compilation units of GLFW
@@ -662,6 +668,8 @@ void _glfwPlatformSetPrimarySelectionString(const char* string);
const char* _glfwPlatformGetPrimarySelectionString(void);
#endif
bool _glfwPlatformInitJoysticks(void);
void _glfwPlatformTerminateJoysticks(void);
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
void _glfwPlatformUpdateGamepadGUID(char* guid);
@@ -713,6 +721,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
@@ -721,6 +730,10 @@ void _glfwPlatformWaitEvents(void);
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
void _glfwPlatformPostEmptyEvent(void);
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs);
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void);
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window);
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
VkPhysicalDevice device,

98
glfw/linux_joystick.c vendored
View File

@@ -264,9 +264,50 @@ static int compareJoysticks(const void* fp, const void* sp)
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize joystick interface
//
bool _glfwInitJoysticksLinux(void)
void _glfwDetectJoystickConnectionLinux(void)
{
if (_glfw.linjs.inotify <= 0)
return;
ssize_t offset = 0;
char buffer[16384];
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
while (size > offset)
{
regmatch_t match;
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
offset += sizeof(struct inotify_event) + e->len;
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
if (e->mask & (IN_CREATE | IN_ATTRIB))
openJoystickDevice(path);
else if (e->mask & IN_DELETE)
{
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
{
closeJoystick(_glfw.joysticks + jid);
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
bool _glfwPlatformInitJoysticks(void)
{
const char* dirname = "/dev/input";
@@ -320,9 +361,7 @@ bool _glfwInitJoysticksLinux(void)
return true;
}
// Close all opened joystick handles
//
void _glfwTerminateJoysticksLinux(void)
void _glfwPlatformTerminateJoysticks(void)
{
int jid;
@@ -333,61 +372,16 @@ void _glfwTerminateJoysticksLinux(void)
closeJoystick(js);
}
regfree(&_glfw.linjs.regex);
if (_glfw.linjs.inotify > 0)
{
if (_glfw.linjs.watch > 0)
inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
close(_glfw.linjs.inotify);
regfree(&_glfw.linjs.regex);
}
}
void _glfwDetectJoystickConnectionLinux(void)
{
if (_glfw.linjs.inotify <= 0)
return;
ssize_t offset = 0;
char buffer[16384];
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
while (size > offset)
{
regmatch_t match;
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
offset += sizeof(struct inotify_event) + e->len;
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
if (e->mask & (IN_CREATE | IN_ATTRIB))
openJoystickDevice(path);
else if (e->mask & IN_DELETE)
{
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
{
closeJoystick(_glfw.joysticks + jid);
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
{
// Read all queued events (non-blocking)

View File

@@ -55,7 +55,4 @@ typedef struct _GLFWlibraryLinux
bool dropped;
} _GLFWlibraryLinux;
bool _glfwInitJoysticksLinux(void);
void _glfwTerminateJoysticksLinux(void);
void _glfwDetectJoystickConnectionLinux(void);

View File

@@ -33,6 +33,15 @@
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformInitJoysticks(void)
{
return true;
}
void _glfwPlatformTerminateJoysticks(void)
{
}
int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED)
{
return false;

4
glfw/null_monitor.c vendored
View File

@@ -90,9 +90,9 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
const GLFWvidmode mode = getVideoMode();
if (xpos)
*xpos = 10;
*xpos = 0;
if (ypos)
ypos = 0;
*ypos = 10;
if (width)
*width = mode.width;
if (height)

View File

@@ -34,10 +34,7 @@
#define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_CURSOR_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
#define _GLFW_EGL_CONTEXT_STATE
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
#include "osmesa_context.h"
#include "posix_time.h"
#include "posix_thread.h"
#include "null_joystick.h"

4
glfw/null_window.c vendored
View File

@@ -372,6 +372,10 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
window->null.floating = enabled;
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
return window->null.opacity;

View File

@@ -54,10 +54,6 @@ typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*);
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa
#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa
// OSMesa-specific per-context data
//
typedef struct _GLFWcontextOSMesa

35
glfw/window.c vendored
View File

@@ -240,13 +240,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->videoMode.blueBits = fbconfig.blueBits;
window->videoMode.refreshRate = _glfw.hints.refreshRate;
window->monitor = (_GLFWmonitor*) monitor;
window->resizable = wndconfig.resizable;
window->decorated = wndconfig.decorated;
window->autoIconify = wndconfig.autoIconify;
window->floating = wndconfig.floating;
window->focusOnShow = wndconfig.focusOnShow;
window->cursorMode = GLFW_CURSOR_NORMAL;
window->monitor = (_GLFWmonitor*) monitor;
window->resizable = wndconfig.resizable;
window->decorated = wndconfig.decorated;
window->autoIconify = wndconfig.autoIconify;
window->floating = wndconfig.floating;
window->focusOnShow = wndconfig.focusOnShow;
window->mousePassthrough = wndconfig.mousePassthrough;
window->cursorMode = GLFW_CURSOR_NORMAL;
window->minwidth = GLFW_DONT_CARE;
window->minheight = GLFW_DONT_CARE;
@@ -273,6 +274,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
}
}
if (wndconfig.mousePassthrough)
_glfwPlatformSetWindowMousePassthrough(window, true);
if (window->monitor)
{
if (wndconfig.centerCursor)
@@ -422,6 +426,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_FOCUS_ON_SHOW:
_glfw.hints.window.focusOnShow = value ? true : false;
return;
case GLFW_MOUSE_PASSTHROUGH:
_glfw.hints.window.mousePassthrough = value ? true : false;
return;
case GLFW_CLIENT_API:
_glfw.hints.context.client = value;
return;
@@ -440,7 +447,7 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_OPENGL_FORWARD_COMPAT:
_glfw.hints.context.forward = value ? true : false;
return;
case GLFW_OPENGL_DEBUG_CONTEXT:
case GLFW_CONTEXT_DEBUG:
_glfw.hints.context.debug = value ? true : false;
return;
case GLFW_CONTEXT_NO_ERROR:
@@ -905,6 +912,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return _glfwPlatformWindowHovered(window);
case GLFW_FOCUS_ON_SHOW:
return window->focusOnShow;
case GLFW_MOUSE_PASSTHROUGH:
return window->mousePassthrough;
case GLFW_TRANSPARENT_FRAMEBUFFER:
return _glfwPlatformFramebufferTransparent(window);
case GLFW_OCCLUDED:
@@ -931,7 +940,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return window->context.robustness;
case GLFW_OPENGL_FORWARD_COMPAT:
return window->context.forward;
case GLFW_OPENGL_DEBUG_CONTEXT:
case GLFW_CONTEXT_DEBUG:
return window->context.debug;
case GLFW_OPENGL_PROFILE:
return window->context.profile;
@@ -985,6 +994,14 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value)
}
else if (attrib == GLFW_FOCUS_ON_SHOW)
window->focusOnShow = value;
else if (attrib == GLFW_MOUSE_PASSTHROUGH)
{
if (window->mousePassthrough == value)
return;
window->mousePassthrough = value;
_glfwPlatformSetWindowMousePassthrough(window, value);
}
else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
}

181
glfw/wl_cursors.c vendored
View File

@@ -1,7 +1,5 @@
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
#include "wl_cursors.h"
#include "internal.h"
#include <assert.h>
@@ -10,155 +8,58 @@
#include <stdio.h>
#include <stdlib.h>
typedef struct {
struct wl_cursor_theme *theme;
int px;
int refcount;
} _themeData;
struct _wlCursorThemeManager {
size_t count;
/** Pointer to the head of an unsorted array of themes with no sentinel.
*
* The lack of sort (and thus forcing a linear search) is intentional;
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
* For those cases, we get no benefit from sorting and added constant overheads.
*
* Don't change this to a flexible array member because that complicates growing/shrinking.
*/
_themeData *themes;
};
static void
_themeInit(_themeData *dest, const char *name, int px) {
dest->px = px;
dest->refcount = 1;
if (_glfw.wl.shm) {
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
if(!dest->theme) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load cursor theme");
static int
pixels_from_scale(int scale) {
static bool queried_env = false;
static int factor = 32;
if (!queried_env) {
const char *env = getenv("XCURSOR_SIZE");
if (env) {
const int retval = atoi(env);
if (retval > 0 && retval < 2048) factor = 32;
}
} else {
dest->theme = NULL;
queried_env = true;
}
return factor * scale;
}
static struct wl_cursor_theme*
_themeAdd(int px, _wlCursorThemeManager *manager) {
++manager->count;
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
if (!temp) {
_glfwInputError(GLFW_OUT_OF_MEMORY,
"OOM during cursor theme management.");
return NULL;
} else {
manager->themes = temp;
_themeInit(manager->themes + manager->count-1,
getenv("XCURSOR_THEME"),
px);
return manager->themes[manager->count-1].theme;
}
}
//WARNING: No input safety checks.
static inline void _themeInc(_themeData
*theme) {
++(theme->refcount);
}
struct wl_cursor_theme*
glfw_wlc_theme_for_scale(int scale) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
for (size_t i = 0; i < t->count; i++) {
if (t->themes[i].scale == scale) return t->themes[i].theme;
}
// WARNING: No input safety checks.
// In particular, doesn't check if theme is actually managed by the manager.
static void
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
if (--(theme->refcount) == 0) {
wl_cursor_theme_destroy(theme->theme);
if (--(manager->count) > 0) {
const _themeData *last_theme = (manager->themes)+(manager->count);
*theme = *last_theme;
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
// We're shrinking here, so it's not catastrophic if realloc fails.
if (temp) manager->themes = temp;
} else {
free(manager->themes);
manager->themes = NULL;
if (t->count >= t->capacity) {
t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16));
if (!t->themes) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes");
return NULL;
}
t->capacity = t->count + 16;
}
}
static _wlCursorThemeManager _default = {0};
_wlCursorThemeManager*
_wlCursorThemeManagerDefault() {
return &_default;
struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm);
if (!ans) {
_glfwInputError(
GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d",
scale, pixels_from_scale(scale)
);
return NULL;
}
GLFWWLCursorTheme *theme = t->themes + t->count++;
theme->scale = scale;
theme->theme = ans;
return ans;
}
void
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
if (manager) {
for (size_t i = 0; i < manager->count; ++i) {
wl_cursor_theme_destroy(manager->themes[i].theme);
}
free(manager->themes);
}
}
glfw_wlc_destroy(void) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
static struct wl_cursor_theme*
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
_themeData *themedata = NULL;
for (size_t i = 0; i < manager->count; ++i) {
_themeData *temp = manager->themes+i;
if (temp->px == px) {
themedata = temp;
break;
}
for (size_t i = 0; i < t->count; i++) {
wl_cursor_theme_destroy(t->themes[i].theme);
}
if (themedata != NULL) {
_themeInc(themedata);
return themedata->theme;
}
return _themeAdd(px, manager);
}
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
//WARNING: Multiple returns.
if (manager == NULL) {
return NULL;
}
if (theme != NULL) {
// Search for the provided theme in the manager.
_themeData *themedata = NULL;
for (size_t i = 0; i < manager->count; ++i) {
_themeData *temp = manager->themes+i;
if (temp->theme == theme) {
themedata = temp;
break;
}
}
if (themedata != NULL) {
// Search succeeded. Check if we can avoid unnecessary operations.
if (themedata->px == px) return theme;
_themeDec(themedata, manager);
} else {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland internal: managed theme isn't in the provided manager");
return theme;
//^ This is probably the sanest behavior for this situation: do nothing.
}
}
return px > 0 ? _wlCursorThemeManagerGet(manager, px) : NULL;
}
int
_wlCursorPxFromScale(int scale) {
const char *envStr = getenv("XCURSOR_SIZE");
if(envStr != NULL) {
const int retval = atoi(envStr);
//^ atoi here is fine since 0 is an invalid value.
if(retval > 0 && retval <= INT_MAX/scale) {
return retval*scale;
}
}
return 32*scale;
free(t->themes);
t->themes = NULL; t->capacity = 0; t->count = 0;
}

32
glfw/wl_cursors.h vendored
View File

@@ -2,29 +2,17 @@
#include <wayland-cursor.h>
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
typedef struct {
struct wl_cursor_theme *theme;
int scale;
} GLFWWLCursorTheme;
/** Returns a pointer to a wlCursorThemeManagerInstance.
* Repeatedly calling this function will return the same instance.
*
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
*/
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
* Pass the desired px as the third argument.
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
*
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
* The provided pointer may be invalidated if it's non-NULL.
*/
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
typedef struct {
GLFWWLCursorTheme *themes;
size_t count, capacity;
} GLFWWLCursorThemes;
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
/** Helper method to determine the appropriate size in pixels for a given scale.
*
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
*/
int _wlCursorPxFromScale(int);
struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale);
void glfw_wlc_destroy(void);

34
glfw/wl_init.c vendored
View File

@@ -148,12 +148,9 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
_wlCursorPxFromScale(scale)
);
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (!theme) return;
cursor = _glfwLoadCursor(shape, theme);
if (!cursor) return;
// TODO: handle animated cursors too.
image = cursor->images[0];
@@ -337,9 +334,9 @@ static void pointerHandleAxis(void* data UNUSED,
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
x = wl_fixed_to_double(value) * -1;
x = -wl_fixed_to_double(value);
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
y = wl_fixed_to_double(value) * -1;
y = -wl_fixed_to_double(value);
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
}
@@ -771,13 +768,6 @@ int _glfwPlatformInit(void)
// Sync so we got all initial output events
wl_display_roundtrip(_glfw.wl.display);
#ifdef __linux__
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return false;
}
#endif
if (!_glfw.wl.wmBase)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -787,13 +777,14 @@ int _glfwPlatformInit(void)
if (_glfw.wl.shm)
{
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
_glfw.wl.cursorSurface =
_glfw.wl.cursorSurface =
wl_compositor_create_surface(_glfw.wl.compositor);
}
else
{
_glfw.wl.cursorThemeManager = NULL;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to find Wayland SHM");
return false;
}
return true;
@@ -801,9 +792,6 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
#ifdef __linux__
_glfwTerminateJoysticksLinux();
#endif
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -822,9 +810,7 @@ void _glfwPlatformTerminate(void)
if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.cursorThemeManager) {
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
}
glfw_wlc_destroy();
if (_glfw.wl.subcompositor)
wl_subcompositor_destroy(_glfw.wl.subcompositor);
if (_glfw.wl.compositor)

11
glfw/wl_platform.h vendored
View File

@@ -50,8 +50,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#endif
#include "backend_utils.h"
#include "xkb_glfw.h"
#include "egl_context.h"
#include "osmesa_context.h"
#include "wl_cursors.h"
#include "wayland-xdg-shell-client-protocol.h"
@@ -66,9 +64,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->wl.native)
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.wl.display)
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl
@@ -137,7 +132,6 @@ typedef struct _GLFWwindowWayland
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
struct wl_cursor_theme* cursorTheme;
char* title;
char appId[256];
@@ -267,8 +261,7 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
_wlCursorThemeManager* cursorThemeManager;
GLFWWLCursorThemes cursor_themes;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -292,7 +285,7 @@ typedef struct _GLFWcursorWayland
struct wl_buffer* buffer;
int width, height;
int xhot, yhot;
int currentImage;
unsigned int currentImage;
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
int scale;
/** Cursor shape stored to allow late cursor loading in setCursorImage. */

80
glfw/wl_window.c vendored
View File

@@ -41,11 +41,6 @@
#include <fcntl.h>
#include <sys/mman.h>
static void
load_cursor_theme(_GLFWwindow* window) {
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager, window->wl.cursorTheme, _wlCursorPxFromScale(window->wl.scale));
}
static void
setCursorImage(_GLFWwindow* window)
@@ -63,18 +58,23 @@ setCursorImage(_GLFWwindow* window)
} else
{
if (cursorWayland->scale != scale) {
if (!window->wl.cursorTheme) load_cursor_theme(window);
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
struct wl_cursor *newCursor = NULL;
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme);
if (newCursor != NULL) {
cursorWayland->cursor = newCursor;
cursorWayland->scale = scale;
cursorWayland->currentImage = 0;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
}
}
if (!cursorWayland->cursor)
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count)
return;
if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0;
image = cursorWayland->cursor->images[cursorWayland->currentImage];
if (!image) image = cursorWayland->cursor->images[0];
if (!image) return;
buffer = wl_cursor_image_get_buffer(image);
if (image->delay && window->cursor) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
@@ -132,7 +132,6 @@ static bool checkScaleChange(_GLFWwindow* window)
{
window->wl.scale = scale;
wl_surface_set_buffer_scale(window->wl.surface, scale);
load_cursor_theme(window);
setCursorImage(window);
return true;
}
@@ -764,14 +763,18 @@ animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) {
static void
abortOnFatalError(int last_error) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
else {
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
_glfwInputWindowCloseRequest(window);
window = window->next;
static bool abort_called = false;
if (!abort_called) {
abort_called = true;
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
else {
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
_glfwInputWindowCloseRequest(window);
window = window->next;
}
}
}
// ensure the tick callback is called
@@ -937,12 +940,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
if (window->wl.cursorTheme) {
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
0);
window->wl.cursorTheme = NULL;
}
if (window == _glfw.wl.pointerFocus)
{
_glfw.wl.pointerFocus = NULL;
@@ -1075,8 +1072,10 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
int* width, int* height)
{
_glfwPlatformGetWindowSize(window, width, height);
*width *= window->wl.scale;
*height *= window->wl.scale;
if (width)
*width *= window->wl.scale;
if (height)
*height *= window->wl.scale;
}
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
@@ -1263,6 +1262,19 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNU
"Wayland: Window attribute setting not implemented yet");
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
if (enabled)
{
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
wl_surface_set_input_region(window->wl.surface, region);
wl_region_destroy(region);
}
else
wl_surface_set_input_region(window->wl.surface, 0);
wl_surface_commit(window->wl.surface);
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
{
return 1.f;
@@ -2008,6 +2020,24 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
return NULL;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
{
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland)
return EGL_PLATFORM_WAYLAND_EXT;
else
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return _glfw.wl.display;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
return window->wl.native;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)

36
glfw/x11_init.c vendored
View File

@@ -327,6 +327,30 @@ static bool initExtensions(void)
}
}
#if defined(__CYGWIN__)
_glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so");
#else
_glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6");
#endif
if (_glfw.x11.xshape.handle)
{
glfw_dlsym(_glfw.x11.xshape.QueryExtension, _glfw.x11.xshape.handle, "XShapeQueryExtension");
glfw_dlsym(_glfw.x11.xshape.ShapeCombineRegion, _glfw.x11.xshape.handle, "XShapeCombineRegion");
glfw_dlsym(_glfw.x11.xshape.QueryVersion, _glfw.x11.xshape.handle, "XShapeQueryVersion");
if (XShapeQueryExtension(_glfw.x11.display,
&_glfw.x11.xshape.errorBase,
&_glfw.x11.xshape.eventBase))
{
if (XShapeQueryVersion(_glfw.x11.display,
&_glfw.x11.xshape.major,
&_glfw.x11.xshape.minor))
{
_glfw.x11.xshape.available = true;
}
}
}
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
@@ -629,15 +653,6 @@ int _glfwPlatformInit(void)
_glfw.x11.helperWindowHandle = createHelperWindow();
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
#if defined(__linux__)
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return false;
if (_glfw.linjs.inotify > 0)
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
}
#endif
_glfwPollMonitorsX11();
return true;
}
@@ -716,9 +731,6 @@ void _glfwPlatformTerminate(void)
_glfwTerminateEGL();
_glfwTerminateGLX();
#if defined(__linux__)
_glfwTerminateJoysticksLinux();
#endif
finalizePollData(&_glfw.x11.eventLoopData);
}

2
glfw/x11_monitor.c vendored
View File

@@ -169,7 +169,7 @@ void _glfwPollMonitorsX11(void)
if (widthMM <= 0 || heightMM <= 0)
{
// HACK: If RandR does not provide a physical size, assume the
// X11 default 96 DPI and calcuate from the CRTC viewport
// X11 default 96 DPI and calculate from the CRTC viewport
// NOTE: These members are affected by rotation, unlike the mode
// info and output info members
widthMM = (int) (ci->width * 25.4f / 96.f);

31
glfw/x11_platform.h vendored
View File

@@ -51,6 +51,9 @@
// The XInput extension provides raw mouse motion input
#include <X11/extensions/XInput2.h>
// The Shape extension provides custom window shapes
#include <X11/extensions/shape.h>
// The libxkb library is used for improved keyboard support
#include "xkb_glfw.h"
#include "backend_utils.h"
@@ -125,6 +128,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*);
typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*);
typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int);
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
@@ -153,8 +166,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
#include "posix_thread.h"
#include "glx_context.h"
#include "egl_context.h"
#include "osmesa_context.h"
#if defined(__linux__)
#include "linux_joystick.h"
#else
@@ -165,9 +176,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->x11.handle)
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display)
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
@@ -382,6 +390,19 @@ typedef struct _GLFWlibraryX11
PFN_XRenderFindVisualFormat FindVisualFormat;
} xrender;
struct {
bool available;
void* handle;
int major;
int minor;
int eventBase;
int errorBase;
PFN_XShapeQueryExtension QueryExtension;
PFN_XShapeCombineRegion ShapeCombineRegion;
PFN_XShapeQueryVersion QueryVersion;
PFN_XShapeCombineMask ShapeCombineMask;
} xshape;
EventLoopData eventLoopData;
} _GLFWlibraryX11;

108
glfw/x11_window.c vendored
View File

@@ -1176,15 +1176,26 @@ static void processEvent(XEvent *event)
else if (event->type == _glfw.x11.xkb.eventBase)
{
XkbEvent *kb_event = (XkbEvent*)event;
if (kb_event->any.device != (unsigned int)_glfw.x11.xkb.keyboard_device_id) return;
switch(kb_event->any.xkb_type) {
case XkbNewKeyboardNotify: {
int32_t old_id = _glfw.x11.xkb.keyboard_device_id;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
if (old_id != _glfw.x11.xkb.keyboard_device_id) keymap_dirty = true;
XkbNewKeyboardNotifyEvent *newkb_event = (XkbNewKeyboardNotifyEvent*)kb_event;
if (_glfw.hints.init.debugKeyboard) printf(
"Got XkbNewKeyboardNotify event with changes: key codes: %d geometry: %d device id: %d\n",
!!(newkb_event->changed & XkbNKN_KeycodesMask), !!(newkb_event->changed & XkbNKN_GeometryMask),
!!(newkb_event->changed & XkbNKN_DeviceIDMask));
if (newkb_event->changed & XkbNKN_DeviceIDMask) {
keymap_dirty = true;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
}
if (newkb_event->changed & XkbNKN_KeycodesMask) {
keymap_dirty = true;
}
return;
}
/* fallthrough */
case XkbMapNotify:
{
if (_glfw.hints.init.debugKeyboard) printf("Got XkbMapNotify event, keymaps will be reloaded\n");
keymap_dirty = true;
return;
}
@@ -2449,13 +2460,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
int rootX, rootY, childX, childY;
unsigned int mask;
if (!XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
{
return false;
}
_glfwGrabErrorHandlerX11();
if (w == window->x11.handle)
const Bool result = XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY,
&childX, &childY, &mask);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode == BadWindow)
w = _glfw.x11.root;
else if (!result)
return false;
else if (w == window->x11.handle)
return true;
}
@@ -2569,6 +2586,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
XFlush(_glfw.x11.display);
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
if (!_glfw.x11.xshape.available)
return;
if (enabled)
{
Region region = XCreateRegion();
XShapeCombineRegion(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
}
else
{
XShapeCombineMask(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, None, ShapeSet);
}
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
float opacity = 1.f;
@@ -2617,7 +2653,8 @@ _glfwDispatchX11Events(void) {
unsigned dispatched = 0;
#if defined(__linux__)
if (_glfw.hints.init.enableJoysticks) _glfwDetectJoystickConnectionLinux();
if (_glfw.joysticksInitialized)
_glfwDetectJoystickConnectionLinux();
#endif
dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush));
@@ -2844,6 +2881,55 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
return getSelectionString(_glfw.x11.PRIMARY);
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
{
int type = 0;
if (_glfw.egl.ANGLE_platform_angle_opengl)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
}
if (_glfw.egl.ANGLE_platform_angle_vulkan)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN)
type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE;
}
if (type)
{
*attribs = calloc(5, sizeof(EGLint));
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
(*attribs)[1] = type;
(*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE;
(*attribs)[3] = EGL_PLATFORM_X11_EXT;
(*attribs)[4] = EGL_NONE;
return EGL_PLATFORM_ANGLE_ANGLE;
}
}
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11)
return EGL_PLATFORM_X11_EXT;
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return _glfw.x11.display;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
if (_glfw.egl.platform)
return &window->x11.handle;
else
return (EGLNativeWindowType) window->x11.handle;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface)

56
glfw/xkb_glfw.c vendored
View File

@@ -371,9 +371,31 @@ load_compose_tables(_GLFWXKBData *xkb) {
xkb_compose_table_unref(compose_table);
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
static void
update_modifiers(_GLFWXKBData *xkb) {
XKBStateGroup *group = &xkb->states;
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
bool
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
const char *err;
debug("Loading new XKB keymaps\n");
release_keyboard_data(xkb);
err = load_keymaps(xkb, map_str);
if (err) {
@@ -396,36 +418,17 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
#undef S
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
size_t capacity = arraysz(xkb->unknownModifiers), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
xkb->states.modifiers = 0;
xkb->states.activeUnknownModifiers = 0;
update_modifiers(xkb);
return true;
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
static void
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
void
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
if (!xkb->keymap) return;
@@ -434,7 +437,7 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
// We have to update the groups in clean_state, as they change for
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
update_modifiers(xkb, &xkb->states);
update_modifiers(xkb);
}
bool
@@ -503,14 +506,19 @@ static inline const char*
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
static char buf[512];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
#define pr(x) { \
int num_needed = -1; \
ssize_t space_left = sizeof(buf) - (p - buf) - 1; \
if (space_left > 0) num_needed = snprintf(p, space_left, "%s", x); \
if (num_needed > 0) p += num_needed; \
}
pr(name); pr(": ");
s = p;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
xkb_mod_mask_t m = 1 << i;
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
}
if (p == s) pr("none");
if (p == s) { pr("none"); }
else p--;
pr(" ");
#undef pr

View File

@@ -559,7 +559,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
for m, g in zip(data['match'], data['groupdicts']):
if m:
path, line = g['path'], g['line']
path = path.split(':')[-1]
path = os.path.expanduser(path.split(':')[-1])
line = int(line)
break
else:

View File

@@ -341,7 +341,7 @@ def request_from_clipboard(use_primary: bool = False) -> str:
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
# Boilerplate to make operations availble via Handler.cmd {{{
# Boilerplate to make operations available via Handler.cmd {{{
def writer(handler: HandlerType, func: Callable) -> Callable:

View File

@@ -20,7 +20,7 @@ from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
from kitty.key_encoding import (
CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
CTRL, PRESS, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
)
from kitty.typing import BossType
from kitty.utils import ScreenSize, get_editor
@@ -38,6 +38,8 @@ UP = K['UP']
DOWN = K['DOWN']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
RIGHT_BRACKET = K['RIGHT_BRACKET']
LEFT_BRACKET = K['LEFT_BRACKET']
TAB = K['TAB']
ESCAPE = K['ESCAPE']
F1 = K['F1']
@@ -478,7 +480,7 @@ class UnicodeInput(Handler):
return
if key_event is enter_key:
self.quit_loop(0)
elif key_event.type is RELEASE:
elif key_event.type is PRESS:
if not key_event.mods:
if key_event.key is ESCAPE:
self.quit_loop(1)
@@ -492,8 +494,8 @@ class UnicodeInput(Handler):
self.switch_mode(FAVORITES)
elif key_event.key is F12 and self.mode is FAVORITES:
self.edit_favorites()
elif key_event.mods == CTRL and key_event.key is TAB:
self.next_mode()
elif key_event.mods == CTRL and key_event.key in (TAB, RIGHT_BRACKET, LEFT_BRACKET):
self.next_mode(-1 if key_event.key is LEFT_BRACKET else 1)
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
self.next_mode(-1)

View File

@@ -49,14 +49,15 @@ from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
log_error, open_url, parse_address_spec, parse_uri_list,
remove_socket_file, safe_print, set_primary_selection, single_instance,
startup_notification_handler
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
single_instance, startup_notification_handler
)
from .window import MatchPatternType, Window
class OSWindowDict(TypedDict):
id: int
platform_window_id: Optional[int]
is_focused: bool
tabs: List[TabDict]
@@ -195,7 +196,7 @@ class Boss:
) -> int:
if os_window_id is None:
opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts
wclass = wclass or self.args.cls or appname
wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
os_window_id = create_os_window(
initial_window_size_func(opts_for_size, self.cached_values),
@@ -212,6 +213,7 @@ class Boss:
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window)),
}
@@ -382,6 +384,8 @@ class Boss:
args.directory = os.path.join(data['cwd'], args.directory)
for session in create_sessions(opts, args, respect_cwd=True):
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
if opts.background_opacity != self.opts.background_opacity:
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
if data.get('notify_on_os_window_death'):
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
else:
@@ -442,8 +446,34 @@ class Boss:
def close_tab(self, tab: Optional[Tab] = None) -> None:
tab = tab or self.active_tab
if tab:
for window in tab:
self.close_window(window)
self.confirm_tab_close(tab)
def confirm_tab_close(self, tab: Tab) -> None:
windows = tuple(tab)
needs_confirmation = self.opts.confirm_os_window_close > 0 and len(windows) >= self.opts.confirm_os_window_close
if not needs_confirmation:
self.close_tab_no_confirm(tab)
return
self._run_kitten('ask', ['--type=yesno', '--message', _(
'Are you sure you want to close this tab, it has {}'
' windows running?').format(len(windows))],
window=tab.active_window,
custom_callback=partial(self.handle_close_tab_confirmation, tab.id)
)
def handle_close_tab_confirmation(self, tab_id: int, data: Dict[str, Any], *a: Any) -> None:
if data['response'] != 'y':
return
for tab in self.all_tabs:
if tab.id == tab_id:
break
else:
return
self.close_tab_no_confirm(tab)
def close_tab_no_confirm(self, tab: Tab) -> None:
for window in tab:
self.close_window(window)
def toggle_fullscreen(self) -> None:
toggle_fullscreen()
@@ -851,6 +881,9 @@ class Boss:
add_history='history' in type_of_input,
add_wrap_markers='screen' in type_of_input
).encode('utf-8')
elif type_of_input == 'selection':
sel = self.data_for_at(which='@selection', window=w)
data = sel.encode('utf-8') if sel else None
elif type_of_input is None:
data = None
else:
@@ -858,9 +891,16 @@ class Boss:
else:
data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8')
copts = common_opts_as_dict(self.opts)
final_args: List[str] = []
for x in args:
if x == '@selection':
sel = self.data_for_at(which='@selection', window=w)
if sel:
x = sel
final_args.append(x)
overlay_window = tab.new_special_window(
SpecialWindow(
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + args,
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + final_args,
stdin=data,
env={
'KITTY_COMMON_OPTS': json.dumps(copts),

View File

@@ -40,7 +40,7 @@ static void (*parse_func)(Screen*, PyObject*, monotonic_t);
typedef struct {
char *data;
size_t sz;
int fd;
id_type peer_id;
} Message;
typedef struct {
@@ -71,9 +71,8 @@ static const Child EMPTY_CHILD = {0};
pthread_mutex_##op(&screen->which##_buf_lock);
#define children_mutex(op) \
pthread_mutex_##op(&children_lock);
#define peer_mutex(op) \
pthread_mutex_##op(&talk_data.peer_lock);
#define talk_mutex(op) \
pthread_mutex_##op(&talk_lock);
static Child children[MAX_CHILDREN] = {{0}};
@@ -82,7 +81,7 @@ static Child add_queue[MAX_CHILDREN] = {{0}}, remove_queue[MAX_CHILDREN] = {{0}}
static unsigned long remove_notify[MAX_CHILDREN] = {0};
static size_t add_queue_count = 0, remove_queue_count = 0;
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
static pthread_mutex_t children_lock;
static pthread_mutex_t children_lock, talk_lock;
static bool kill_signal_received = false;
static ChildMonitor *the_monitor = NULL;
static uint8_t drain_buf[1024];
@@ -131,6 +130,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL;
}
if ((ret = pthread_mutex_init(&talk_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create talk_lock mutex: %s", strerror(ret));
return NULL;
}
self = (ChildMonitor *)type->tp_alloc(type, 0);
if (!init_loop_data(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
if (!install_signal_handlers(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
@@ -152,7 +155,13 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
static void
dealloc(ChildMonitor* self) {
if (self->messages) {
for (size_t i = 0; i < self->messages_count; i++) free(self->messages[i].data);
free(self->messages); self->messages = NULL;
self->messages_count = 0; self->messages_capacity = 0;
}
pthread_mutex_destroy(&children_lock);
pthread_mutex_destroy(&talk_lock);
Py_CLEAR(self->dump_callback);
Py_CLEAR(self->death_notify);
Py_TYPE(self)->tp_free((PyObject*)self);
@@ -174,7 +183,7 @@ wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
static void* io_loop(void *data);
static void* talk_loop(void *data);
static void send_response(int fd, const char *msg, size_t msg_sz);
static void send_response(id_type peer_id, const char *msg, size_t msg_sz);
static void wakeup_talk_loop(bool);
static bool talk_thread_started = false;
@@ -324,7 +333,6 @@ parse_input(ChildMonitor *self) {
size_t count = 0, remove_count = 0;
bool input_read = false;
monotonic_t now = monotonic();
PyObject *msg = NULL;
children_mutex(lock);
while (remove_queue_count) {
remove_queue_count--;
@@ -333,18 +341,6 @@ parse_input(ChildMonitor *self) {
FREE_CHILD(remove_queue[remove_queue_count]);
}
if (UNLIKELY(self->messages_count)) {
msg = PyTuple_New(self->messages_count);
if (msg) {
for (size_t i = 0; i < self->messages_count; i++) {
Message *m = self->messages + i;
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
free(m->data); m->data = NULL; m->sz = 0;
}
self->messages_count = 0;
} else fatal("Out of memory");
}
if (UNLIKELY(kill_signal_received)) {
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
global_state.has_pending_closes = true;
@@ -358,15 +354,35 @@ parse_input(ChildMonitor *self) {
}
}
children_mutex(unlock);
if (msg) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
Message *msgs = NULL;
size_t msgs_count = 0;
talk_mutex(lock);
if (UNLIKELY(self->messages_count)) {
msgs = malloc(sizeof(Message) * self->messages_count);
if (msgs) {
memcpy(msgs, self->messages, sizeof(Message) * self->messages_count);
msgs_count = self->messages_count;
}
memset(self->messages, 0, sizeof(Message) * self->messages_capacity);
self->messages_count = 0;
}
talk_mutex(unlock);
if (msgs_count) {
for (size_t i = 0; i < msgs_count; i++) {
Message *msg = msgs + i;
PyObject *resp = NULL;
if (msg->data) {
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#", msg->data, (int)msg->sz);
free(msg->data);
if (!resp) PyErr_Print();
}
if (resp && PyBytes_Check(resp)) send_response(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else send_response(msg->peer_id, NULL, 0);
Py_CLEAR(resp);
}
Py_CLEAR(msg);
free(msgs); msgs = NULL;
}
while(remove_count) {
@@ -503,7 +519,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
int n = t / d;
do_draw_cursor = n % 2 == 0 ? true : false;
monotonic_t bucket = ms_to_monotonic_t((n + 1) * d);
monotonic_t bucket = ms_to_monotonic_t((monotonic_t)(n + 1) * d);
monotonic_t delay = bucket - time_since_start_blink;
set_maximum_wait(delay);
}
@@ -766,7 +782,7 @@ thread_write(void *x) {
if (pos < data->sz) {
log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno));
}
safe_close(data->fd);
safe_close(data->fd, __FILE__, __LINE__);
free_twd(data);
return 0;
}
@@ -783,7 +799,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
data->fd = fd;
memcpy(data->buf, buf, data->sz);
int ret = pthread_create(&thread, NULL, thread_write, data);
if (ret != 0) { safe_close(fd); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
pthread_detach(thread);
Py_RETURN_NONE;
}
@@ -1017,7 +1033,7 @@ hangup(pid_t pid) {
static inline void
cleanup_child(ssize_t i) {
safe_close(children[i].fd);
safe_close(children[i].fd, __FILE__, __LINE__);
hangup(children[i].pid);
}
@@ -1279,36 +1295,33 @@ io_loop(void *data) {
// {{{ Talk thread functions
typedef struct {
char *data;
size_t capacity, used;
id_type id;
size_t num_of_unresponded_messages_sent_to_main_thread;
bool finished_reading;
int fd;
bool finished, close_socket, is_peer_command;
} PeerReadData;
static PeerReadData empty_prd = {.fd = -1, 0};
struct {
char *data;
size_t capacity, used, command_end;
bool finished;
} read;
struct {
char *data;
size_t capacity, used;
bool failed;
} write;
} Peer;
static id_type peer_id_counter = 0;
typedef struct {
char *data;
size_t sz, pos;
int fd;
bool finished;
} PeerWriteData;
static PeerWriteData empty_pwd = {.fd = -1, 0};
typedef struct {
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
struct pollfd *fds;
PeerReadData *reads;
PeerWriteData *writes;
PeerWriteData *queued_writes;
size_t num_peers, peers_capacity;
Peer *peers;
LoopData loop_data;
pthread_mutex_t peer_lock;
} TalkData;
static TalkData talk_data = {0};
typedef struct pollfd PollFD;
#define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s); }
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
static inline bool
accept_peer(int listen_fd, bool shutting_down) {
@@ -1318,13 +1331,12 @@ accept_peer(int listen_fd, bool shutting_down) {
if (!shutting_down) perror("accept() on talk socket failed!");
return false;
}
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
talk_data.num_talk_fds++;
if (talk_data.num_peers < PEER_LIMIT) {
ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false);
Peer *p = talk_data.peers + talk_data.num_peers++;
memset(p, 0, sizeof(Peer));
p->fd = peer; p->id = ++peer_id_counter;
if (!p->id) p->id = ++peer_id_counter;
} else {
log_error("Too many peers want to talk, ignoring one.");
nuke_socket(peer);
@@ -1332,148 +1344,102 @@ accept_peer(int listen_fd, bool shutting_down) {
return true;
}
static inline void
free_peer(Peer *peer) {
free(peer->read.data); peer->read.data = NULL;
free(peer->write.data); peer->write.data = NULL;
if (peer->fd > -1) { nuke_socket(peer->fd); peer->fd = -1; }
}
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
static inline void
queue_peer_message(ChildMonitor *self, char *buf, size_t sz, int fd) {
children_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
queue_peer_message(ChildMonitor *self, Peer *peer) {
talk_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
m->data = buf; m->sz = sz; m->fd = fd;
children_mutex(unlock);
memset(m, 0, sizeof(Message));
if (peer->read.used) {
m->data = malloc(peer->read.used);
if (m->data) {
memcpy(m->data, peer->read.data, peer->read.used);
m->sz = peer->read.used;
}
}
m->peer_id = peer->id;
peer->num_of_unresponded_messages_sent_to_main_thread++;
talk_mutex(unlock);
wakeup_main_loop();
}
static inline void
dispatch_peer_command(ChildMonitor *self, PeerReadData *rd, int fd) {
size_t end = 0;
for (size_t i = 0; i < rd->used - 1; i++) {
if (rd->data[i] == 0x1b && rd->data[i+1] == '\\') {
end = i + 2;
break;
static inline bool
has_complete_peer_command(Peer *peer) {
peer->read.command_end = 0;
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
for (size_t i = sizeof(KITTY_CMD_PREFIX)-1; i < peer->read.used - 1; i++) {
if (peer->read.data[i] == 0x1b && peer->read.data[i+1] == '\\') {
peer->read.command_end = i + 2;
break;
}
}
}
if (!end) return;
char *buf = malloc(end + 8);
if (buf) {
memcpy(buf, rd->data, end);
queue_peer_message(self, buf, end, fd);
}
rd->is_peer_command = false;
if (rd->used > end) {
rd->used -= end;
memmove(rd->data, rd->data + end, rd->used);
if (rd->used >= sizeof(KITTY_CMD_PREFIX) - 1 && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
} else rd->used = 0;
return peer->read.command_end ? true : false;
}
static inline bool
read_from_peer(ChildMonitor *self, int s) {
bool read_finished = false;
for (size_t i = 0; i < talk_data.num_reads; i++) {
PeerReadData *rd = talk_data.reads + i;
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
if (rd->fd == s) {
if (rd->used >= rd->capacity) {
if (rd->capacity >= 64 * 1024) failed("Ignoring too large message from peer");
rd->capacity = MAX(8192u, rd->capacity * 2);
rd->data = realloc(rd->data, rd->capacity);
if (!rd->data) failed("Out of memory");
}
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
if (n == 0) {
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
read_finished = true; rd->finished = true;
if (rd->used) queue_peer_message(self, rd->data, rd->used, s);
else free(rd->data);
rd->data = NULL;
} else if (n < 0) {
if (errno != EINTR) {
perror("Error reading from talk peer");
failed("");
}
} else {
if (!rd->used && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
rd->used += n;
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
}
break;
}
static inline void
dispatch_peer_command(ChildMonitor *self, Peer *peer) {
if (peer->read.command_end) {
size_t used = peer->read.used;
peer->read.used = peer->read.command_end;
queue_peer_message(self, peer);
peer->read.used = used;
if (peer->read.used > peer->read.command_end) {
peer->read.used -= peer->read.command_end;
memmove(peer->read.data, peer->read.data + peer->read.command_end, peer->read.used);
} else peer->read.used = 0;
peer->read.command_end = 0;
}
}
static inline void
read_from_peer(ChildMonitor *self, Peer *peer) {
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
if (peer->read.used >= peer->read.capacity) {
if (peer->read.capacity >= 64 * 1024) failed("Ignoring too large message from peer");
peer->read.capacity = MAX(8192u, peer->read.capacity * 2);
peer->read.data = realloc(peer->read.data, peer->read.capacity);
if (!peer->read.data) failed("Out of memory");
}
ssize_t n = recv(peer->fd, peer->read.data + peer->read.used, peer->read.capacity - peer->read.used, 0);
if (n == 0) {
peer->read.finished = true;
shutdown(peer->fd, SHUT_RD);
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
queue_peer_message(self, peer);
free(peer->read.data); peer->read.data = NULL;
peer->read.used = 0; peer->read.capacity = 0;
} else if (n < 0) {
if (errno != EINTR) failed(strerror(errno));
} else {
peer->read.used += n;
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
}
#undef failed
return read_finished;
}
static inline bool
write_to_peer(int fd) {
bool write_finished = false;
for (size_t i = 0; i < talk_data.num_writes; i++) {
PeerWriteData *wd = talk_data.writes + i;
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
if (wd->fd == fd) {
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
if (n == 0) { failed("send() to peer failed to send any data"); }
else if (n < 0) {
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
} else {
wd->pos += n;
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
}
break;
}
}
#undef failed
return write_finished;
}
static inline void
remove_poll_fd(int fd) {
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
struct pollfd *pfd = talk_data.fds + i;
if (pfd->fd == fd) {
size_t num_to_right = count - 1 - i;
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
talk_data.num_talk_fds--;
break;
}
}
}
static inline void
prune_finished_reads(void) {
if (!talk_data.num_reads) return;
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
PeerReadData *rd = talk_data.reads + i;
if (rd->finished) {
remove_poll_fd(rd->fd);
if (rd->close_socket) { nuke_socket(rd->fd); }
else shutdown(rd->fd, SHUT_RD);
free(rd->data);
ssize_t num_to_right = talk_data.num_reads - 1 - i;
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
else talk_data.reads[i] = empty_prd;
talk_data.num_reads--;
}
}
}
static inline void
prune_finished_writes(void) {
if (!talk_data.num_writes) return;
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
PeerWriteData *wd = talk_data.writes + i;
if (wd->finished) {
remove_poll_fd(wd->fd);
shutdown(wd->fd, SHUT_WR); safe_close(wd->fd);
free(wd->data);
ssize_t num_to_right = talk_data.num_writes - 1 - i;
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
else talk_data.writes[i] = empty_pwd;
talk_data.num_writes--;
}
write_to_peer(Peer *peer) {
talk_mutex(lock);
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
if (n == 0) { log_error("send() to peer failed to send any data"); peer->write.used = 0; peer->write.failed = true; }
else if (n < 0) {
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
} else {
if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
peer->write.used -= n;
}
talk_mutex(unlock);
}
static void
@@ -1481,92 +1447,107 @@ wakeup_talk_loop(bool in_signal_handler) {
if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop");
}
static inline void
move_queued_writes(void) {
while (talk_data.num_queued_writes) {
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
talk_data.writes[talk_data.num_writes++] = *src;
talk_data.num_talk_fds++;
} else {
log_error("Cannot send response to peer, too many peers");
free(src->data); nuke_socket(src->fd);
prune_peers(void) {
for (size_t i = 0; i < talk_data.num_peers; i++) {
size_t idx = talk_data.num_peers - 1 - i;
Peer *p = talk_data.peers + idx;
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
free_peer(p);
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
}
*src = empty_pwd;
}
}
static void*
talk_loop(void *data) {
// The talk thread loop
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyPeerMon");
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
PollFD fds[PEER_LIMIT + 8] = {0};
size_t num_listen_fds = 0, num_peer_fds = 0;
#define add_listener(which) \
if (self->which > -1) { \
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
fds[num_listen_fds].fd = self->which; fds[num_listen_fds++].events = POLLIN; \
}
add_listener(talk_fd); add_listener(listen_fd);
#undef add_listener
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
fds[num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; fds[num_listen_fds++].events = POLLIN;
while (LIKELY(!self->shutting_down)) {
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
num_peer_fds = 0;
if (talk_data.num_peers > 0) {
talk_mutex(lock);
prune_peers();
for (size_t i = 0; i < talk_data.num_peers; i++) {
Peer *p = talk_data.peers + i;
if (!p->read.finished || p->write.used) {
fds[num_listen_fds + num_peer_fds].fd = p->fd;
fds[num_listen_fds + num_peer_fds].revents = 0;
int flags = 0;
if (!p->read.finished) flags |= POLLIN;
if (p->write.used) flags |= POLLOUT;
fds[num_listen_fds + num_peer_fds++].events = flags;
}
}
talk_mutex(unlock);
}
for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0;
int ret = poll(fds, num_listen_fds + num_peer_fds, -1);
if (ret > 0) {
bool has_finished_reads = false, has_finished_writes = false;
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
for (size_t i = 0; i < num_listen_fds - 1; i++) {
if (fds[i].revents & POLLIN) {
if (!accept_peer(fds[i].fd, self->shutting_down)) goto end;
}
}
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
if (fds[num_listen_fds - 1].revents & POLLIN) {
drain_fd(fds[num_listen_fds - 1].fd); // wakeup
}
for (size_t i = num_listen_fds, k = 0; i < num_peer_fds + num_listen_fds; i++, k++) {
Peer *p = talk_data.peers + k;
if (fds[i].revents & (POLLIN | POLLHUP)) read_from_peer(self, p);
if (fds[i].revents & POLLOUT) write_to_peer(p);
if (fds[i].revents & POLLNVAL) {
p->read.finished = true;
p->write.failed = true; p->write.used = 0;
}
}
if (has_finished_reads) prune_finished_reads();
if (has_finished_writes) prune_finished_writes();
peer_mutex(lock);
if (talk_data.num_queued_writes) move_queued_writes();
peer_mutex(unlock);
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
}
end:
free_loop_data(&talk_data.loop_data);
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
for (size_t i = 0; i < talk_data.num_peers; i++) free_peer(talk_data.peers + i);
free(talk_data.peers);
return 0;
}
static inline bool
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
bool ok = false;
peer_mutex(lock);
if (talk_data.num_queued_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
ok = true;
}
} else log_error("Cannot send response to peer, too many peers");
peer_mutex(unlock);
return ok;
}
static void
send_response(int fd, const char *msg, size_t msg_sz) {
if (msg == NULL) { shutdown(fd, SHUT_WR); safe_close(fd); return; }
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); safe_close(fd); }
else wakeup_talk_loop(false);
send_response(id_type peer_id, const char *msg, size_t msg_sz) {
bool wakeup = false;
talk_mutex(lock);
for (size_t i = 0; i < talk_data.num_peers; i++) {
Peer *peer = talk_data.peers + i;
if (peer->id == peer_id) {
if (peer->num_of_unresponded_messages_sent_to_main_thread) peer->num_of_unresponded_messages_sent_to_main_thread--;
if (!peer->write.failed) {
if (peer->write.capacity - peer->write.used < msg_sz) {
void *data = realloc(peer->write.data, peer->write.capacity + msg_sz);
if (data) {
peer->write.data = data;
peer->write.capacity += msg_sz;
} else fatal("Out of memory");
}
memcpy(peer->write.data + peer->write.used, msg, msg_sz);
peer->write.used += msg_sz;
}
wakeup = true;
break;
}
}
talk_mutex(unlock);
if (wakeup) wakeup_talk_loop(false);
}
// }}}

View File

@@ -99,28 +99,28 @@ spawn(PyObject *self UNUSED, PyObject *args) {
// On BSD open() does not establish the controlling terminal
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
#endif
safe_close(tfd);
safe_close(tfd, __FILE__, __LINE__);
// Redirect stdin/stdout/stderr to the pty
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
safe_close(stdin_read_fd);
safe_close(stdin_write_fd);
safe_close(stdin_read_fd, __FILE__, __LINE__);
safe_close(stdin_write_fd, __FILE__, __LINE__);
} else {
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
}
safe_close(slave);
safe_close(master);
safe_close(slave, __FILE__, __LINE__);
safe_close(master, __FILE__, __LINE__);
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
safe_close(ready_write_fd);
safe_close(ready_write_fd, __FILE__, __LINE__);
wait_for_terminal_ready(ready_read_fd);
safe_close(ready_read_fd);
safe_close(ready_read_fd, __FILE__, __LINE__);
// Close any extra fds inherited from parent
for (int c = 3; c < 201; c++) safe_close(c);
for (int c = 3; c < 201; c++) safe_close(c, __FILE__, __LINE__);
environ = env;
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,

View File

@@ -210,6 +210,8 @@ class Child:
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
if env is None:
env = self._final_env = default_env().copy()
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get('lc_ctype_before_python'):
del env['LC_CTYPE']
env.update(self.env)
env['TERM'] = self.opts.term
env['COLORTERM'] = 'truecolor'

View File

@@ -34,6 +34,14 @@ def screen_bell() -> None:
pass
def screen_normal_keypad_mode() -> None:
write('\x1b>')
def screen_alternate_keypad_mode() -> None:
write('\x1b=')
def screen_cursor_position(y: int, x: int) -> None:
write(CSI + '%s;%sH' % (y, x))
@@ -80,6 +88,18 @@ def screen_indexn(n: int) -> None:
write(CSI + '%dS' % n)
def screen_delete_characters(count: int) -> None:
write(CSI + '%dP' % count)
def screen_insert_characters(count: int) -> None:
write(CSI + '%d@' % count)
def screen_scroll(count: int) -> None:
write(CSI + '%dS' % count)
def screen_erase_in_display(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'J')
@@ -108,6 +128,10 @@ def screen_linefeed() -> None:
write('\n')
def screen_tab() -> None:
write('\t')
def screen_backspace() -> None:
write('\x08')

View File

@@ -390,6 +390,12 @@ cocoa_focus_window(void *w) {
[window makeKeyWindow];
}
long
cocoa_window_number(void *w) {
NSWindow *window = (NSWindow*)w;
return [window windowNumber];
}
size_t
cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) {
NSWindow *window = (NSWindow*)w;

View File

@@ -783,10 +783,10 @@ Note that this does not currently work on Wayland.
'''))
o('confirm_os_window_close', 0, option_type=positive_int, long_text=_('''
Ask for confirmation when closing an OS window that has at least this
Ask for confirmation when closing an OS window or a tab that has at least this
number of kitty windows in it. A value of zero disables confirmation.
This confirmation also applies to requests to quit the entire application (all
OS windows, via the quite action).
OS windows, via the quit action).
'''))
# }}}
@@ -1402,6 +1402,7 @@ g('shortcuts.fonts') # {{{
k('increase_font_size', 'kitty_mod+equal', 'change_font_size all +2.0', _('Increase font size'))
if is_macos:
k('increase_font_size', 'cmd+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('increase_font_size', 'cmd+shift+equal', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('decrease_font_size', 'kitty_mod+minus', 'change_font_size all -2.0', _('Decrease font size'))
if is_macos:
k('decrease_font_size', 'cmd+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)

View File

@@ -20,7 +20,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
version: Version = Version(0, 18, 1)
version: Version = Version(0, 18, 2)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -110,10 +110,10 @@
#define ESC_DECRC '8'
// Set normal keypad mode
#define ESC_DECPNM '>'
#define ESC_DECKPNM '>'
// Set alternate keypad mode
#define ESC_DECPAM '='
#define ESC_DECKPAM '='
// ECMA-48 CSI sequences.
// ---------------------

View File

@@ -409,7 +409,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGGlyph glyph = glyph_id;
CGContextSetTextMatrix(ctx, transform);
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - 1.2f * baseline)); // lower the emoji a bit so its bottom is not on the baseline
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - baseline));
CGPoint p = CGPointMake(0, 0);
CTFontDrawGlyphs(font, &glyph, &p, 1, ctx);
CGContextRelease(ctx);

View File

@@ -142,7 +142,7 @@ close_tty(PyObject *self UNUSED, PyObject *args) {
TTY_ARGS
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
free(termios_p);
safe_close(fd);
safe_close(fd, __FILE__, __LINE__);
Py_RETURN_NONE;
}

View File

@@ -312,5 +312,10 @@ void play_canberra_sound(const char *which_sound, const char *event_id);
SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int);
SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE);
static inline void safe_close(int fd) { while(close(fd) != 0 && errno == EINTR); }
static inline void safe_close(int fd, const char* file UNUSED, const int line UNUSED) {
#if 0
printf("Closing fd: %d from file: %s line: %d\n", fd, file, line);
#endif
while(close(fd) != 0 && errno == EINTR);
}
void log_event(const char *format, ...) __attribute__((format(printf, 1, 2)));

View File

@@ -275,7 +275,7 @@ GLFW_CONTEXT_VERSION_MINOR: int
GLFW_CONTEXT_REVISION: int
GLFW_CONTEXT_ROBUSTNESS: int
GLFW_OPENGL_FORWARD_COMPAT: int
GLFW_OPENGL_DEBUG_CONTEXT: int
GLFW_CONTEXT_DEBUG: int
GLFW_OPENGL_PROFILE: int
GLFW_OPENGL_API: int
GLFW_OPENGL_ES_API: int
@@ -781,6 +781,10 @@ def x11_window_id(os_window_id: int) -> int:
pass
def cocoa_window_id(os_window_id: int) -> int:
pass
def swap_tabs(os_window_id: int, a: int, b: int) -> None:
pass

View File

@@ -176,7 +176,7 @@ font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y
static inline void
clear_canvas(FontGroup *fg) {
if (fg->canvas) memset(fg->canvas, 0, CELLS_IN_CANVAS * fg->cell_width * fg->cell_height * sizeof(pixel));
if (fg->canvas) memset(fg->canvas, 0, sizeof(pixel) * CELLS_IN_CANVAS * fg->cell_width * fg->cell_height);
}
@@ -457,7 +457,7 @@ calc_cell_metrics(FontGroup *fg) {
fg->cell_width = cell_width; fg->cell_height = cell_height;
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness;
free(fg->canvas);
fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
fg->canvas = calloc((size_t)CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
}
@@ -1132,6 +1132,11 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
}
}
static inline bool
is_non_emoji_dingbat(char_type ch) {
return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch);
}
void
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
@@ -1154,7 +1159,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
if (
cell_font_idx != MISSING_FONT &&
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && is_private_use(cpu_cell->ch)))
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && (is_private_use(cpu_cell->ch))) || is_non_emoji_dingbat(cpu_cell->ch))
) {
unsigned int desired_cells = 1;
if (cell_font_idx > 0) {
@@ -1168,7 +1173,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
unsigned int num_spaces = 0;
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
&& num_spaces < desired_cells
&& num_spaces + 1 < 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.
@@ -1388,7 +1393,7 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
PyObject *cells;
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
PyObject *ans = PyBytes_FromStringAndSize(NULL, 4 * cell_width * cell_height * num_cells);
PyObject *ans = PyBytes_FromStringAndSize(NULL, (size_t)4 * cell_width * cell_height * num_cells);
if (ans == NULL) return PyErr_NoMemory();
pixel *dest = (pixel*)PyBytes_AS_STRING(ans);
for (r = 0; r < cell_height; r++) {

View File

@@ -8,10 +8,10 @@
#
import math
from functools import partial as p
from functools import partial as p, wraps
from itertools import repeat
from typing import (
Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
Any, Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
Sequence, Tuple, cast
)
@@ -146,18 +146,49 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
half_vline(buf, width, height, level=d, which='bottom')
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
src_width = 4 * dest_width
def average_intensity_in_src(dest_x: int, dest_y: int) -> int:
src_y = dest_y * factor
src_x = dest_x * factor
total = 0
for y in range(src_y, src_y + factor):
offset = src_width * y
for x in range(src_x, src_x + factor):
total += src[offset + x]
return total // (factor * factor)
for y in range(dest_height):
offset = dest_width * y
for x in range(dest_width):
dest[offset + x] = min(255, dest[offset + x] + average_intensity_in_src(x, y))
def supersampled(supersample_factor: int = 4) -> Callable:
# Anti-alias the drawing performed by the wrapped function by
# using supersampling
class SSByteArray(bytearray):
supersample_factor = 1
def create_wrapper(f: Callable) -> Callable:
@wraps(f)
def supersampled_wrapper(buf: BufType, width: int, height: int, *args: Any, **kw: Any) -> None:
w, h = supersample_factor * width, supersample_factor * height
ssbuf = SSByteArray(w * h)
ssbuf.supersample_factor = supersample_factor
f(ssbuf, w, h, *args, **kw)
downsample(ssbuf, buf, width, height, factor=supersample_factor)
return supersampled_wrapper
return create_wrapper
def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]]) -> None:
for y in range(height):
offset = y * width
for x, (upper, lower) in enumerate(xlimits):
buf[x + offset] = 255 if upper <= y <= lower else 0
# Anti-alias the boundary, simple y-axis anti-aliasing
for x, limits in enumerate(xlimits):
for yf in limits:
for ypx in range(int(math.floor(yf)), int(math.ceil(yf)) + 1):
if 0 <= ypx < height:
off = ypx * width + x
buf[off] = min(255, buf[off] + int((1 - abs(yf - ypx)) * 255))
def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
@@ -170,6 +201,7 @@ def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
return y
@supersampled()
def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
ay1, by1, y2 = 0, height - 1, height // 2
if left:
@@ -182,6 +214,7 @@ def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
fill_region(buf, width, height, xlimits)
@supersampled()
def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
if corner == 'top-right' or corner == 'bottom-left':
diagonal_y = line_equation(0, 0, width - 1, height - 1)
@@ -198,85 +231,35 @@ def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
fill_region(buf, width, height, xlimits)
def antialiased_1px_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
# Draw an antialiased line using the Wu algorithm
x1, y1 = p1
x2, y2 = p2
dx, dy = x2 - x1, y2 - y1
off_limit = height * width
steep = abs(dx) < abs(dy)
def thick_line(buf: BufType, width: int, height: int, thickness_in_pixels: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
if p1[0] > p2[0]:
p1, p2 = p2, p1
leq = line_equation(*p1, *p2)
delta, extra = divmod(thickness_in_pixels, 2)
if steep:
x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx
def p(x: int, y: int) -> Tuple[int, int]:
return y, x
else:
def p(x: int, y: int) -> Tuple[int, int]:
return x, y
if x2 < x1:
x1, x2, y1, y2 = x2, x1, y2, y1
def fpart(x: float) -> float:
return x - int(x)
def rfpart(x: float) -> float:
return 1 - fpart(x)
def putpixel(p: Tuple[int, int], alpha: float) -> None:
x, y = p
off = int(x + y * width)
if 0 <= off < off_limit:
buf[off] = int(min(buf[off] + (alpha * 255), 255))
def draw_endpoint(pt: Tuple[int, int]) -> int:
x, y = pt
xend = round(x)
yend = y + grad * (xend - x)
xgap = rfpart(x + 0.5)
px, py = int(xend), int(yend)
putpixel(p(px, py), rfpart(yend) * xgap)
putpixel(p(px, py+1), fpart(yend) * xgap)
return px
grad = dy/dx
intery = y1 + rfpart(x1) * grad
xstart = draw_endpoint(p(*p1))
xend = draw_endpoint(p(*p2))
if xstart > xend:
xstart, xend = xend, xstart
xstart += 1
for x in range(xstart, xend):
y = int(intery)
putpixel(p(x, y), rfpart(intery))
putpixel(p(x, y+1), fpart(intery))
intery += grad
def antialiased_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int], level: int = 1) -> None:
th = thickness(level)
if th < 2:
return antialiased_1px_line(buf, width, height, p1, p2)
(x1, y1), (x2, y2) = p1, p2
dh = th // 2
items = range(-dh, dh + (th % 2))
for delta in items:
antialiased_1px_line(buf, width, height, (x1, y1 + delta), (x2, y2 + delta))
for x in range(p1[0], p2[0] + 1):
if 0 <= x < width:
y_p = leq(x)
r = range(int(y_p) - delta, int(y_p) + delta + extra)
for y in r:
if 0 <= y < height:
buf[x + y * width] = 255
@supersampled()
def cross_line(buf: BufType, width: int, height: int, left: bool = True, level: int = 1) -> None:
if left:
p1, p2 = (0, 0), (width - 1, height - 1)
else:
p1, p2 = (width - 1, 0), (0, height - 1)
antialiased_line(buf, width, height, p1, p2, level=level)
supersample_factor = getattr(buf, 'supersample_factor')
thick_line(buf, width, height, supersample_factor * thickness(level), p1, p2)
@supersampled()
def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', level: int = 1) -> None:
supersample_factor = getattr(buf, 'supersample_factor')
thickness_in_pixels = thickness(level) * supersample_factor
my = (height - 1) // 2
if which == 'tl':
p1 = 0, 0
@@ -290,7 +273,7 @@ def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', le
else:
p2 = width - 1, height - 1
p1 = 0, my
antialiased_line(buf, width, height, p1, p2, level=level)
thick_line(buf, width, height, thickness_in_pixels, p1, p2)
BezierFunc = Callable[[float], float]
@@ -360,6 +343,7 @@ def get_bezier_limits(bezier_x: BezierFunc, bezier_y: BezierFunc) -> Generator[T
yield upper, lower
@supersampled()
def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
c1x = find_bezier_for_D(width, height)
start = (0, 0)
@@ -380,6 +364,53 @@ def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
buf[offset + dest_x] = mbuf[offset + src_x]
def draw_parametrized_curve(buf: BufType, width: int, height: int, thickness_in_pixels: int, xfunc: BezierFunc, yfunc: BezierFunc) -> None:
num_samples = height*4
seen = set()
delta, extra = divmod(thickness_in_pixels, 2)
for i in range(num_samples + 1):
t = (i / num_samples)
p = x_p, y_p = int(xfunc(t)), int(yfunc(t))
if p in seen:
continue
seen.add(p)
for y in range(int(y_p) - delta, int(y_p) + delta + extra):
if 0 <= y < height:
offset = y * width
for x in range(int(x_p) - delta, int(x_p) + delta + extra):
if 0 <= x < width:
pos = offset + x
buf[pos] = min(255, buf[pos] + 255)
@supersampled()
def rounded_corner(buf: BufType, width: int, height: int, level: int = 1, which: str = '') -> None:
supersample_factor = getattr(buf, 'supersample_factor')
thickness_in_pixels = thickness(level) * supersample_factor
if which == '':
start = width // 2, height - 1
end = width - 1, height // 2
c1 = width // 2, int(0.75 * height)
c2 = width // 2, height // 2 + 1
elif which == '':
start = 0, height // 2
end = width // 2, height - 1
c1 = width // 2, height // 2 + 1
c2 = width // 2, int(0.75 * height)
elif which == '':
start = width // 2, 0
end = width - 1, height // 2
c1 = width // 2, int(0.25 * height)
c2 = width // 2 - 1, height // 2 - 1
elif which == '':
start = 0, height // 2
end = width // 2, 0
c1 = width // 2 - 1, height // 2 - 1
c2 = width // 2, int(0.25 * height)
xfunc, yfunc = cubic_bezier(start, end, c1, c2)
draw_parametrized_curve(buf, width, height, thickness_in_pixels, xfunc, yfunc)
def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> Tuple[int, int]:
x1, x2 = (0, width // 2) if which == 'left' else (width // 2, width)
gap = thickness(level + 1, horizontal=False)
@@ -591,17 +622,29 @@ box_chars: Dict[str, List[Callable]] = {
'': [p(corner_triangle, corner='top-right')],
'': [dhline],
'': [dvline],
'': [vline, p(half_dhline, which='right')],
'': [vline, half_dhline],
'': [hline, p(half_dvline, which='bottom')],
'': [hline, half_dvline],
'': [vline, half_dhline, p(half_dhline, which='right')],
'': [hline, half_dvline, p(half_dvline, which='bottom')],
'': [p(inner_corner, which=x) for x in 'tl tr bl br'.split()],
'': [p(inner_corner, which='tr'), p(inner_corner, which='br'), p(dvline, only='left')],
'': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
'': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
'': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
'': [p(cross_line, left=False)],
'': [cross_line],
'': [cross_line, p(cross_line, left=False)],
@@ -643,8 +686,8 @@ t, f = 1, 3
for start in '┌┐└┘':
for i, (hlevel, vlevel) in enumerate(((t, t), (f, t), (t, f), (f, f))):
box_chars[chr(ord(start) + i)] = [p(corner, which=start, hlevel=hlevel, vlevel=vlevel)]
for ch, c in zip('╭╮╯╰', '┌┐┘└'):
box_chars[ch] = [p(corner, which=c)] # TODO: Make these rounded
for ch in '╭╮╯╰':
box_chars[ch] = [p(rounded_corner, which=ch)]
for i, (a_, b_, c_, d_) in enumerate((
(t, t, t, t), (f, t, t, t), (t, f, t, t), (f, f, t, t), (t, t, f, t), (t, t, t, f), (t, t, f, f),

View File

@@ -457,7 +457,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
// it matters
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
int factor = (int)ceilf(ratio);
uint8_t *dest = calloc(4, width * cell_height);
uint8_t *dest = calloc(4, (size_t)width * cell_height);
if (dest == NULL) fatal("Out of memory");
uint8_t *d = dest;
@@ -497,7 +497,8 @@ static inline bool
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
(void)baseline;
unsigned short best = 0, diff = USHRT_MAX;
for (short i = 0; i < self->face->num_fixed_sizes; i++) {
const short limit = self->face->num_fixed_sizes;
for (short i = 0; i < limit; i++) {
unsigned short w = self->face->available_sizes[i].width;
unsigned short d = w > (unsigned short)cell_width ? w - (unsigned short)cell_width : (unsigned short)cell_width - w;
if (d < diff) {

41
kitty/glfw-wrapper.h generated
View File

@@ -569,7 +569,7 @@
* @analysis Application programmer error. Fix the offending call.
*/
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
/*! @brief The reuqested feature is not provided by the platform.
/*! @brief The requested feature is not provided by the platform.
*
* The requested feature is not provided by the platform, so GLFW is unable to
* implement it. The documentation for each function notes if it could emit
@@ -670,11 +670,19 @@
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
*/
#define GLFW_FOCUS_ON_SHOW 0x0002000C
/*! @brief Mouse input transparency window hint and attribute
*
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
*/
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
/*! @brief Occlusion window attribute
*
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
*/
#define GLFW_OCCLUDED 0x0002000D
#define GLFW_OCCLUDED 0x0002000E
/*! @brief Framebuffer bit depth hint.
*
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
@@ -792,12 +800,17 @@
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
*/
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
/*! @brief OpenGL debug context hint and attribute.
/*! @brief Debug mode context hint and attribute.
*
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
*/
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
#define GLFW_CONTEXT_DEBUG 0x00022007
/*! @brief Legacy name for compatibility.
*
* This is an alias for compatibility with earlier versions.
*/
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
/*! @brief OpenGL profile hint and attribute.
*
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
@@ -880,6 +893,14 @@
#define GLFW_EGL_CONTEXT_API 0x00036002
#define GLFW_OSMESA_CONTEXT_API 0x00036003
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
/*! @defgroup shapes Standard cursor shapes
* @brief Standard system cursor shapes.
*
@@ -913,8 +934,12 @@ typedef enum {
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
*/
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
#define GLFW_DEBUG_KEYBOARD 0x00050002
#define GLFW_ENABLE_JOYSTICKS 0x00050003
/*! @brief ANGLE rendering backend init hint.
*
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
*/
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
#define GLFW_DEBUG_KEYBOARD 0x00050003
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).

View File

@@ -11,6 +11,7 @@
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w, bool);
extern void cocoa_focus_window(void *w);
extern long cocoa_window_number(void *w);
extern void cocoa_create_global_menu(void);
extern void cocoa_hide_window_title(void *w);
extern void cocoa_hide_titlebar(void *w);
@@ -364,6 +365,7 @@ static void
application_close_requested_callback(int flags) {
if (flags) {
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
global_state.has_pending_closes = true;
request_tick_callback();
} else {
if (global_state.quit_request == NO_CLOSE_REQUESTED) {
@@ -589,7 +591,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
#endif
if (!global_state.is_wayland) {
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
// which wont happen until the event loop ticks, so using a temp window is useless.
// which won't happen until the event loop ticks, so using a temp window is useless.
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
}
@@ -813,9 +815,6 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
glfwSetErrorCallback(error_callback);
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
// Joysticks cause slow startup on some linux systems, see
// https://github.com/kovidgoyal/kitty/issues/830
glfwInitHint(GLFW_ENABLE_JOYSTICKS, 0);
OPT(debug_keyboard) = debug_keyboard != 0;
#ifdef __APPLE__
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
@@ -1025,18 +1024,35 @@ x11_display(PYNOARG) {
Py_RETURN_NONE;
}
static OSWindow*
find_os_window(PyObject *os_wid) {
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (w->id == os_window_id) return w;
}
return NULL;
}
static PyObject*
x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
if (glfwGetX11Window) {
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (w->id == os_window_id) return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
}
}
else { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found");
OSWindow *w = find_os_window(os_wid);
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
if (!glfwGetX11Window) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
}
static PyObject*
cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) {
OSWindow *w = find_os_window(os_wid);
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; }
#ifdef __APPLE__
return Py_BuildValue("l", (long)cocoa_window_number(glfwGetCocoaWindow(w->handle)));
#else
PyErr_SetString(PyExc_RuntimeError, "cocoa_window_id() is only supported on Mac");
return NULL;
#endif
}
static PyObject*
@@ -1078,7 +1094,7 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
for (size_t i = 0; i < count; i++) {
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
if (gimages[i].width * gimages[i].height * 4 != sz) {
if ((Py_ssize_t)gimages[i].width * gimages[i].height * 4 != sz) {
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
return NULL;
}
@@ -1220,6 +1236,7 @@ static PyMethodDef module_methods[] = {
#ifndef __APPLE__
METHODB(dbus_send_notification, METH_VARARGS),
#endif
METHODB(cocoa_window_id, METH_O),
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
@@ -1531,7 +1548,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_CONTEXT_REVISION);
ADDC(GLFW_CONTEXT_ROBUSTNESS);
ADDC(GLFW_OPENGL_FORWARD_COMPAT);
ADDC(GLFW_OPENGL_DEBUG_CONTEXT);
ADDC(GLFW_CONTEXT_DEBUG);
ADDC(GLFW_OPENGL_PROFILE);
// ---

View File

@@ -348,7 +348,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
break;
case RGB:
case RGBA:
img->load_data.data_sz = g->data_width * g->data_height * (fmt / 8);
img->load_data.data_sz = (size_t)g->data_width * g->data_height * (fmt / 8);
if (!img->load_data.data_sz) ABRT(EINVAL, "Zero width/height not allowed");
img->load_data.is_4byte_aligned = fmt == RGBA || (img->width % 4 == 0);
img->load_data.is_opaque = fmt == RGB;
@@ -404,7 +404,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
else fd = open(fname, O_CLOEXEC | O_RDONLY);
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);
safe_close(fd);
safe_close(fd, __FILE__, __LINE__);
if (tt == 't') {
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
else unlink(fname);
@@ -461,7 +461,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
} else img->load_data.data = img->load_data.mapped_file;
}
}
size_t required_sz = (img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
size_t required_sz = (size_t)(img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
if (img->load_data.data_sz != required_sz) ABRT(EINVAL, "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", img->width, img->height, img->load_data.data_sz, required_sz);
if (LIKELY(img->data_loaded && send_to_gpu)) {
send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_opaque, img->load_data.is_4byte_aligned, false, REPEAT_CLAMP);
@@ -891,12 +891,12 @@ W(shm_write) {
int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
int ret = ftruncate(fd, sz);
if (ret != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
memcpy(addr, data, sz);
if (munmap(addr, sz) != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
safe_close(fd);
if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
safe_close(fd, __FILE__, __LINE__);
Py_RETURN_NONE;
}

View File

@@ -146,6 +146,7 @@ on_key_input(GLFWkeyevent *ev) {
schedule_write_to_child(w->id, 1, text, strlen(text));
debug("committed pre-edit text: %s\n", text);
} else debug("committed pre-edit text: (null)\n");
screen_draw_overlay_text(screen, NULL);
return;
case 0:
// for macOS, update ime position on every key input

View File

@@ -86,8 +86,8 @@ newly launched child process.
--location
type=choices
default=last
choices=first,after,before,neighbor,last,vsplit,hsplit
default=default
choices=first,after,before,neighbor,last,vsplit,hsplit,default
Where to place the newly created window when it is added to a tab which
already has existing windows in it. :code:`after` and :code:`before` place the new
window before or after the active window. :code:`neighbor` is a synonym for :code:`after`.
@@ -95,7 +95,8 @@ Also applies to creating a new tab, where the value of :code:`after`
will cause the new tab to be placed next to the current tab instead of at the end.
The values of :code:`vsplit` and :code:`hsplit` are only used by the :code:`splits`
layout and control if the new window is placed in a vertical or horizontal split
with the currently active window.
with the currently active window. The default is to place the window in a
layout dependent manner, typically, after the currently active window.
--allow-remote-control
@@ -254,7 +255,7 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
kw['cwd_from'] = active_child.pid_for_cwd
else:
kw['cwd'] = opts.cwd
if opts.location != 'last':
if opts.location != 'default':
kw['location'] = opts.location
if opts.copy_colors and active:
kw['copy_colors_from'] = active

View File

@@ -6,7 +6,7 @@ from functools import partial
from itertools import repeat
from typing import (
Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, Sequence,
Tuple, Union, cast
Tuple
)
from kitty.constants import Edges, WindowGeometry
@@ -261,24 +261,14 @@ class Layout:
assert w is not None
return self.neighbors_for_window(w, all_windows)
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool:
# delta can be either a number or a string such as 'left', 'top', etc
# for neighborhood moves
def move_window(self, all_windows: WindowList, delta: int = 1) -> bool:
if all_windows.num_groups < 2 or not delta:
return False
if isinstance(delta, int):
return all_windows.move_window_group(by=delta)
which = delta.lower()
which = {'up': 'top', 'down': 'bottom'}.get(which, which)
w = all_windows.active_window
if w is None:
return False
neighbors = self.neighbors_for_window(w, all_windows)
q: List[int] = cast(List[int], neighbors.get(which, []))
if not q:
return False
return all_windows.move_window_group(to_group=q[0])
return all_windows.move_window_group(by=delta)
def move_window_to_group(self, all_windows: WindowList, group: int) -> bool:
return all_windows.move_window_group(to_group=group)
def add_window(self, all_windows: WindowList, window: WindowType, location: Optional[str] = None, overlay_for: Optional[int] = None) -> None:
if overlay_for is not None and overlay_for in all_windows:

View File

@@ -4,6 +4,7 @@
from functools import lru_cache
from itertools import repeat
from math import ceil, floor
from typing import Callable, Dict, Generator, List, Optional, Sequence, Tuple
from kitty.constants import Edges
@@ -235,9 +236,17 @@ class Grid(Layout):
def side(row: int, col: int, delta: int) -> List[int]:
neighbor_col = col + delta
if col_counts[neighbor_col] == col_counts[col]:
neighbor_nrows = col_counts[neighbor_col]
nrows = col_counts[col]
if neighbor_nrows == nrows:
return neighbors(row, neighbor_col)
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
start_row = floor(neighbor_nrows * row / nrows)
end_row = ceil(neighbor_nrows * (row + 1) / nrows)
xs = []
for neighbor_row in range(start_row, end_row):
xs.extend(neighbors(neighbor_row, neighbor_col))
return xs
return {
'top': neighbors(row-1, col) if row else [],

View File

@@ -244,7 +244,7 @@ class Pair:
return parent.modify_size_of_child(which, increment, is_horizontal, layout_object)
return False
def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits') -> None:
def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits', all_windows: WindowList) -> None:
def quadrant(is_horizontal: bool, is_first: bool) -> Tuple[EdgeLiteral, EdgeLiteral]:
if is_horizontal:
@@ -255,13 +255,27 @@ class Pair:
return 'top', 'bottom'
return 'bottom', 'top'
geometries = dict((group.id, group.geometry) for group in all_windows.groups if group.geometry)
def extend(other: Union[int, 'Pair', None], edge: EdgeLiteral, which: EdgeLiteral) -> None:
if not ans[which] and other:
if isinstance(other, Pair):
ans[which].extend(other.edge_windows(edge))
neighbors = (
w for w in other.edge_windows(edge)
if is_neighbouring_geometry(geometries[w], geometries[window_id], which))
ans[which].extend(neighbors)
else:
ans[which].append(other)
def is_neighbouring_geometry(a: WindowGeometry, b: WindowGeometry, direction: str) -> bool:
def edges(g: WindowGeometry) -> Tuple[int, int]:
return (g.top, g.bottom) if direction in ['left', 'right'] else (g.left, g.right)
a1, a2 = edges(a)
b1, b2 = edges(b)
return a1 < b2 and a2 > b1
other = self.two if self.one == window_id else self.one
extend(other, *quadrant(self.horizontal, self.one == window_id))
@@ -418,15 +432,15 @@ class Splits(Layout):
pair = self.pairs_root.pair_for_window(wg.id)
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
if pair is not None:
pair.neighbors_for_window(wg.id, ans, self)
pair.neighbors_for_window(wg.id, ans, self, all_windows)
return ans
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool:
def move_window_to_group(self, all_windows: WindowList, group: int) -> bool:
before = all_windows.active_group
if before is None:
return False
before_idx = all_windows.active_group_idx
moved = super().move_window(all_windows, delta)
moved = super().move_window_to_group(all_windows, group)
after = all_windows.groups[before_idx]
if moved and before.id != after.id:
self.pairs_root.swap_windows(before.id, after.id)

View File

@@ -51,6 +51,12 @@ linebuf_mark_line_clean(LineBuf *self, index_type y) {
self->line_attrs[y] &= ~TEXT_DIRTY_MASK;
}
void
linebuf_mark_line_as_not_continued(LineBuf *self, index_type y) {
self->line_attrs[y] &= ~CONTINUED_MASK;
}
static PyObject*
clear(LineBuf *self, PyObject *a UNUSED) {
#define clear_doc "Clear all lines in this LineBuf"
@@ -530,8 +536,8 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
if (other->xnum == self->xnum && other->ynum == self->ynum) {
memcpy(other->line_map, self->line_map, sizeof(index_type) * self->ynum);
memcpy(other->line_attrs, self->line_attrs, sizeof(bool) * self->ynum);
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, self->xnum * self->ynum * sizeof(CPUCell));
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, self->xnum * self->ynum * sizeof(GPUCell));
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(CPUCell));
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(GPUCell));
*num_content_lines_before = self->ynum; *num_content_lines_after = self->ynum;
return;
}

View File

@@ -96,6 +96,7 @@ void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int );
void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *);
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
void linebuf_mark_line_as_not_continued(LineBuf *self, index_type y);
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
void linebuf_refresh_sprite_positions(LineBuf *self);
void historybuf_add_line(HistoryBuf *self, const Line *line);

View File

@@ -31,7 +31,8 @@ log_error(const char *fmt, ...) {
#define bufprint(func, ...) { if ((size_t)(p - logbuf) < sizeof(logbuf) - 2) { p += func(p, sizeof(logbuf) - (p - logbuf), __VA_ARGS__); } }
if (!use_os_log) { // Apple's os_log already records timestamps
gettimeofday(&tv, NULL);
struct tm *tmp = localtime(&tv.tv_sec);
struct tm stack_tmp;
struct tm *tmp = localtime_r(&tv.tv_sec, &stack_tmp);
if (tmp) {
char tbuf[256] = {0}, buf[256] = {0};
if (strftime(buf, sizeof(buf), "%j %H:%M:%S.%%06u", tmp) != 0) {

View File

@@ -48,7 +48,7 @@ handle_signal(int sig_num) {
void
free_loop_data(LoopData *ld) {
#define CLOSE(which, idx) if (ld->which[idx] > -1) safe_close(ld->which[idx]); ld->which[idx] = -1;
#define CLOSE(which, idx) if (ld->which[idx] > -1) safe_close(ld->which[idx], __FILE__, __LINE__); ld->which[idx] = -1;
#ifndef HAS_EVENT_FD
CLOSE(wakeup_fds, 0); CLOSE(wakeup_fds, 1);
#endif
@@ -56,9 +56,9 @@ free_loop_data(LoopData *ld) {
CLOSE(signal_fds, 0); CLOSE(signal_fds, 1);
#endif
#undef CLOSE
if (ld->signal_read_fd) {
if (ld->signal_read_fd > -1) {
#ifdef HAS_SIGNAL_FD
safe_close(ld->signal_read_fd);
safe_close(ld->signal_read_fd, __FILE__, __LINE__);
SIGNAL_SET
sigprocmask(SIG_UNBLOCK, &signals, NULL);
#else
@@ -69,7 +69,7 @@ free_loop_data(LoopData *ld) {
signal(SIGCHLD, SIG_DFL);
}
#ifdef HAS_EVENT_FD
safe_close(ld->wakeup_read_fd);
safe_close(ld->wakeup_read_fd, __FILE__, __LINE__);
#endif
ld->signal_read_fd = -1; ld->wakeup_read_fd = -1;
}

View File

@@ -67,7 +67,12 @@
#define MOUSE_SGR_MODE (1006 << 5)
#define MOUSE_URXVT_MODE (1015 << 5)
// Save cursor (DECSC)
#define SAVE_CURSOR (1048 << 5)
// Alternate screen buffer
#define TOGGLE_ALT_SCREEN_1 (47 << 5)
#define TOGGLE_ALT_SCREEN_2 (1047 << 5)
#define ALTERNATE_SCREEN (1049 << 5)
// Bracketed paste mode

View File

@@ -225,9 +225,9 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
CALL_ED(screen_save_cursor); break;
case ESC_DECRC:
CALL_ED(screen_restore_cursor); break;
case ESC_DECPNM:
case ESC_DECKPNM:
CALL_ED(screen_normal_keypad_mode); break;
case ESC_DECPAM:
case ESC_DECKPAM:
CALL_ED(screen_alternate_keypad_mode); break;
case '%':
case '(':
@@ -387,10 +387,10 @@ static inline const char*
repr_csi_params(unsigned int *params, unsigned int num_params) {
if (!num_params) return "";
static char buf[256];
unsigned int pos = 0;
while (pos < 200 && num_params && sizeof(buf) > pos + 1) {
const char *fmt = num_params > 1 ? "%u " : "%u";
int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[num_params--]);
unsigned int pos = 0, i = 0;
while (pos < 200 && i++ < num_params && sizeof(buf) > pos + 1) {
const char *fmt = i < num_params ? "%u, " : "%u";
int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[i-1]);
if (ret < 0) return "An error occurred formatting the params array";
pos += ret;
}
@@ -783,6 +783,12 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
REPORT_ERROR("Unknown DECSTR CSI sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
}
break;
case 'm':
if (start_modifier == '>' && (!end_modifier || end_modifier == ';')) {
REPORT_ERROR("Ignoring xterm specific key modifier resource options (CSI > m)");
break;
}
/* fallthrough */
default:
REPORT_ERROR("Unknown CSI code: '%s' with start_modifier: '%c' and end_modifier: '%c' and parameters: '%s'", utf8(code), start_modifier, end_modifier, repr_csi_params(params, num_params));
}
@@ -847,7 +853,8 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#undef CMD_PREFIX
#define PRINT_PREFIX "kitty-print|"
} else if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 1, PRINT_PREFIX)) {
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + sizeof(PRINT_PREFIX), screen->parser_buf_pos - sizeof(PRINT_PREFIX));
const size_t pp_size = sizeof(PRINT_PREFIX);
PyObject *msg = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + pp_size, screen->parser_buf_pos - pp_size);
if (msg != NULL) {
REPORT_OSC2(screen_handle_print, (char)screen->parser_buf[0], msg);
screen_handle_print(screen, msg);

View File

@@ -77,7 +77,7 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) {
png_read_update_info(png, info);
int rowbytes = png_get_rowbytes(png, info);
d->sz = rowbytes * d->height * sizeof(png_byte);
d->sz = sizeof(png_byte) * rowbytes * d->height;
d->decompressed = malloc(d->sz + 16);
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
d->row_pointers = malloc(d->height * sizeof(png_bytep));

View File

@@ -36,7 +36,7 @@ If specified close the tab this command is run in, rather than the active tab.
for tab in self.tabs_for_match_payload(boss, window, payload_get):
if window:
if tab:
boss.close_tab(tab)
boss.close_tab_no_confirm(tab)
close_tab = CloseTab()

View File

@@ -123,7 +123,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
) {
Py_CLEAR(self); return NULL;
}
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
self->alt_tabstops = self->main_tabstops + self->columns;
self->tabstops = self->main_tabstops;
init_tabstops(self->main_tabstops, self->columns);
init_tabstops(self->alt_tabstops, self->columns);
@@ -137,7 +137,7 @@ static inline Line* range_line_(Screen *self, int y);
void
screen_reset(Screen *self) {
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
if (self->overlay_line.is_active) deactivate_overlay_line(self);
linebuf_clear(self->linebuf, BLANK_CHAR);
historybuf_clear(self->historybuf);
@@ -236,7 +236,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
PyMem_Free(self->main_tabstops);
self->main_tabstops = PyMem_Calloc(2*self->columns, sizeof(bool));
if (self->main_tabstops == NULL) { PyErr_NoMemory(); return false; }
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
self->alt_tabstops = self->main_tabstops + self->columns;
self->tabstops = self->main_tabstops;
init_tabstops(self->main_tabstops, self->columns);
init_tabstops(self->alt_tabstops, self->columns);
@@ -641,12 +641,12 @@ screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const u
void
screen_toggle_screen_buffer(Screen *self) {
screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_screen) {
bool to_alt = self->linebuf == self->main_linebuf;
grman_clear(self->alt_grman, true, self->cell_size); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway
if (to_alt) {
linebuf_clear(self->alt_linebuf, BLANK_CHAR);
screen_save_cursor(self);
if (clear_alt_screen) linebuf_clear(self->alt_linebuf, BLANK_CHAR);
if (save_cursor) screen_save_cursor(self);
self->linebuf = self->alt_linebuf;
self->tabstops = self->alt_tabstops;
self->grman = self->alt_grman;
@@ -655,7 +655,7 @@ screen_toggle_screen_buffer(Screen *self) {
} else {
self->linebuf = self->main_linebuf;
self->tabstops = self->main_tabstops;
screen_restore_cursor(self);
if (save_cursor) screen_restore_cursor(self);
self->grman = self->main_grman;
}
screen_history_scroll(self, SCROLL_FULL, false);
@@ -727,9 +727,14 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
case CONTROL_CURSOR_BLINK:
self->cursor->blink = val;
break;
case SAVE_CURSOR:
screen_save_cursor(self);
break;
case TOGGLE_ALT_SCREEN_1:
case TOGGLE_ALT_SCREEN_2:
case ALTERNATE_SCREEN:
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self);
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self);
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
break;
default:
private = mode >= 1 << 5;
@@ -1188,12 +1193,14 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
}
linebuf_mark_line_dirty(self->linebuf, i);
linebuf_mark_line_as_not_continued(self->linebuf, i);
}
self->is_dirty = true;
self->selection = EMPTY_SELECTION;
}
if (how != 2) {
screen_erase_in_line(self, how, private);
if (how == 1) linebuf_mark_line_as_not_continued(self->linebuf, self->cursor->y);
}
if (how == 3 && self->linebuf == self->main_linebuf) {
historybuf_clear(self->historybuf);
@@ -1574,7 +1581,7 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
// Rendering {{{
static inline void
update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
size_t base = dest_y * line->xnum * sizeof(GPUCell);
size_t base = sizeof(GPUCell) * dest_y * line->xnum;
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
}
@@ -2392,7 +2399,7 @@ is_main_linebuf(Screen *self, PyObject *a UNUSED) {
static PyObject*
toggle_alt_screen(Screen *self, PyObject *a UNUSED) {
screen_toggle_screen_buffer(self);
screen_toggle_screen_buffer(self, true, true);
Py_RETURN_NONE;
}

View File

@@ -138,7 +138,7 @@ void screen_erase_in_line(Screen *, unsigned int, bool);
void screen_erase_in_display(Screen *, unsigned int, bool);
void screen_draw(Screen *screen, uint32_t codepoint);
void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins);
void screen_toggle_screen_buffer(Screen *self);
void screen_toggle_screen_buffer(Screen *self, bool, bool);
void screen_normal_keypad_mode(Screen *self);
void screen_alternate_keypad_mode(Screen *self);
void screen_change_default_color(Screen *self, unsigned int which, uint32_t col);

View File

@@ -43,6 +43,7 @@ class Session:
self.active_tab_idx = 0
self.default_title = default_title
self.os_window_size: Optional[WindowSizeOpts] = None
self.os_window_class: Optional[str] = None
def add_tab(self, opts: Options, name: str = '') -> None:
if self.tabs and not self.tabs[-1].windows:
@@ -124,6 +125,8 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
from kitty.config_data import window_size
w, h = map(window_size, rest.split(maxsplit=1))
ans.os_window_size = WindowSizeOpts(w, h, opts.window_margin_width, opts.window_padding_width, False)
elif cmd == 'os_window_class':
ans.os_window_class = rest
else:
raise ValueError('Unknown command in session file: {}'.format(cmd))
yield finalize_session(ans)

View File

@@ -65,7 +65,7 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int
copy_image_warned = true;
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
}
size_t sz = width * height * num_levels;
size_t sz = (size_t)width * height * num_levels;
pixel *src = malloc(sz * sizeof(pixel));
if (src == NULL) { fatal("Out of memory."); }
glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id);
@@ -334,7 +334,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
}
if (screen->reload_all_gpu_data || screen_resized || screen_is_selection_dirty(screen)) {
sz = screen->lines * screen->columns;
sz = (size_t)screen->lines * screen->columns;
address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_apply_selection(screen, address, sz);
unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;

View File

@@ -261,7 +261,7 @@ class TabBar:
return
self.cell_width = cell_width
s = self.screen
viewport_width = tab_bar.width - 2 * self.margin_width
viewport_width = max(4 * cell_width, tab_bar.width - 2 * self.margin_width)
ncells = viewport_width // cell_width
s.resize(1, ncells)
s.reset_mode(DECAWM)

View File

@@ -15,18 +15,18 @@ from typing import (
from .borders import Borders
from .child import Child
from .cli_stub import CLIOptions
from .constants import appname, is_macos, is_wayland
from .constants import appname
from .fast_data_types import (
add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty,
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
set_active_window, swap_tabs, sync_os_window_title, x11_window_id
set_active_window, swap_tabs, sync_os_window_title
)
from .layout.base import Layout, Rect
from .layout.interface import create_layout_object_for, evict_cached_layouts
from .options_stub import Options
from .tab_bar import TabBar, TabBarData
from .typing import SessionTab, SessionType, TypedDict
from .utils import log_error, resolved_shell
from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict
from .utils import log_error, platform_window_id, resolved_shell
from .window import Watchers, Window, WindowDict
from .window_list import WindowList
@@ -283,12 +283,9 @@ class Tab: # {{{
if env:
fenv.update(env)
fenv['KITTY_WINDOW_ID'] = str(next_window_id())
if not is_macos and not is_wayland():
try:
fenv['WINDOWID'] = str(x11_window_id(self.os_window_id))
except Exception:
import traceback
traceback.print_exc()
pwid = platform_window_id(self.os_window_id)
if pwid is not None:
fenv['WINDOWID'] = str(pwid)
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from, allow_remote_control=allow_remote_control)
ans.fork()
return ans
@@ -410,15 +407,37 @@ class Tab: # {{{
prev_window = previous_window
def neighboring_window(self, which: str) -> None:
neighbors = self.current_layout.neighbors(self.windows)
candidates = cast(Optional[Tuple[int, ...]], neighbors.get(which))
if candidates:
self.windows.set_active_group(candidates[0])
def most_recent_group(self, groups: Sequence[int]) -> Optional[int]:
groups_set = frozenset(groups)
def move_window(self, delta: Union[str, int] = 1) -> None:
if self.current_layout.move_window(self.windows, delta):
self.relayout()
for window_id in reversed(self.windows.active_window_history):
group = self.windows.group_for_window(window_id)
if group and group.id in groups_set:
return group.id
if groups:
return groups[0]
def neighboring_group_id(self, which: EdgeLiteral) -> Optional[int]:
neighbors = self.current_layout.neighbors(self.windows)
candidates = neighbors.get(which)
if candidates:
return self.most_recent_group(candidates)
def neighboring_window(self, which: EdgeLiteral) -> None:
neighbor = self.neighboring_group_id(which)
if neighbor:
self.windows.set_active_group(neighbor)
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
if isinstance(delta, int):
if self.current_layout.move_window(self.windows, delta):
self.relayout()
elif isinstance(delta, str):
neighbor = self.neighboring_group_id(delta)
if neighbor:
if self.current_layout.move_window_to_group(self.windows, neighbor):
self.relayout()
def move_window_to_top(self) -> None:
n = self.windows.num_groups

View File

@@ -42,6 +42,17 @@ def expandvars(val: str, env: Dict[str, str] = {}) -> str:
return re.sub(r'\$\{(\S+?)\}', sub, val)
def platform_window_id(os_window_id: int) -> Optional[int]:
if is_macos:
from .fast_data_types import cocoa_window_id
with suppress(Exception):
return cocoa_window_id(os_window_id)
if not is_wayland():
from .fast_data_types import x11_window_id
with suppress(Exception):
return x11_window_id(os_window_id)
def load_shaders(name: str) -> Tuple[str, str]:
from .fast_data_types import GLSL_VERSION
with open(os.path.join(BASE, '{}_vertex.glsl'.format(name))) as f:

View File

@@ -222,6 +222,7 @@ class Window:
self.action_on_removal: Optional[Callable] = None
self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = None
self.pty_resized_once = False
self.last_reported_pty_size = (-1, -1, -1, -1)
self.needs_attention = False
self.override_title = override_title
self.default_title = os.path.basename(child.argv[0] or appname)
@@ -381,18 +382,21 @@ class Window:
if self.needs_layout or new_geometry.xnum != self.screen.columns or new_geometry.ynum != self.screen.lines:
boss = get_boss()
self.screen.resize(new_geometry.ynum, new_geometry.xnum)
current_pty_size = (
self.screen.lines, self.screen.columns,
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
sg = self.update_position(new_geometry)
self.needs_layout = False
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
else:
sg = self.update_position(new_geometry)
current_pty_size = (
self.screen.lines, self.screen.columns,
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
if current_pty_size != self.last_reported_pty_size:
boss.child_monitor.resize_pty(self.id, *current_pty_size)
if not self.pty_resized_once:
self.pty_resized_once = True
self.child.mark_terminal_ready()
self.call_watchers(self.watchers.on_resize, {'old_geometry': self.geometry, 'new_geometry': new_geometry})
else:
sg = self.update_position(new_geometry)
self.last_reported_pty_size = current_pty_size
self.geometry = g = new_geometry
set_window_render_data(self.os_window_id, self.tab_id, self.id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4])
self.update_effective_padding()

View File

@@ -211,7 +211,7 @@ class WindowList:
def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]:
return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups)
def make_previous_group_active(self, which: int = 1, notify: bool = False) -> None:
def make_previous_group_active(self, which: int = 1, notify: bool = True) -> None:
which = max(1, which)
gid_map = {g.id: i for i, g in enumerate(self.groups)}
num = len(self.active_group_history)

View File

@@ -250,6 +250,10 @@ class TestLayout(BaseTest):
self.ae(q.pairs_root.pair_for_window(2).horizontal, False)
q.add_window(all_windows, Window(4), location='vsplit')
windows = list(all_windows)
windows[0].set_geometry(WindowGeometry(0, 0, 10, 20, 0, 0))
windows[1].set_geometry(WindowGeometry(11, 0, 20, 10, 0, 0))
windows[2].set_geometry(WindowGeometry(11, 11, 15, 20, 0, 0))
windows[3].set_geometry(WindowGeometry(16, 11, 20, 20, 0, 0))
self.ae(q.neighbors_for_window(windows[0], all_windows), {'left': [], 'right': [2, 3], 'top': [], 'bottom': []})
self.ae(q.neighbors_for_window(windows[1], all_windows), {'left': [1], 'right': [], 'top': [], 'bottom': [3, 4]})
self.ae(q.neighbors_for_window(windows[2], all_windows), {'left': [1], 'right': [4], 'top': [2], 'bottom': []})

View File

@@ -176,21 +176,29 @@ class TestScreen(BaseTest):
def all_lines(s):
return tuple(str(s.line(i)) for i in range(s.lines))
def continuations(s):
return tuple(s.line(i).is_continued() for i in range(s.lines))
init()
s.erase_in_display()
s.erase_in_display(0)
self.ae(all_lines(s), ('12345', '12', '', '', ''))
self.ae(continuations(s), (False, True, False, False, False))
init()
s.erase_in_display(1)
self.ae(all_lines(s), ('', ' 45', '12345', '12345', '12345'))
self.ae(continuations(s), (False, False, True, True, True))
init()
s.erase_in_display(2)
self.ae(all_lines(s), ('', '', '', '', ''))
self.assertTrue(s.line(0).cursor_from(1).bold)
self.ae(continuations(s), (False, False, False, False, False))
init()
s.erase_in_display(2, True)
self.ae(all_lines(s), ('', '', '', '', ''))
self.ae(continuations(s), (False, False, False, False, False))
self.assertFalse(s.line(0).cursor_from(1).bold)
def test_cursor_movement(self):

View File

@@ -3,8 +3,13 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
import unittest
from . import BaseTest
is32bit = sys.maxsize <= (1 << 32)
class TestTUI(BaseTest):
@@ -43,7 +48,7 @@ class TestTUI(BaseTest):
le.backspace()
self.assertTrue(le.pending_bell)
@unittest.skipIf(is32bit, 'Fails for some unknown reason on 32bit builds')
def test_multiprocessing_spawn(self):
return # temporarily disable this test till I can figure out why its failing in the build env
from kitty.multiprocessing import test_spawn
test_spawn()

View File

@@ -42,14 +42,18 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
#endif
static inline void
set_bundle_exe_dir(const wchar_t *exe_dir) {
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype) {
wchar_t buf[PATH_MAX+1] = {0};
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
PySys_AddXOption(buf);
if (lc_ctype) {
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
PySys_AddXOption(buf);
}
}
#ifdef FOR_BUNDLE
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) {
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) {
int num;
Py_NoSiteFlag = 1;
Py_FrozenFlag = 1;
@@ -62,7 +66,7 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
int ret = 1;
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
set_bundle_exe_dir(exe_dir);
set_xoptions(exe_dir, lc_ctype);
wchar_t stdlib[PATH_MAX+1] = {0};
#ifdef __APPLE__
const char *python_relpath = "../Resources/Python/lib";
@@ -96,11 +100,11 @@ end:
return ret;
}
#else
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv) {
static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wchar_t **argv, const char *lc_ctype) {
(void)libpath;
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
set_bundle_exe_dir(exe_dir);
set_xoptions(exe_dir, lc_ctype);
#ifdef FROM_SOURCE
PySys_AddXOption(L"kitty_from_source=1");
#endif
@@ -155,6 +159,10 @@ read_exe_path(char *exe, size_t buf_sz) {
int main(int argc, char *argv[]) {
char exe[PATH_MAX+1] = {0};
const char *lc_ctype = NULL;
#ifdef __APPLE__
lc_ctype = getenv("LC_CTYPE");
#endif
if (!read_exe_path(exe, sizeof(exe))) return 1;
char *exe_dir = dirname(exe);
@@ -182,7 +190,9 @@ int main(int argc, char *argv[]) {
ret = 1; goto end;
}
}
ret = run_embedded(exe_dir, lib, num_args, argvw);
if (lc_ctype) lc_ctype = strdup(lc_ctype);
ret = run_embedded(exe_dir, lib, num_args, argvw, lc_ctype);
if (lc_ctype) free((void*)lc_ctype);
end:
for (i = 0; i < num_args; i++) { if(argvw[i]) PyMem_RawFree(argvw[i]); }
return ret;

View File

@@ -8,7 +8,7 @@ set wildignore+=tags
set expandtab
set tabstop=4
set shiftwidth=4
set softtabstop=0
set softtabstop=0
set smarttab
python3 <<endpython
import sys