Compare commits

..

236 Commits

Author SHA1 Message Date
Kovid Goyal
36a20f7b00 Temporarily disable spawn test as it is failing in the Linux build container
Will investigate later
2020-06-23 17:37:04 +05:30
Kovid Goyal
35cbf49b08 version 0.18.1 2020-06-23 17:29:22 +05:30
Kovid Goyal
21836cedda Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-21 22:07:44 +05:30
Luflosi
0a892b72e6 Fix GLU header inclusion being disabled
From upstream: 81f475bccb.
2020-06-21 18:07:31 +02:00
Kovid Goyal
2c6e5a6e73 Get multiprocessing working in kitty
Monkeypatch the stdlib multiprocessing module to
use kitty as its python interpreter for spawning worker
processes.
2020-06-21 14:47:24 +05:30
Kovid Goyal
2cb25cf5a8 Build the launcher when freezing on macOS as well
Can be used by the test suite
2020-06-21 14:37:36 +05:30
Kovid Goyal
e07916425e macOS: Fix for diff kitten not working with python 3.8
Fixes #2780
2020-06-20 19:03:45 +05:30
Kovid Goyal
f62e2374e4 Ensure mimetype is sent to github 2020-06-20 13:02:34 +05:30
Kovid Goyal
eb4ee2f0af version 0.18.0 2020-06-20 12:48:59 +05:30
Kovid Goyal
be3c76da20 Fix remote control command docs order randomized 2020-06-20 12:45:05 +05:30
Kovid Goyal
b15e58621c Fix the kitty shell launched via a mapping needlessly requiring :opt:allow_remote_control to be turned on. 2020-06-20 12:43:46 +05:30
Kovid Goyal
601a6c9e3d Allow sending arbitrary signals to the current foreground process in a window using either a mapping in kitty.conf or via remote control
Fixes #2778
2020-06-20 12:37:27 +05:30
Kovid Goyal
3ff184348e Merge branch 'unused-macro' of https://github.com/Luflosi/kitty 2020-06-19 18:33:28 +05:30
Luflosi
c8efcd2be1 Remove unused macro 2020-06-19 14:31:58 +02:00
Kovid Goyal
9c116a2975 Fix #2776 2020-06-19 12:13:32 +05:30
Kovid Goyal
8818b5d8df Set CFBundleAllowMixedLocalizations in the app bundle to have NSLocale.currentLocale return the correct locale on non-English systems 2020-06-18 20:16:36 +05:30
Kovid Goyal
e96dfadae7 Ensure cocoa_get_lang() returns a valid locale or None
See #1233
2020-06-18 20:15:01 +05:30
Kovid Goyal
deb564e5a6 Even more integrations 2020-06-17 18:09:34 +05:30
Kovid Goyal
ffc71c348c A couple more integrations with external tools 2020-06-17 18:07:19 +05:30
Kovid Goyal
19df7b94ea Clarify documentation a bit 2020-06-16 09:53:08 +05:30
Kovid Goyal
a3ecc79f0d Dont actually need -- since kitty's option processing code is smart enough to stop looking for options after the first non-optional argument 2020-06-16 09:31:42 +05:30
Kovid Goyal
b140b15119 Add a -- to linenum hints examples 2020-06-16 08:30:34 +05:30
Kovid Goyal
b7b2b6075a Add mdcat 2020-06-15 09:31:51 +05:30
Kovid Goyal
7525e152b8 Make comment a bit better 2020-06-15 06:55:52 +05:30
Kovid Goyal
bc9dfec615 Fix #2764 2020-06-15 06:53:35 +05:30
Kovid Goyal
7824739034 Ensure selection GPU buffer is refreshed after a resize
Fixes #2747 (I hope since I cannot reproduce)
2020-06-13 22:59:46 +05:30
Kovid Goyal
e7b60a3628 Fix #2763 2020-06-13 20:09:26 +05:30
Kovid Goyal
97c3061b8f Allow having no tab separator
Fixes #2761
2020-06-13 12:33:15 +05:30
Kovid Goyal
1a042c873c Add a link to vim-test 2020-06-11 08:02:17 +05:30
Kovid Goyal
064aa3a150 Wayland: Ensure cursor theme is loaded before trying to set cursor 2020-06-11 07:57:08 +05:30
Kovid Goyal
8a178ebe55 Wayland: Apply the same framebuffer selection optimisation
Not as much of a win, since EGL only causes approx 1K getpid() calls of
which this optimization eliminates only half
2020-06-09 21:59:25 +05:30
Kovid Goyal
9a97f0bced X11: Further reduce startup time
Use the GLX 1.3 API function to choose the best framebuffer config
instead of doing it ourselves. Reduces the number fo getpid() calls by
a further ~15K
2020-06-09 20:18:07 +05:30
Kovid Goyal
5c7471910d Fix #2752 2020-06-09 09:51:15 +05:30
Kovid Goyal
27b6517594 X11: Reduce startup time by ~25% by only querying GLX for framebuffer configurations once
On my system reduces the number of getpid() calls from 67982 to 17327
I dont know why get framebuffer config attributes requires getpid() and
why it is so slow, probably a driver bug. Fixes #2754
2020-06-09 01:04:09 +05:30
Kovid Goyal
cd4dacb406 Fix #2753 2020-06-08 21:40:59 +05:30
Kovid Goyal
5f6faa1bf0 Make a central list of integrations in the docs
Fixes #2750
2020-06-08 14:49:59 +05:30
Kovid Goyal
cfc5a952f8 Add --no-response to @ last-used-layout 2020-06-08 09:16:26 +05:30
Kovid Goyal
10b186957b Update link to termpdf.py 2020-06-08 08:57:50 +05:30
Kovid Goyal
f597481736 Update changelog for last merge 2020-06-06 22:32:52 +05:30
Kovid Goyal
9fa97e6187 Merge branch 'mouse-events' of https://github.com/trygveaa/kitty 2020-06-06 22:27:54 +05:30
Kovid Goyal
3844321f20 Use a multiple of 8 size for mouse_button_pressed 2020-06-06 22:24:01 +05:30
Trygve Aaberge
8efe08c45b Support more mouse buttons for terminal mouse events
Previously, the mouse back and forward buttons sent the same codes as
scroll up and down. Now they instead send the same codes as xterm. Mouse
button 10 (in X11 numbering) also now sends the same as xterm, instead
of not sending anything.

This also changes the `send_mouse_event` function which can be called
from kittens to use X11 numbering for mouse buttons instead of what it
previously used, which turns out to be a hybrid of X11 and GLFW. It was
documented to use GLFW numbering, but GLFW doesn't have numbers for
scroll events (that's separate events with x/y offsets) and 4 and 5 in
GLFW is actually back and forward, while `send_mouse_event` interpreted
it as scroll up and down.

That means that this is a breaking change for `send_mouse_event` because
it swaps the number for the middle and right button to be consistent
with X11. I did this because I think it's better to use one consistent
numbering scheme for the function, and because people probably know X11
numbering better than GLFW numbering and GLFW doesn't have numbers for
the scroll buttons.
2020-06-06 14:19:06 +02:00
Trygve Aaberge
c78563b452 Report modifier key state when sending move and drag events to the terminal program 2020-06-06 12:28:42 +02:00
Trygve Aaberge
122e172092 Track modifier state correctly
When a modifier key is pressed, that modifier is not included in mods.
When it is released, it is included. Therefore, we have to special case
the modifier keys when storing the modifiers that are active. When a
modifier key is pressed, we add the modifier to
mods_at_last_key_or_button_event, and when it is released, we remove it.

This fixes an issue where move and drag events would still be sent to
the terminal program after pressing shift, but would stop being sent
after releasing shift until another key or button was pressed.
2020-06-06 12:28:42 +02:00
Kovid Goyal
8f85c0d167 Use bypy in update-on-ubuntu 2020-06-06 09:23:36 +05:30
Kovid Goyal
ac6d1ce174 Use bypy in the publish script 2020-06-06 09:19:48 +05:30
Kovid Goyal
03e0bf4c0e Migrate update-on-ox to use bypy 2020-06-06 09:11:50 +05:30
Kovid Goyal
1cb183f5e1 Add a note about what sort of ligatures disable_ligatures affects 2020-06-06 09:11:32 +05:30
Kovid Goyal
d78a553910 Fix #2739 2020-06-06 07:50:43 +05:30
Kovid Goyal
1326532850 macOS: Notarize the kitty application
This required the kitty package to be moved from Frameworks to
Resources, because Apple cant codesign .pyc files and its notarization
requires everything in Frameworks to be signed. Hopefully that does not
break anything. Also removed the kitty-deref-symlink since it is not
needed anyway and was only present for backwards compatibility.

Fixes #2040
2020-06-05 22:52:30 +05:30
Kovid Goyal
408508247d Merge branch 'replace_glfw_true' of https://github.com/Luflosi/kitty 2020-06-05 22:32:53 +05:30
Luflosi
e2be09e028 Remove GLFW_TRUE in comment
In 55ad228166 I forgot to replace `GLFW_TRUE` with `true` in a comment.
2020-06-05 18:51:50 +02:00
Kovid Goyal
c7c14f184c X11: Recompile keymaps on XkbNewKeyboardNotify events
Hopefully fixes #2726
2020-06-05 22:11:15 +05:30
Kovid Goyal
24ca919f33 Merge branch 'fake_scroll_correct_window' of https://github.com/Luflosi/kitty 2020-06-05 20:20:40 +05:30
Luflosi
85fe783652 Fix fake scroll when scrolling in unfocused kitty window
Scrolling should happen in the kitty window under the mouse cursor, not in the currently active kitty window.
2020-06-05 16:44:11 +02:00
Kovid Goyal
04b6bf3b74 bypy builds on macOS 2020-06-05 19:38:00 +05:30
Kovid Goyal
8b2fb105d0 Fix #2735 2020-06-05 19:24:40 +05:30
Kovid Goyal
558caa232d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-05 07:10:39 +05:30
Luflosi
d8886edbeb Null: Make platform more conformant
From upstream: e0c77f71f9.
2020-06-05 00:27:47 +02:00
Kovid Goyal
f72ecf1791 Start work on building macOS binary with bypy 2020-06-04 23:18:48 +05:30
Kovid Goyal
45d1b978d8 bypy now builds the Linux binary 2020-06-04 19:54:01 +05:30
Kovid Goyal
0b780cc065 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-04 19:24:40 +05:30
Luflosi
5263f6598c Add feature available/implemented errors
From upstream: 9a87c2a4b4.
2020-06-04 13:55:52 +02:00
Kovid Goyal
f9e60e7ef6 Start work on using bypy to build the binaries 2020-06-04 13:51:50 +05:30
Kovid Goyal
45c00d0b1f Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-04 07:15:09 +05:30
Luflosi
c70e2f7d3e Add statement to docs for glfwTerminate
From upstream: 7486e12f88.
2020-06-03 20:07:09 +02:00
Kovid Goyal
1ec0bed1ce Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-03 22:45:24 +05:30
Luflosi
46b5e4513a Win32: Fix VS /W4 compile warnings
From upstream: a84a30ab63.
2020-06-03 19:05:05 +02:00
Kovid Goyal
ffb5196f3b Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-03 22:04:21 +05:30
Luflosi
107f1af3c4 Fix desktop GL header not always being suppressed
From upstream: 105cf32e0b.
2020-06-03 11:32:59 +02:00
Kovid Goyal
03b1484c96 Wayland: Abort on fatal error should bypass quit confirmation 2020-06-03 14:39:27 +05:30
Kovid Goyal
aaf64ad63e Add links to config settings to change mouse modifiers in the main mouse docs 2020-06-03 08:17:28 +05:30
Kovid Goyal
f548e795d5 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-02 17:05:02 +05:30
Luflosi
767da68239 Reduce difference to GLFW upstream
Only whitespace, formatting and comments were changed. This does not alter any functionality.
2020-06-02 11:45:08 +02:00
Kovid Goyal
f44e0ff4bd Merge branch 'patch-1' of https://github.com/TheDaemoness/kitty 2020-06-02 10:54:44 +05:30
TheDaemoness
7f226f2ea1 Avoid incrementing hidden cursors' images 2020-06-02 05:10:51 +00:00
Kovid Goyal
1c82a39a6e Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-02 08:13:33 +05:30
Kovid Goyal
b70cf276b4 Change the cursor image immediately on scale change 2020-06-02 08:09:27 +05:30
Kovid Goyal
80b2df2ef4 Better code when using a stack cursor 2020-06-02 07:59:32 +05:30
Kovid Goyal
ad27452275 Wayland: Avoid infinite timer fires if arrow cursor is animated 2020-06-02 07:53:15 +05:30
Kovid Goyal
196a0b6b7e Merge branch 'patch-1' of https://github.com/TheDaemoness/kitty 2020-06-02 07:49:57 +05:30
Luflosi
f356f63994 wayland: save serial from all input sources
From upstream: 92b3fd02e5.
2020-06-01 21:29:14 +02:00
TheDaemoness
4947fb6b57 Fix a missing cursorWayland-scale assignment. 2020-06-01 17:41:16 +00:00
Kovid Goyal
622f12b84e Add a changelog entry for wayland hidpi cursors 2020-06-01 21:26:18 +05:30
Kovid Goyal
c1f84ce603 cleanup for wayland hidpi cursors merge 2020-06-01 21:14:56 +05:30
Kovid Goyal
14912461ad Use left_ptr as default arrow cursor on Wayland
Matches X11.
2020-06-01 20:33:54 +05:30
Kovid Goyal
a9f265e8b7 Initialize a couple of local vars 2020-06-01 20:33:54 +05:30
TheDaemoness
199ee20adb Fixed an instance of eager cursor loading.
Also fixed a minor formatting issue.
2020-06-01 20:33:53 +05:30
TheDaemoness
7c3c87abf6 Initial implementation of proper HiDPI cursor support on Wayland. 2020-06-01 20:33:53 +05:30
Kovid Goyal
b0ad44bcf2 Fix #2714 2020-06-01 20:31:05 +05:30
Kovid Goyal
9ba808ec3c Merge branch 'ft/csi-code-rep' of https://github.com/keynslug/kitty 2020-06-01 14:31:34 +05:30
Kovid Goyal
eacf849e3a Add bounds check to left_shift_line()
Fixes #2710
2020-05-31 08:10:58 +05:30
Andrew Mayorov
42c2791182 Do not cap repetitions by row length
Instead cap by 65535 as a safeguard. This is more in line with ECMA 48.
2020-05-29 20:36:30 +03:00
Andrew Mayorov
390e883ecf Update terminfo with CSI REP support 2020-05-29 01:06:16 +03:00
Andrew Mayorov
9fe631ee3f Provide support for CSI REP control code 2020-05-29 01:05:49 +03:00
Kovid Goyal
82d8c4b230 Fix #2699 2020-05-27 20:18:05 +05:30
Kovid Goyal
9775bb0a9d Merge branch 'patch-1' of https://github.com/aslpavel/kitty 2020-05-27 09:05:09 +05:30
Pavel
139c9a2d6d Fix incorrect examples in graphic protocol
Examples in `Deleting images` section were incorrect.
2020-05-26 20:39:31 +01:00
Kovid Goyal
542880f87b Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-25 22:17:42 +05:30
Luflosi
3846286bee Wayland: Implement raw mouse motion control
From upstream: 44af6bb936.
2020-05-25 18:21:32 +02:00
Kovid Goyal
72be402a2d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-25 21:47:56 +05:30
Luflosi
c6b9cb759a Wayland: Rename handlers for more unicity
From upstream: 754b7feaa1.

1fa66ca45d didn't include the changes to `wl_window.c`.
2020-05-25 15:29:17 +02:00
Kovid Goyal
13ccf269a6 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-25 15:25:01 +05:30
Luflosi
fcc7a0255a Align joystick axis to gamepad button behavior
From upstream: 02874d9c14.
2020-05-25 11:44:14 +02:00
Luflosi
e7447f38cf Fix half-axis to gamepad button value mapping
From upstream: c32dc3a085.
2020-05-25 11:44:02 +02:00
Kovid Goyal
7ae35631a6 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-25 10:32:54 +05:30
Luflosi
55ad228166 Rename raw input to raw mouse motion, cleanup
From upstream: 1155c83013.
2020-05-24 21:57:29 +02:00
Luflosi
48fb051253 Add GLFW_RAW_INPUT and glfwRawInputSupported
From upstream: 9e29f556fd.
2020-05-24 20:49:52 +02:00
Kovid Goyal
f97e8b7eeb The title for @ set-window-title is optional 2020-05-24 21:51:33 +05:30
Kovid Goyal
84f914742b Fix command names using undersore instead of hyphens in completion for the shell 2020-05-24 21:34:34 +05:30
Kovid Goyal
0d704f3715 kitty shell: show the previously active window id 2020-05-24 16:40:10 +05:30
Kovid Goyal
d8896eb210 Fix #2688 2020-05-24 12:40:54 +05:30
Kovid Goyal
b9d74e2012 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-24 08:16:51 +05:30
Luflosi
803fba9cfd Cocoa: Replace display link with IOKit query
From upstream: 4ec7daf3e9.
2020-05-23 16:28:31 +02:00
Kovid Goyal
581548ed67 Merge branch 'freebsd' of https://github.com/rkitover/kitty 2020-05-23 19:56:51 +05:30
Kovid Goyal
9a8c0f8e7e Merge branch 'cut_utf-8' of https://github.com/Luflosi/kitty 2020-05-23 19:53:40 +05:30
Rafael Kitover
328f569c16 Fix child spawn on FreeBSD.
Fix typo: "#ifdef TIOCSTTY" -> "#ifdef TIOCSCTTY"

Include standard header sys/ioctl.h for ioctl(), needed on MacOS.

Include standard header termios.h for TIOCSCTTY.

Fixes this error on spawn of child shell process:

bash: cannot set terminal process group (-1): Inappropriate ioctl for
device
bash: no job control in this shell

Tested on FreeBSD and Linux.

- Fix #2529

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2020-05-23 12:21:57 +00:00
Luflosi
7931654a7c Ensure valid UTF-8 when cutting string 2020-05-23 13:59:23 +02:00
Kovid Goyal
6eb0c10339 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-23 17:24:03 +05:30
Luflosi
269f294d19 Use CALayer instead of NSView for EGLNativeWindowType
From upstream: 91eebe922d.
2020-05-23 12:48:26 +02:00
Kovid Goyal
d19e2f1350 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-23 16:17:30 +05:30
Luflosi
3b745b82db X11: Clarify function name and comment
From upstream: 5f9dfe3d83.
2020-05-23 11:34:17 +02:00
Kovid Goyal
02f30c2a03 Merge branch 'master' of https://github.com/ahlinc/kitty 2020-05-23 10:47:26 +05:30
Andrew Hlynskyi
54002b386a Fixed issue with a cwd for kitty_mod+h scroll buffer viewer
There is an issue with ability to save scroll buffer from a default less
scroll buffer viewer by just typing relative path to an output file,
because the running less instance inherits cwd from Kitty's cwd
instead of cwd of active window.

How to reproduce:
  1. Start Kitty.
  2. Go to some place.
  3. Press 'kitty_mod+h'
  4. Press 's' and you'll see a less prompt like 'log file:'
  5. Type a just file name without any path and press Enter then press 'q'
  6. See that there is no such file in a current shell directory.
2020-05-23 07:19:45 +03:00
Kovid Goyal
0e2ff077af Fix #2682 2020-05-23 09:03:44 +05:30
Kovid Goyal
012cf70321 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-05-23 07:22:07 +05:30
Luflosi
9da0ba52db X11: Clarify code flow for Clang static analysis
From upstream: ef5220d6b1.
2020-05-22 23:32:12 +02:00
Kovid Goyal
7bfb07e59e Merge branch 'simplify' of https://github.com/Luflosi/kitty 2020-05-22 19:58:48 +05:30
Luflosi
9cb2c4b1ff Simplify code
There is already a check if `m` is `NULL`, so there is no need for a second `NULL` check after that.
2020-05-22 16:04:28 +02:00
Kovid Goyal
8c088fad0f Merge branch 'master' of https://github.com/ahlinc/kitty 2020-05-22 08:23:55 +05:30
Andrew Hlynskyi
392722aed0 Fixed misprint in .github/ISSUE_TEMPLATE/bug_report.md 2020-05-21 22:24:04 +03:00
Kovid Goyal
fabfb30fc4 Merge branch 'convert-tabs-to-spaces' of https://github.com/Luflosi/kitty 2020-05-21 19:51:02 +05:30
Luflosi
c7cb021e92 Convert two tabs to spaces
The rest of the file is also indented with spaces.
2020-05-21 15:34:08 +02:00
Kovid Goyal
be05d9c3e9 Proper confirm_os_window_close check condition 2020-05-21 15:12:20 +05:30
Kovid Goyal
e38690e8d9 Dont link against librt and libdl on OpenBSD
These are part of libc there
2020-05-21 11:43:55 +05:30
Kovid Goyal
7172115559 Merge branch 'patch-1' of https://github.com/TheDaemoness/kitty 2020-05-21 07:37:56 +05:30
TheDaemoness
7314019832 Fixed a typo and added some missing files. 2020-05-20 20:10:37 +00:00
TheDaemoness
b9684941c4 Create .editorconfig
[Most major editors](https://editorconfig.org/#download) support EditorConfig for declaring per-file per-project indentation rules. It costs almost nothing to maintain, and it makes it easier to follow the coding styles already used in kitty.
2020-05-20 19:46:13 +00:00
Kovid Goyal
a27289352c Merge branch 'master' of https://github.com/ahlinc/kitty 2020-05-20 19:56:58 +05:30
Andrew Hlynskyi
dc77009e55 Fixed confirm_os_window_close bug introduced in commit 7bf83603c8 2020-05-20 15:29:21 +03:00
Kovid Goyal
d12e10830d Wayland: Fix kitty being killed on some Wayland compositors if a hidden window has a lot of output
Sway falls over and dies if it receives many render frame
requests. So send only a single one per damaged window and hope and pray
that the compositor hasn't dropped it. Shrug, Wayland, no surprise.
Fixes #2329
2020-05-20 14:19:09 +05:30
Kovid Goyal
936f6c22f0 Merge branch 'configure_library_paths' of https://github.com/Luflosi/kitty 2020-05-20 07:17:07 +05:30
Kovid Goyal
9712be903d Fix #2667 2020-05-20 07:05:35 +05:30
Luflosi
7441cefdbb Make paths to libraries configurable at build time 2020-05-19 18:36:46 +02:00
Kovid Goyal
7bf83603c8 Forgot to treat confirm_os_window_close as a count 2020-05-19 08:19:17 +05:30
Kovid Goyal
6768bc1705 Fix SIGTERM/SIGINT on macOS causing kitty to enter an infinite loop 2020-05-18 17:53:35 +05:30
Kovid Goyal
707cb37212 Have the confirm on close also apply to quitting kitty
Use a dedicated API for handling quit requests on macOS rather than a
quit canary. Also create a mappable "quit" action for all platforms.
2020-05-18 16:06:40 +05:30
Kovid Goyal
1cf0a8b78e Allow only a single OS window close confirmation per OS window 2020-05-18 13:56:19 +05:30
Kovid Goyal
3d2cb37af0 Linux: Workaround for broken Nvidia drivers for old cards
Fixes #456
2020-05-18 13:41:23 +05:30
Kovid Goyal
09fbd6f5e0 Merge branch 'master' of https://github.com/ahlinc/kitty 2020-05-18 09:59:31 +05:30
Andrew Hlynskyi
474bef65cb Fixed marks grouping and coloring for simultaneous multi marks definition 2020-05-18 06:53:56 +03:00
Kovid Goyal
e97454c4a5 And ever more coalesce tests 2020-05-17 23:20:26 +05:30
Kovid Goyal
936dc74a27 Some more coalesce tests 2020-05-17 23:08:27 +05:30
Kovid Goyal
362e4266bb Better coalescing of multiple symbol_maps
When specifying multiple symbol_map values in kitty.conf later ones
should override conflicting previous ones. Fixes #2662
2020-05-17 23:03:28 +05:30
Kovid Goyal
d08f2e10f1 Add bug label to bug bug report template 2020-05-17 13:12:35 +05:30
Kovid Goyal
f1e5ee74a6 Add a feature request template 2020-05-17 13:11:23 +05:30
Kovid Goyal
7832d33348 Add a bug report template 2020-05-17 13:07:49 +05:30
Kovid Goyal
de25f94fae When extending selections to words, recognize ://
As part of the word even if : is not in select_word_chars. This allows
double-clicking to select common URLs with the default select_word_chars
2020-05-16 13:49:39 +05:30
Kovid Goyal
98625e9c36 Add an option confirm_on_os_window_close to ask for confirmation when closing an OS window with multiple kitty windows. 2020-05-15 13:39:58 +05:30
Kovid Goyal
5768cc98b6 ... 2020-05-15 13:31:35 +05:30
Kovid Goyal
da1cc6c1c5 Add a yes/no mode to the ask kitten 2020-05-15 13:16:14 +05:30
Kovid Goyal
50384fac97 DRYer 2020-05-14 09:18:29 +05:30
Kovid Goyal
424bd8a6cd Only compile tab template once 2020-05-14 09:15:11 +05:30
Kovid Goyal
421fae2b57 Merge branch 'fix/tab-title-template-f-string' of https://github.com/chriszarate/kitty 2020-05-14 09:09:18 +05:30
chriszarate
8bb8615a5a Compile template and restrict globals for eval 2020-05-13 23:30:12 -04:00
Kovid Goyal
80f8692e92 Tall/fat layout: Fix resizing of windows when number of windows is <= num of full size windows not working 2020-05-13 23:33:23 +05:30
Kovid Goyal
25623c758d Tall and Fat layouts: Add a `mirrored` option to put the full size window on the opposite edge of the screen
Fixes #2654
2020-05-13 23:19:34 +05:30
Kovid Goyal
e1a0a195a9 Forgot to change one call site for switch_focus_to 2020-05-13 21:39:26 +05:30
Kovid Goyal
f8cf49a90c Merge branch 'simplify' of https://github.com/Luflosi/kitty 2020-05-13 17:22:39 +05:30
Luflosi
ac9f8595c9 DRYer
Don't write `cppflags.extend()` twice.
2020-05-13 13:43:43 +02:00
Kovid Goyal
97f22bbff6 Update changelog 2020-05-13 11:01:04 +05:30
chriszarate
d6ddfa9d18 Use f-string instead of format for tab_title templates 2020-05-12 22:29:03 -04:00
Kovid Goyal
5e48ae8bac pep8 2020-05-12 22:54:08 +05:30
Kovid Goyal
cdd943ded4 Port layout tests to new API 2020-05-12 22:43:55 +05:30
Kovid Goyal
39b2bf963c Port the splits layout to the new groups API 2020-05-12 22:43:54 +05:30
Kovid Goyal
06827fd437 Update docs to point to new layouts package 2020-05-12 22:43:54 +05:30
Kovid Goyal
70ccc1cf6d Port Grid layout to new groups API 2020-05-12 22:43:54 +05:30
Kovid Goyal
f801c47d1e Port vertical/horz layouts to new groups API 2020-05-12 22:43:54 +05:30
Kovid Goyal
01c0e8da93 Tall and Fat layouts ported to new groups API 2020-05-12 22:43:54 +05:30
Kovid Goyal
e844ad6db3 Respect per-window margin/padding in stack layout
Also use the margin/padding of the base window for all overlay windows
2020-05-12 22:43:54 +05:30
Kovid Goyal
3f8c042bc3 Preserve active idx when removing windows/tabs 2020-05-12 22:43:54 +05:30
Kovid Goyal
707ad705a4 Notify on active window changed when adding/removing to same group 2020-05-12 22:43:54 +05:30
Kovid Goyal
79a780004f Remove references to overlay_for from boss 2020-05-12 22:43:54 +05:30
Kovid Goyal
81b28bc1bd More work on refactoring window groups, stack layout works 2020-05-12 22:43:54 +05:30
Kovid Goyal
e9c4d540b1 More work on refactoring window groups 2020-05-12 22:43:54 +05:30
Kovid Goyal
50d9718c68 Start work on refactoring management of windows 2020-05-12 22:43:53 +05:30
Kovid Goyal
e39da2b2bb Merge branch 'icat-http-timeout' of https://github.com/lamby/kitty 2020-05-11 16:58:21 +05:30
Chris Lamb
f08ec1522a Set a default socket timeout when retrieving remote items in icat.
Otherwise we can easily hang when "icat"-ing resources from unreliable
websites/webcams, etc.
2020-05-11 12:24:57 +01:00
Kovid Goyal
e61e93712c Merge branch 'patch-3' of https://github.com/ctrlcctrlv/kitty 2020-05-11 11:47:14 +05:30
Fredrick Brennan
c9e198e02e Graphics protocol: note best detection method
Per https://github.com/kovidgoyal/kitty/issues/2636#issuecomment-626467852
2020-05-10 23:02:12 -07:00
Kovid Goyal
0ce181b578 Fix #2639 2020-05-10 21:28:55 +05:30
Kovid Goyal
99950fe448 DECQRM responses for mouse tracking modes 2020-05-09 20:55:51 +05:30
Kovid Goyal
d8daf84a09 Fix #2636 2020-05-09 17:21:23 +05:30
Kovid Goyal
3d32202b3a version 0.17.4 2020-05-09 10:48:21 +05:30
Kovid Goyal
4f4d562670 Fix background_tint making the window semi-transparent
Fixes #2618
2020-05-09 09:06:18 +05:30
Kovid Goyal
28fc9c69da Allow showing the name of the current layout and the number of windows in tab titles
Fixes #2634
2020-05-09 08:00:17 +05:30
Kovid Goyal
6edf00e699 Wayland: Fix OS window title not updating for hidden windows
Fixes #2629
2020-05-05 17:51:36 +05:30
Kovid Goyal
a0d6445dda Move expandvars to utils 2020-05-05 08:22:08 +05:30
Kovid Goyal
e74797037c ... 2020-05-04 13:30:35 +05:30
Kovid Goyal
3fb125dba7 Fix mypy editor integration for changes in syntastic 2020-05-04 13:19:03 +05:30
Kovid Goyal
3c8640f9ad Infrastructure for close confirmation 2020-05-02 18:19:08 +05:30
Kovid Goyal
2e63a4c83f Wayland: Remove unused link-time dependency
6aca3e99f0
2020-05-01 15:00:38 +05:30
Kovid Goyal
9da5bee178 X11: Filter out Xlib errors from other connections
2c8e0512dd
2020-05-01 14:59:16 +05:30
Kovid Goyal
2fc6f3b542 Merge changes from upstream
e65de2941c
2020-05-01 14:57:55 +05:30
Kovid Goyal
4754f42e51 Merge branch 'omit-final-tab-separator' of https://github.com/trygveaa/kitty 2020-05-01 14:05:26 +05:30
Trygve Aaberge
941fe918fb Omit final tab separator
This makes tab separators only drawn between the tabs.

Relates to #2616
2020-05-01 10:29:18 +02:00
Kovid Goyal
f4e82e972e ... 2020-05-01 08:10:11 +05:30
Kovid Goyal
f66dbf111d Pass env to get_vcs_revs() 2020-05-01 08:03:31 +05:30
Kovid Goyal
1a2da5c49d Use python3 in session.vim 2020-04-30 23:12:37 +05:30
Kovid Goyal
8327dabb29 Allow @ send-text over a socket to work with more than 1MB of text 2020-04-30 08:58:46 +05:30
Kovid Goyal
195ea6c140 Dont send back an rc disallowed response for commands that dont expect responses 2020-04-29 21:53:20 +05:30
Kovid Goyal
fcc0707174 Fix kitty @ send-text not working with text larger than 1024 bytes when using --listen-on
Fixes #2607
2020-04-29 21:48:09 +05:30
Kovid Goyal
aa9c3cd634 Report modifier key state when sending wheel events to the terminal program 2020-04-29 20:02:55 +05:30
Kovid Goyal
b541341681 Avoid use of goto 2020-04-29 14:37:26 +05:30
Kovid Goyal
e5d50aca6d ... 2020-04-29 09:21:36 +05:30
Kovid Goyal
b738f4d4ff Fix a regression that broke drag and drop on macOS
Fixes #2605
2020-04-29 09:12:04 +05:30
Kovid Goyal
5c38aabf0e ... 2020-04-29 07:50:01 +05:30
Kovid Goyal
03e9ed6947 Fix set_window_padding() only working on first window 2020-04-28 22:34:32 +05:30
Kovid Goyal
63493fad22 Fix a regression that caused clicking in the padding/margins of windows in the stack layout to switch the window to the first window
Fixes #2604
2020-04-28 22:23:38 +05:30
Kovid Goyal
696b857f91 By default, double clicking no longer considers the : as part of words
Fixes #2602
2020-04-28 19:57:40 +05:30
Kovid Goyal
53ae2bbe0d Merge branch 'use_loop' of https://github.com/Luflosi/kitty 2020-04-28 19:40:40 +05:30
Luflosi
01069e919d Use loop instead of calling dlopen() multiple times
Having an array of library names and iterating over it looks nicer than having several variables and calling `dlopen()` multiple times manually.
It should also make it easier to use custom library names instead by using the preprocessor, which I plan to do in the near future.
2020-04-28 15:53:58 +02:00
Kovid Goyal
fc0095f505 Merge branch 'master' of https://github.com/ixydo/kitty 2020-04-27 21:34:52 +05:30
Jinn Koriech
421528e130 Update note about icat + tmux to be more generic 2020-04-27 16:40:24 +01:00
Jinn Koriech
7b70356515 Add note clarifying that icat won't work with tmux
When using icat with tmux we get the following:

```
$ kitty icat ~/Pictures/fc.jpg 
Terminal does not support reporting screen sizes via the TIOCGWINSZ ioctl
```

I found a suggestion[1] this is due to limitations between kitty and tmux, so I'm raising this PR to either confirm this is the case and make it clear in the documentation, or to have it rejected so an issue can be raised to address the feature.

1: https://unix.stackexchange.com/a/484776/41996
2020-04-27 14:11:16 +01:00
Kovid Goyal
e803505aad CoreText: When centering glyphs ignore negative origins
This is needed for FiraCode ligatures to line up correctly. See
https://github.com/kovidgoyal/kitty/issues/2591#issuecomment-619937030
2020-04-27 18:02:53 +05:30
Kovid Goyal
ad1ff455a0 macOS: Fix a regression in the previous release that caused ligatures to be not be centered horizontally
Fixes #2591
2020-04-27 16:17:05 +05:30
Kovid Goyal
7692cb5c3d Merge branch 'graphics-big-endian' of https://github.com/jamessan/kitty 2020-04-26 09:36:53 +05:30
James McCoy
a94e208db3 Use T_PYSSIZET for GraphicsManager.image_count
GraphicsManager.image_count is declared as a size_t, so the Python type
needs to be the same width.  On 64-bit big-endian architectures, this
mismatch was showing up as test failures:

    ======================================================================
    FAIL: test_gr_delete (kitty_tests.graphics.TestGraphics)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/<<PKGBUILDDIR>>/kitty_tests/graphics.py", line 319, in test_gr_delete
        self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
        ch = 20
        cw = 10
        delete = <function TestGraphics.test_gr_delete.<locals>.delete at 0x3ffb7fc38b0>
        dx = 0.2
        dy = 0.4
        layers = <function put_helpers.<locals>.layers at 0x3ffb77709d0>
        put_image = <function put_helpers.<locals>.put_image at 0x3ffb77708b0>
        put_ref = <function put_helpers.<locals>.put_ref at 0x3ffb7770940>
        rect_eq = <function put_helpers.<locals>.rect_eq at 0x3ffb7770a60>
        s = <fast_data_types.Screen object at 0x1b60df0>
        self = <kitty_tests.graphics.TestGraphics testMethod=test_gr_delete>
    AssertionError: 0 != 1
2020-04-25 23:25:18 -04:00
Kovid Goyal
ba9d5cf3ff Merge branch 'use-fPIC' of https://github.com/jamessan/kitty 2020-04-25 21:11:10 +05:30
James McCoy
0dff0d03f9 Use -fPIC instead of -fpic
With large objects, use of -fpic will fail to link on certain
architectures due to size limitations of the global offset table (GOT).
For example, the link stage for glfw-wayland.so fails on sparc64, as
below.  Using -fPIC avoids these size limitations.

    ./glfw/osmesa_context.c:73:(.text+0xf2c): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `swapIntervalEGL':
    ./glfw/egl_context.c:238:(.text+0xf68): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `destroyContextOSMesa':
    ./glfw/osmesa_context.c:80:(.text+0x10a4): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `data_offer_action':
    ./glfw/wl_window.c:1692:(.text+0x15e4): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `data_offer_source_actions':
    ./glfw/wl_window.c:1683:(.text+0x1648): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `keyboardHandleRepeatInfo':
    ./glfw/wl_init.c:471:(.text+0x1704): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `checkScaleChange':
    ./glfw/wl_window.c:55:(.text+0x1768): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `primary_selection_copy_callback_done':
    ./glfw/wl_window.c:1833:(.text+0x18bc): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `clipboard_copy_callback_done':
    ./glfw/wl_window.c:1825:(.text+0x1910): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `primary_selection_source_canceled':
    ./glfw/wl_window.c:1582:(.text+0x1968): relocation truncated to fit: R_SPARC_GOT13 against symbol `_glfw' defined in .bss section in /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o
    /tmp/glfw-wayland.so.SYvdxr.ltrans0.ltrans.o: in function `data_source_canceled':
    ./glfw/wl_window.c:1576:(.text+0x19c0): additional relocation overflows omitted from the output
    collect2: error: ld returned 1 exit status
2020-04-25 10:08:28 -04:00
Kovid Goyal
c92aca5d19 Use a Literal as the type for choice based options 2020-04-24 19:45:51 +05:30
Kovid Goyal
5762baeed7 Typing fixes 2020-04-24 19:27:22 +05:30
Kovid Goyal
601cf01222 Fix #2581 2020-04-24 19:24:11 +05:30
Kovid Goyal
0eef48b516 Use literals as the type for cli opts that take choices 2020-04-24 15:28:04 +05:30
Kovid Goyal
3c1954f329 ... 2020-04-24 15:07:44 +05:30
134 changed files with 6298 additions and 3099 deletions

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
indent_style = spaces
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
[{Makefile,*.terminfo}]
indent_style = tab
# Autogenerated files with tabs below this line.
[kitty/{unicode-data.c,emoji.h,wcwidth-std.h}]
indent_style = tab
[kittens/unicode_input/names.h]
indent_style = tab
[glfw/wayland-*-protocol.{c,h}]
indent_style = tab

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. XXX
2. YYY
3. ZZZ
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Enviroment details**
OS: Name and version of operating system(s)
```
Output of kitty --debug-config
```
**Additional context**
Try to reproduce the problem with `kitty --config NONE` if you cannot then post a minimal kitty.conf that reproduces the problem. If the problem involves interaction with some other terminal program post a minimal config for that program to reproduce the problem as well.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ __pycache__/
/docs/generated/
/.mypy_cache
.DS_Store
bypy/b

75
bypy/init_env.py Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import shutil
import subprocess
import sys
from bypy.constants import (
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
)
from bypy.utils import run_shell
def read_src_file(name):
with open(os.path.join(KITTY_DIR, 'kitty', name), 'rb') as f:
return f.read().decode('utf-8')
def initialize_constants():
kitty_constants = {}
src = read_src_file('constants.py')
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
kitty_constants['version'] = '%s.%s.%s' % (nv.group(1), nv.group(2), nv.group(3))
kitty_constants['appname'] = re.search(
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
).group(2)
return kitty_constants
def run(*args, **extra_env):
env = os.environ.copy()
env.update(worker_env)
env.update(extra_env)
env['SW'] = PREFIX
env['LD_LIBRARY_PATH'] = LIBDIR
if ismacos:
env['PKGCONFIG_EXE'] = os.path.join(PREFIX, 'bin', 'pkg-config')
cwd = env.pop('cwd', KITTY_DIR)
return subprocess.call(list(args), env=env, cwd=cwd)
def build_c_extensions(ext_dir, args):
writeable_src_dir = os.path.join(ext_dir, 'src')
shutil.copytree(
KITTY_DIR, writeable_src_dir, symlinks=True,
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
cmd = [PYTHON, 'setup.py']
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
cmd.append(bundle)
dest = kitty_constants['appname'] + ('.app' if ismacos else '')
dest = os.path.join(ext_dir, dest)
cmd += ['--prefix', dest]
if run(*cmd, cwd=writeable_src_dir) != 0:
print('Building of kitty package failed', file=sys.stderr)
os.chdir(KITTY_DIR)
run_shell()
raise SystemExit('Building of kitty package failed')
return ext_dir
def run_tests(path_to_kitty, cwd_on_failure):
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
if ret != 0:
os.chdir(cwd_on_failure)
print(
'running kitty tests failed with return code:', ret, file=sys.stderr)
run_shell()
raise SystemExit('running kitty tests failed')
if __name__ == 'program':
kitty_constants = initialize_constants()

3
bypy/linux.conf Normal file
View File

@@ -0,0 +1,3 @@
image 'https://partner-images.canonical.com/core/xenial/current/ubuntu-xenial-core-cloudimg-{}-root.tar.gz'
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'

209
bypy/linux/__main__.py Normal file
View File

@@ -0,0 +1,209 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import errno
import os
import shutil
import stat
import subprocess
import tarfile
import time
from bypy.constants import (
OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
)
from bypy.utils import get_dll_path, py_compile, walk
j = os.path.join
self_dir = os.path.dirname(os.path.abspath(__file__))
arch = 'x86_64' if is64bit else 'i686'
py_ver = '.'.join(map(str, python_major_minor_version()))
iv = globals()['init_env']
kitty_constants = iv['kitty_constants']
def binary_includes():
return tuple(map(get_dll_path, (
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16',
# dont include freetype because fontconfig is closely coupled to it
# and also distros often patch freetype
# 'iconv.so.2', 'pcre.so.1', 'graphite2.so.3', 'glib-2.0.so.0', 'freetype.so.6',
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
'ncursesw', 'readline',
))) + (
get_dll_path('bz2', 2), get_dll_path('ssl', 3), get_dll_path('crypto', 3),
get_dll_path('python' + py_ver, 2),
)
class Env:
def __init__(self, package_dir):
self.base = package_dir
self.lib_dir = j(self.base, 'lib')
self.py_dir = j(self.lib_dir, 'python' + py_ver)
os.makedirs(self.py_dir)
self.bin_dir = j(self.base, 'bin')
def ignore_in_lib(base, items, ignored_dirs=None):
ans = []
if ignored_dirs is None:
ignored_dirs = {'.svn', '.bzr', '.git', 'test', 'tests', 'testing'}
for name in items:
path = j(base, name)
if os.path.isdir(path):
if name in ignored_dirs or not os.path.exists(j(path, '__init__.py')):
if name != 'plugins':
ans.append(name)
else:
if name.rpartition('.')[-1] not in ('so', 'py'):
ans.append(name)
return ans
def import_site_packages(srcdir, dest):
if not os.path.exists(dest):
os.mkdir(dest)
for x in os.listdir(srcdir):
ext = x.rpartition('.')[-1]
f = j(srcdir, x)
if ext in ('py', 'so'):
shutil.copy2(f, dest)
elif ext == 'pth' and x != 'setuptools.pth':
for line in open(f, 'rb').read().splitlines():
src = os.path.abspath(j(srcdir, line))
if os.path.exists(src) and os.path.isdir(src):
import_site_packages(src, dest)
elif os.path.exists(j(f, '__init__.py')):
shutil.copytree(f, j(dest, x), ignore=ignore_in_lib)
def copy_libs(env):
print('Copying libs...')
for x in binary_includes():
dest = env.bin_dir if '/bin/' in x else env.lib_dir
shutil.copy2(x, dest)
def copy_python(env):
print('Copying python...')
srcdir = j(PREFIX, 'lib/python' + py_ver)
for x in os.listdir(srcdir):
y = j(srcdir, x)
ext = os.path.splitext(x)[1]
if os.path.isdir(y) and x not in ('test', 'hotshot', 'distutils', 'tkinter', 'turtledemo',
'site-packages', 'idlelib', 'lib2to3', 'dist-packages'):
shutil.copytree(y, j(env.py_dir, x), ignore=ignore_in_lib)
if os.path.isfile(y) and ext in ('.py', '.so'):
shutil.copy2(y, env.py_dir)
srcdir = j(srcdir, 'site-packages')
dest = j(env.py_dir, 'site-packages')
import_site_packages(srcdir, dest)
py_compile(env.py_dir)
py_compile(os.path.join(env.base, 'lib', 'kitty'))
def is_elf(path):
with open(path, 'rb') as f:
return f.read(4) == b'\x7fELF'
def fix_permissions(files):
for path in files:
os.chmod(path, 0o755)
STRIPCMD = ['strip']
def find_binaries(env):
files = {j(env.bin_dir, x) for x in os.listdir(env.bin_dir)} | {
x for x in {
j(os.path.dirname(env.bin_dir), x) for x in os.listdir(env.bin_dir)} if os.path.exists(x)}
for x in walk(env.lib_dir):
x = os.path.realpath(x)
if x not in files and is_elf(x):
files.add(x)
return files
def strip_files(files, argv_max=(256 * 1024)):
""" Strip a list of files """
while files:
cmd = list(STRIPCMD)
pathlen = sum(len(s) + 1 for s in cmd)
while pathlen < argv_max and files:
f = files.pop()
cmd.append(f)
pathlen += len(f) + 1
if len(cmd) > len(STRIPCMD):
all_files = cmd[len(STRIPCMD):]
unwritable_files = tuple(filter(None, (None if os.access(x, os.W_OK) else (x, os.stat(x).st_mode) for x in all_files)))
[os.chmod(x, stat.S_IWRITE | old_mode) for x, old_mode in unwritable_files]
subprocess.check_call(cmd)
[os.chmod(x, old_mode) for x, old_mode in unwritable_files]
def strip_binaries(files):
print('Stripping %d files...' % len(files))
before = sum(os.path.getsize(x) for x in files)
strip_files(files)
after = sum(os.path.getsize(x) for x in files)
print('Stripped %.1f MB' % ((before - after) / (1024 * 1024.)))
def create_tarfile(env, compression_level='9'):
print('Creating archive...')
base = OUTPUT_DIR
try:
shutil.rmtree(base)
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
os.mkdir(base)
dist = os.path.join(base, '%s-%s-%s.tar' % (kitty_constants['appname'], kitty_constants['version'], arch))
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
cwd = os.getcwd()
os.chdir(env.base)
try:
for x in os.listdir('.'):
tf.add(x)
finally:
os.chdir(cwd)
print('Compressing archive...')
ans = dist.rpartition('.')[0] + '.txz'
start_time = time.time()
subprocess.check_call(['xz', '--threads=0', '-f', '-' + compression_level, dist])
secs = time.time() - start_time
print('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60))
os.rename(dist + '.xz', ans)
print('Archive %s created: %.2f MB' % (
os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))
def main():
args = globals()['args']
ext_dir = globals()['ext_dir']
if not args.skip_tests:
run_tests = iv['run_tests']
run_tests(None, os.path.join(ext_dir, 'src'))
env = Env(os.path.join(ext_dir, kitty_constants['appname']))
copy_libs(env)
copy_python(env)
files = find_binaries(env)
fix_permissions(files)
if not args.dont_strip:
strip_binaries(files)
create_tarfile(env, args.compression_level)
if __name__ == '__main__':
main()

6
bypy/macos.conf Normal file
View File

@@ -0,0 +1,6 @@
# Requires installation of XCode 10.3 and Python 3 and
# python3 -m pip install certifi
vm_name 'macos-kitty-build'
root '/Users/Shared/kitty-build'
python '/usr/local/bin/python3'

459
bypy/macos/__main__.py Normal file
View File

@@ -0,0 +1,459 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import glob
import json
import os
import shutil
import stat
import subprocess
import sys
import tempfile
import zipfile
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
from bypy.macos_sign import (
codesign, create_entitlements_file, make_certificate_useable, notarize_app,
verify_signature
)
from bypy.utils import current_dir, py_compile, run_shell, timeit, walk
iv = globals()['init_env']
kitty_constants = iv['kitty_constants']
join = os.path.join
basename = os.path.basename
abspath = os.path.abspath
APPNAME = kitty_constants['appname']
VERSION = kitty_constants['version']
py_ver = '.'.join(map(str, python_major_minor_version()))
def flush(func):
def ff(*args, **kwargs):
sys.stdout.flush()
sys.stderr.flush()
ret = func(*args, **kwargs)
sys.stdout.flush()
sys.stderr.flush()
return ret
return ff
def flipwritable(fn, mode=None):
"""
Flip the writability of a file and return the old mode. Returns None
if the file is already writable.
"""
if os.access(fn, os.W_OK):
return None
old_mode = os.stat(fn).st_mode
os.chmod(fn, stat.S_IWRITE | old_mode)
return old_mode
STRIPCMD = ('/usr/bin/strip', '-x', '-S', '-')
def strip_files(files, argv_max=(256 * 1024)):
"""
Strip a list of files
"""
tostrip = [(fn, flipwritable(fn)) for fn in files if os.path.exists(fn)]
while tostrip:
cmd = list(STRIPCMD)
flips = []
pathlen = sum(len(s) + 1 for s in cmd)
while pathlen < argv_max:
if not tostrip:
break
added, flip = tostrip.pop()
pathlen += len(added) + 1
cmd.append(added)
flips.append((added, flip))
else:
cmd.pop()
tostrip.append(flips.pop())
os.spawnv(os.P_WAIT, cmd[0], cmd)
for args in flips:
flipwritable(*args)
def files_in(folder):
for record in os.walk(folder):
for f in record[-1]:
yield os.path.join(record[0], f)
def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
items = set(items)
dirs = set(x for x in items if os.path.isdir(x))
items.difference_update(dirs)
for x in dirs:
items.update({y for y in files_in(x) if not exclude(y)})
return items
def do_sign(app_dir):
with current_dir(os.path.join(app_dir, 'Contents')):
# Sign all .so files
so_files = {x for x in files_in('.') if x.endswith('.so')}
codesign(so_files)
# Sign everything else in Frameworks
with current_dir('Frameworks'):
fw = set(glob.glob('*.framework'))
codesign(fw)
items = set(os.listdir('.')) - fw
codesign(expand_dirs(items))
# Now sign the main app
codesign(app_dir)
verify_signature(app_dir)
def sign_app(app_dir, notarize):
# Copied from iTerm2: https://github.com/gnachman/iTerm2/blob/master/iTerm2.entitlements
create_entitlements_file({
'com.apple.security.automation.apple-events': True,
'com.apple.security.cs.allow-jit': True,
'com.apple.security.device.audio-input': True,
'com.apple.security.device.camera': True,
'com.apple.security.personal-information.addressbook': True,
'com.apple.security.personal-information.calendars': True,
'com.apple.security.personal-information.location': True,
'com.apple.security.personal-information.photos-library': True,
})
with make_certificate_useable():
do_sign(app_dir)
if notarize:
notarize_app(app_dir)
class Freeze(object):
FID = '@executable_path/../Frameworks'
def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False):
self.build_dir = build_dir
self.sign_installers = sign_installers
self.notarize = notarize
self.dont_strip = dont_strip
self.contents_dir = join(self.build_dir, 'Contents')
self.resources_dir = join(self.contents_dir, 'Resources')
self.frameworks_dir = join(self.contents_dir, 'Frameworks')
self.to_strip = []
self.warnings = []
self.py_ver = py_ver
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver)
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
self.run()
def run_shell(self):
with current_dir(self.contents_dir):
run_shell()
def run(self):
ret = 0
self.add_python_framework()
self.add_site_packages()
self.add_stdlib()
self.add_misc_libraries()
self.compile_py_modules()
self.fix_dependencies_in_kitty()
if not self.dont_strip:
self.strip_files()
# self.run_shell()
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION)
return ret
@flush
def strip_files(self):
print('\nStripping files...')
strip_files(self.to_strip)
@flush
def set_id(self, path_to_lib, new_id):
old_mode = flipwritable(path_to_lib)
subprocess.check_call(
['install_name_tool', '-id', new_id, path_to_lib])
if old_mode is not None:
flipwritable(path_to_lib, old_mode)
@flush
def get_dependencies(self, path_to_lib):
install_name = subprocess.check_output(
['otool', '-D', path_to_lib]).decode('utf-8').splitlines()[-1].strip()
raw = subprocess.check_output(['otool', '-L', path_to_lib]).decode('utf-8')
for line in raw.splitlines():
if 'compatibility' not in line or line.strip().endswith(':'):
continue
idx = line.find('(')
path = line[:idx].strip()
yield path, path == install_name
@flush
def get_local_dependencies(self, path_to_lib):
for x, is_id in self.get_dependencies(path_to_lib):
for y in (PREFIX + '/lib/', PREFIX + '/python/Python.framework/'):
if x.startswith(y):
if y == PREFIX + '/python/Python.framework/':
y = PREFIX + '/python/'
yield x, x[len(y):], is_id
break
@flush
def change_dep(self, old_dep, new_dep, is_id, path_to_lib):
cmd = ['-id', new_dep] if is_id else ['-change', old_dep, new_dep]
subprocess.check_call(['install_name_tool'] + cmd + [path_to_lib])
@flush
def fix_dependencies_in_lib(self, path_to_lib):
self.to_strip.append(path_to_lib)
old_mode = flipwritable(path_to_lib)
for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
ndep = self.FID + '/' + bname
self.change_dep(dep, ndep, is_id, path_to_lib)
ldeps = list(self.get_local_dependencies(path_to_lib))
if ldeps:
print('\nFailed to fix dependencies in', path_to_lib)
print('Remaining local dependencies:', ldeps)
raise SystemExit(1)
if old_mode is not None:
flipwritable(path_to_lib, old_mode)
@flush
def add_python_framework(self):
print('\nAdding Python framework')
src = join(PREFIX + '/python', 'Python.framework')
x = join(self.frameworks_dir, 'Python.framework')
curr = os.path.realpath(join(src, 'Versions', 'Current'))
currd = join(x, 'Versions', basename(curr))
rd = join(currd, 'Resources')
os.makedirs(rd)
shutil.copy2(join(curr, 'Resources', 'Info.plist'), rd)
shutil.copy2(join(curr, 'Python'), currd)
self.set_id(
join(currd, 'Python'),
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr))
self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty'))
# The following is needed for codesign
with current_dir(x):
os.symlink(basename(curr), 'Versions/Current')
for y in ('Python', 'Resources'):
os.symlink('Versions/Current/%s' % y, y)
@flush
def install_dylib(self, path, set_id=True):
shutil.copy2(path, self.frameworks_dir)
if set_id:
self.set_id(
join(self.frameworks_dir, basename(path)),
self.FID + '/' + basename(path))
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
@flush
def add_misc_libraries(self):
for x in (
'sqlite3.0',
'z.1',
'harfbuzz.0',
'png16.16',
'crypto.1.0.0',
'ssl.1.0.0',
):
print('\nAdding', x)
x = 'lib%s.dylib' % x
src = join(PREFIX, 'lib', x)
shutil.copy2(src, self.frameworks_dir)
dest = join(self.frameworks_dir, x)
self.set_id(dest, self.FID + '/' + x)
self.fix_dependencies_in_lib(dest)
base = join(self.frameworks_dir, 'kitty')
for lib in walk(base):
if lib.endswith('.so'):
self.set_id(lib, self.FID + '/' + os.path.relpath(lib, self.frameworks_dir))
self.fix_dependencies_in_lib(lib)
@flush
def add_package_dir(self, x, dest=None):
def ignore(root, files):
ans = []
for y in files:
ext = os.path.splitext(y)[1]
if ext not in ('', '.py', '.so') or \
(not ext and not os.path.isdir(join(root, y))):
ans.append(y)
return ans
if dest is None:
dest = self.site_packages
dest = join(dest, basename(x))
shutil.copytree(x, dest, symlinks=True, ignore=ignore)
self.postprocess_package(x, dest)
for f in walk(dest):
if f.endswith('.so'):
self.fix_dependencies_in_lib(f)
@flush
def fix_dependencies_in_kitty(self):
for f in walk(join(self.resources_dir, 'kitty')):
if f.endswith('.so'):
self.fix_dependencies_in_lib(f)
@flush
def postprocess_package(self, src_path, dest_path):
pass
@flush
def add_stdlib(self):
print('\nAdding python stdlib')
src = PREFIX + '/python/Python.framework/Versions/Current/lib/python' + self.py_ver
dest = self.python_stdlib
if not os.path.exists(dest):
os.makedirs(dest)
for x in os.listdir(src):
if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk',
'lib-old', 'idlelib', 'plat-mac', 'plat-darwin',
'site.py', 'distutils', 'turtledemo', 'tkinter'):
continue
x = join(src, x)
if os.path.isdir(x):
self.add_package_dir(x, dest)
elif os.path.splitext(x)[1] in ('.so', '.py'):
shutil.copy2(x, dest)
dest2 = join(dest, basename(x))
if dest2.endswith('.so'):
self.fix_dependencies_in_lib(dest2)
@flush
def add_site_packages(self):
print('\nAdding site-packages')
os.makedirs(self.site_packages)
sys_path = json.loads(subprocess.check_output([
PYTHON, '-c', 'import sys, json; json.dump(sys.path, sys.stdout)']))
paths = reversed(tuple(map(abspath, [x for x in sys_path if x.startswith('/') and not x.startswith('/Library/')])))
upaths = []
for x in paths:
if x not in upaths and (x.endswith('.egg') or x.endswith('/site-packages')):
upaths.append(x)
for x in upaths:
print('\t', x)
tdir = None
try:
if not os.path.isdir(x):
zf = zipfile.ZipFile(x)
tdir = tempfile.mkdtemp()
zf.extractall(tdir)
x = tdir
self.add_modules_from_dir(x)
self.add_packages_from_dir(x)
finally:
if tdir is not None:
shutil.rmtree(tdir)
self.remove_bytecode(self.site_packages)
@flush
def add_modules_from_dir(self, src):
for x in glob.glob(join(src, '*.py')) + glob.glob(join(src, '*.so')):
shutil.copy2(x, self.site_packages)
if x.endswith('.so'):
self.fix_dependencies_in_lib(x)
@flush
def add_packages_from_dir(self, src):
for x in os.listdir(src):
x = join(src, x)
if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')):
if self.filter_package(basename(x)):
continue
self.add_package_dir(x)
@flush
def filter_package(self, name):
return name in ('Cython', 'modulegraph', 'macholib', 'py2app',
'bdist_mpkg', 'altgraph')
@flush
def remove_bytecode(self, dest):
for x in os.walk(dest):
root = x[0]
for f in x[-1]:
if os.path.splitext(f) == '.pyc':
os.remove(join(root, f))
@flush
def compile_py_modules(self):
print('\nCompiling Python modules')
self.remove_bytecode(join(self.resources_dir, 'Python'))
py_compile(join(self.resources_dir, 'Python'))
self.remove_bytecode(join(self.resources_dir, 'kitty'))
py_compile(join(self.resources_dir, 'kitty'))
@flush
def makedmg(self, d, volname, internet_enable=True, format='ULFO'):
''' Copy a directory d into a dmg named volname '''
print('\nMaking dmg...')
sys.stdout.flush()
destdir = os.path.join(SW, 'dist')
try:
shutil.rmtree(destdir)
except FileNotFoundError:
pass
os.mkdir(destdir)
dmg = os.path.join(destdir, volname + '.dmg')
if os.path.exists(dmg):
os.unlink(dmg)
tdir = tempfile.mkdtemp()
appdir = os.path.join(tdir, os.path.basename(d))
shutil.copytree(d, appdir, symlinks=True)
if self.sign_installers:
with timeit() as times:
sign_app(appdir, self.notarize)
print('Signing completed in %d minutes %d seconds' % tuple(times))
os.symlink('/Applications', os.path.join(tdir, 'Applications'))
size_in_mb = int(
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
.split()[0]) / 1024.
cmd = [
'/usr/bin/hdiutil', 'create', '-srcfolder', tdir, '-volname',
volname, '-format', format
]
if 190 < size_in_mb < 250:
# We need -size 255m because of a bug in hdiutil. When the size of
# srcfolder is close to 200MB hdiutil fails with
# diskimages-helper: resize request is above maximum size allowed.
cmd += ['-size', '255m']
print('\nCreating dmg...')
with timeit() as times:
subprocess.check_call(cmd + [dmg])
if internet_enable:
subprocess.check_call(
['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg])
print('dmg created in %d minutes and %d seconds' % tuple(times))
shutil.rmtree(tdir)
size = os.stat(dmg).st_size / (1024 * 1024.)
print('\nInstaller size: %.2fMB\n' % size)
return dmg
def main():
args = globals()['args']
ext_dir = globals()['ext_dir']
if not args.skip_tests:
run_tests = iv['run_tests']
run_tests(None, os.path.join(ext_dir, 'src'))
Freeze(
os.path.join(ext_dir, kitty_constants['appname'] + '.app'),
dont_strip=args.dont_strip,
sign_installers=args.sign_installers,
notarize=args.notarize
)
if __name__ == '__main__':
main()

1
bypy/rsync.conf Normal file
View File

@@ -0,0 +1 @@
to_vm_excludes '/build /dist /kitty/launcher/kitty /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'

256
bypy/sources.json Normal file
View File

@@ -0,0 +1,256 @@
[
{
"name": "zlib",
"unix": {
"filename": "zlib-1.2.11.tar.xz",
"hash": "sha256:4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066",
"urls": ["https://zlib.net/{filename}"]
}
},
{
"name": "bzip2",
"os": "linux",
"unix": {
"filename": "bzip2-1.0.8.tar.gz",
"hash": "sha256:ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269",
"urls": ["https://www.sourceware.org/pub/bzip2/bzip2-latest.tar.gz"]
}
},
{
"name": "pkg-config",
"os": "macos",
"unix": {
"filename": "pkg-config-0.29.2.tar.gz",
"hash": "sha256:6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591",
"urls": ["https://pkg-config.freedesktop.org/releases/{filename}"]
}
},
{
"name": "expat",
"unix": {
"filename": "expat-2.2.6.tar.bz2",
"hash": "sha256:17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2",
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_2_6/{filename}"]
}
},
{
"name": "xkbcommon",
"os": "linux",
"unix": {
"filename": "libxkbcommon-0.10.0.tar.xz",
"hash": "sha256:57c3630cdc38fb4734cd57fa349e92244f5ae3862813e533cedbd86721a0b6f2",
"urls": ["http://xkbcommon.org/download/{filename}"]
}
},
{
"name": "sqlite",
"unix": {
"filename": "sqlite-autoconf-3310100.tar.gz",
"hash": "sha1:0c30f5b22152a8166aa3bebb0f4bc1f3e9cc508b",
"urls": ["https://www.sqlite.org/2020/{filename}"]
}
},
{
"name": "libffi",
"unix": {
"filename": "libffi-3.2.1.tar.gz",
"hash": "sha1:280c265b789e041c02e5c97815793dfc283fb1e6",
"urls": ["ftp://sourceware.org/pub/libffi/{filename}"]
}
},
{
"name": "openssl",
"unix": {
"filename": "openssl-1.0.2u.tar.gz",
"hash": "sha256:ecd0c6ffb493dd06707d38b14bb4d8c2288bb7033735606569d8f90f89669d16",
"urls": ["https://www.openssl.org/source/{filename}"]
}
},
{
"name": "ncurses",
"os": "linux",
"unix": {
"filename": "ncurses-6.2.tar.gz",
"hash": "sha256:30306e0c76e0f9f1f0de987cf1c82a5c21e1ce6568b9227f7da5b71cbea86c9d",
"urls": ["https://invisible-mirror.net/archives/ncurses/{filename}"]
}
},
{
"name": "readline",
"os": "linux",
"unix": {
"filename": "readline-8.0.tar.gz",
"hash": "sha256:e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461",
"urls": ["http://ftp.gnu.org/gnu/readline/{filename}"]
}
},
{
"name": "xz",
"os": "macos,linux",
"unix": {
"filename": "xz-5.2.5.tar.gz",
"hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10",
"urls": ["https://tukaani.org/xz/{filename}"]
}
},
{
"name": "python",
"unix": {
"filename": "Python-3.8.3.tar.xz",
"hash": "sha256:dfab5ec723c218082fe3d5d7ae17ecbdebffa9a1aea4d64aa3a2ecdd2e795864",
"urls": ["https://www.python.org/ftp/python/3.8.3/{filename}"]
}
},
{
"name": "setuptools",
"unix": {
"filename": "setuptools-41.0.1.zip",
"hash": "sha256:a222d126f5471598053c9a77f4b5d4f26eaa1f150ad6e01dcf1a42e185d05613",
"urls": ["pypi"]
}
},
{
"name": "pygments",
"unix": {
"filename": "Pygments-2.6.1.tar.gz",
"hash": "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
"urls": ["pypi"]
}
},
{
"name": "cmake",
"os": "macos",
"unix": {
"filename": "cmake-3.6.0.tar.gz",
"hash": "sha1:6b0d2d7d93c2e74f8890c4b8b824deb3b36d24b6",
"urls": ["https://cmake.org/files/v3.6/{filename}"]
}
},
{
"name": "libpng",
"unix": {
"filename": "libpng-1.6.37.tar.xz",
"hash": "sha256:505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca",
"urls": ["http://downloads.sourceforge.net/sourceforge/libpng/{filename}"]
}
},
{
"name": "graphite",
"os": "linux",
"unix": {
"filename": "graphite2-1.3.5.tgz",
"hash": "md5:5b8d22a8bbf031838e31432868c0109c",
"urls": ["http://downloads.sourceforge.net/silgraphite/{filename}"]
}
},
{
"name": "pcre",
"os": "linux",
"unix": {
"filename": "pcre-8.42.tar.bz2",
"hash": "md5:085b6aa253e0f91cae70b3cdbe8c1ac2",
"urls": ["https://ftp.pcre.org/pub/pcre/{filename}"]
}
},
{
"name": "iconv",
"os": "linux",
"unix": {
"filename": "libiconv-1.16.tar.gz",
"hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
"urls": ["http://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
}
},
{
"name": "glib",
"os": "linux",
"unix": {
"filename": "glib-2.57.1.tar.xz",
"hash": "sha256:d029e7c4536835f1f103472f7510332c28d58b9b7d6cd0e9f45c2653e670d9b4",
"urls": ["http://ftp.gnome.org/pub/GNOME/sources/glib/2.57/{filename}"]
}
},
{
"name": "freetype",
"os": "linux",
"unix": {
"filename": "freetype-2.9.1.tar.bz2",
"hash": "md5:60ef7d8160cd4bf8cb118ee9d65367ca",
"urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"]
}
},
{
"name": "fontconfig",
"os": "linux",
"unix": {
"filename": "fontconfig-2.12.0.tar.bz2",
"hash": "sha256:b433e4efff1f68fdd8aac221ed1df3ff1e580ffedbada020a703fe64017d8224",
"urls": ["http://www.fontconfig.org/release/{filename}"]
}
},
{
"name": "harfbuzz",
"unix": {
"filename": "harfbuzz-1.7.2.tar.bz2",
"hash": "sha256:a790585e35c1a87f0dcc23580c84b7cc2324e6f67a2946178d278c2a36c790cb",
"urls": ["https://www.freedesktop.org/software/harfbuzz/release/{filename}"]
}
},
{
"name": "libxml2",
"os": "linux",
"unix": {
"filename": "libxml2-2.9.4.tar.gz",
"hash": "sha1:958ae70baf186263a4bd801a81dd5d682aedd1db",
"urls": ["ftp://xmlsoft.org/libxml2/{filename}"]
}
},
{
"name": "wayland",
"os": "linux",
"unix": {
"filename": "wayland-1.18.0.tar.xz",
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
}
},
{
"name": "wayland-protocols",
"os": "linux",
"unix": {
"filename": "wayland-protocols-1.20.tar.xz",
"hash": "sha256:9782b7a1a863d82d7c92478497d13c758f52e7da4f197aa16443f73de77e4de7",
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
}
}
]

View File

@@ -46,7 +46,7 @@ particular desktop, but it should work for most major desktop environments.
# Place the kitty.desktop file somewhere it can be found by the OS
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
# Update the path to the kitty icon in the kitty.desktop file
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty.desktop

View File

@@ -4,6 +4,90 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.18.1 [2020-06-23]
--------------------
- macOS: Fix for diff kitten not working with python 3.8 (:iss:`2780`)
0.18.0 [2020-06-20]
--------------------
- Allow multiple overlay windows per normal window
- Add an option :opt:`confirm_os_window_close` to ask for confirmation
when closing an OS window with multiple kitty windows.
- Tall and Fat layouts: Add a ``mirrored`` option to put the full size window
on the opposite edge of the screen (:iss:`2654`)
- Tall and Fat layouts: Add mappable actions to increase or decrease the number
of full size windows (:iss:`2688`)
- Allow sending arbitrary signals to the current foreground process in a window
using either a mapping in kitty.conf or via remote control (:iss:`2778`)
- Allow sending the back and forward mouse buttons to terminal applications
(:pull:`2742`)
- **Backwards incompatibility**: The numbers used to encode mouse buttons
for the ``send_mouse_event`` function that can be used in kittens have
been changed (see :ref:`send_mouse_event`).
- Add a new mappable ``quit`` action to quit kitty completely.
- Fix marks using different colors with regexes using only a single color
(:pull:`2663`)
- Linux: Workaround for broken Nvidia drivers for old cards (:iss:`456`)
- Wayland: Fix kitty being killed on some Wayland compositors if a hidden window
has a lot of output (:iss:`2329`)
- BSD: Fix controlling terminal not being established (:pull:`2686`)
- Add support for the CSI REP escape code (:pull:`2702`)
- Wayland: Fix mouse cursor rendering on HiDPI screens (:pull:`2709`)
- X11: Recompile keymaps on XkbNewKeyboardNotify events (:iss:`2726`)
- X11: Reduce startup time by ~25% by only querying GLX for framebuffer
configurations once (:iss:`2754`)
- macOS: Notarize the kitty application bundle (:iss:`2040`)
- Fix the kitty shell launched via a mapping needlessly requiring
:opt:`allow_remote_control` to be turned on.
0.17.4 [2020-05-09]
--------------------
- Allow showing the name of the current layout and the number of windows
in tab titles (:iss:`2634`)
- macOS: Fix a regression in the previous release that caused ligatures to be
not be centered horizontally (:iss:`2591`)
- By default, double clicking no longer considers the : as part of words, see
:opt:`select_by_word_characters` (:iss:`2602`)
- Fix a regression that caused clicking in the padding/margins of windows in
the stack layout to switch the window to the first window (:iss:`2604`)
- macOS: Fix a regression that broke drag and drop (:iss:`2605`)
- Report modifier key state when sending wheel events to the terminal program
- Fix kitty @ send-text not working with text larger than 1024 bytes when using
:option:`kitty --listen-on` (:iss:`2607`)
- Wayland: Fix OS window title not updating for hidden windows (:iss:`2629`)
- Fix :opt:`background_tint` making the window semi-transparent (:iss:`2618`)
0.17.3 [2020-04-23]
--------------------

View File

@@ -275,7 +275,7 @@ if you specify a program-to-run you can use the special placeholder
p('\n\n' + as_rst(
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
from kitty.rc.base import cli_params_for
for cmd_name in all_command_names():
for cmd_name in sorted(all_command_names()):
func = command_for_name(cmd_name)
p(f'.. _at_{func.name}:\n')
p('kitty @', func.name + '\n' + '-' * 120)
@@ -326,7 +326,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
p(' ', desc), p()
p(), p()
with open(f'generated/rc.rst', 'w') as f:
with open('generated/rc.rst', 'w') as f:
p = partial(print, file=f)
for name in sorted(all_command_names()):
cmd = command_for_name(name)

View File

@@ -169,7 +169,7 @@ only monospace fonts, since every cell in the grid has to be the same size. If
your font is not listed in ``kitty list-fonts`` it means that it is not
monospace. On Linux you can list all monospace fonts with::
fc-list : family spacing | grep -e spacing=100 -e spacing=90
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
Note that the spacing property is calculated by fontconfig based on actual
glyph widths in the font. If for some reason fontconfig concludes your favorite

View File

@@ -30,10 +30,11 @@ alpha-blending and text over graphics.
Some programs that use the kitty graphics protocol:
* `termpdf <https://github.com/dsanson/termpdf>`_ - a terminal PDF/DJVU/CBR viewer
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
image previews, see this `PR <https://github.com/ranger/ranger/pull/1077>`_
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
information tool
@@ -262,8 +263,8 @@ received. Finally, terminals must not display anything, until the entire sequenc
received and validated.
Detecting available transmission mediums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Querying support and available transmission mediums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory
with the terminal emulator, it can send an id with the control data, using the ``i`` key
@@ -289,6 +290,27 @@ use the *query action*, set ``a=q``. Then the terminal emulator will try to load
the image and respond with either OK or an error, as above, but it will not
replace an existing image with the same id, nor will it store the image.
While as of May 2020, kitty is the only terminal emulator to support this
graphics protocol, we intend that any terminal emulator that wishes to support
it can. To check if a terminal emulator supports the graphics protocol the best way
is to send the above *query action* followed by a request for the
`primary device attributes <https://vt100.net/docs/vt510-rm/DA1.html>`. If you
get back an answer for the device attributes without getting back an answer for
the *query action* the terminal emulator does not support the graphics
protocol.
This means that terminal emulators that support the graphics protocol, **must**
reply to *query actions* immediately without processing other input. Most
terminal emulators handle input in a FIFO manner, anyway.
So for example, you could send::
<ESC>_Gi=31,s=1,v=1,a=q,t=d,f=24;<NUL><NUL><NUL><ESC>\<ESC>[c
If you get back a response to the graphics query, the terminal emulator supports
the protocol, if you get back a response to the device attributes query without
a response to the graphics query, it does not.
Display images on screen
-----------------------------
@@ -368,10 +390,10 @@ Value of ``d`` Meaning
Some examples::
<ESC>_Ga=d<ESC>\ # delete all visible images
<ESC>_Ga=d,i=10<ESC>\ # delete the image with id=10
<ESC>_Ga=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
<ESC>_Ga=P,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4)
<ESC>_Ga=d<ESC>\ # delete all visible images
<ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
<ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
<ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4), without freeing data
Image persistence and storage quotas
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -360,11 +360,12 @@ Mouse features
* You can triple click to select a line and then drag to select more lines.
* You can right click to extend a previous selection.
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
columns.
columns (see also :opt:`rectangle_select_modifiers`).
* Selecting text automatically copies it to the primary clipboard (on
platforms with a primary clipboard).
* You can select text with kitty even when a terminal program has grabbed
the mouse by holding down the :kbd:`shift` key.
the mouse by holding down the :kbd:`shift` key (see also
:opt:`terminal_select_modifiers`).
Font control
@@ -438,6 +439,15 @@ The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
.. _completion:
Cool integrations for kitty with other CLI tools
--------------------------------------------------
kitty provides extremely powerful interfaces such as :doc:`remote-control` and
:doc:`kittens/custom` and :doc:`kittens/icat`
that allow it to be integrated with other tools seamlessly. For a list of such
user created integrations, see: :doc:`integrations`.
Completion for kitty
---------------------------------

108
docs/integrations.rst Normal file
View File

@@ -0,0 +1,108 @@
Integrations with other tools
================================
kitty provides extremely powerful interfaces such as :doc:`remote-control` and
:doc:`kittens/custom` and :doc:`kittens/icat`
that allow it to be integrated with other tools seamlessly.
Image and document viewers
----------------------------
Powered by kitty's :doc:`graphics-protocol` there exist many tools for viewing
images and other types of documents directly in your terminal, even over SSH.
`termpdf.py <https://github.com/dsanson/termpdf.py>`_
a terminal PDF/DJVU/CBR viewer
`mdcat <https://github.com/lunaryorn/mdcat>`_
Display markdown files nicely formatted with images in the terminal
`ranger <https://github.com/ranger/ranger>`_
a terminal file manager, with previews of file contents powered by kitty's graphics protocol.
`nnn <https://github.com/jarun/nnn/>`_
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
`koneko <https://github.com/twenty5151/koneko>`_
browse images from the pixiv artist community directly in kitty
System and data visualisation tools
---------------------------------------
`neofetch <https://github.com/dylanaraps/neofetch>`_
A command line system information tool that shows images using kitty's graphics protocol
`matplotlib <https://github.com/jktr/matplotlib-backend-kitty>`_
show matplotlib plots directly in kitty
`KittyTerminalImages.jl <https://github.com/simonschoelly/KittyTerminalImages.jl>`_
show images from Julia directly in kitty
`gnuplot <http://www.gnuplot.info/>`_
a graphing and data visualization tool that can be made to display its
output in kitty with the following bash snippet::
function iplot {
cat <<EOF | gnuplot
set terminal pngcairo enhanced font 'Fira Sans,10'
set autoscale
set samples 1000
set output '|kitty +kitten icat --stdin yes'
set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb"#fdf6e3" behind
plot $@
set output '/dev/null'
EOF
}
Add this to bashrc and then to plot a function, simply do::
iplot 'sin(x*3)*exp(x*.2)'
Editor integration
-----------------------
kitty can be integrated into many different terminal editors to add features
such a split windows, previews, REPLs etc.
`kakoune <https://kakoune.org/>`_
integrates with kitty to use native kitty windows for its windows/panels and REPLs.
`vim-slime <https://github.com/jpalardy/vim-slime#kitty>`_
uses kitty remote control for a Lisp REPL.
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
`vim-test <https://github.com/vim-test/vim-test>`_
allows easily running tests in a terminal window
Scrollback manipulation
-------------------------
`kitty-search <https://github.com/trygveaa/kitty-kitten-search>`_
Live incremental search of the scrollback buffer.
`kitty-grab <https://github.com/yurikhan/kitty_grab>`_
keyboard based text selection for the kitty scrollback buffer.
Miscellaneous
------------------
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_
use keys to either control tabs or pass them onto running applications if
no tabs are present
`kitty-smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
use keys to either scroll or pass them onto running applications if
no scrollback buffer is present
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
reload key bindings from :file:`kitty.conf` without needing to restart
kitty

View File

@@ -132,6 +132,8 @@ layout, by simply adding the line::
To the ``handle_result()`` function, above.
.. _send_mouse_event:
Sending mouse events
--------------------
@@ -142,12 +144,13 @@ those using::
send_mouse_event(screen, x, y, button, action, mods)
``screen`` is the ``screen`` attribute of the window you want to send the event
to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is
``GLFW_MOUSE_BUTTON_{button}`` where ``{button}`` is one of ``LEFT``,
``RIGHT``, ``MIDDLE`` or a digit from ``1`` to ``8``. ``action`` is one of
``PRESS``, ``RELEASE``, ``DRAG`` or ``MOVE``. ``mods`` is a bitmask of
``GLFW_MOD_{mod}`` where ``{mod}`` is one of ``SHIFT``, ``CONTROL`` or ``ALT``.
All the mentioned constants are imported from ``kitty.fast_data_types``.
to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is a number using
the same numbering as X11 (left: ``1``, middle: ``2``, right: ``3``, scroll up:
``4``, scroll down: ``5``, scroll left: ``6``, scroll right: ``7``, back:
``8``, forward: ``9``). ``action`` is one of ``PRESS``, ``RELEASE``, ``DRAG``
or ``MOVE``. ``mods`` is a bitmask of ``GLFW_MOD_{mod}`` where ``{mod}`` is one
of ``SHIFT``, ``CONTROL`` or ``ALT``. All the mentioned constants are imported
from ``kitty.fast_data_types``.
For example, to send a left click at position x: 2, y: 3 to the active window::

View File

@@ -23,7 +23,7 @@ like a path or filename followed by a colon and a line number and open
the file in vim at the specified line number. The patterns and editor
to be used can be modified using options passed to the kitten. For example::
map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} +{path}
map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} {path}
will open the selected file in a new tab inside neovim when you press
:kbd:`ctrl+g`.

View File

@@ -21,6 +21,12 @@ Then you can simply use ``icat image.png`` to view images.
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
work.
.. note::
kitty's image display protocol may not work when used within a terminal
multiplexer such as ``screen`` or ``tmux``, depending on whether the
multiplexer has added support for it or not.
.. program:: kitty +kitten icat

View File

@@ -46,10 +46,12 @@ Displays one (or optionally more) full height windows on the left half of the
screen. Remaining windows are tiled vertically on the right half of the screen.
There are options to control how the screen is split horizontally ``bias``
(an integer between ``10`` and ``90``) and options to control how many
full-height windows there are ``full_size`` (a positive integer). The syntax
full-height windows there are ``full_size`` (a positive integer). The
``mirrored`` option when set to ``true`` will cause the short windows to be
on the left side of the screen instead of the right. The syntax
for the options is shown below::
enabled_layouts tall:bias=50;full_size=1
enabled_layouts tall:bias=50;full_size=1;mirrored=false
┌──────────────┬───────────────┐
│ │ │
@@ -65,6 +67,13 @@ for the options is shown below::
│ │ │
└──────────────┴───────────────┘
In addition, you can map keys to increase or decrease the number of full size
windows, for example::
map ctrl+[ layout_action decrease_num_full_size_windows
map ctrl+] layout_action increase_num_full_size_windows
The Fat Layout
----------------
@@ -72,10 +81,12 @@ Displays one (or optionally more) full width windows on the top half of the
screen. Remaining windows are tiled horizontally on the bottom half of the screen.
There are options to control how the screen is split vertically ``bias``
(an integer between ``10`` and ``90``) and options to control how many
full-height windows there are ``full_size`` (a positive integer). The syntax
for the options is shown below::
full-height windows there are ``full_size`` (a positive integer). The
``mirrored`` option when set to ``true`` will cause the narrow windows to be
on the top of the screen instead of the bottom. The syntax for the options is
shown below::
enabled_layouts fat:bias=50;full_size=1
enabled_layouts fat:bias=50;full_size=1;mirrored=false
┌──────────────────────────────┐
│ │
@@ -241,7 +252,7 @@ This will have ``2`` instead of a single tall window, that occupy ``70%``
instead of ``50%`` of available width. ``bias`` can be any number between 10
and 90.
Writing a new layout only requires about a hundred lines of code, so if there
is some layout you want, take a look at `layout.py
<https://github.com/kovidgoyal/kitty/blob/master/kitty/layout.py>`_ and submit
a pull request!
Writing a new layout only requires about two hundred lines of code, so if there
is some layout you want, take a look at one of the existing layouts in the
`layout <https://github.com/kovidgoyal/kitty/tree/master/kitty/layout>`_
package and submit a pull request!

View File

@@ -287,7 +287,7 @@ def category_test(
p('\tswitch(code) {')
for spec in get_ranges(list(chars)):
write_case(spec, p)
p(f'\t\t\treturn true;')
p('\t\t\treturn true;')
p('\t} // }}}\n')
p('\treturn false;\n}\n')
@@ -421,8 +421,8 @@ def gen_names() -> None:
p('}; // }}}\n')
# The trie
p(f'typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
all_trie_nodes: List['TrieNode'] = []
p('typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
all_trie_nodes: List['TrieNode'] = [] # noqa
class TrieNode:

18
glfw/backend_utils.c vendored
View File

@@ -332,3 +332,21 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ
}
return read_ok;
}
// Duplicate a UTF-8 encoded string
// but cut it so that it has at most max_length bytes plus the null byte.
// This does not take combining characters into account.
GLFWAPI char* utf_8_strndup(const char* source, size_t max_length) {
if (!source) return NULL;
size_t length = strnlen(source, max_length);
if (length >= max_length) {
for (length = max_length; length > 0; length--) {
if ((source[length] & 0xC0) != 0x80) break;
}
}
char* result = malloc(length + 1);
memcpy(result, source, length);
result[length] = 0;
return result;
}

View File

@@ -95,3 +95,4 @@ unsigned dispatchTimers(EventLoopData *eld);
void finalizePollData(EventLoopData *eld);
bool initPollData(EventLoopData *eld, int display_fd);
void wakeupEventLoop(EventLoopData *eld);
char* utf_8_strndup(const char* source, size_t max_length);

View File

@@ -424,11 +424,7 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
(void)sender;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
_glfwInputWindowCloseRequest(window);
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(0);
return NSTerminateCancel;
}

View File

@@ -236,26 +236,65 @@ static bool refreshMonitorScreen(_GLFWmonitor* monitor)
return false;
}
// Returns a fallback refresh rate for when Core Graphics says it is zero
// Returns the display refresh rate queried from the I/O registry
//
static double getFallbackRefreshRate(_GLFWmonitor* monitor)
static double getFallbackRefreshRate(CGDirectDisplayID displayID)
{
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
double refreshRate = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
double refreshRate = 60.0;
if (refreshRate == 0.0)
io_iterator_t it;
io_service_t service;
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
IOServiceMatching("IOFramebuffer"),
&it) != 0)
{
CVDisplayLinkRef link = NULL;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
if (!(time.flags & kCVTimeIsIndefinite))
refreshRate = (int) (time.timeScale / (double) time.timeValue);
CVDisplayLinkRelease(link);
return refreshRate;
}
while ((service = IOIteratorNext(it)) != 0)
{
const CFNumberRef indexRef =
IORegistryEntryCreateCFProperty(service,
CFSTR("IOFramebufferOpenGLIndex"),
kCFAllocatorDefault,
kNilOptions);
if (!indexRef)
continue;
uint32_t index = 0;
CFNumberGetValue(indexRef, kCFNumberIntType, &index);
CFRelease(indexRef);
if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
continue;
const CFNumberRef clockRef =
IORegistryEntryCreateCFProperty(service,
CFSTR("IOFBCurrentPixelClock"),
kCFAllocatorDefault,
kNilOptions);
const CFNumberRef countRef =
IORegistryEntryCreateCFProperty(service,
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 (clock > 0 && count > 0)
refreshRate = clock / (double) count;
break;
}
IOObjectRelease(it);
return refreshRate;
}
@@ -351,11 +390,15 @@ void _glfwPollMonitorsNS(void)
_GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(monitor);
createDisplayLink(monitor->ns.displayID);
free(name);
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
if (CGDisplayModeGetRefreshRate(mode) == 0.0)
monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
CGDisplayModeRelease(mode);
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
}

View File

@@ -103,7 +103,7 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.view)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns

View File

@@ -1133,7 +1133,7 @@ is_ascii_control_char(char x) {
break;
}
_glfwInputScroll(window, deltaX, deltaY, flags);
_glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags]));
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
@@ -1460,6 +1460,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
{
// EGL implementation on macOS use CALayer* EGLNativeWindowType so we
// need to get the layer for EGL window surface creation.
[window->ns.view setWantsLayer:YES];
window->ns.layer = [window->ns.view layer];
if (!_glfwInitEGL())
return false;
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
@@ -1522,7 +1527,8 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char* title)
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
int count UNUSED, const GLFWimage* images UNUSED)
{
// Regular windows do not have icons
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Cocoa: Regular windows do not have icons on macOS");
}
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
@@ -1858,6 +1864,17 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
[window->ns.object setAlphaValue:opacity];
}
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
{
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Cocoa: Raw mouse motion not yet implemented");
}
bool _glfwPlatformRawMouseMotionSupported(void)
{
return false;
}
void
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
_GLFWwindow *w = _glfw.windowListHead;

168
glfw/context.c vendored
View File

@@ -173,174 +173,6 @@ bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
return true;
}
// Chooses the framebuffer config that best matches the desired one
//
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives,
unsigned int count)
{
unsigned int i;
unsigned int missing, leastMissing = UINT_MAX;
unsigned int colorDiff, leastColorDiff = UINT_MAX;
unsigned int extraDiff, leastExtraDiff = UINT_MAX;
const _GLFWfbconfig* current;
const _GLFWfbconfig* closest = NULL;
for (i = 0; i < count; i++)
{
current = alternatives + i;
if (desired->stereo > 0 && current->stereo == 0)
{
// Stereo is a hard constraint
continue;
}
if (desired->doublebuffer != current->doublebuffer)
{
// Double buffering is a hard constraint
continue;
}
// Count number of missing buffers
{
missing = 0;
if (desired->alphaBits > 0 && current->alphaBits == 0)
missing++;
if (desired->depthBits > 0 && current->depthBits == 0)
missing++;
if (desired->stencilBits > 0 && current->stencilBits == 0)
missing++;
if (desired->auxBuffers > 0 &&
current->auxBuffers < desired->auxBuffers)
{
missing += desired->auxBuffers - current->auxBuffers;
}
if (desired->samples > 0 && current->samples == 0)
{
// Technically, several multisampling buffers could be
// involved, but that's a lower level implementation detail and
// not important to us here, so we count them as one
missing++;
}
if (desired->transparent != current->transparent)
missing++;
}
// These polynomials make many small channel size differences matter
// less than one large channel size difference
// Calculate color channel size difference value
{
colorDiff = 0;
if (desired->redBits != GLFW_DONT_CARE)
{
colorDiff += (desired->redBits - current->redBits) *
(desired->redBits - current->redBits);
}
if (desired->greenBits != GLFW_DONT_CARE)
{
colorDiff += (desired->greenBits - current->greenBits) *
(desired->greenBits - current->greenBits);
}
if (desired->blueBits != GLFW_DONT_CARE)
{
colorDiff += (desired->blueBits - current->blueBits) *
(desired->blueBits - current->blueBits);
}
}
// Calculate non-color channel size difference value
{
extraDiff = 0;
if (desired->alphaBits != GLFW_DONT_CARE)
{
extraDiff += (desired->alphaBits - current->alphaBits) *
(desired->alphaBits - current->alphaBits);
}
if (desired->depthBits != GLFW_DONT_CARE)
{
extraDiff += (desired->depthBits - current->depthBits) *
(desired->depthBits - current->depthBits);
}
if (desired->stencilBits != GLFW_DONT_CARE)
{
extraDiff += (desired->stencilBits - current->stencilBits) *
(desired->stencilBits - current->stencilBits);
}
if (desired->accumRedBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumRedBits - current->accumRedBits) *
(desired->accumRedBits - current->accumRedBits);
}
if (desired->accumGreenBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumGreenBits - current->accumGreenBits) *
(desired->accumGreenBits - current->accumGreenBits);
}
if (desired->accumBlueBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumBlueBits - current->accumBlueBits) *
(desired->accumBlueBits - current->accumBlueBits);
}
if (desired->accumAlphaBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumAlphaBits - current->accumAlphaBits) *
(desired->accumAlphaBits - current->accumAlphaBits);
}
if (desired->samples != GLFW_DONT_CARE)
{
extraDiff += (desired->samples - current->samples) *
(desired->samples - current->samples);
}
if (desired->sRGB && !current->sRGB)
extraDiff++;
}
// Figure out if the current one is better than the best one found so far
// Least number of missing buffers is the most important heuristic,
// then color buffer size match and lastly size match for other buffers
if (missing < leastMissing)
closest = current;
else if (missing == leastMissing)
{
if ((colorDiff < leastColorDiff) ||
(colorDiff == leastColorDiff && extraDiff < leastExtraDiff))
{
closest = current;
}
}
if (current == closest)
{
leastMissing = missing;
leastColorDiff = colorDiff;
leastExtraDiff = extraDiff;
}
}
return closest;
}
// Retrieves the attributes of the current context
//
bool _glfwRefreshContextAttribs(_GLFWwindow* window,

114
glfw/egl_context.c vendored
View File

@@ -76,6 +76,7 @@ static const char* getEGLErrorString(EGLint error)
}
}
#ifdef _GLFW_X11
// Returns the specified attribute of the specified EGLConfig
//
static int getEGLConfigAttrib(EGLConfig config, int attrib)
@@ -84,6 +85,7 @@ static int getEGLConfigAttrib(EGLConfig config, int attrib)
eglGetConfigAttrib(_glfw.egl.display, config, attrib, &value);
return value;
}
#endif
// Return the EGLConfig most closely matching the specified hints
//
@@ -91,39 +93,44 @@ static bool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* desired,
EGLConfig* result)
{
EGLConfig* nativeConfigs;
_GLFWfbconfig* usableConfigs;
const _GLFWfbconfig* closest;
int i, nativeCount, usableCount;
EGLConfig configs[512];
int i = 0, nativeCount = 0, ans_idx = 0;
EGLint attributes[64];
#define ATTR(k, v) { attributes[i++] = k; attributes[i++] = v; }
ATTR(EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER);
ATTR(EGL_SURFACE_TYPE, EGL_WINDOW_BIT);
if (ctxconfig->client == GLFW_OPENGL_ES_API) {
if (ctxconfig->major == 1) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT)
else ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT);
}
else if (ctxconfig->client == GLFW_OPENGL_API) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT);
if (desired->samples > 0) ATTR(EGL_SAMPLES, desired->samples);
if (desired->depthBits > 0) ATTR(EGL_DEPTH_SIZE, desired->depthBits);
if (desired->stencilBits > 0) ATTR(EGL_STENCIL_SIZE, desired->stencilBits);
if (desired->redBits > 0) ATTR(EGL_RED_SIZE, desired->redBits);
if (desired->greenBits > 0) ATTR(EGL_GREEN_SIZE, desired->greenBits);
if (desired->blueBits > 0) ATTR(EGL_BLUE_SIZE, desired->blueBits);
if (desired->alphaBits > 0) ATTR(EGL_ALPHA_SIZE, desired->alphaBits);
ATTR(EGL_NONE, EGL_NONE);
#undef ATTR
if (!eglChooseConfig(_glfw.egl.display, attributes, configs, sizeof(configs)/sizeof(configs[0]), &nativeCount)) {
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: eglChooseConfig failed");
return false;
}
eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount);
if (!nativeCount)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: No EGLConfigs returned");
return false;
}
nativeConfigs = calloc(nativeCount, sizeof(EGLConfig));
eglGetConfigs(_glfw.egl.display, nativeConfigs, nativeCount, &nativeCount);
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
usableCount = 0;
for (i = 0; i < nativeCount; i++)
{
const EGLConfig n = nativeConfigs[i];
_GLFWfbconfig* u = usableConfigs + usableCount;
// Only consider RGB(A) EGLConfigs
if (getEGLConfigAttrib(n, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
continue;
// Only consider window EGLConfigs
if (!(getEGLConfigAttrib(n, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
continue;
#if defined(_GLFW_X11)
{
const EGLConfig n = configs[i];
XVisualInfo vi = {0};
// Only consider EGLConfigs with associated Visuals
@@ -138,55 +145,18 @@ static bool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
XGetVisualInfo(_glfw.x11.display, VisualIDMask, &vi, &count);
if (vis)
{
u->transparent = _glfwIsVisualTransparentX11(vis[0].visual);
bool transparent = _glfwIsVisualTransparentX11(vis[0].visual);
XFree(vis);
if (!transparent) continue;
}
}
}
#endif // _GLFW_X11
if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (ctxconfig->major == 1)
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT))
continue;
}
else
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT))
continue;
}
}
else if (ctxconfig->client == GLFW_OPENGL_API)
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT))
continue;
}
u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE);
u->greenBits = getEGLConfigAttrib(n, EGL_GREEN_SIZE);
u->blueBits = getEGLConfigAttrib(n, EGL_BLUE_SIZE);
u->alphaBits = getEGLConfigAttrib(n, EGL_ALPHA_SIZE);
u->depthBits = getEGLConfigAttrib(n, EGL_DEPTH_SIZE);
u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE);
u->samples = getEGLConfigAttrib(n, EGL_SAMPLES);
u->doublebuffer = true;
u->handle = (uintptr_t) n;
usableCount++;
ans_idx = i;
break;
}
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
if (closest)
*result = (EGLConfig) closest->handle;
free(nativeConfigs);
free(usableConfigs);
return closest != NULL;
*result = configs[ans_idx];
return true;
}
static void makeContextCurrentEGL(_GLFWwindow* window)
@@ -340,6 +310,7 @@ bool _glfwInitEGL(void)
glfw_dlsym(_glfw.egl.GetConfigAttrib, _glfw.egl.handle, "eglGetConfigAttrib");
glfw_dlsym(_glfw.egl.GetConfigs, _glfw.egl.handle, "eglGetConfigs");
glfw_dlsym(_glfw.egl.ChooseConfig, _glfw.egl.handle, "eglChooseConfig");
glfw_dlsym(_glfw.egl.GetDisplay, _glfw.egl.handle, "eglGetDisplay");
glfw_dlsym(_glfw.egl.GetError, _glfw.egl.handle, "eglGetError");
glfw_dlsym(_glfw.egl.Initialize, _glfw.egl.handle, "eglInitialize");
@@ -357,6 +328,7 @@ bool _glfwInitEGL(void)
if (!_glfw.egl.GetConfigAttrib ||
!_glfw.egl.GetConfigs ||
!_glfw.egl.ChooseConfig ||
!_glfw.egl.GetDisplay ||
!_glfw.egl.GetError ||
!_glfw.egl.Initialize ||
@@ -572,18 +544,16 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
}
// Set up attributes for surface creation
index = 0;
if (fbconfig->sRGB)
{
int index = 0;
if (fbconfig->sRGB)
{
if (_glfw.egl.KHR_gl_colorspace)
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
setAttrib(EGL_NONE, EGL_NONE);
if (_glfw.egl.KHR_gl_colorspace)
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display,
config,

3
glfw/egl_context.h vendored
View File

@@ -120,6 +120,7 @@ typedef void* EGLSurface;
// EGL function pointer typedefs
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglChooseConfig)(EGLDisplay,EGLint const*,EGLConfig*,EGLint,EGLint*);
typedef EGLDisplay (EGLAPIENTRY * PFN_eglGetDisplay)(EGLNativeDisplayType);
typedef EGLint (EGLAPIENTRY * PFN_eglGetError)(void);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglInitialize)(EGLDisplay,EGLint*,EGLint*);
@@ -136,6 +137,7 @@ typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
#define eglGetConfigs _glfw.egl.GetConfigs
#define eglChooseConfig _glfw.egl.ChooseConfig
#define eglGetDisplay _glfw.egl.GetDisplay
#define eglGetError _glfw.egl.GetError
#define eglInitialize _glfw.egl.Initialize
@@ -185,6 +187,7 @@ typedef struct _GLFWlibraryEGL
PFN_eglGetConfigAttrib GetConfigAttrib;
PFN_eglGetConfigs GetConfigs;
PFN_eglChooseConfig ChooseConfig;
PFN_eglGetDisplay GetDisplay;
PFN_eglGetError GetError;
PFN_eglInitialize Initialize;

View File

@@ -6,7 +6,7 @@ import json
import os
import re
import sys
from typing import Callable, List, Optional, Tuple
from typing import Callable, Dict, List, Optional, Tuple
_plat = sys.platform.lower()
is_linux = 'linux' in _plat
@@ -19,6 +19,7 @@ class Env:
cppflags: List[str] = []
cflags: List[str] = []
ldflags: List[str] = []
library_paths: Dict[str, List[str]] = {}
ldpaths: List[str] = []
ccver: Tuple[int, int]
@@ -32,13 +33,13 @@ class Env:
def __init__(
self, cc: str = '', cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [],
ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0)
library_paths: Dict[str, List[str]] = {}, ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0)
):
self.cc, self.cppflags, self.cflags, self.ldflags, self.ldpaths = cc, cppflags, cflags, ldflags, [] if ldpaths is None else ldpaths
self.ccver = ccver
self.cc, self.cppflags, self.cflags, self.ldflags, self.library_paths = cc, cppflags, cflags, ldflags, library_paths
self.ldpaths, self.ccver = [] if ldpaths is None else ldpaths, ccver
def copy(self) -> 'Env':
ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), list(self.ldpaths), self.ccver)
ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), dict(self.library_paths), list(self.ldpaths), self.ccver)
ans.all_headers = list(self.all_headers)
ans.sources = list(self.sources)
ans.wayland_packagedir = self.wayland_packagedir
@@ -55,7 +56,7 @@ def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env:
ans = env.copy()
ans.cflags.append('-fpic')
ans.cflags.append('-fPIC')
ans.cppflags.append('-D_GLFW_' + module.upper())
ans.cppflags.append('-D_GLFW_BUILD_DLL')
@@ -95,7 +96,7 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
for p in ans.wayland_protocols:
ans.sources.append(wayland_protocol_file_name(p))
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split():
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
ans.ldpaths.extend(pkg_config(dep, '--libs'))
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE

180
glfw/glfw3.h vendored
View File

@@ -195,7 +195,38 @@ extern "C" {
#endif /*__APPLE__*/
#elif !defined(GLFW_INCLUDE_NONE)
#elif defined(GLFW_INCLUDE_GLU)
#if defined(__APPLE__)
#if defined(GLFW_INCLUDE_GLU)
#include <OpenGL/glu.h>
#endif
#else /*__APPLE__*/
#if defined(GLFW_INCLUDE_GLU)
#include <GL/glu.h>
#endif
#endif /*__APPLE__*/
#elif !defined(GLFW_INCLUDE_NONE) && \
!defined(__gl_h_) && \
!defined(__gles1_gl_h_) && \
!defined(__gles2_gl2_h_) && \
!defined(__gles2_gl3_h_) && \
!defined(__gles2_gl31_h_) && \
!defined(__gles2_gl32_h_) && \
!defined(__gl_glcorearb_h_) && \
!defined(__gl2_h_) /*legacy*/ && \
!defined(__gl3_h_) /*legacy*/ && \
!defined(__gl31_h_) /*legacy*/ && \
!defined(__gl32_h_) /*legacy*/ && \
!defined(__glcorearb_h_) /*legacy*/ && \
!defined(__GL_H__) /*non-standard*/ && \
!defined(__gltypes_h_) /*non-standard*/ && \
!defined(__glee_h_) /*non-standard*/
#if defined(__APPLE__)
@@ -203,9 +234,6 @@ extern "C" {
#define GL_GLEXT_LEGACY
#endif
#include <OpenGL/gl.h>
#if defined(GLFW_INCLUDE_GLU)
#include <OpenGL/glu.h>
#endif
#else /*__APPLE__*/
@@ -213,9 +241,6 @@ extern "C" {
#if defined(GLFW_INCLUDE_GLEXT)
#include <GL/glext.h>
#endif
#if defined(GLFW_INCLUDE_GLU)
#include <GL/glu.h>
#endif
#endif /*__APPLE__*/
@@ -806,6 +831,33 @@ 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.
*
* 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
* this error.
*
* @analysis Platform or platform version limitation. The error can be ignored
* unless the feature is critical to the application.
*
* @par
* A function call that emits this error has no effect other than the error and
* updating any existing out parameters.
*/
#define GLFW_FEATURE_UNAVAILABLE 0x0001000C
/*! @brief The requested feature is not implemented for the platform.
*
* The requested feature has not yet been implemented in GLFW for this platform.
*
* @analysis An incomplete implementation of GLFW for this platform, hopefully
* fixed in a future release. The error can be ignored unless the feature is
* critical to the application.
*
* @par
* A function call that emits this error has no effect other than the error and
* updating any existing out parameters.
*/
#define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D
/*! @} */
/*! @addtogroup window
@@ -1076,6 +1128,7 @@ extern "C" {
#define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_RAW_MOUSE_MOTION 0x00033005
#define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002
@@ -1319,6 +1372,22 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int);
*/
typedef void (* GLFWwindowclosefun)(GLFWwindow*);
/*! @brief The function pointer type for application close callbacks.
*
* This is the function pointer type for application close callbacks. A application
* close callback function has the following signature:
* @code
* void function_name(int flags)
* @endcode
*
* @param[in] flags 0 for a user requested application quit, 1 if a fatal error occurred and application should quit ASAP
*
* @sa @ref glfwSetApplicationCloseCallback
*
* @ingroup window
*/
typedef void (* GLFWapplicationclosefun)(int);
/*! @brief The function pointer type for window content refresh callbacks.
*
* This is the function pointer type for window content refresh callbacks.
@@ -1550,6 +1619,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
* scrolling cancelled and value 6 if scrolling may begin soon.
* @param[int] mods The keyboard modifiers
*
* @sa @ref scrolling
* @sa @ref glfwSetScrollCallback
@@ -1559,7 +1629,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
*
* @ingroup input
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
/*! @brief The function pointer type for key callbacks.
*
@@ -1835,6 +1905,8 @@ GLFWAPI void glfwRemoveTimer(unsigned long long);
* call this function, as it is called by @ref glfwInit before it returns
* failure.
*
* This function has no effect if GLFW is not initialized.
*
* @errors Possible errors include @ref GLFW_PLATFORM_ERROR.
*
* @remark This function may be called before @ref glfwInit.
@@ -2832,21 +2904,21 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
* @param[in] images The images to create the icon from. This is ignored if
* count is zero.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
*
* @pointer_lifetime The specified image data is copied before this function
* returns.
*
* @remark @macos The GLFW window has no icon, as it is not a document
* window, so this function does nothing. The dock icon will be the same as
* @remark @macos Regular windows do not have icons on macOS. This function
* will emit @ref GLFW_FEATURE_UNAVAILABLE. The dock icon will be the same as
* the application bundle's icon. For more information on bundles, see the
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* in the Mac Developer Library.
*
* @remark @wayland There is no existing protocol to change an icon, the
* window will thus inherit the one defined in the application's desktop file.
* This function always emits @ref GLFW_PLATFORM_ERROR.
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread.
*
@@ -2872,12 +2944,12 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* i
* @param[out] ypos Where to store the y-coordinate of the upper-left corner of
* the content area, or `NULL`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
*
* @remark @wayland There is no way for an application to retrieve the global
* position of its windows, this function will always emit @ref
* GLFW_PLATFORM_ERROR.
* position of its windows. This function will emit @ref
* GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread.
*
@@ -2906,12 +2978,12 @@ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos);
* @param[in] xpos The x-coordinate of the upper-left corner of the content area.
* @param[in] ypos The y-coordinate of the upper-left corner of the content area.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
*
* @remark @wayland There is no way for an application to set the global
* position of its windows, this function will always emit @ref
* GLFW_PLATFORM_ERROR.
* position of its windows. This function will emit @ref
* GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread.
*
@@ -3289,8 +3361,11 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* window);
* @param[in] window The window to set the opacity for.
* @param[in] opacity The desired opacity of the specified window.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
*
* @remark @wayland There is no way to set an opacity factor for a window.
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread.
*
@@ -3457,11 +3532,11 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window);
*
* @param[in] window The window to give input focus.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
*
* @remark @wayland It is not possible for an application to bring its windows
* to front, this function will always emit @ref GLFW_PLATFORM_ERROR.
* @remark @wayland It is not possible for an application to set the input
* focus. This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread.
*
@@ -3826,6 +3901,7 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind
* @ingroup window
*/
GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback);
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback);
/*! @brief Sets the refresh callback for the specified window.
*
@@ -4075,11 +4151,13 @@ GLFWAPI void glfwPostEmptyEvent(void);
*
* This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
* @ref GLFW_RAW_MOUSE_MOTION.
*
* @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
* `GLFW_RAW_MOUSE_MOTION`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
@@ -4098,7 +4176,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
*
* This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
* @ref GLFW_RAW_MOUSE_MOTION.
*
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes:
@@ -4130,13 +4209,21 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on,
* and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on.
*
* If the mode is `GLFW_RAW_MOUSE_MOTION`, the value must be either `true`
* to enable raw (unscaled and unaccelerated) mouse motion when the cursor is
* disabled, or `false` to disable it. If raw motion is not supported,
* attempting to set this will emit @ref GLFW_FEATURE_UNAVAILABLE. Call @ref
* glfwRawMouseMotionSupported to check for support.
*
* @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
* `GLFW_RAW_MOUSE_MOTION`.
* @param[in] value The new value of the specified input mode.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR.
* GLFW_INVALID_ENUM, @ref GLFW_PLATFORM_ERROR and @ref
* GLFW_FEATURE_UNAVAILABLE (see above).
*
* @thread_safety This function must only be called from the main thread.
*
@@ -4148,6 +4235,35 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
*/
GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
/*! @brief Returns whether raw mouse motion is supported.
*
* This function returns whether raw mouse motion is supported on the current
* system. This status does not change after GLFW has been initialized so you
* only need to check this once. If you attempt to enable raw motion on
* a system that does not support it, @ref GLFW_PLATFORM_ERROR will be emitted.
*
* Raw mouse motion is closer to the actual motion of the mouse across
* a surface. It is not affected by the scaling and acceleration applied to
* the motion of the desktop cursor. That processing is suitable for a cursor
* while raw motion is better for controlling for example a 3D camera. Because
* of this, raw mouse motion is only provided when the cursor is disabled.
*
* @return `true` if raw mouse motion is supported on the current machine,
* or `false` otherwise.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref raw_mouse_motion
* @sa @ref glfwSetInputMode
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI int glfwRawMouseMotionSupported(void);
/*! @brief Returns the layout-specific name of the specified printable key.
*
* This function returns the name of the specified printable key, encoded as

111
glfw/glx_context.c vendored
View File

@@ -47,99 +47,87 @@ static int getGLXFBConfigAttrib(GLXFBConfig fbconfig, int attrib)
return value;
}
static GLXFBConfig*
choose_fb_config(const _GLFWfbconfig* desired, bool trust_window_bit, int *nelements) {
int attrib_list[64];
int pos = 0;
#define ATTR(x, y) { attrib_list[pos++] = x; attrib_list[pos++] = y; }
ATTR(GLX_DOUBLEBUFFER, desired->doublebuffer ? True : False);
if (desired->stereo > 0) ATTR(GLX_STEREO, desired->stereo ? True : False);
if (desired->auxBuffers > 0) ATTR(GLX_AUX_BUFFERS, desired->auxBuffers);
if (_glfw.glx.ARB_multisample && desired->samples > 0) ATTR(GLX_SAMPLES, desired->samples);
if (desired->depthBits != GLFW_DONT_CARE) ATTR(GLX_DEPTH_SIZE, desired->depthBits);
if (desired->stencilBits != GLFW_DONT_CARE) ATTR(GLX_STENCIL_SIZE, desired->stencilBits);
if (desired->redBits != GLFW_DONT_CARE) ATTR(GLX_RED_SIZE, desired->redBits);
if (desired->greenBits != GLFW_DONT_CARE) ATTR(GLX_GREEN_SIZE, desired->greenBits);
if (desired->blueBits != GLFW_DONT_CARE) ATTR(GLX_BLUE_SIZE, desired->blueBits);
if (desired->alphaBits != GLFW_DONT_CARE) ATTR(GLX_ALPHA_SIZE, desired->alphaBits);
if (desired->accumRedBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_RED_SIZE, desired->accumRedBits);
if (desired->accumGreenBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_GREEN_SIZE, desired->accumGreenBits);
if (desired->accumBlueBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_BLUE_SIZE, desired->accumBlueBits);
if (desired->accumAlphaBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_ALPHA_SIZE, desired->accumAlphaBits);
if (!trust_window_bit) ATTR(GLX_DRAWABLE_TYPE, 0);
ATTR(None, None);
return glXChooseFBConfig(_glfw.x11.display, _glfw.x11.screen, attrib_list, nelements);
#undef ATTR
}
// Return the GLXFBConfig most closely matching the specified hints
//
static bool chooseGLXFBConfig(const _GLFWfbconfig* desired,
GLXFBConfig* result)
{
GLXFBConfig* nativeConfigs;
_GLFWfbconfig* usableConfigs;
const _GLFWfbconfig* closest;
int i, nativeCount, usableCount;
int i, nativeCount, ans_idx = 0;
const char* vendor;
bool trustWindowBit = true;
static _GLFWfbconfig prev_desired = {0};
static GLXFBConfig prev_result = 0;
if (prev_result != 0 && memcmp(&prev_desired, desired, sizeof(_GLFWfbconfig)) == 0) {
*result = prev_result;
return true;
}
prev_desired = *desired;
// HACK: This is a (hopefully temporary) workaround for Chromium
// (VirtualBox GL) not setting the window bit on any GLXFBConfigs
vendor = glXGetClientString(_glfw.x11.display, GLX_VENDOR);
if (vendor && strcmp(vendor, "Chromium") == 0)
trustWindowBit = false;
nativeConfigs =
glXGetFBConfigs(_glfw.x11.display, _glfw.x11.screen, &nativeCount);
nativeConfigs = choose_fb_config(desired, trustWindowBit, &nativeCount);
if (!nativeConfigs || !nativeCount)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "GLX: No GLXFBConfigs returned");
return false;
}
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
usableCount = 0;
for (i = 0; i < nativeCount; i++)
{
const GLXFBConfig n = nativeConfigs[i];
_GLFWfbconfig* u = usableConfigs + usableCount;
// Only consider RGBA GLXFBConfigs
if (!(getGLXFBConfigAttrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT))
continue;
// Only consider window GLXFBConfigs
if (!(getGLXFBConfigAttrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT))
{
if (trustWindowBit)
continue;
}
if (desired->transparent)
{
bool transparency_matches = true, srgb_matches = true;
if (desired->transparent) {
transparency_matches = false;
XVisualInfo* vi = glXGetVisualFromFBConfig(_glfw.x11.display, n);
if (vi)
{
u->transparent = _glfwIsVisualTransparentX11(vi->visual);
XFree(vi);
}
if (vi && _glfwIsVisualTransparentX11(vi->visual)) transparency_matches = true;
}
u->redBits = getGLXFBConfigAttrib(n, GLX_RED_SIZE);
u->greenBits = getGLXFBConfigAttrib(n, GLX_GREEN_SIZE);
u->blueBits = getGLXFBConfigAttrib(n, GLX_BLUE_SIZE);
if (desired->sRGB && (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)) {
srgb_matches = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB) ? true : false;
}
u->alphaBits = getGLXFBConfigAttrib(n, GLX_ALPHA_SIZE);
u->depthBits = getGLXFBConfigAttrib(n, GLX_DEPTH_SIZE);
u->stencilBits = getGLXFBConfigAttrib(n, GLX_STENCIL_SIZE);
if (transparency_matches && srgb_matches) {
ans_idx = i; break;
}
u->accumRedBits = getGLXFBConfigAttrib(n, GLX_ACCUM_RED_SIZE);
u->accumGreenBits = getGLXFBConfigAttrib(n, GLX_ACCUM_GREEN_SIZE);
u->accumBlueBits = getGLXFBConfigAttrib(n, GLX_ACCUM_BLUE_SIZE);
u->accumAlphaBits = getGLXFBConfigAttrib(n, GLX_ACCUM_ALPHA_SIZE);
u->auxBuffers = getGLXFBConfigAttrib(n, GLX_AUX_BUFFERS);
if (getGLXFBConfigAttrib(n, GLX_STEREO))
u->stereo = true;
if (getGLXFBConfigAttrib(n, GLX_DOUBLEBUFFER))
u->doublebuffer = true;
if (_glfw.glx.ARB_multisample)
u->samples = getGLXFBConfigAttrib(n, GLX_SAMPLES);
if (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)
u->sRGB = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB);
u->handle = (uintptr_t) n;
usableCount++;
}
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
if (closest)
*result = (GLXFBConfig) closest->handle;
*result = nativeConfigs[ans_idx];
prev_result = nativeConfigs[ans_idx];
XFree(nativeConfigs);
free(usableConfigs);
return closest != NULL;
return true;
}
// Create the OpenGL context using legacy API
@@ -287,6 +275,7 @@ bool _glfwInitGLX(void)
glfw_dlsym(_glfw.glx.GetFBConfigs, _glfw.glx.handle, "glXGetFBConfigs");
glfw_dlsym(_glfw.glx.GetFBConfigAttrib, _glfw.glx.handle, "glXGetFBConfigAttrib");
glfw_dlsym(_glfw.glx.ChooseFBConfig, _glfw.glx.handle, "glXChooseFBConfig");
glfw_dlsym(_glfw.glx.GetClientString, _glfw.glx.handle, "glXGetClientString");
glfw_dlsym(_glfw.glx.QueryExtension, _glfw.glx.handle, "glXQueryExtension");
glfw_dlsym(_glfw.glx.QueryVersion, _glfw.glx.handle, "glXQueryVersion");

3
glfw/glx_context.h vendored
View File

@@ -73,6 +73,7 @@ typedef struct __GLXcontext* GLXContext;
typedef void (*__GLXextproc)(void);
typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*);
typedef GLXFBConfig* (*PFNGLXCHOOSEFBCONFIGPROC)(Display*,int,const int*,int*);
typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int);
typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*);
typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*);
@@ -95,6 +96,7 @@ typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLX
// libGL.so function pointer typedefs
#define glXGetFBConfigs _glfw.glx.GetFBConfigs
#define glXGetFBConfigAttrib _glfw.glx.GetFBConfigAttrib
#define glXChooseFBConfig _glfw.glx.ChooseFBConfig
#define glXGetClientString _glfw.glx.GetClientString
#define glXQueryExtension _glfw.glx.QueryExtension
#define glXQueryVersion _glfw.glx.QueryVersion
@@ -134,6 +136,7 @@ typedef struct _GLFWlibraryGLX
// GLX 1.3 functions
PFNGLXGETFBCONFIGSPROC GetFBConfigs;
PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib;
PFNGLXCHOOSEFBCONFIGPROC ChooseFBConfig;
PFNGLXGETCLIENTSTRINGPROC GetClientString;
PFNGLXQUERYEXTENSIONPROC QueryExtension;
PFNGLXQUERYVERSIONPROC QueryVersion;

11
glfw/init.c vendored
View File

@@ -168,6 +168,10 @@ void _glfwInputError(int code, const char* format, ...)
strcpy(description, "The requested format is unavailable");
else if (code == GLFW_NO_WINDOW_CONTEXT)
strcpy(description, "The specified window has no context");
else if (code == GLFW_FEATURE_UNAVAILABLE)
strcpy(description, "The requested feature cannot be implemented for this platform");
else if (code == GLFW_FEATURE_UNIMPLEMENTED)
strcpy(description, "The requested feature has not yet been implemented for this platform");
else
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
}
@@ -363,3 +367,10 @@ GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval,
GLFWAPI void glfwRemoveTimer(unsigned long long timer_id) {
_glfwPlatformRemoveTimer(timer_id);
}
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.application_close, cbfun);
return cbfun;
}

42
glfw/input.c vendored
View File

@@ -300,10 +300,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev)
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods)
{
if (window->callbacks.scroll)
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags, mods);
}
// Notifies shared code of a mouse button click event
@@ -675,6 +675,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->stickyMouseButtons;
case GLFW_LOCK_KEY_MODS:
return window->lockKeyMods;
case GLFW_RAW_MOUSE_MOTION:
return window->rawMouseMotion;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
@@ -754,10 +756,32 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
{
window->lockKeyMods = value ? true : false;
}
else if (mode == GLFW_RAW_MOUSE_MOTION)
{
if (!_glfwPlatformRawMouseMotionSupported())
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Raw mouse motion is not supported on this system");
return;
}
value = value ? true : false;
if (window->rawMouseMotion == value)
return;
window->rawMouseMotion = value;
_glfwPlatformSetRawMouseMotion(window, value);
}
else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
}
GLFWAPI int glfwRawMouseMotionSupported(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(false);
return _glfwPlatformRawMouseMotionSupported();
}
GLFWAPI const char* glfwGetKeyName(int key, int native_key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -1412,8 +1436,18 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
if (e->type == _GLFW_JOYSTICK_AXIS)
{
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
if (value > 0.f)
state->buttons[i] = GLFW_PRESS;
// HACK: This should be baked into the value transform
// TODO: Bake into transform when implementing output modifiers
if (e->axisOffset < 0 || (e->axisOffset == 0 && e->axisScale > 0))
{
if (value >= 0.f)
state->buttons[i] = GLFW_PRESS;
}
else
{
if (value <= 0.f)
state->buttons[i] = GLFW_PRESS;
}
}
else if (e->type == _GLFW_JOYSTICK_HATBIT)
{

10
glfw/internal.h vendored
View File

@@ -429,6 +429,7 @@ struct _GLFWwindow
char keys[GLFW_KEY_LAST + 1];
// Virtual cursor position when cursor is disabled
double virtualCursorPosX, virtualCursorPosY;
bool rawMouseMotion;
_GLFWcontext context;
@@ -601,8 +602,10 @@ struct _GLFWlibrary
struct {
GLFWmonitorfun monitor;
GLFWjoystickfun joystick;
GLFWapplicationclosefun application_close;
} callbacks;
// This is defined in the window API's platform.h
_GLFW_PLATFORM_LIBRARY_WINDOW_STATE;
// This is defined in the context API's context.h
@@ -631,6 +634,8 @@ const char* _glfwPlatformGetVersionString(void);
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled);
bool _glfwPlatformRawMouseMotionSupported(void);
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, int xhot, int yhot, int count);
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape);
@@ -755,7 +760,7 @@ void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods);
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
@@ -789,9 +794,6 @@ void _glfwDebug(const char* format, ...);
//////////////////////////////////////////////////////////////////////////
bool _glfwStringInExtensionString(const char* string, const char* extensions);
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives,
unsigned int count);
bool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig);
bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig);

5
glfw/null_init.c vendored
View File

@@ -29,6 +29,8 @@
#include "internal.h"
#include <stdlib.h>
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -36,11 +38,14 @@
int _glfwPlatformInit(void)
{
_glfwPollMonitorsNull();
return true;
}
void _glfwPlatformTerminate(void)
{
free(_glfw.null.clipboardString);
_glfwTerminateOSMesa();
}

96
glfw/null_monitor.c vendored
View File

@@ -29,13 +29,45 @@
#include "internal.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>
// The the sole (fake) video mode of our (sole) fake monitor
//
static GLFWvidmode getVideoMode(void)
{
GLFWvidmode mode;
mode.width = 1920;
mode.height = 1080;
mode.redBits = 8;
mode.greenBits = 8;
mode.blueBits = 8;
mode.refreshRate = 60;
return mode;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
void _glfwPollMonitorsNull(void)
{
const float dpi = 141.f;
const GLFWvidmode mode = getVideoMode();
_GLFWmonitor* monitor = _glfwAllocMonitor("Null SuperNoop 0",
(int) (mode.width * 25.4f / dpi),
(int) (mode.height * 25.4f / dpi));
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_FIRST);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor UNUSED)
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
{
_glfwFreeGammaArrays(&monitor->null.ramp);
}
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos UNUSED, int* ypos UNUSED)
@@ -52,26 +84,72 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED,
}
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
int* xpos UNUSED, int* ypos UNUSED,
int* width UNUSED, int* height UNUSED)
int* xpos, int* ypos,
int* width, int* height)
{
const GLFWvidmode mode = getVideoMode();
if (xpos)
*xpos = 10;
if (ypos)
ypos = 0;
if (width)
*width = mode.width;
if (height)
*height = mode.height - 10;
}
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor UNUSED, int* found UNUSED)
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor UNUSED, int* found)
{
return NULL;
GLFWvidmode* mode = calloc(1, sizeof(GLFWvidmode));
*mode = getVideoMode();
*found = 1;
return mode;
}
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor UNUSED, GLFWvidmode* mode UNUSED)
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor UNUSED, GLFWvidmode* mode)
{
*mode = getVideoMode();
}
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp UNUSED)
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
return false;
if (!monitor->null.ramp.size)
{
_glfwAllocGammaArrays(&monitor->null.ramp, 256);
for (unsigned int i = 0; i < monitor->null.ramp.size; i++)
{
const float gamma = 2.2f;
float value;
value = i / (float) (monitor->null.ramp.size - 1);
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
value = _glfw_fminf(value, 65535.f);
monitor->null.ramp.red[i] = (unsigned short) value;
monitor->null.ramp.green[i] = (unsigned short) value;
monitor->null.ramp.blue[i] = (unsigned short) value;
}
}
_glfwAllocGammaArrays(ramp, monitor->null.ramp.size);
memcpy(ramp->red, monitor->null.ramp.red, sizeof(short) * ramp->size);
memcpy(ramp->green, monitor->null.ramp.green, sizeof(short) * ramp->size);
memcpy(ramp->blue, monitor->null.ramp.blue, sizeof(short) * ramp->size);
return true;
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor UNUSED, const GLFWgammaramp* ramp UNUSED)
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
{
if (monitor->null.ramp.size != ramp->size)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Null: Gamma ramp size must match current ramp size");
return;
}
memcpy(monitor->null.ramp.red, ramp->red, sizeof(short) * ramp->size);
memcpy(monitor->null.ramp.green, ramp->green, sizeof(short) * ramp->size);
memcpy(monitor->null.ramp.blue, ramp->blue, sizeof(short) * ramp->size);
}

39
glfw/null_platform.h vendored
View File

@@ -27,12 +27,12 @@
#include <dlfcn.h>
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNull null
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNull null
#define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_MONITOR_STATE
#define _GLFW_PLATFORM_CURSOR_STATE
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
#define _GLFW_EGL_CONTEXT_STATE
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
@@ -56,7 +56,36 @@
//
typedef struct _GLFWwindowNull
{
int width;
int height;
int xpos;
int ypos;
int width;
int height;
char* title;
bool visible;
bool iconified;
bool maximized;
bool resizable;
bool decorated;
bool floating;
bool transparent;
float opacity;
} _GLFWwindowNull;
// Null-specific per-monitor data
//
typedef struct _GLFWmonitorNull
{
GLFWgammaramp ramp;
} _GLFWmonitorNull;
// Null-specific global data
//
typedef struct _GLFWlibraryNull
{
int xcursor;
int ycursor;
char* clipboardString;
_GLFWwindow* focusedWindow;
} _GLFWlibraryNull;
void _glfwPollMonitorsNull(void);

496
glfw/null_window.c vendored
View File

@@ -30,12 +30,71 @@
#include "internal.h"
#include "../kitty/monotonic.h"
#include <stdlib.h>
static void applySizeLimits(_GLFWwindow* window, int* width, int* height)
{
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
{
const float ratio = (float) window->numer / (float) window->denom;
*height = (int) (*width / ratio);
}
if (window->minwidth != GLFW_DONT_CARE && *width < window->minwidth)
*width = window->minwidth;
else if (window->maxwidth != GLFW_DONT_CARE && *width > window->maxwidth)
*width = window->maxwidth;
if (window->minheight != GLFW_DONT_CARE && *height < window->minheight)
*height = window->minheight;
else if (window->maxheight != GLFW_DONT_CARE && *height > window->maxheight)
*height = window->maxheight;
}
static void fitToMonitor(_GLFWwindow* window)
{
GLFWvidmode mode;
_glfwPlatformGetVideoMode(window->monitor, &mode);
_glfwPlatformGetMonitorPos(window->monitor,
&window->null.xpos,
&window->null.ypos);
window->null.width = mode.width;
window->null.height = mode.height;
}
static void acquireMonitor(_GLFWwindow* window)
{
_glfwInputMonitorWindow(window->monitor, window);
}
static void releaseMonitor(_GLFWwindow* window)
{
if (window->monitor->window != window)
return;
_glfwInputMonitorWindow(window->monitor, NULL);
}
static int createNativeWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig)
const _GLFWwndconfig* wndconfig,
const _GLFWfbconfig* fbconfig)
{
window->null.width = wndconfig->width;
window->null.height = wndconfig->height;
if (window->monitor)
fitToMonitor(window);
else
{
window->null.xpos = 17;
window->null.ypos = 17;
window->null.width = wndconfig->width;
window->null.height = wndconfig->height;
}
window->null.visible = wndconfig->visible;
window->null.decorated = wndconfig->decorated;
window->null.maximized = wndconfig->maximized;
window->null.floating = wndconfig->floating;
window->null.transparent = fbconfig->transparent;
window->null.opacity = 1.f;
return true;
}
@@ -50,7 +109,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
if (!createNativeWindow(window, wndconfig))
if (!createNativeWindow(window, wndconfig, fbconfig))
return false;
if (ctxconfig->client != GLFW_NO_API)
@@ -70,11 +129,24 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
}
if (window->monitor)
{
_glfwPlatformShowWindow(window);
_glfwPlatformFocusWindow(window);
acquireMonitor(window);
}
return true;
}
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
if (window->monitor)
releaseMonitor(window);
if (_glfw.null.focusedWindow == window)
_glfw.null.focusedWindow = NULL;
if (window->context.destroy)
window->context.destroy(window);
}
@@ -88,20 +160,60 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED, int count UNUSED,
{
}
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window UNUSED,
_GLFWmonitor* monitor UNUSED,
int xpos UNUSED, int ypos UNUSED,
int width UNUSED, int height UNUSED,
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
_GLFWmonitor* monitor,
int xpos, int ypos,
int width, int height,
int refreshRate UNUSED)
{
if (window->monitor == monitor)
{
if (!monitor)
{
_glfwPlatformSetWindowPos(window, xpos, ypos);
_glfwPlatformSetWindowSize(window, width, height);
}
return;
}
if (window->monitor)
releaseMonitor(window);
_glfwInputWindowMonitor(window, monitor);
if (window->monitor)
{
window->null.visible = true;
acquireMonitor(window);
fitToMonitor(window);
}
else
{
_glfwPlatformSetWindowPos(window, xpos, ypos);
_glfwPlatformSetWindowSize(window, width, height);
}
}
void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int* ypos UNUSED)
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
if (xpos)
*xpos = window->null.xpos;
if (ypos)
*ypos = window->null.ypos;
}
void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED)
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
{
if (window->monitor)
return;
if (window->null.xpos != xpos || window->null.ypos != ypos)
{
window->null.xpos = xpos;
window->null.ypos = ypos;
_glfwInputWindowPos(window, xpos, ypos);
}
}
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
@@ -114,18 +226,34 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
window->null.width = width;
window->null.height = height;
if (window->monitor)
return;
if (window->null.width != width || window->null.height != height)
{
window->null.width = width;
window->null.height = height;
_glfwInputWindowSize(window, width, height);
_glfwInputFramebufferSize(window, width, height);
}
}
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window UNUSED,
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth UNUSED, int minheight UNUSED,
int maxwidth UNUSED, int maxheight UNUSED)
{
int width = window->null.width;
int height = window->null.height;
applySizeLimits(window, &width, &height);
_glfwPlatformSetWindowSize(window, width, height);
}
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED, int n UNUSED, int d UNUSED)
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int n UNUSED, int d UNUSED)
{
int width = window->null.width;
int height = window->null.height;
applySizeLimits(window, &width, &height);
_glfwPlatformSetWindowSize(window, width, height);
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED)
@@ -140,10 +268,17 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* heigh
*height = window->null.height;
}
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window UNUSED,
int* left UNUSED, int* top UNUSED,
int* right UNUSED, int* bottom UNUSED)
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* left, int* top,
int* right, int* bottom)
{
if (window->null.decorated && !window->monitor)
{
*left = 1;
*top = 10;
*right = 1;
*bottom = 1;
}
}
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED,
@@ -160,59 +295,107 @@ monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
return ms_to_monotonic_t(500ll);
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window UNUSED)
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
if (_glfw.null.focusedWindow == window)
{
_glfw.null.focusedWindow = NULL;
_glfwInputWindowFocus(window, false);
}
if (!window->null.iconified)
{
window->null.iconified = true;
_glfwInputWindowIconify(window, true);
if (window->monitor)
releaseMonitor(window);
}
}
void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{
if (window->null.iconified)
{
window->null.iconified = false;
_glfwInputWindowIconify(window, false);
if (window->monitor)
acquireMonitor(window);
}
else if (window->null.maximized)
{
window->null.maximized = false;
_glfwInputWindowMaximize(window, false);
}
}
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{
if (!window->null.maximized)
{
window->null.maximized = true;
_glfwInputWindowMaximize(window, true);
}
}
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
{
return window->null.maximized;
}
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return _glfw.null.xcursor >= window->null.xpos &&
_glfw.null.ycursor >= window->null.ypos &&
_glfw.null.xcursor <= window->null.xpos + window->null.width - 1 &&
_glfw.null.ycursor <= window->null.ypos + window->null.height - 1;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{
return window->null.transparent;
}
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled)
{
window->null.resizable = enabled;
}
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
{
window->null.decorated = enabled;
}
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
{
window->null.floating = enabled;
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
return window->null.opacity;
}
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
{
window->null.opacity = opacity;
}
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
{
}
void _glfwPlatformRestoreWindow(_GLFWwindow* window UNUSED)
bool _glfwPlatformRawMouseMotionSupported(void)
{
return true;
}
void _glfwPlatformMaximizeWindow(_GLFWwindow* window UNUSED)
void _glfwPlatformShowWindow(_GLFWwindow* window)
{
window->null.visible = true;
}
int _glfwPlatformWindowMaximized(_GLFWwindow* window UNUSED)
{
return false;
}
int _glfwPlatformWindowHovered(_GLFWwindow* window UNUSED)
{
return false;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window UNUSED)
{
return false;
}
void _glfwPlatformSetWindowResizable(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
}
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
}
void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
{
return 1.f;
}
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window UNUSED, float opacity UNUSED)
{
}
void _glfwPlatformShowWindow(_GLFWwindow* window UNUSED)
{
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
{
}
@@ -222,21 +405,41 @@ int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
return false;
}
void _glfwPlatformUnhideWindow(_GLFWwindow* window UNUSED)
void _glfwPlatformHideWindow(_GLFWwindow* window)
{
if (_glfw.null.focusedWindow == window)
{
_glfw.null.focusedWindow = NULL;
_glfwInputWindowFocus(window, false);
}
window->null.visible = false;
}
void _glfwPlatformHideWindow(_GLFWwindow* window UNUSED)
void _glfwPlatformFocusWindow(_GLFWwindow* window)
{
if (_glfw.null.focusedWindow == window)
return;
if (!window->null.visible)
return;
_GLFWwindow* previous = _glfw.null.focusedWindow;
_glfw.null.focusedWindow = window;
if (previous)
{
_glfwInputWindowFocus(previous, false);
if (previous->monitor && previous->autoIconify)
_glfwPlatformIconifyWindow(previous);
}
_glfwInputWindowFocus(window, true);
}
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
}
int _glfwPlatformWindowFocused(_GLFWwindow* window UNUSED)
{
return false;
return _glfw.null.focusedWindow == window;
}
int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED)
@@ -244,14 +447,14 @@ int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED)
return false;
}
int _glfwPlatformWindowIconified(_GLFWwindow* window UNUSED)
int _glfwPlatformWindowIconified(_GLFWwindow* window)
{
return false;
return window->null.iconified;
}
int _glfwPlatformWindowVisible(_GLFWwindow* window UNUSED)
int _glfwPlatformWindowVisible(_GLFWwindow* window)
{
return false;
return window->null.visible;
}
void _glfwPlatformPollEvents(void)
@@ -270,12 +473,18 @@ void _glfwPlatformPostEmptyEvent(void)
{
}
void _glfwPlatformGetCursorPos(_GLFWwindow* window UNUSED, double* xpos UNUSED, double* ypos UNUSED)
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{
if (xpos)
*xpos = _glfw.null.xcursor - window->null.xpos;
if (ypos)
*ypos = _glfw.null.ycursor - window->null.ypos;
}
void _glfwPlatformSetCursorPos(_GLFWwindow* window UNUSED, double x UNUSED, double y UNUSED)
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
{
_glfw.null.xcursor = window->null.xpos + (int) x;
_glfw.null.ycursor = window->null.ypos + (int) y;
}
void _glfwPlatformSetCursorMode(_GLFWwindow* window UNUSED, int mode UNUSED)
@@ -302,23 +511,142 @@ void _glfwPlatformSetCursor(_GLFWwindow* window UNUSED, _GLFWcursor* cursor UNUS
{
}
void _glfwPlatformSetClipboardString(const char* string UNUSED)
void _glfwPlatformSetClipboardString(const char* string)
{
char* copy = _glfw_strdup(string);
free(_glfw.null.clipboardString);
_glfw.null.clipboardString = copy;
}
const char* _glfwPlatformGetClipboardString(void)
{
return _glfw.null.clipboardString;
}
const char* _glfwPlatformGetNativeKeyName(int native_key)
{
switch (scancode)
{
case GLFW_KEY_APOSTROPHE:
return "'";
case GLFW_KEY_COMMA:
return ",";
case GLFW_KEY_MINUS:
case GLFW_KEY_KP_SUBTRACT:
return "-";
case GLFW_KEY_PERIOD:
case GLFW_KEY_KP_DECIMAL:
return ".";
case GLFW_KEY_SLASH:
case GLFW_KEY_KP_DIVIDE:
return "/";
case GLFW_KEY_SEMICOLON:
return ";";
case GLFW_KEY_EQUAL:
case GLFW_KEY_KP_EQUAL:
return "=";
case GLFW_KEY_LEFT_BRACKET:
return "[";
case GLFW_KEY_RIGHT_BRACKET:
return "]";
case GLFW_KEY_KP_MULTIPLY:
return "*";
case GLFW_KEY_KP_ADD:
return "+";
case GLFW_KEY_BACKSLASH:
case GLFW_KEY_WORLD_1:
case GLFW_KEY_WORLD_2:
return "\\";
case GLFW_KEY_0:
case GLFW_KEY_KP_0:
return "0";
case GLFW_KEY_1:
case GLFW_KEY_KP_1:
return "1";
case GLFW_KEY_2:
case GLFW_KEY_KP_2:
return "2";
case GLFW_KEY_3:
case GLFW_KEY_KP_3:
return "3";
case GLFW_KEY_4:
case GLFW_KEY_KP_4:
return "4";
case GLFW_KEY_5:
case GLFW_KEY_KP_5:
return "5";
case GLFW_KEY_6:
case GLFW_KEY_KP_6:
return "6";
case GLFW_KEY_7:
case GLFW_KEY_KP_7:
return "7";
case GLFW_KEY_8:
case GLFW_KEY_KP_8:
return "8";
case GLFW_KEY_9:
case GLFW_KEY_KP_9:
return "9";
case GLFW_KEY_A:
return "a";
case GLFW_KEY_B:
return "b";
case GLFW_KEY_C:
return "c";
case GLFW_KEY_D:
return "d";
case GLFW_KEY_E:
return "e";
case GLFW_KEY_F:
return "f";
case GLFW_KEY_G:
return "g";
case GLFW_KEY_H:
return "h";
case GLFW_KEY_I:
return "i";
case GLFW_KEY_J:
return "j";
case GLFW_KEY_K:
return "k";
case GLFW_KEY_L:
return "l";
case GLFW_KEY_M:
return "m";
case GLFW_KEY_N:
return "n";
case GLFW_KEY_O:
return "o";
case GLFW_KEY_P:
return "p";
case GLFW_KEY_Q:
return "q";
case GLFW_KEY_R:
return "r";
case GLFW_KEY_S:
return "s";
case GLFW_KEY_T:
return "t";
case GLFW_KEY_U:
return "u";
case GLFW_KEY_V:
return "v";
case GLFW_KEY_W:
return "w";
case GLFW_KEY_X:
return "x";
case GLFW_KEY_Y:
return "y";
case GLFW_KEY_Z:
return "z";
}
return NULL;
}
const char* _glfwPlatformGetNativeKeyName(int native_key UNUSED)
int _glfwPlatformGetNativeKeyForKey(int key)
{
return "";
}
int _glfwPlatformGetNativeKeyForKey(int key UNUSED)
{
return -1;
return key;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions UNUSED)
@@ -338,6 +666,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance UNUSED,
VkSurfaceKHR* surface UNUSED)
{
// This seems like the most appropriate error to return here
return VK_ERROR_INITIALIZATION_FAILED;
return VK_ERROR_EXTENSION_NOT_PRESENT;
}

View File

@@ -54,6 +54,7 @@
"headers": [
"wl_platform.h",
"posix_thread.h",
"wl_cursors.h",
"xkb_glfw.h",
"dbus_glfw.h",
"ibus_glfw.h",
@@ -78,6 +79,7 @@
"wl_init.c",
"wl_monitor.c",
"wl_window.c",
"wl_cursors.c",
"posix_thread.c",
"xkb_glfw.c",
"dbus_glfw.c",

164
glfw/wl_cursors.c vendored Normal file
View File

@@ -0,0 +1,164 @@
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
#include "wl_cursors.h"
#include "internal.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#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");
}
} else {
dest->theme = NULL;
}
}
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);
}
// 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;
}
}
}
static _wlCursorThemeManager _default = {0};
_wlCursorThemeManager*
_wlCursorThemeManagerDefault() {
return &_default;
}
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);
}
}
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;
}
}
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;
}

30
glfw/wl_cursors.h vendored Normal file
View File

@@ -0,0 +1,30 @@
// Declarations for a HiDPI-aware cursor theme manager.
#include <wayland-cursor.h>
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
/** 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);
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);

69
glfw/wl_init.c vendored
View File

@@ -113,7 +113,7 @@ static void pointerHandleEnter(void* data UNUSED,
}
window->wl.decorations.focus = focus;
_glfw.wl.pointerSerial = serial;
_glfw.wl.serial = serial;
_glfw.wl.pointerFocus = window;
window->wl.hovered = true;
@@ -134,20 +134,26 @@ static void pointerHandleLeave(void* data UNUSED,
window->wl.hovered = false;
_glfw.wl.pointerSerial = serial;
_glfw.wl.serial = serial;
_glfw.wl.pointerFocus = NULL;
_glfwInputCursorEnter(window, false);
_glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR;
}
static void setCursor(GLFWCursorShape shape)
static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
{
struct wl_buffer* buffer;
struct wl_cursor* cursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
cursor = _glfwLoadCursor(shape);
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
_wlCursorPxFromScale(scale)
);
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
if (!cursor) return;
// TODO: handle animated cursors too.
image = cursor->images[0];
@@ -158,10 +164,11 @@ static void setCursor(GLFWCursorShape shape)
buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
surface,
image->hotspot_x,
image->hotspot_y);
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
@@ -225,7 +232,7 @@ static void pointerHandleMotion(void* data UNUSED,
assert(0);
}
if (_glfw.wl.cursorPreviousShape != cursorShape)
setCursor(cursorShape);
setCursor(cursorShape, window);
}
static void pointerHandleButton(void* data UNUSED,
@@ -301,7 +308,7 @@ static void pointerHandleButton(void* data UNUSED,
if (window->wl.decorations.focus != mainWindow)
return;
_glfw.wl.pointerSerial = serial;
_glfw.wl.serial = serial;
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
* codes. */
@@ -334,7 +341,7 @@ static void pointerHandleAxis(void* data UNUSED,
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
y = wl_fixed_to_double(value) * -1;
_glfwInputScroll(window, x, y, 1);
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
}
static const struct wl_pointer_listener pointerListener = {
@@ -372,7 +379,7 @@ static void keyboardHandleKeymap(void* data UNUSED,
static void keyboardHandleEnter(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial UNUSED,
uint32_t serial,
struct wl_surface* surface,
struct wl_array* keys)
{
@@ -388,6 +395,7 @@ static void keyboardHandleEnter(void* data UNUSED,
return;
}
_glfw.wl.serial = serial;
_glfw.wl.keyboardFocus = window;
_glfwInputWindowFocus(window, true);
uint32_t* key;
@@ -403,7 +411,7 @@ static void keyboardHandleEnter(void* data UNUSED,
static void keyboardHandleLeave(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial UNUSED,
uint32_t serial,
struct wl_surface* surface UNUSED)
{
_GLFWwindow* window = _glfw.wl.keyboardFocus;
@@ -411,6 +419,7 @@ static void keyboardHandleLeave(void* data UNUSED,
if (!window)
return;
_glfw.wl.serial = serial;
_glfw.wl.keyboardFocus = NULL;
_glfwInputWindowFocus(window, false);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
@@ -427,7 +436,7 @@ dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) {
static void keyboardHandleKey(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial UNUSED,
uint32_t serial,
uint32_t time UNUSED,
uint32_t key,
uint32_t state)
@@ -436,6 +445,8 @@ static void keyboardHandleKey(void* data UNUSED,
if (!window)
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
_glfw.wl.serial = serial;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
bool repeatable = false;
_glfw.wl.keyRepeatInfo.key = 0;
@@ -454,12 +465,13 @@ static void keyboardHandleKey(void* data UNUSED,
static void keyboardHandleModifiers(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial UNUSED,
uint32_t serial,
uint32_t modsDepressed,
uint32_t modsLatched,
uint32_t modsLocked,
uint32_t group)
{
_glfw.wl.serial = serial;
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
}
@@ -775,26 +787,14 @@ int _glfwPlatformInit(void)
if (_glfw.wl.shm)
{
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
char *cursorSizeEnd;
int cursorSize = 32;
if (cursorSizeStr)
{
errno = 0;
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
cursorSize = (int)cursorSizeLong;
}
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
if (!_glfw.wl.cursorTheme)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load default cursor theme");
return false;
}
_glfw.wl.cursorSurface =
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
_glfw.wl.cursorSurface =
wl_compositor_create_surface(_glfw.wl.compositor);
}
else
{
_glfw.wl.cursorThemeManager = NULL;
}
return true;
}
@@ -814,8 +814,6 @@ void _glfwPlatformTerminate(void)
glfw_xkb_release(&_glfw.wl.xkb);
glfw_dbus_terminate(&_glfw.wl.dbus);
if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
if (_glfw.wl.cursor.handle)
{
_glfw_dlclose(_glfw.wl.cursor.handle);
@@ -824,6 +822,9 @@ void _glfwPlatformTerminate(void)
if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.cursorThemeManager) {
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
}
if (_glfw.wl.subcompositor)
wl_subcompositor_destroy(_glfw.wl.subcompositor);
if (_glfw.wl.compositor)

4
glfw/wl_monitor.c vendored
View File

@@ -202,7 +202,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp UNUSED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: Gamma ramp access is not available");
return false;
}
@@ -210,7 +210,7 @@ bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor UNUSED,
const GLFWgammaramp* ramp UNUSED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: Gamma ramp access is not available");
}

28
glfw/wl_platform.h vendored
View File

@@ -52,6 +52,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "xkb_glfw.h"
#include "egl_context.h"
#include "osmesa_context.h"
#include "wl_cursors.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
@@ -76,18 +77,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
struct wl_cursor_image {
uint32_t width;
uint32_t height;
uint32_t hotspot_x;
uint32_t hotspot_y;
uint32_t delay;
};
struct wl_cursor {
unsigned int image_count;
struct wl_cursor_image** images;
char* name;
};
typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*);
typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*);
typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*);
@@ -148,6 +137,7 @@ typedef struct _GLFWwindowWayland
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
struct wl_cursor_theme* cursorTheme;
char* title;
char appId[256];
@@ -236,10 +226,9 @@ typedef struct _GLFWlibraryWayland
int compositorVersion;
int seatVersion;
struct wl_cursor_theme* cursorTheme;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t pointerSerial;
uint32_t serial;
int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay;
@@ -278,6 +267,8 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
_wlCursorThemeManager* cursorThemeManager;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -291,8 +282,7 @@ typedef struct _GLFWmonitorWayland
int x;
int y;
int scale;
} _GLFWmonitorWayland;
} _GLFWmonitorWayland;
// Wayland-specific per-cursor data
//
@@ -303,6 +293,10 @@ typedef struct _GLFWcursorWayland
int width, height;
int xhot, yhot;
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. */
GLFWCursorShape shape;
} _GLFWcursorWayland;
@@ -310,5 +304,5 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*);
void destroy_data_offer(_GLFWWaylandDataOffer*);

277
glfw/wl_window.c vendored
View File

@@ -41,8 +41,67 @@
#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)
{
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
struct wl_cursor_image* image = NULL;
struct wl_buffer* buffer = NULL;
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
if (cursorWayland->scale < 0) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
if (cursorWayland->scale != scale) {
if (!window->wl.cursorTheme) load_cursor_theme(window);
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
if (newCursor != NULL) {
cursorWayland->cursor = newCursor;
cursorWayland->scale = scale;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
}
}
if (!cursorWayland->cursor)
return;
image = cursorWayland->cursor->images[cursorWayland->currentImage];
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));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
} else {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
}
if (!buffer)
return;
cursorWayland->width = image->width;
cursorWayland->height = image->height;
cursorWayland->xhot = image->hotspot_x;
cursorWayland->yhot = image->hotspot_y;
}
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
surface,
cursorWayland->xhot / scale,
cursorWayland->yhot / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursorWayland->width, cursorWayland->height);
wl_surface_commit(surface);
}
static bool checkScaleChange(_GLFWwindow* window)
@@ -73,6 +132,8 @@ 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;
}
if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) {
@@ -388,9 +449,9 @@ static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener =
xdgDecorationHandleConfigure,
};
static void handleEnter(void *data,
struct wl_surface *surface UNUSED,
struct wl_output *output)
static void surfaceHandleEnter(void *data,
struct wl_surface *surface UNUSED,
struct wl_output *output)
{
_GLFWwindow* window = data;
_GLFWmonitor* monitor = wl_output_get_user_data(output);
@@ -411,9 +472,9 @@ static void handleEnter(void *data,
}
}
static void handleLeave(void *data,
struct wl_surface *surface UNUSED,
struct wl_output *output)
static void surfaceHandleLeave(void *data,
struct wl_surface *surface UNUSED,
struct wl_output *output)
{
_GLFWwindow* window = data;
_GLFWmonitor* monitor = wl_output_get_user_data(output);
@@ -436,8 +497,8 @@ static void handleLeave(void *data,
}
static const struct wl_surface_listener surfaceListener = {
handleEnter,
handleLeave
surfaceHandleEnter,
surfaceHandleLeave
};
static void setIdleInhibitor(_GLFWwindow* window, bool enable)
@@ -679,56 +740,16 @@ static bool createXdgSurface(_GLFWwindow* window)
return true;
}
static void
setCursorImage(_GLFWcursorWayland* cursorWayland)
{
struct wl_cursor_image* image;
struct wl_buffer* buffer;
struct wl_surface* surface = _glfw.wl.cursorSurface;
if (!cursorWayland->cursor) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
image = cursorWayland->cursor->images[cursorWayland->currentImage];
buffer = wl_cursor_image_get_buffer(image);
if (image->delay) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
} else {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
}
if (!buffer)
return;
cursorWayland->width = image->width;
cursorWayland->height = image->height;
cursorWayland->xhot = image->hotspot_x;
cursorWayland->yhot = image->hotspot_y;
}
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursorWayland->xhot,
cursorWayland->yhot);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursorWayland->width, cursorWayland->height);
wl_surface_commit(surface);
}
static void
incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == mainWindow) {
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
_GLFWcursor* cursor = window->wl.currentCursor;
if (cursor && cursor->wl.cursor)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(&cursor->wl);
setCursorImage(window);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
return;
}
@@ -744,11 +765,14 @@ 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));
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
_glfwInputWindowCloseRequest(window);
window = window->next;
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
_glfw.wl.eventLoopData.wakeup_data_read = true;
@@ -804,24 +828,25 @@ handleEvents(monotonic_t timeout)
}
static struct wl_cursor*
try_cursor_names(int arg_count, ...) {
try_cursor_names(struct wl_cursor_theme* theme, int arg_count, ...) {
struct wl_cursor* ans = NULL;
va_list ap;
va_start(ap, arg_count);
for (int i = 0; i < arg_count && !ans; i++) {
const char *name = va_arg(ap, const char *);
ans = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name);
ans = wl_cursor_theme_get_cursor(theme, name);
}
va_end(ap);
return ans;
}
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape)
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* theme)
{
static bool warnings[GLFW_INVALID_CURSOR] = {0};
if (!theme) return NULL;
#define NUMARGS(...) (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))
#define C(name, ...) case name: { \
ans = try_cursor_names(NUMARGS(__VA_ARGS__), __VA_ARGS__); \
ans = try_cursor_names(theme, NUMARGS(__VA_ARGS__), __VA_ARGS__); \
if (!ans && !warnings[name]) {\
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Could not find standard cursor: %s", #name); \
warnings[name] = true; \
@@ -831,7 +856,7 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape)
struct wl_cursor* ans = NULL;
switch (shape)
{
C(GLFW_ARROW_CURSOR, "arrow", "left_ptr", "default")
C(GLFW_ARROW_CURSOR, "left_ptr", "arrow", "default")
C(GLFW_IBEAM_CURSOR, "xterm", "ibeam", "text")
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "cross")
C(GLFW_HAND_CURSOR, "hand2", "grab", "grabbing", "closedhand")
@@ -901,6 +926,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
window->wl.currentCursor = NULL;
// Don't set window->wl.cursorTheme to NULL here.
window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
window->wl.monitorsCount = 0;
@@ -911,6 +937,12 @@ 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;
@@ -929,6 +961,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
@@ -957,12 +990,10 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
if (window->wl.title)
free(window->wl.title);
window->wl.title = _glfw_strdup(title);
// Wayland cannot handle requests larger than ~8200 bytes. Sending
// one causes an abort(). Since titles this large are meaningless anyway
// ensure they do not happen. One should really truncate ensuring valid UTF-8
// but I cant be bothered.
if (title && strnlen(title, 2048) >= 2048) window->wl.title[2048] = 0;
// ensure they do not happen.
window->wl.title = utf_8_strndup(title, 2048);
if (window->wl.xdg.toplevel)
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
}
@@ -970,8 +1001,8 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
int count UNUSED, const GLFWimage* images UNUSED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Setting window icon not supported");
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the window icon");
}
void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int* ypos UNUSED)
@@ -979,16 +1010,16 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int
// A Wayland client is not aware of its position, so just warn and leave it
// as (0, 0)
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Window position retrieval not supported");
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not provide the window position");
}
void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED)
{
// A Wayland client can not set its position, so just warn
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Window position setting not supported");
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the window position");
}
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
@@ -1029,6 +1060,8 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED,
{
// TODO: find out how to trigger a resize.
// The actual limits are checked in the xdg_toplevel::configure handler.
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window aspect ratio not yet implemented");
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED,
@@ -1133,7 +1166,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
// TODO
static bool notified = false;
if (!notified) {
_glfwInputError(GLFW_PLATFORM_ERROR,
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window attention request not implemented yet");
notified = true;
}
@@ -1154,8 +1187,8 @@ int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Focusing a window requires user interaction");
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the input focus");
}
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
@@ -1208,7 +1241,7 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
void _glfwPlatformSetWindowResizable(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window attribute setting not implemented yet");
}
@@ -1226,7 +1259,7 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window attribute setting not implemented yet");
}
@@ -1237,6 +1270,18 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window UNUSED, float opacity UNUSED)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the window opacity");
}
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
{
// This is handled in relativePointerHandleRelativeMotion
}
bool _glfwPlatformRawMouseMotionSupported(void)
{
return true;
}
void _glfwPlatformPollEvents(void)
@@ -1309,17 +1354,19 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
cursor->wl.yhot = yhot;
cursor->wl.scale = -1;
cursor->wl.shape = GLFW_INVALID_CURSOR;
return true;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
{
struct wl_cursor* standardCursor;
standardCursor = _glfwLoadCursor(shape);
if (!standardCursor) return false;
cursor->wl.cursor = standardCursor;
// Don't actually load the cursor at this point,
// because there's not enough info to be properly HiDPI aware.
cursor->wl.cursor = NULL;
cursor->wl.currentImage = 0;
cursor->wl.scale = 0;
cursor->wl.shape = shape;
return true;
}
@@ -1333,31 +1380,42 @@ void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
wl_buffer_destroy(cursor->wl.buffer);
}
static void handleRelativeMotion(void* data,
struct zwp_relative_pointer_v1* pointer UNUSED,
uint32_t timeHi UNUSED,
uint32_t timeLo UNUSED,
wl_fixed_t dx UNUSED,
wl_fixed_t dy UNUSED,
wl_fixed_t dxUnaccel,
wl_fixed_t dyUnaccel)
static void relativePointerHandleRelativeMotion(void* data,
struct zwp_relative_pointer_v1* pointer UNUSED,
uint32_t timeHi UNUSED,
uint32_t timeLo UNUSED,
wl_fixed_t dx,
wl_fixed_t dy,
wl_fixed_t dxUnaccel,
wl_fixed_t dyUnaccel)
{
_GLFWwindow* window = data;
double xpos = window->virtualCursorPosX;
double ypos = window->virtualCursorPosY;
if (window->cursorMode != GLFW_CURSOR_DISABLED)
return;
_glfwInputCursorPos(window,
window->virtualCursorPosX + wl_fixed_to_double(dxUnaccel),
window->virtualCursorPosY + wl_fixed_to_double(dyUnaccel));
if (window->rawMouseMotion)
{
xpos += wl_fixed_to_double(dxUnaccel);
ypos += wl_fixed_to_double(dyUnaccel);
}
else
{
xpos += wl_fixed_to_double(dx);
ypos += wl_fixed_to_double(dy);
}
_glfwInputCursorPos(window, xpos, ypos);
}
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
handleRelativeMotion
relativePointerHandleRelativeMotion
};
static void handleLocked(void* data UNUSED,
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
static void lockedPointerHandleLocked(void* data UNUSED,
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
{
}
@@ -1377,14 +1435,14 @@ static void unlockPointer(_GLFWwindow* window)
static void lockPointer(_GLFWwindow* window UNUSED);
static void handleUnlocked(void* data UNUSED,
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
static void lockedPointerHandleUnlocked(void* data UNUSED,
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
{
}
static const struct zwp_locked_pointer_v1_listener lockedPointerListener = {
handleLocked,
handleUnlocked
lockedPointerHandleLocked,
lockedPointerHandleUnlocked
};
static void lockPointer(_GLFWwindow* window)
@@ -1421,7 +1479,7 @@ static void lockPointer(_GLFWwindow* window)
window->wl.pointerLock.relativePointer = relativePointer;
window->wl.pointerLock.lockedPointer = lockedPointer;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
NULL, 0, 0);
}
@@ -1432,8 +1490,6 @@ static bool isPointerLocked(_GLFWwindow* window)
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
struct wl_cursor* defaultCursor;
if (!_glfw.wl.pointer)
return;
@@ -1450,21 +1506,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
if (window->cursorMode == GLFW_CURSOR_NORMAL)
{
if (cursor)
setCursorImage(&cursor->wl);
else
{
defaultCursor = _glfwLoadCursor(GLFW_ARROW_CURSOR);
if (!defaultCursor) return;
_GLFWcursorWayland cursorWayland = {
defaultCursor,
NULL,
0, 0,
0, 0,
0
};
setCursorImage(&cursorWayland);
}
setCursorImage(window);
}
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
{
@@ -1473,8 +1515,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
}
else if (window->cursorMode == GLFW_CURSOR_HIDDEN)
{
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
NULL, 0, 0);
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, NULL, 0, 0);
}
}

56
glfw/x11_init.c vendored
View File

@@ -42,11 +42,11 @@
#include <unistd.h>
// Check whether the specified atom is supported
// Return the atom ID only if it is listed in the specified array
//
static Atom getSupportedAtom(Atom* supportedAtoms,
unsigned long atomCount,
const char* atomName)
static Atom getAtomIfSupported(Atom* supportedAtoms,
unsigned long atomCount,
const char* atomName)
{
const Atom atom = XInternAtom(_glfw.x11.display, atomName, False);
@@ -64,9 +64,8 @@ static Atom getSupportedAtom(Atom* supportedAtoms,
static void detectEWMH(void)
{
// First we read the _NET_SUPPORTING_WM_CHECK property on the root window
Window* windowFromRoot = NULL;
// Then we look for the _NET_SUPPORTING_WM_CHECK property of the root window
Window* windowFromRoot = NULL;
if (!_glfwGetWindowPropertyX11(_glfw.x11.root,
_glfw.x11.NET_SUPPORTING_WM_CHECK,
XA_WINDOW,
@@ -93,6 +92,7 @@ static void detectEWMH(void)
_glfwReleaseErrorHandlerX11();
// If the property exists, it should contain the XID of the window
if (*windowFromRoot != *windowFromChild)
{
XFree(windowFromRoot);
@@ -103,49 +103,50 @@ static void detectEWMH(void)
XFree(windowFromRoot);
XFree(windowFromChild);
// We are now fairly sure that an EWMH-compliant window manager is running
// We are now fairly sure that an EWMH-compliant WM is currently running
// We can now start querying the WM about what features it supports by
// looking in the _NET_SUPPORTED property on the root window
// It should contain a list of supported EWMH protocol and state atoms
Atom* supportedAtoms = NULL;
const unsigned long atomCount = _glfwGetWindowPropertyX11(_glfw.x11.root,
_glfw.x11.NET_SUPPORTED,
XA_ATOM,
(unsigned char**) &supportedAtoms);
const unsigned long atomCount =
_glfwGetWindowPropertyX11(_glfw.x11.root,
_glfw.x11.NET_SUPPORTED,
XA_ATOM,
(unsigned char**) &supportedAtoms);
if (!supportedAtoms)
return;
// See which of the atoms we support that are supported by the WM
_glfw.x11.NET_WM_STATE =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE");
_glfw.x11.NET_WM_STATE_ABOVE =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
_glfw.x11.NET_WM_STATE_FULLSCREEN =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
_glfw.x11.NET_WM_FULLSCREEN_MONITORS =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
_glfw.x11.NET_WM_WINDOW_TYPE =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
_glfw.x11.NET_WORKAREA =
getSupportedAtom(supportedAtoms, atomCount, "_NET_WORKAREA");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
_glfw.x11.NET_CURRENT_DESKTOP =
getSupportedAtom(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
_glfw.x11.NET_ACTIVE_WINDOW =
getSupportedAtom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
_glfw.x11.NET_FRAME_EXTENTS =
getSupportedAtom(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
getSupportedAtom(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
XFree(supportedAtoms);
}
@@ -502,8 +503,11 @@ static Window createHelperWindow(void)
// X error handler
//
static int errorHandler(Display *display UNUSED, XErrorEvent* event)
static int errorHandler(Display *display, XErrorEvent* event)
{
if (_glfw.x11.display != display)
return 0;
_glfw.x11.errorCode = event->error_code;
return 0;
}

27
glfw/x11_monitor.c vendored
View File

@@ -241,7 +241,8 @@ void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
if (_glfwCompareVideoModes(&current, best) == 0)
return;
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
@@ -289,7 +290,8 @@ void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor)
if (monitor->x11.oldMode == None)
return;
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
XRRSetCrtcConfig(_glfw.x11.display,
@@ -321,9 +323,10 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
{
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
{
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
if (ci)
{
if (xpos)
@@ -333,6 +336,7 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
XRRFreeCrtcInfo(ci);
}
XRRFreeScreenResources(sr);
}
}
@@ -352,8 +356,8 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
{
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
areaX = ci->x;
@@ -446,7 +450,8 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
{
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
@@ -493,17 +498,19 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
{
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
{
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRScreenResources* sr =
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
if (ci) {
if (ci)
{
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
if (mi) // mi can be NULL if the monitor has been disconnected
*mode = vidmodeFromModeInfo(mi, ci);
XRRFreeCrtcInfo(ci);
}
XRRFreeScreenResources(sr);
}
else

117
glfw/x11_window.c vendored
View File

@@ -455,22 +455,41 @@ static void updateCursorImage(_GLFWwindow* window)
}
}
// Enable XI2 raw mouse motion events
//
static void enableRawMouseMotion(_GLFWwindow* window UNUSED)
{
XIEventMask em;
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
// Disable XI2 raw mouse motion events
//
static void disableRawMouseMotion(_GLFWwindow* window UNUSED)
{
XIEventMask em;
unsigned char mask[] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
// Apply disabled cursor mode to a focused window
//
static void disableCursor(_GLFWwindow* window)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
if (window->rawMouseMotion)
enableRawMouseMotion(window);
_glfw.x11.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
@@ -490,17 +509,8 @@ static void disableCursor(_GLFWwindow* window)
//
static void enableCursor(_GLFWwindow* window)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
if (window->rawMouseMotion)
disableRawMouseMotion(window);
_glfw.x11.disabledCursorWindow = NULL;
XUngrabPointer(_glfw.x11.display, CurrentTime);
@@ -1122,6 +1132,7 @@ static void processEvent(XEvent *event)
_GLFWwindow* window = _glfw.x11.disabledCursorWindow;
if (window &&
window->rawMouseMotion &&
event->xcookie.extension == _glfw.x11.xi.majorOpcode &&
XGetEventData(_glfw.x11.display, &event->xcookie) &&
event->xcookie.evtype == XI_RawMotion)
@@ -1166,8 +1177,11 @@ static void processEvent(XEvent *event)
{
XkbEvent *kb_event = (XkbEvent*)event;
switch(kb_event->any.xkb_type) {
case XkbNewKeyboardNotify:
if(!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
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;
}
/* fallthrough */
case XkbMapNotify:
{
@@ -1265,13 +1279,13 @@ static void processEvent(XEvent *event)
// Modern X provides scroll events as mouse button presses
else if (event->xbutton.button == Button4)
_glfwInputScroll(window, 0.0, 1.0, 0);
_glfwInputScroll(window, 0.0, 1.0, 0, mods);
else if (event->xbutton.button == Button5)
_glfwInputScroll(window, 0.0, -1.0, 0);
_glfwInputScroll(window, 0.0, -1.0, 0, mods);
else if (event->xbutton.button == Button6)
_glfwInputScroll(window, 1.0, 0.0, 0);
_glfwInputScroll(window, 1.0, 0.0, 0, mods);
else if (event->xbutton.button == Button7)
_glfwInputScroll(window, -1.0, 0.0, 0);
_glfwInputScroll(window, -1.0, 0.0, 0, mods);
else
{
@@ -1363,7 +1377,7 @@ static void processEvent(XEvent *event)
{
if (_glfw.x11.disabledCursorWindow != window)
return;
if (_glfw.x11.xi.available)
if (window->rawMouseMotion)
return;
const int dx = x - window->x11.lastCursorPosX;
@@ -1408,12 +1422,18 @@ static void processEvent(XEvent *event)
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
{
Window dummy;
_glfwGrabErrorHandlerX11();
XTranslateCoordinates(_glfw.x11.display,
window->x11.parent,
_glfw.x11.root,
xpos, ypos,
&xpos, &ypos,
&dummy);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to translate ConfigureNotiy co-ords for reparented window");
return;
}
}
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
@@ -1550,17 +1570,21 @@ static void processEvent(XEvent *event)
const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff;
const int yabs = (event->xclient.data.l[2]) & 0xffff;
Window dummy;
int xpos, ypos;
int xpos = 0, ypos = 0;
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return;
_glfwGrabErrorHandlerX11();
XTranslateCoordinates(_glfw.x11.display,
_glfw.x11.root,
window->x11.handle,
xabs, yabs,
&xpos, &ypos,
&dummy);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success)
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position");
_glfwInputCursorPos(window, xpos, ypos);
@@ -1815,7 +1839,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
Visual* visual;
Visual* visual = NULL;
int depth;
if (ctxconfig->client != GLFW_NO_API)
@@ -1841,8 +1865,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
}
if (ctxconfig->client == GLFW_NO_API ||
ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
if (!visual)
{
visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen);
depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen);
@@ -1988,10 +2011,14 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
{
Window dummy;
int x, y;
int x = 0, y = 0;
_glfwGrabErrorHandlerX11();
XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root,
0, 0, &x, &y, &dummy);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success)
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get window position");
if (xpos)
*xpos = x;
@@ -2390,6 +2417,7 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
{
return maximized;
}
const unsigned long count =
_glfwGetWindowPropertyX11(window->x11.handle,
_glfw.x11.NET_WM_STATE,
@@ -2616,6 +2644,25 @@ _glfwDispatchX11Events(void) {
return dispatched;
}
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled)
{
if (!_glfw.x11.xi.available)
return;
if (_glfw.x11.disabledCursorWindow != window)
return;
if (enabled)
enableRawMouseMotion(window);
else
disableRawMouseMotion(window);
}
bool _glfwPlatformRawMouseMotionSupported(void)
{
return _glfw.x11.xi.available;
}
void _glfwPlatformPollEvents(void)
{
_glfwDispatchX11Events();

View File

@@ -3,6 +3,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from contextlib import suppress
from typing import TYPE_CHECKING, List, Optional, Tuple
@@ -12,7 +13,7 @@ from kitty.constants import cache_dir
from kitty.typing import BossType
from ..tui.handler import result_handler
from ..tui.operations import alternate_screen, styled
from ..tui.operations import alternate_screen, set_cursor_visible, styled
if TYPE_CHECKING:
import readline
@@ -69,7 +70,7 @@ class HistoryCompleter:
def option_text() -> str:
return '''\
--type -t
choices=line
choices=line,yesno
default=line
Type of input. Defaults to asking for a line of text.
@@ -96,14 +97,30 @@ class Response(TypedDict):
response: Optional[str]
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
import tty
with alternate_screen():
if cli_opts.message:
print(styled(cli_opts.message, bold=True))
print()
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o', set_cursor_visible(False))
sys.stdout.flush()
tty.setraw(sys.stdin.fileno())
try:
response = sys.stdin.buffer.read(1)
yes = response in (b'y', b'Y', b'\r', b'\n' b' ')
return {'items': items, 'response': 'y' if yes else 'n'}
finally:
sys.stdout.write(set_cursor_visible(True))
tty.setcbreak(sys.stdin.fileno())
sys.stdout.flush()
def main(args: List[str]) -> Response:
# For some reason importing readline in a key handler in the main kitty process
# causes a crash of the python interpreter, probably because of some global
# lock
global readline
import readline as rl
readline = rl
from kitty.shell import init_readline
msg = 'Ask the user for input'
try:
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty ask', result_class=AskCLIOptions)
@@ -113,6 +130,12 @@ def main(args: List[str]) -> Response:
input('Press enter to quit...')
raise SystemExit(e.code)
if cli_opts.type == 'yesno':
return yesno(cli_opts, items)
import readline as rl
readline = rl
from kitty.shell import init_readline
init_readline(readline)
response = None
@@ -134,7 +157,6 @@ def handle_result(args: List[str], data: Response, target_window_id: int, boss:
if __name__ == '__main__':
import sys
ans = main(sys.argv)
if ans:
print(ans)

View File

@@ -12,6 +12,7 @@ from pygments.formatter import Formatter # type: ignore
from pygments.lexers import get_lexer_for_filename # type: ignore
from pygments.util import ClassNotFound # type: ignore
from kitty.multiprocessing import get_process_pool_executor
from kitty.rgb import color_as_sgr, parse_sharp
from .collect import Collection, Segment, data_for_path, lines_for_path
@@ -139,7 +140,7 @@ def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
jobs = {}
ans: Dict[str, DiffHighlight] = {}
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
with get_process_pool_executor(prefer_fork=True) as executor:
for path, item_type, other_path in collection:
if item_type != 'rename':
for p in (path, other_path):

View File

@@ -113,13 +113,13 @@ class DiffHandler(Handler):
return self.scroll_lines(amt)
if func == 'change_context':
new_ctx = self.current_context_count
to = int(args[0])
to = args[0]
if to == 'all':
new_ctx = 100000
elif to == 'default':
new_ctx = self.original_context_count
else:
new_ctx += to
new_ctx += int(to)
return self.change_context_count(new_ctx)
if func == 'start_search':
self.start_search(bool(args[0]), bool(args[1]))

View File

@@ -419,10 +419,11 @@ programs.
--type
default=url
choices=url,regex,path,line,hash,word,linenum
The type of text to search for. A value of :code:`linenum` looks for error messages
using the pattern specified with :option:`--regex`, which must have the named groups,
path and line. If not specified, will look for :code:`path:line`.
The :option:`--linenum-action` option controls what to do with the selected error message.
The type of text to search for. A value of :code:`linenum` is special, it looks
for error messages using the pattern specified with :option:`--regex`, which
must have the named groups, :code:`path` and :code:`line`. If not specified,
will look for :code:`path:line`. The :option:`--linenum-action` option
controls what to do with the selected error message, other options are ignored.
--regex

View File

@@ -2,9 +2,11 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import contextlib
import mimetypes
import os
import re
import socket
import signal
import sys
import zlib
@@ -339,6 +341,16 @@ help_text = (
usage = 'image-file-or-url-or-directory ...'
@contextlib.contextmanager
def socket_timeout(seconds: int) -> Generator[None, None, None]:
old = socket.getdefaulttimeout()
socket.setdefaulttimeout(seconds)
try:
yield
finally:
socket.setdefaulttimeout(old)
def process_single_item(
item: Union[bytes, str],
args: IcatCLIOptions,
@@ -358,7 +370,8 @@ def process_single_item(
from urllib.request import urlretrieve
with NamedTemporaryFile(prefix='url-image-data-', delete=False) as tf:
try:
urlretrieve(item, filename=tf.name)
with socket_timeout(30):
urlretrieve(item, filename=tf.name)
except Exception as e:
raise SystemExit('Failed to download image at URL: {} with error: {}'.format(item, e))
item = tf.name

View File

@@ -4,15 +4,16 @@
from enum import IntFlag
from itertools import chain
from typing import List, Optional, Sequence, Tuple
from typing import Sequence, Tuple
from .fast_data_types import (
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
os_window_has_background_image
)
from .options_stub import Options
from .typing import LayoutType
from .utils import load_shaders
from .typing import WindowType, LayoutType
from .window_list import WindowGroup, WindowList
class BorderColor(IntFlag):
@@ -30,10 +31,12 @@ def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, lef
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: WindowType, borders: bool = False) -> None:
geometry = window.geometry
pl, pt = window.effective_padding('left'), window.effective_padding('top')
pr, pb = window.effective_padding('right'), window.effective_padding('bottom')
def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: WindowGroup, borders: bool = False) -> None:
geometry = wg.geometry
if geometry is None:
return
pl, pt = wg.effective_padding('left'), wg.effective_padding('top')
pr, pb = wg.effective_padding('right'), wg.effective_padding('bottom')
left = geometry.left - pl
top = geometry.top - pt
lr = geometry.right
@@ -41,7 +44,7 @@ def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: Wi
bt = geometry.bottom
bottom = bt + pb
if borders:
width = window.effective_border()
width = wg.effective_border()
bt = bottom
lr = right
left -= width
@@ -69,8 +72,7 @@ class Borders:
def __call__(
self,
windows: List[WindowType],
active_window: Optional[WindowType],
all_windows: WindowList,
current_layout: LayoutType,
extra_blank_rects: Sequence[Tuple[int, int, int, int]],
draw_window_borders: bool = True,
@@ -82,31 +84,33 @@ class Borders:
left, top, right, bottom = br
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg)
bw = 0
if windows:
bw = windows[0].effective_border()
groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True))
if groups:
bw = groups[0].effective_border()
draw_borders = bw > 0 and draw_window_borders
if draw_borders:
border_data = current_layout.resolve_borders(windows, active_window)
border_data = current_layout.resolve_borders(all_windows)
active_group = all_windows.active_group
for i, w in enumerate(windows):
window_bg = w.screen.color_profile.default_bg
for i, wg in enumerate(groups):
window_bg = wg.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders:
# Draw the border rectangles
if w is active_window and self.draw_active_borders:
if wg is active_group and self.draw_active_borders:
color = BorderColor.active
else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
color = BorderColor.bell if wg.needs_attention else BorderColor.inactive
try:
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(self.os_window_id, self.tab_id, colors, w, borders=True)
draw_edges(self.os_window_id, self.tab_id, colors, wg, borders=True)
except StopIteration:
pass
if not has_background_image:
# Draw the background rectangles over the padding region
colors = window_bg, window_bg, window_bg, window_bg
draw_edges(self.os_window_id, self.tab_id, colors, w)
draw_edges(self.os_window_id, self.tab_id, colors, wg)
color = BorderColor.inactive
for (left, top, right, bottom) in current_layout.window_independent_borders(windows, active_window):
for (left, top, right, bottom) in current_layout.window_independent_borders(all_windows):
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, color)

View File

@@ -27,16 +27,18 @@ from .constants import (
appname, config_dir, is_macos, kitty_exe, supports_primary_selection
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
ChildMonitor, background_opacity_of, change_background_opacity,
change_os_window_state, cocoa_set_menubar_title, create_os_window,
current_os_window, destroy_global_data, focus_os_window,
get_clipboard_string, global_font_size, mark_os_window_for_close,
os_window_font_size, patch_global_colors, safe_pipe, set_background_image,
set_boss, set_clipboard_string, set_in_sequence_mode, thread_write,
current_application_quit_request, current_os_window, destroy_global_data,
focus_os_window, get_clipboard_string, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
safe_pipe, set_application_quit_request, set_background_image, set_boss,
set_clipboard_string, set_in_sequence_mode, thread_write,
toggle_fullscreen, toggle_maximized
)
from .keys import get_shortcut, shortcut_matches
from .layout import set_layout_options
from .layout.base import set_layout_options
from .options_stub import Options
from .rgb import Color, color_from_int
from .session import Session, create_sessions
@@ -304,10 +306,9 @@ class Boss:
@property
def active_window_for_cwd(self) -> Optional[Window]:
w = self.active_window
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
w = self.window_id_map[w.overlay_for]
return w
t = self.active_tab
if t is not None:
return t.active_window_for_cwd
def new_os_window_with_cwd(self, *args: str) -> None:
w = self.active_window_for_cwd
@@ -336,7 +337,13 @@ class Boss:
if not getattr(err, 'hide_traceback', False):
response['tb'] = traceback.format_exc()
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
no_response = False
try:
no_response = json.loads(cmd).get('no_response')
except Exception:
pass
if not no_response:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
return response
def remote_control(self, *args: str) -> None:
@@ -355,15 +362,16 @@ class Boss:
self.show_error(_('remote_control mapping failed'), str(e))
def peer_message_received(self, msg_bytes: bytes) -> Optional[bytes]:
msg = msg_bytes.decode('utf-8')
cmd_prefix = '\x1bP@kitty-cmd'
if msg.startswith(cmd_prefix):
cmd = msg[len(cmd_prefix):-2]
cmd_prefix = b'\x1bP@kitty-cmd'
terminator = b'\x1b\\'
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
response = self._handle_remote_command(cmd, from_peer=True)
if response is None:
return None
return (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8')
data = json.loads(msg)
return cmd_prefix + json.dumps(response).encode('utf-8') + terminator
data = json.loads(msg_bytes.decode('utf-8'))
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
from .cli_stub import CLIOptions
startup_id = data.get('startup_id')
@@ -717,6 +725,32 @@ class Boss:
text = '\n'.join(parse_uri_list(text))
w.paste(text)
def close_os_window(self) -> None:
tm = self.active_tab_manager
if tm is not None:
self.confirm_os_window_close(tm.os_window_id)
def confirm_os_window_close(self, os_window_id: int) -> None:
tm = self.os_window_map.get(os_window_id)
needs_confirmation = tm is not None and self.opts.confirm_os_window_close > 0 and tm.number_of_windows >= self.opts.confirm_os_window_close
if not needs_confirmation:
mark_os_window_for_close(os_window_id)
return
if tm is not None:
w = tm.active_window
self._run_kitten('ask', ['--type=yesno', '--message', _(
'Are you sure you want to close this OS window, it has {}'
' windows running?').format(tm.number_of_windows)],
window=w,
custom_callback=partial(self.handle_close_os_window_confirmation, os_window_id)
)
def handle_close_os_window_confirmation(self, os_window_id: int, data: Dict[str, Any], *a: Any) -> None:
if data['response'] == 'y':
mark_os_window_for_close(os_window_id)
else:
mark_os_window_for_close(os_window_id, NO_CLOSE_REQUESTED)
def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None:
self.cached_values['window-size'] = viewport_width, viewport_height
tm = self.os_window_map.pop(os_window_id, None)
@@ -730,6 +764,28 @@ class Boss:
if action is not None:
action()
def quit(self, *args: Any) -> None:
tm = self.active_tab
num = 0
for q in self.os_window_map.values():
num += q.number_of_windows
needs_confirmation = tm is not None and self.opts.confirm_os_window_close > 0 and num >= self.opts.confirm_os_window_close
if not needs_confirmation:
set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED)
return
if current_application_quit_request() == CLOSE_BEING_CONFIRMED:
return
assert tm is not None
self._run_kitten('ask', ['--type=yesno', '--message', _(
'Are you sure you want to quit kitty, it has {} windows running?').format(num)],
window=tm.active_window,
custom_callback=self.handle_quit_confirmation
)
set_application_quit_request(CLOSE_BEING_CONFIRMED)
def handle_quit_confirmation(self, data: Dict[str, Any], *a: Any) -> None:
set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED if data['response'] == 'y' else NO_CLOSE_REQUESTED)
def notify_on_os_window_death(self, address: str) -> None:
import socket
s = socket.socket(family=socket.AF_UNIX)
@@ -742,9 +798,9 @@ class Boss:
def display_scrollback(self, window: Window, data: Optional[bytes], cmd: Optional[List[str]]) -> None:
tab = self.active_tab
if tab is not None and window.overlay_for is None:
if tab is not None:
tab.new_special_window(
SpecialWindow(cmd, data, _('History'), overlay_for=window.id),
SpecialWindow(cmd, data, _('History'), overlay_for=window.id, cwd=window.cwd_of_child),
copy_colors_from=self.active_window
)
@@ -785,7 +841,7 @@ class Boss:
if end_kitten.no_ui:
return end_kitten(None, getattr(w, 'id', None), self)
if w is not None and tab is not None and w.overlay_for is None:
if w is not None and tab is not None:
args[0:0] = [config_dir, kitten]
if input_data is None:
type_of_input = end_kitten.type_of_input
@@ -886,24 +942,35 @@ class Boss:
custom_callback=done, action_on_removal=done2)
def kitty_shell(self, window_type: str) -> None:
cmd = ['@', kitty_exe(), '@']
kw: Dict[str, Any] = {}
cmd = [kitty_exe(), '@']
aw = self.active_window
if aw is not None:
kw['env'] = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)}
if window_type == 'tab':
self._new_tab(cmd)
tab = self._new_tab(SpecialWindow(cmd, **kw))
if tab is not None:
for w in tab:
w.allow_remote_control = True
elif window_type == 'os_window':
os_window_id = self._new_os_window(cmd)
self.os_window_map[os_window_id]
os_window_id = self._new_os_window(SpecialWindow(cmd, **kw))
for tab in self.os_window_map[os_window_id]:
for w in tab:
w.allow_remote_control = True
elif window_type == 'overlay':
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
if aw is not None and tab is not None:
kw['overlay_for'] = aw.id
tab.new_special_window(SpecialWindow(cmd, **kw), allow_remote_control=True)
else:
self._new_window(cmd)
tab = self.active_tab
if tab is not None:
tab.new_special_window(SpecialWindow(cmd, **kw), allow_remote_control=True)
def switch_focus_to(self, window_idx: int) -> None:
def switch_focus_to(self, window_id: int) -> None:
tab = self.active_tab
if tab:
tab.set_active_window_idx(window_idx)
tab.set_active_window(window_id)
def open_url(self, url: str, program: Optional[Union[str, List[str]]] = None, cwd: Optional[str] = None) -> None:
if url:
@@ -1037,7 +1104,7 @@ class Boss:
continue
arg = q
cmdline.append(arg)
overlay_for = w.id if w and as_overlay and w.overlay_for is None else None
overlay_for = w.id if w and as_overlay else None
return SpecialWindow(cmd, input_data, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
def run_background_process(
@@ -1308,11 +1375,8 @@ class Boss:
else:
return
underlaid_window, overlaid_window = src_tab.detach_window(window)
if underlaid_window:
target_tab.attach_window(underlaid_window)
if overlaid_window:
target_tab.attach_window(overlaid_window)
for detached_window in src_tab.detach_window(window):
target_tab.attach_window(detached_window)
self._cleanup_tab_after_window_removal(src_tab)
target_tab.make_active()
@@ -1358,8 +1422,6 @@ class Boss:
done_tab_id = tab_id_map[int(data['groupdicts'][0]['index'])]
def done2(target_window_id: int, self: Boss) -> None:
if not hasattr(done, 'tab_id'):
return
tab_id = done_tab_id
target_window = None
for w in self.all_windows:

View File

@@ -346,7 +346,10 @@ parse_input(ChildMonitor *self) {
}
if (UNLIKELY(kill_signal_received)) {
global_state.terminate = true;
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
global_state.has_pending_closes = true;
request_tick_callback();
kill_signal_received = false;
} else {
count = self->count;
for (size_t i = 0; i < count; i++) {
@@ -461,10 +464,10 @@ set_iutf8(int UNUSED fd, bool UNUSED on) {
static PyObject*
pyset_iutf8(ChildMonitor *self, PyObject *args) {
unsigned long window_id;
id_type window_id;
int on;
PyObject *found = Py_False;
if (!PyArg_ParseTuple(args, "kp", &window_id, &on)) return NULL;
if (!PyArg_ParseTuple(args, "Kp", &window_id, &on)) return NULL;
children_mutex(lock);
for (size_t i = 0; i < self->count; i++) {
if (children[i].id == window_id) {
@@ -523,17 +526,6 @@ change_menubar_title(PyObject *title UNUSED) {
#endif
}
static inline bool
update_window_title(Window *w, OSWindow *os_window) {
if (w->title && w->title != os_window->window_title) {
os_window->window_title = w->title;
Py_INCREF(os_window->window_title);
set_os_window_title(os_window, PyUnicode_AsUTF8(w->title));
return true;
}
return false;
}
static inline bool
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) {
#define TD os_window->tab_bar_render_data
@@ -577,7 +569,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
*active_window_id = w->id;
collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window);
if (w->cursor_visible_at_last_render != WD.screen->cursor_render_info.is_visible || w->last_cursor_x != WD.screen->cursor_render_info.x || w->last_cursor_y != WD.screen->cursor_render_info.y || w->last_cursor_shape != WD.screen->cursor_render_info.shape) needs_render = true;
update_window_title(w, os_window);
set_os_window_title_from_window(w, os_window);
*active_window_bg = window_bg;
} else WD.screen->cursor_render_info.is_visible = false;
if (send_cell_data_to_gpu(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
@@ -625,15 +617,6 @@ render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_windo
#undef TD
}
static inline void
update_os_window_title(OSWindow *os_window) {
Tab *tab = os_window->tabs + os_window->active_tab;
if (tab->num_windows) {
Window *w = tab->windows + tab->active_window;
update_window_title(w, os_window);
}
}
static void
draw_resizing_text(OSWindow *w) {
char text[32] = {0};
@@ -649,7 +632,13 @@ draw_resizing_text(OSWindow *w) {
static inline bool
no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) {
bool ans = now - w->last_render_frame_received_at > max_wait;
if (ans && global_state.debug_rendering) log_error("No render frame received in %.2f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
if (ans && global_state.debug_rendering) {
if (global_state.is_wayland) {
log_error("No render frame received in %.2f seconds", monotonic_t_to_s_double(max_wait));
} else {
log_error("No render frame received in %.2f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
}
}
return ans;
}
@@ -864,32 +853,53 @@ process_pending_resizes(monotonic_t now) {
}
static inline void
close_all_windows(void) {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
close_os_window(ChildMonitor *self, OSWindow *os_window) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
}
static inline bool
process_pending_closes(ChildMonitor *self) {
global_state.has_pending_closes = false;
if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) {
call_boss(quit, "");
}
if (global_state.quit_request == IMPERATIVE_CLOSE_REQUESTED) {
for (size_t w = 0; w < global_state.num_os_windows; w++) global_state.os_windows[w].close_request = IMPERATIVE_CLOSE_REQUESTED;
}
bool has_open_windows = false;
for (size_t w = global_state.num_os_windows; w > 0; w--) {
OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
} else has_open_windows = true;
switch(os_window->close_request) {
case NO_CLOSE_REQUESTED:
has_open_windows = true;
break;
case CONFIRMABLE_CLOSE_REQUESTED:
os_window->close_request = CLOSE_BEING_CONFIRMED;
call_boss(confirm_os_window_close, "K", os_window->id);
if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) {
close_os_window(self, os_window);
} else has_open_windows = true;
break;
case CLOSE_BEING_CONFIRMED:
has_open_windows = true;
break;
case IMPERATIVE_CLOSE_REQUESTED:
close_os_window(self, os_window);
break;
}
}
global_state.has_pending_closes = false;
#ifdef __APPLE__
if (!OPT(macos_quit_when_last_window_closed)) {
if (!has_open_windows && !application_quit_requested()) has_open_windows = true;
if (!has_open_windows && global_state.quit_request != IMPERATIVE_CLOSE_REQUESTED) has_open_windows = true;
}
#endif
return has_open_windows;
return !has_open_windows;
}
#ifdef __APPLE__
@@ -950,23 +960,16 @@ process_global_state(void *data) {
cocoa_pending_actions = 0;
}
#endif
if (global_state.terminate) {
global_state.terminate = false;
close_all_windows();
#ifdef __APPLE__
request_application_quit();
#endif
}
report_reaped_pids();
bool has_open_windows = true;
if (global_state.has_pending_closes) has_open_windows = process_pending_closes(self);
if (has_open_windows) {
bool should_quit = false;
if (global_state.has_pending_closes) should_quit = process_pending_closes(self);
if (should_quit) {
stop_main_loop();
} else {
if (maximum_wait >= 0) {
if (maximum_wait == 0) request_tick_callback();
else state_check_timer_enabled = true;
}
} else {
stop_main_loop();
}
update_main_loop_timer(state_check_timer, MAX(0, maximum_wait), state_check_timer_enabled);
}
@@ -1279,7 +1282,7 @@ typedef struct {
char *data;
size_t capacity, used;
int fd;
bool finished, close_socket;
bool finished, close_socket, is_peer_command;
} PeerReadData;
static PeerReadData empty_prd = {.fd = -1, 0};
@@ -1329,6 +1332,41 @@ accept_peer(int listen_fd, bool shutting_down) {
return true;
}
#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);
Message *m = self->messages + self->messages_count++;
m->data = buf; m->sz = sz; m->fd = fd;
children_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;
}
}
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;
}
static inline bool
read_from_peer(ChildMonitor *self, int s) {
bool read_finished = false;
@@ -1337,26 +1375,28 @@ read_from_peer(ChildMonitor *self, int s) {
#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 >= 1024 * 1024) failed("Ignoring too large message from peer");
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;
children_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
m->data = rd->data; rd->data = NULL; m->sz = rd->used; m->fd = s;
children_mutex(unlock);
wakeup_main_loop();
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 rd->used += n;
} 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;
}
}
@@ -1541,6 +1581,7 @@ static PyMethodDef methods[] = {
METHOD(main_loop, METH_NOARGS)
METHOD(mark_for_close, METH_VARARGS)
METHOD(resize_pty, METH_VARARGS)
{"set_iutf8_winid", (PyCFunction)pyset_iutf8, METH_VARARGS, ""},
{NULL} /* Sentinel */
};
@@ -1582,7 +1623,6 @@ static PyMethodDef module_methods[] = {
{"add_timer", (PyCFunction)add_python_timer, METH_VARARGS, ""},
{"remove_timer", (PyCFunction)remove_python_timer, METH_VARARGS, ""},
METHODB(monitor_pid, METH_VARARGS),
{"set_iutf8_winid", (PyCFunction)pyset_iutf8, METH_VARARGS, ""},
METHODB(cocoa_set_menubar_title, METH_VARARGS),
{NULL} /* Sentinel */
};

View File

@@ -12,6 +12,8 @@
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
static inline char**
serialize_string_tuple(PyObject *src) {
@@ -93,7 +95,7 @@ spawn(PyObject *self UNUSED, PyObject *args) {
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
#ifdef TIOCSTTY
#ifdef TIOCSCTTY
// 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

View File

@@ -289,9 +289,9 @@ class PrintHelpForSeq:
leading_indent = indent
j = '\n' + (' ' * indent)
lines: List[str] = []
for l in text.splitlines():
if l:
lines.extend(wrap(l, limit=linesz - indent))
for ln in text.splitlines():
if ln:
lines.extend(wrap(ln, limit=linesz - indent))
else:
lines.append('')
a((' ' * leading_indent) + j.join(lines))
@@ -409,7 +409,10 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, e
elif otype == 'list':
t = 'typing.Sequence[str]'
elif otype in ('choice', 'choices'):
t = 'str'
if opt['choices']:
t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt['choices']))
else:
t = 'str'
elif otype.startswith('bool-'):
t = 'bool'
else:

View File

@@ -60,8 +60,8 @@ def screen_cursor_to_column(c: int) -> None:
write(CSI + '%dG' % c)
def screen_cursor_to_line(l: int) -> None:
write(CSI + '%dd' % l)
def screen_cursor_to_line(ln: int) -> None:
write(CSI + '%dd' % ln)
def screen_set_mode(x: int, private: bool) -> None:

View File

@@ -14,6 +14,7 @@
// Needed for _NSGetProgname
#include <crt_externs.h>
#include <objc/runtime.h>
#include <xlocale.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -419,7 +420,13 @@ cocoa_get_lang(PyObject UNUSED *self) {
locale = [[NSLocale currentLocale] localeIdentifier];
}
if (!locale) { Py_RETURN_NONE; }
return Py_BuildValue("s", [locale UTF8String]);
// Make sure the locale value is valid, that is it can be used
// to construct an actual locale
const char* locale_utf8 = [locale UTF8String];
locale_t test_locale = newlocale(LC_ALL_MASK, locale_utf8, NULL);
if (!test_locale) { Py_RETURN_NONE; }
freelocale(test_locale);
return Py_BuildValue("s", locale_utf8);
} // autoreleasepool
}

View File

@@ -9,7 +9,7 @@ from typing import (
Set, Tuple, Union, get_type_hints
)
from .utils import to_bool
from .utils import Choice, to_bool
def to_string(x: str) -> str:
@@ -56,6 +56,8 @@ class Option:
if type(self.option_type) is type:
return type_name(self.option_type)
if isinstance(self.option_type, Choice):
return 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in self.option_type.all_choices))
th = get_type_hints(self.option_type)
try:
rettype = th['return']

View File

@@ -68,17 +68,21 @@ def python_string(text: str) -> str:
return ans
def choices(*choices: str) -> Callable[[str], str]:
defval = choices[0]
uc = frozenset(choices)
class Choice:
def choice(x: str) -> str:
def __init__(self, choices: Sequence[str]):
self.defval = choices[0]
self.all_choices = frozenset(choices)
def __call__(self, x: str) -> str:
x = x.lower()
if x not in uc:
x = defval
if x not in self.all_choices:
x = self.defval
return x
return choice
def choices(*choices: str) -> Choice:
return Choice(choices)
def parse_line(

View File

@@ -9,8 +9,8 @@ import sys
from contextlib import contextmanager, suppress
from functools import partial
from typing import (
Any, Callable, Dict, Generator, Iterable, List, Match, NamedTuple,
Optional, Sequence, Set, Tuple, Type, Union
Any, Callable, Dict, Generator, Iterable, List, NamedTuple, Optional,
Sequence, Set, Tuple, Type, Union
)
from . import fast_data_types as defines
@@ -24,7 +24,7 @@ from .constants import cache_dir, defconf, is_macos
from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub
from .typing import EdgeLiteral, TypedDict
from .utils import log_error
from .utils import expandvars, log_error
KeySpec = Tuple[int, bool, int]
KeyMap = Dict[KeySpec, 'KeyAction']
@@ -140,6 +140,20 @@ def float_parse(func: str, rest: str) -> FuncArgsType:
return func, (float(rest),)
@func_with_args('signal_child')
def signal_child_parse(func: str, rest: str) -> FuncArgsType:
import signal
signals = []
for q in rest.split():
try:
signum = getattr(signal, q.upper())
except AttributeError:
log_error(f'Unknown signal: {rest} ignoring')
else:
signals.append(signum)
return func, tuple(signals)
@func_with_args('change_font_size')
def parse_change_font_size(func: str, rest: str) -> Tuple[str, Tuple[bool, Optional[str], float]]:
vals = rest.strip().split(maxsplit=1)
@@ -311,7 +325,7 @@ def parse_marker_spec(ftype: str, parts: Sequence[str]) -> Tuple[str, Union[str,
def toggle_marker(func: str, rest: str) -> FuncArgsType:
parts = rest.split(maxsplit=1)
if len(parts) != 2:
raise ValueError('{} if not a valid marker specification'.format(rest))
raise ValueError('{} is not a valid marker specification'.format(rest))
ftype, spec = parts
parts = spec.split()
return func, list(parse_marker_spec(ftype, parts))
@@ -585,20 +599,6 @@ def handle_deprecated_macos_show_window_title_in_menubar_alias(key: str, val: st
ans['macos_show_window_title_in'] = macos_show_window_title_in
def expandvars(val: str, env: Dict[str, str] = {}) -> str:
def sub(m: Match) -> str:
key = m.group(1)
result = env.get(key)
if result is None:
result = os.environ.get(key)
if result is None:
result = m.group()
return result
return re.sub(r'\$\{(\S+?)\}', sub, val)
@special_handler
def handle_env(key: str, val: str, ans: Dict[str, Any]) -> None:
key, val = val.partition('=')[::2]

View File

@@ -18,7 +18,7 @@ from .conf.utils import (
)
from .constants import FloatEdges, config_dir, is_macos
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts
from .layout.interface import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
from .utils import log_error
@@ -304,6 +304,9 @@ or by defining shortcuts for it in kitty.conf, for example::
map alt+2 disable_ligatures_in all never
map alt+3 disable_ligatures_in tab cursor
Note that this refers to programming ligatures, typically implemented using the
:code:`calt` OpenType feature. For disabling general ligatures, use the
:opt:`font_features` setting.
'''))
o('font_features', 'none', long_text=_('''
@@ -445,7 +448,8 @@ Program with which to view scrollback in a new window. The scrollback buffer is
passed as STDIN to this program. If you change it, make sure the program you
use can handle ANSI escape sequences for colors and text formatting.
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
representing which line should be at the top of the screen.'''))
representing which line should be at the top of the screen. Similarly CURSOR_LINE and CURSOR_COLUMN
will be replaced by the current cursor position.'''))
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
@@ -548,7 +552,7 @@ rectangular block with the mouse)'''))
o('terminal_select_modifiers', 'shift', option_type=to_modifiers, long_text=_('''
The modifiers to override mouse selection even when a terminal application has grabbed the mouse'''))
o('select_by_word_characters', ':@-./_~?&=%+#', long_text=_('''
o('select_by_word_characters', '@-./_~?&=%+#', long_text=_('''
Characters considered part of a word when double clicking. In addition to these characters
any character that is marked as an alphanumeric character in the unicode
database will be matched.'''))
@@ -778,6 +782,12 @@ this option can be used to keep the margins as small as possible when resizing t
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
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).
'''))
# }}}
g('tabbar') # {{{
@@ -788,6 +798,8 @@ def tab_separator(x: str) -> str:
for q in '\'"':
if x.startswith(q) and x.endswith(q):
x = x[1:-1]
if not x:
return ''
break
if not x.strip():
x = ('\xa0' * len(x)) if x else default_tab_separator
@@ -870,6 +882,11 @@ A template to render the tab title. The default just renders
the title. If you wish to include the tab-index as well,
use something like: :code:`{index}: {title}`. Useful
if you have shortcuts mapped for :code:`goto_tab N`.
In addition you can use :code:`{layout_name}` for the current
layout name and :code:`{num_windows}` for the number of windows
in the tab. Note that formatting is done by Python's string formatting
machinery, so you can use, for instance, :code:`{layout_name[:2].upper()}` to
show only the first two letters of the layout name, upper-cased.
'''))
o('active_tab_title_template', 'none', option_type=active_tab_title_template, long_text=_('''
Template to use for active tabs, if not specified falls back
@@ -1369,8 +1386,7 @@ if is_macos:
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
if is_macos:
k('close_tab', 'cmd+w', 'close_tab', _('Close tab'), add_to_docs=False)
# Not yet implemented
# k('close_os_window', 'shift+cmd+w', 'close_os_window', _('Close os window'), add_to_docs=False)
k('close_os_window', 'shift+cmd+w', 'close_os_window', _('Close OS window'), add_to_docs=False)
k('move_tab_forward', 'kitty_mod+.', 'move_tab_forward', _('Move tab forward'))
k('move_tab_backward', 'kitty_mod+,', 'move_tab_backward', _('Move tab backward'))
k('set_tab_title', 'kitty_mod+alt+t', 'set_tab_title', _('Set tab title'))

View File

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

View File

@@ -188,6 +188,9 @@
// *Horizontal position relative*: Same as :data:`CUF`.
#define HPR 'a'
// Repeat the preceding graphic character Ps times.
#define REP 'b'
// *Device Attributes*.
#define DA 'c'

View File

@@ -506,8 +506,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width);
}
if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) {
// center glyphs (two cell emoji and PUA glyphs)
CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f) - br.origin.x;
// center glyphs (two cell emoji, PUA glyphs, ligatures, etc)
CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f);
// FiraCode ligatures result in negative origins
if (br.origin.x > 0) delta -= br.origin.x;
if (delta >= 1.f) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta));
}
return true;

View File

@@ -216,59 +216,57 @@ PyInit_fast_data_types(void) {
if (m == NULL) return NULL;
init_monotonic();
if (m != NULL) {
if (!init_logging(m)) return NULL;
if (!init_LineBuf(m)) return NULL;
if (!init_HistoryBuf(m)) return NULL;
if (!init_Line(m)) return NULL;
if (!init_Cursor(m)) return NULL;
if (!init_child_monitor(m)) return NULL;
if (!init_ColorProfile(m)) return NULL;
if (!init_Screen(m)) return NULL;
if (!init_glfw(m)) return NULL;
if (!init_child(m)) return NULL;
if (!init_state(m)) return NULL;
if (!init_keys(m)) return NULL;
if (!init_graphics(m)) return NULL;
if (!init_shaders(m)) return NULL;
if (!init_mouse(m)) return NULL;
if (!init_kittens(m)) return NULL;
if (!init_png_reader(m)) return NULL;
if (!init_logging(m)) return NULL;
if (!init_LineBuf(m)) return NULL;
if (!init_HistoryBuf(m)) return NULL;
if (!init_Line(m)) return NULL;
if (!init_Cursor(m)) return NULL;
if (!init_child_monitor(m)) return NULL;
if (!init_ColorProfile(m)) return NULL;
if (!init_Screen(m)) return NULL;
if (!init_glfw(m)) return NULL;
if (!init_child(m)) return NULL;
if (!init_state(m)) return NULL;
if (!init_keys(m)) return NULL;
if (!init_graphics(m)) return NULL;
if (!init_shaders(m)) return NULL;
if (!init_mouse(m)) return NULL;
if (!init_kittens(m)) return NULL;
if (!init_png_reader(m)) return NULL;
#ifdef __APPLE__
if (!init_macos_process_info(m)) return NULL;
if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL;
if (!init_macos_process_info(m)) return NULL;
if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL;
#else
if (!init_freetype_library(m)) return NULL;
if (!init_fontconfig_library(m)) return NULL;
if (!init_desktop(m)) return NULL;
if (!init_freetype_library(m)) return NULL;
if (!init_fontconfig_library(m)) return NULL;
if (!init_desktop(m)) return NULL;
#endif
if (!init_fonts(m)) return NULL;
if (!init_fonts(m)) return NULL;
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DIM", DIM_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddIntConstant(m, "MARK", MARK_SHIFT);
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
PyModule_AddStringMacro(m, ERROR_PREFIX);
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DIM", DIM_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddIntConstant(m, "MARK", MARK_SHIFT);
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);
PyModule_AddStringMacro(m, KITTY_VCS_REV);
#endif
PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
PyModule_AddIntMacro(m, DECAWM);
PyModule_AddIntMacro(m, DECCOLM);
PyModule_AddIntMacro(m, DECOM);
PyModule_AddIntMacro(m, IRM);
PyModule_AddIntMacro(m, CSI);
PyModule_AddIntMacro(m, DCS);
PyModule_AddIntMacro(m, APC);
PyModule_AddIntMacro(m, OSC);
}
PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
PyModule_AddIntMacro(m, DECAWM);
PyModule_AddIntMacro(m, DECCOLM);
PyModule_AddIntMacro(m, DECOM);
PyModule_AddIntMacro(m, IRM);
PyModule_AddIntMacro(m, CSI);
PyModule_AddIntMacro(m, DCS);
PyModule_AddIntMacro(m, APC);
PyModule_AddIntMacro(m, OSC);
return m;
}

View File

@@ -253,17 +253,6 @@ typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE;
#define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0;
#define left_shift_line(line, at, num) { \
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
COPY_CELL(line, __i__ + (num), line, __i__) \
} \
if ((((line)->gpu_cells[(at)].attrs) & WIDTH_MASK) != 1) { \
(line)->cpu_cells[(at)].ch = BLANK_CHAR; \
(line)->gpu_cells[(at)].attrs = BLANK_CHAR ? 1 : 0; \
clear_sprite_position((line)->gpu_cells[(at)]); \
}\
}
#define ensure_space_for(base, array, type, num, capacity, initial_cap, zero_mem) \
if ((base)->capacity < num) { \
size_t _newcap = MAX((size_t)initial_cap, MAX(2 * (base)->capacity, (size_t)num)); \
@@ -313,8 +302,7 @@ void set_mouse_cursor(MouseShape);
void enter_event(void);
void mouse_event(int, int, int);
void focus_in_event(void);
void scroll_event(double, double, int);
void fake_scroll(int, bool);
void scroll_event(double, double, int, int);
void set_special_key_combo(int glfw_key, int mods, bool is_native);
void on_key_input(GLFWkeyevent *ev);
void request_window_attention(id_type, bool);

View File

@@ -30,18 +30,26 @@ static void* libsn_handle = NULL;
static PyObject*
init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) {
static bool done = false;
static const char* libname = "libstartup-notification-1.so";
// some installs are missing the .so symlink, so try the full name
static const char* libname2 = "libstartup-notification-1.so.0";
static const char* libname3 = "libstartup-notification-1.so.0.0.0";
if (!done) {
done = true;
libsn_handle = dlopen(libname, RTLD_LAZY);
if (libsn_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
if (libsn_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
const char* libnames[] = {
#if defined(_KITTY_STARTUP_NOTIFICATION_LIBRARY)
_KITTY_STARTUP_NOTIFICATION_LIBRARY,
#else
"libstartup-notification-1.so",
// some installs are missing the .so symlink, so try the full name
"libstartup-notification-1.so.0",
"libstartup-notification-1.so.0.0.0",
#endif
NULL
};
for (int i = 0; libnames[i]; i++) {
libsn_handle = dlopen(libnames[i], RTLD_LAZY);
if (libsn_handle) break;
}
if (libsn_handle == NULL) {
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror());
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libnames[0], dlerror());
return NULL;
}
dlerror(); /* Clear any existing error */
@@ -105,18 +113,26 @@ load_libcanberra_functions(void) {
static void
load_libcanberra(void) {
static const char* libname = "libcanberra.so";
// some installs are missing the .so symlink, so try the full name
static const char* libname2 = "libcanberra.so.0";
static const char* libname3 = "libcanberra.so.0.2.5";
static bool done = false;
if (done) return;
done = true;
libcanberra_handle = dlopen(libname, RTLD_LAZY);
if (libcanberra_handle == NULL) libcanberra_handle = dlopen(libname2, RTLD_LAZY);
if (libcanberra_handle == NULL) libcanberra_handle = dlopen(libname3, RTLD_LAZY);
const char* libnames[] = {
#if defined(_KITTY_CANBERRA_LIBRARY)
_KITTY_CANBERRA_LIBRARY,
#else
"libcanberra.so",
// some installs are missing the .so symlink, so try the full name
"libcanberra.so.0",
"libcanberra.so.0.2.5",
#endif
NULL
};
for (int i = 0; libnames[i]; i++) {
libcanberra_handle = dlopen(libnames[i], RTLD_LAZY);
if (libcanberra_handle) break;
}
if (libcanberra_handle == NULL) {
fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libname, dlerror());
fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libnames[0], dlerror());
return;
}
load_libcanberra_functions();

View File

@@ -10,6 +10,9 @@ from kitty.options_stub import Options
# Constants {{{
KITTY_VCS_REV: str
NO_CLOSE_REQUESTED: int
IMPERATIVE_CLOSE_REQUESTED: int
CLOSE_BEING_CONFIRMED: int
ERROR_PREFIX: str
GLSL_VERSION: int
GLFW_IBEAM_CURSOR: int
@@ -544,12 +547,16 @@ def update_window_title(
def update_window_visibility(
os_window_id: int, tab_id: int, window_id: int, window_idx: int,
os_window_id: int, tab_id: int, window_id: int,
visible: bool
) -> None:
pass
def sync_os_window_title(os_window_id: int) -> None:
pass
def set_options(
opts: Options,
is_wayland: bool = False,
@@ -702,7 +709,15 @@ def cocoa_get_lang() -> Optional[str]:
pass
def mark_os_window_for_close(os_window_id: int, yes: bool = True) -> bool:
def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool:
pass
def set_application_quit_request(cr_type: int = 2) -> None:
pass
def current_application_quit_request() -> int:
pass
@@ -770,15 +785,11 @@ def swap_tabs(os_window_id: int, a: int, b: int) -> None:
pass
def swap_windows(os_window_id: int, tab_id: int, a: int, b: int) -> None:
pass
def set_active_tab(os_window_id: int, a: int) -> None:
pass
def set_active_window(os_window_id: int, tab_id: int, window_idx: int) -> None:
def set_active_window(os_window_id: int, tab_id: int, window_id: int) -> None:
pass
@@ -1059,7 +1070,7 @@ def set_tab_bar_render_data(
def set_window_render_data(
os_window_id: int, tab_id: int, window_id: int, window_idx: int,
os_window_id: int, tab_id: int, window_id: int,
xstart: float, ystart: float, dx: float, dy: float, screen: Screen,
left: int, top: int, right: int, bottom: int
) -> None:

View File

@@ -1004,14 +1004,20 @@ merge_groups_for_pua_space_ligature(void) {
G(groups)->is_space_ligature = true;
}
static inline bool
is_group_calt_ligature(const Group *group) {
GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx;
return group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1;
}
static inline void
split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) {
*left = 0; *right = 0;
for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) {
Group *group = G(groups) + idx;
if (group->first_cell_idx <= cursor_offset && cursor_offset < group->first_cell_idx + group->num_cells) {
GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx;
if (group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1) {
if (is_group_calt_ligature(group)) {
// likely a calt ligature
*left = group->first_cell_idx; *right = group->first_cell_idx + group->num_cells;
}
@@ -1039,6 +1045,11 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) {
// there exist stupid fonts like Powerline that have no space glyph,
// so special case it: https://github.com/kovidgoyal/kitty/issues/1225
unsigned int num_glyphs = group->is_space_ligature ? 1 : group->num_glyphs;
#ifdef __APPLE__
// FiraCode ligatures need to be centered on macOS
// see https://github.com/kovidgoyal/kitty/issues/2591
if (is_group_calt_ligature(group)) center_glyph = true;
#endif
render_group(fg, group->num_cells, num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
idx++;
}

View File

@@ -6,7 +6,9 @@ import ctypes
import sys
from functools import partial
from math import ceil, cos, floor, pi
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
from typing import (
Any, Callable, Dict, Generator, List, Optional, Tuple, Union, cast
)
from kitty.config import defaults
from kitty.constants import is_macos
@@ -43,24 +45,94 @@ def font_for_family(family: str) -> Tuple[FontObject, bool, bool]:
return font_for_family_fontconfig(family)
Range = Tuple[Tuple[int, int], str]
def merge_ranges(a: Range, b: Range, priority_map: Dict[Tuple[int, int], int]) -> Generator[Range, None, None]:
a_start, a_end = a[0]
b_start, b_end = b[0]
a_val, b_val = a[1], b[1]
a_prio, b_prio = priority_map[a[0]], priority_map[b[0]]
if b_start > a_end:
if b_start == a_end + 1 and a_val == b_val:
# ranges can be coalesced
r = ((a_start, b_end), a_val)
priority_map[r[0]] = max(a_prio, b_prio)
yield r
return
# disjoint ranges
yield a
yield b
return
if a_val == b_val:
# mergeable ranges
r = ((a_start, max(a_end, b_end)), a_val)
priority_map[r[0]] = max(a_prio, b_prio)
yield r
return
before_range = mid_range = after_range = None
before_range_prio = mid_range_prio = after_range_prio = 0
if b_start > a_start:
before_range = ((a_start, b_start - 1), a_val)
before_range_prio = a_prio
mid_end = min(a_end, b_end)
if mid_end >= b_start:
# overlap range
mid_range = ((b_start, mid_end), a_val if priority_map[a[0]] >= priority_map[b[0]] else b_val)
mid_range_prio = max(a_prio, b_prio)
# after range
if mid_end is a_end:
if b_end > a_end:
after_range = ((a_end + 1, b_end), b_val)
after_range_prio = b_prio
else:
if a_end > b_end:
after_range = ((b_end + 1, a_end), a_val)
after_range_prio = a_prio
# check if the before, mid and after ranges can be coalesced
ranges: List[Range] = []
priorities: List[int] = []
for rq, prio in ((before_range, before_range_prio), (mid_range, mid_range_prio), (after_range, after_range_prio)):
if rq is None:
continue
r = rq
if ranges:
x = ranges[-1]
if x[0][1] + 1 == r[0][0] and x[1] == r[1]:
ranges[-1] = ((x[0][0], r[0][1]), x[1])
priorities[-1] = max(priorities[-1], prio)
else:
ranges.append(r)
priorities.append(prio)
else:
ranges.append(r)
priorities.append(prio)
for r, p in zip(ranges, priorities):
priority_map[r[0]] = p
yield from ranges
def coalesce_symbol_maps(maps: Dict[Tuple[int, int], str]) -> Dict[Tuple[int, int], str]:
if not maps:
return maps
items = tuple((k, maps[k]) for k in sorted(maps))
ans = [items[0]]
priority_map = {r: i for i, r in enumerate(maps.keys())}
ranges = tuple((r, maps[r]) for r in sorted(maps))
ans = [ranges[0]]
def merge(prev_item: Tuple[Tuple[int, int], str], item: Tuple[Tuple[int, int], str]) -> None:
s, e = item[0]
pe = prev_item[0][1]
ans[-1] = ((prev_item[0][0], max(pe, e)), prev_item[1])
for item in items[1:]:
current_item = ans[-1]
if current_item[1] != item[1] or item[0][0] > current_item[0][1] + 1:
ans.append(item)
for i in range(1, len(ranges)):
r = ranges[i]
new_ranges = merge_ranges(ans[-1], r, priority_map)
if ans:
del ans[-1]
if not ans:
ans = list(new_ranges)
else:
merge(current_item, item)
for r in new_ranges:
prev = ans[-1]
if prev[0][1] + 1 == r[0][0] and prev[1] == r[1]:
ans[-1] = (prev[0][0], r[0][1]), prev[1]
else:
ans.append(r)
return dict(ans)

6
kitty/glfw-wrapper.c generated
View File

@@ -216,6 +216,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetWindowCloseCallback_impl) = dlsym(handle, "glfwSetWindowCloseCallback");
if (glfwSetWindowCloseCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowCloseCallback with error: %s", dlerror());
*(void **) (&glfwSetApplicationCloseCallback_impl) = dlsym(handle, "glfwSetApplicationCloseCallback");
if (glfwSetApplicationCloseCallback_impl == NULL) fail("Failed to load glfw function glfwSetApplicationCloseCallback with error: %s", dlerror());
*(void **) (&glfwSetWindowRefreshCallback_impl) = dlsym(handle, "glfwSetWindowRefreshCallback");
if (glfwSetWindowRefreshCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowRefreshCallback with error: %s", dlerror());
@@ -246,6 +249,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetInputMode_impl) = dlsym(handle, "glfwSetInputMode");
if (glfwSetInputMode_impl == NULL) fail("Failed to load glfw function glfwSetInputMode with error: %s", dlerror());
*(void **) (&glfwRawMouseMotionSupported_impl) = dlsym(handle, "glfwRawMouseMotionSupported");
if (glfwRawMouseMotionSupported_impl == NULL) fail("Failed to load glfw function glfwRawMouseMotionSupported with error: %s", dlerror());
*(void **) (&glfwGetKeyName_impl) = dlsym(handle, "glfwGetKeyName");
if (glfwGetKeyName_impl == NULL) fail("Failed to load glfw function glfwGetKeyName with error: %s", dlerror());

55
kitty/glfw-wrapper.h generated
View File

@@ -569,6 +569,33 @@
* @analysis Application programmer error. Fix the offending call.
*/
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
/*! @brief The reuqested 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
* this error.
*
* @analysis Platform or platform version limitation. The error can be ignored
* unless the feature is critical to the application.
*
* @par
* A function call that emits this error has no effect other than the error and
* updating any existing out parameters.
*/
#define GLFW_FEATURE_UNAVAILABLE 0x0001000C
/*! @brief The requested feature is not implemented for the platform.
*
* The requested feature has not yet been implemented in GLFW for this platform.
*
* @analysis An incomplete implementation of GLFW for this platform, hopefully
* fixed in a future release. The error can be ignored unless the feature is
* critical to the application.
*
* @par
* A function call that emits this error has no effect other than the error and
* updating any existing out parameters.
*/
#define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D
/*! @} */
/*! @addtogroup window
@@ -839,6 +866,7 @@
#define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_RAW_MOUSE_MOTION 0x00033005
#define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002
@@ -1082,6 +1110,22 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int);
*/
typedef void (* GLFWwindowclosefun)(GLFWwindow*);
/*! @brief The function pointer type for application close callbacks.
*
* This is the function pointer type for application close callbacks. A application
* close callback function has the following signature:
* @code
* void function_name(int flags)
* @endcode
*
* @param[in] flags 0 for a user requested application quit, 1 if a fatal error occurred and application should quit ASAP
*
* @sa @ref glfwSetApplicationCloseCallback
*
* @ingroup window
*/
typedef void (* GLFWapplicationclosefun)(int);
/*! @brief The function pointer type for window content refresh callbacks.
*
* This is the function pointer type for window content refresh callbacks.
@@ -1313,6 +1357,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
* scrolling cancelled and value 6 if scrolling may begin soon.
* @param[int] mods The keyboard modifiers
*
* @sa @ref scrolling
* @sa @ref glfwSetScrollCallback
@@ -1322,7 +1367,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
*
* @ingroup input
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
/*! @brief The function pointer type for key callbacks.
*
@@ -1857,6 +1902,10 @@ typedef GLFWwindowclosefun (*glfwSetWindowCloseCallback_func)(GLFWwindow*, GLFWw
GFW_EXTERN glfwSetWindowCloseCallback_func glfwSetWindowCloseCallback_impl;
#define glfwSetWindowCloseCallback glfwSetWindowCloseCallback_impl
typedef GLFWapplicationclosefun (*glfwSetApplicationCloseCallback_func)(GLFWapplicationclosefun);
GFW_EXTERN glfwSetApplicationCloseCallback_func glfwSetApplicationCloseCallback_impl;
#define glfwSetApplicationCloseCallback glfwSetApplicationCloseCallback_impl
typedef GLFWwindowrefreshfun (*glfwSetWindowRefreshCallback_func)(GLFWwindow*, GLFWwindowrefreshfun);
GFW_EXTERN glfwSetWindowRefreshCallback_func glfwSetWindowRefreshCallback_impl;
#define glfwSetWindowRefreshCallback glfwSetWindowRefreshCallback_impl
@@ -1897,6 +1946,10 @@ typedef void (*glfwSetInputMode_func)(GLFWwindow*, int, int);
GFW_EXTERN glfwSetInputMode_func glfwSetInputMode_impl;
#define glfwSetInputMode glfwSetInputMode_impl
typedef int (*glfwRawMouseMotionSupported_func)(void);
GFW_EXTERN glfwRawMouseMotionSupported_func glfwRawMouseMotionSupported_impl;
#define glfwRawMouseMotionSupported glfwRawMouseMotionSupported_impl
typedef const char* (*glfwGetKeyName_func)(int, int);
GFW_EXTERN glfwGetKeyName_func glfwGetKeyName_impl;
#define glfwGetKeyName glfwGetKeyName_impl

View File

@@ -152,19 +152,15 @@ blank_os_window(OSWindow *w) {
static void
window_close_callback(GLFWwindow* window) {
if (!set_callback_window(window)) return;
global_state.has_pending_closes = true;
request_tick_callback();
if (global_state.callback_os_window->close_request == NO_CLOSE_REQUESTED) {
global_state.callback_os_window->close_request = CONFIRMABLE_CLOSE_REQUESTED;
global_state.has_pending_closes = true;
request_tick_callback();
}
glfwSetWindowShouldClose(window, false);
global_state.callback_os_window = NULL;
}
#ifdef __APPLE__
static void
application_quit_canary_close_requested(GLFWwindow *window UNUSED) {
global_state.has_pending_closes = true;
request_tick_callback();
}
#endif
static void
window_occlusion_callback(GLFWwindow *window, bool occluded UNUSED) {
if (!set_callback_window(window)) return;
@@ -231,10 +227,38 @@ refresh_callback(GLFWwindow *w) {
static int mods_at_last_key_or_button_event = 0;
static inline int
key_to_modifier(int key) {
switch(key) {
case GLFW_KEY_LEFT_SHIFT:
case GLFW_KEY_RIGHT_SHIFT:
return GLFW_MOD_SHIFT;
case GLFW_KEY_LEFT_CONTROL:
case GLFW_KEY_RIGHT_CONTROL:
return GLFW_MOD_CONTROL;
case GLFW_KEY_LEFT_ALT:
case GLFW_KEY_RIGHT_ALT:
return GLFW_MOD_ALT;
case GLFW_KEY_LEFT_SUPER:
case GLFW_KEY_RIGHT_SUPER:
return GLFW_MOD_SUPER;
default:
return -1;
}
}
static void
key_callback(GLFWwindow *w, GLFWkeyevent *ev) {
if (!set_callback_window(w)) return;
mods_at_last_key_or_button_event = ev->mods;
int key_modifier = key_to_modifier(ev->key);
if (key_modifier != -1) {
if (ev->action == GLFW_RELEASE) {
mods_at_last_key_or_button_event &= ~key_modifier;
} else {
mods_at_last_key_or_button_event |= key_modifier;
}
}
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST) {
global_state.callback_os_window->is_key_pressed[ev->key] = ev->action == GLFW_RELEASE ? false : true;
@@ -287,12 +311,12 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
}
static void
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) {
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mods) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags);
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags, mods);
request_tick_callback();
global_state.callback_os_window = NULL;
}
@@ -323,25 +347,32 @@ window_focus_callback(GLFWwindow *w, int focused) {
static int
drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) {
if (!set_callback_window(w)) return 0;
#define RETURN(x) { global_state.callback_os_window = NULL; return x; }
if (!data) {
if (strcmp(mime, "text/uri-list") == 0) return 3;
if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2;
if (strcmp(mime, "text/plain") == 0) return 1;
return 0;
if (strcmp(mime, "text/uri-list") == 0) RETURN(3);
if (strcmp(mime, "text/plain;charset=utf-8") == 0) RETURN(2);
if (strcmp(mime, "text/plain") == 0) RETURN(1);
RETURN(0);
}
WINDOW_CALLBACK(on_drop, "sy#", mime, data, (int)sz);
WINDOW_CALLBACK(on_drop, "sy#", mime, data, (Py_ssize_t)sz);
request_tick_callback();
/* PyObject *s = PyTuple_New(count); */
/* if (s) { */
/* for (int i = 0; i < count; i++) PyTuple_SET_ITEM(s, i, PyUnicode_FromString(strings[i])); */
/* WINDOW_CALLBACK(on_drop, "O", s); */
/* Py_CLEAR(s); */
/* request_tick_callback(); */
/* } */
global_state.callback_os_window = NULL;
return 0;
RETURN(0);
#undef RETURN
}
static void
application_close_requested_callback(int flags) {
if (flags) {
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
request_tick_callback();
} else {
if (global_state.quit_request == NO_CLOSE_REQUESTED) {
global_state.has_pending_closes = true;
global_state.quit_request = CONFIRMABLE_CLOSE_REQUESTED;
request_tick_callback();
}
}
}
// }}}
void
@@ -459,6 +490,8 @@ toggle_maximized_for_os_window(OSWindow *w) {
#ifdef __APPLE__
static GLFWwindow *apple_preserve_common_context = NULL;
static int
filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned long flags) {
if ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) {
@@ -468,8 +501,6 @@ filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned
return 0;
}
static GLFWwindow *application_quit_canary = NULL;
static bool
on_application_reopen(int has_visible_windows) {
if (has_visible_windows) return true;
@@ -523,13 +554,13 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwWindowHint(GLFW_DEPTH_BITS, 0);
glfwWindowHint(GLFW_STENCIL_BITS, 0);
if (OPT(hide_window_decorations) & 1) glfwWindowHint(GLFW_DECORATED, false);
glfwSetApplicationCloseCallback(application_close_requested_callback);
#ifdef __APPLE__
cocoa_set_activation_policy(OPT(macos_hide_from_tasks));
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
glfwSetApplicationShouldHandleReopen(on_application_reopen);
glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu);
#endif
}
#ifndef __APPLE__
@@ -549,15 +580,13 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
// The temp window is used to get the DPI.
glfwWindowHint(GLFW_VISIBLE, false);
GLFWwindow *common_context = global_state.num_os_windows ? global_state.os_windows[0].handle : NULL;
#ifdef __APPLE__
if (is_first_window && !application_quit_canary) {
application_quit_canary = glfwCreateWindow(100, 200, "quit_canary", NULL, NULL);
glfwSetWindowCloseCallback(application_quit_canary, application_quit_canary_close_requested);
}
if (!common_context) common_context = application_quit_canary;
#endif
GLFWwindow *temp_window = NULL;
#ifdef __APPLE__
if (!apple_preserve_common_context) {
apple_preserve_common_context = glfwCreateWindow(100, 200, "kitty", NULL, common_context);
}
if (!common_context) common_context = apple_preserve_common_context;
#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.
@@ -760,21 +789,6 @@ focus_os_window(OSWindow *w, bool also_raise) {
}
}
#ifdef __APPLE__
bool
application_quit_requested() {
return !application_quit_canary || glfwWindowShouldClose(application_quit_canary);
}
void
request_application_quit() {
if (application_quit_canary) {
global_state.has_pending_closes = true;
glfwSetWindowShouldClose(application_quit_canary, true);
}
}
#endif
// Global functions {{{
static void
error_callback(int error, const char* description) {
@@ -979,12 +993,6 @@ wakeup_main_loop() {
glfwPostEmptyEvent();
}
void
mark_os_window_for_close(OSWindow* w, bool yes) {
global_state.has_pending_closes = true;
glfwSetWindowShouldClose(w->handle, yes);
}
bool
should_os_window_be_rendered(OSWindow* w) {
return (
@@ -994,11 +1002,6 @@ should_os_window_be_rendered(OSWindow* w) {
) ? false : true;
}
bool
should_os_window_close(OSWindow* w) {
return glfwWindowShouldClose(w->handle) ? true : false;
}
static PyObject*
primary_monitor_size(PYNOARG) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
@@ -1137,8 +1140,12 @@ wayland_frame_request_callback(id_type os_window_id) {
void
request_frame_render(OSWindow *w) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->render_state = RENDER_FRAME_REQUESTED;
// Some Wayland compositors are too fragile to handle multiple
// render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329
if (w->render_state != RENDER_FRAME_REQUESTED) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->render_state = RENDER_FRAME_REQUESTED;
}
}
void
@@ -1183,6 +1190,10 @@ run_main_loop(tick_callback_fun cb, void* cb_data) {
void
stop_main_loop(void) {
#ifdef __APPLE__
if (apple_preserve_common_context) glfwDestroyWindow(apple_preserve_common_context);
apple_preserve_common_context = NULL;
#endif
glfwStopMainLoop();
}

View File

@@ -939,7 +939,7 @@ static PyMethodDef methods[] = {
};
static PyMemberDef members[] = {
{"image_count", T_UINT, offsetof(GraphicsManager, image_count), 0, "image_count"},
{"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
{NULL},
};

View File

@@ -203,8 +203,7 @@ on_key_input(GLFWkeyevent *ev) {
}
void
fake_scroll(int amount, bool upwards) {
Window *w = active_window();
fake_scroll(Window *w, int amount, bool upwards) {
if (!w) return;
int key = upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN;
while (amount-- > 0) {

View File

@@ -42,7 +42,7 @@ choices=window,tab,os-window,overlay,background,clipboard,primary
Where to launch the child process, in a new kitty window in the current tab,
a new tab, or a new OS window or an overlay over the current window.
Note that if the current window already has an overlay, then it will
open a new window. The value of none means the process will be
open a new window. The value of background means the process will be
run in the background. The values clipboard and primary are meant
to work with :option:`launch --stdin-source` to copy data to the system
clipboard or primary selection.
@@ -273,10 +273,10 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
x = str(active.id)
final_cmd.append(x)
kw['cmd'] = final_cmd
if opts.type == 'overlay' and active and not active.overlay_window_id:
if opts.type == 'overlay' and active:
kw['overlay_for'] = active.id
if opts.stdin_source and opts.stdin_source != 'none':
q = opts.stdin_source
if opts.stdin_source != 'none':
q = str(opts.stdin_source)
if opts.stdin_add_formatting:
if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback'):
q = '@ansi_' + q[1:]

File diff suppressed because it is too large Load Diff

0
kitty/layout/__init__.py Normal file
View File

405
kitty/layout/base.py Normal file
View File

@@ -0,0 +1,405 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
from itertools import repeat
from typing import (
Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, Sequence,
Tuple, Union, cast
)
from kitty.constants import Edges, WindowGeometry
from kitty.fast_data_types import (
Region, set_active_window, viewport_for_window
)
from kitty.options_stub import Options
from kitty.typing import TypedDict, WindowType
from kitty.window_list import WindowGroup, WindowList
class Borders(NamedTuple):
left: bool
top: bool
right: bool
bottom: bool
class LayoutOpts:
def __init__(self, data: Dict[str, str]):
pass
class LayoutData(NamedTuple):
content_pos: int = 0
cells_per_window: int = 0
space_before: int = 0
space_after: int = 0
content_size: int = 0
all_borders = Borders(True, True, True, True)
no_borders = Borders(False, False, False, False)
DecorationPairs = Sequence[Tuple[int, int]]
LayoutDimension = Generator[LayoutData, None, None]
ListOfWindows = List[WindowType]
class NeighborsMap(TypedDict):
left: List[int]
top: List[int]
right: List[int]
bottom: List[int]
class LayoutGlobalData:
draw_minimal_borders: bool = True
draw_active_borders: bool = True
align_top_left: bool = False
central: Region = Region((0, 0, 199, 199, 200, 200))
cell_width: int = 20
cell_height: int = 20
lgd = LayoutGlobalData()
def idx_for_id(win_id: int, windows: Iterable[WindowType]) -> Optional[int]:
for i, w in enumerate(windows):
if w.id == win_id:
return i
def set_layout_options(opts: Options) -> None:
lgd.draw_minimal_borders = opts.draw_minimal_borders and sum(opts.window_margin_width) == 0
lgd.draw_active_borders = opts.active_border_color is not None
lgd.align_top_left = opts.placement_strategy == 'top-left'
def calculate_cells_map(bias: Optional[Sequence[float]], number_of_windows: int, number_of_cells: int) -> List[int]:
cells_per_window = number_of_cells // number_of_windows
if bias is not None and 1 < number_of_windows == len(bias) and cells_per_window > 5:
cells_map = [int(b * number_of_cells) for b in bias]
while min(cells_map) < 5:
maxi, mini = map(cells_map.index, (max(cells_map), min(cells_map)))
if maxi == mini:
break
cells_map[mini] += 1
cells_map[maxi] -= 1
else:
cells_map = list(repeat(cells_per_window, number_of_windows))
extra = number_of_cells - sum(cells_map)
if extra > 0:
cells_map[-1] += extra
return cells_map
def layout_dimension(
start_at: int, length: int, cell_length: int,
decoration_pairs: DecorationPairs,
left_align: bool = False,
bias: Optional[Sequence[float]] = None
) -> LayoutDimension:
number_of_windows = len(decoration_pairs)
number_of_cells = length // cell_length
space_needed_for_decorations: int = sum(map(sum, decoration_pairs))
extra = length - number_of_cells * cell_length
while extra < space_needed_for_decorations:
number_of_cells -= 1
extra = length - number_of_cells * cell_length
cells_map = calculate_cells_map(bias, number_of_windows, number_of_cells)
assert sum(cells_map) == number_of_cells
extra = length - number_of_cells * cell_length - space_needed_for_decorations
pos = start_at
if not left_align:
pos += extra // 2
last_i = len(cells_map) - 1
for i, cells_per_window in enumerate(cells_map):
before_dec, after_dec = decoration_pairs[i]
pos += before_dec
if i == 0:
before_space = pos - start_at
else:
before_space = before_dec
content_size = cells_per_window * cell_length
if i == last_i:
after_space = (start_at + length) - (pos + content_size)
else:
after_space = after_dec
yield LayoutData(pos, cells_per_window, before_space, after_space, content_size)
pos += content_size + after_space
class Rect(NamedTuple):
left: int
top: int
right: int
bottom: int
def blank_rects_for_window(wg: WindowGeometry) -> Generator[Rect, None, None]:
left_width, right_width = wg.spaces.left, wg.spaces.right
top_height, bottom_height = wg.spaces.top, wg.spaces.bottom
if left_width > 0:
yield Rect(wg.left - left_width, wg.top - top_height, wg.left, wg.bottom + bottom_height)
if top_height > 0:
yield Rect(wg.left, wg.top - top_height, wg.right + right_width, wg.top)
if right_width > 0:
yield Rect(wg.right, wg.top, wg.right + right_width, wg.bottom + bottom_height)
if bottom_height > 0:
yield Rect(wg.left, wg.bottom, wg.right, wg.bottom + bottom_height)
def window_geometry(xstart: int, xnum: int, ystart: int, ynum: int, left: int, top: int, right: int, bottom: int) -> WindowGeometry:
return WindowGeometry(
left=xstart, top=ystart, xnum=xnum, ynum=ynum,
right=xstart + lgd.cell_width * xnum, bottom=ystart + lgd.cell_height * ynum,
spaces=Edges(left, top, right, bottom)
)
def window_geometry_from_layouts(x: LayoutData, y: LayoutData) -> WindowGeometry:
return window_geometry(x.content_pos, x.cells_per_window, y.content_pos, y.cells_per_window, x.space_before, y.space_before, x.space_after, y.space_after)
def layout_single_window(xdecoration_pairs: DecorationPairs, ydecoration_pairs: DecorationPairs, left_align: bool = False) -> WindowGeometry:
x = next(layout_dimension(lgd.central.left, lgd.central.width, lgd.cell_width, xdecoration_pairs, left_align=lgd.align_top_left))
y = next(layout_dimension(lgd.central.top, lgd.central.height, lgd.cell_height, ydecoration_pairs, left_align=lgd.align_top_left))
return window_geometry_from_layouts(x, y)
def safe_increment_bias(old_val: float, increment: float) -> float:
return max(0.1, min(old_val + increment, 0.9))
def normalize_biases(biases: List[float]) -> List[float]:
s = sum(biases)
if s == 1:
return biases
return [x/s for x in biases]
def distribute_indexed_bias(base_bias: Sequence[float], index_bias_map: Dict[int, float]) -> Sequence[float]:
if not index_bias_map:
return base_bias
ans = list(base_bias)
limit = len(ans)
for row, increment in index_bias_map.items():
if row >= limit or not increment:
continue
other_increment = -increment / (limit - 1)
ans = [safe_increment_bias(b, increment if i == row else other_increment) for i, b in enumerate(ans)]
return normalize_biases(ans)
def variable_bias(num_windows: int, candidate: Dict[int, float]) -> Sequence[float]:
return distribute_indexed_bias(list(repeat(1/(num_windows), num_windows)), candidate)
class Layout:
name: Optional[str] = None
needs_window_borders = True
must_draw_borders = False # can be overridden to customize behavior from kittens
layout_opts = LayoutOpts({})
only_active_window_visible = False
def __init__(self, os_window_id: int, tab_id: int, layout_opts: str = '') -> None:
self.os_window_id = os_window_id
self.tab_id = tab_id
self.set_active_window_in_os_window = partial(set_active_window, os_window_id, tab_id)
# A set of rectangles corresponding to the blank spaces at the edges of
# this layout, i.e. spaces that are not covered by any window
self.blank_rects: List[Rect] = []
self.layout_opts = self.parse_layout_opts(layout_opts)
assert self.name is not None
self.full_name = self.name + ((':' + layout_opts) if layout_opts else '')
self.remove_all_biases()
def bias_increment_for_cell(self, is_horizontal: bool) -> float:
self._set_dimensions()
if is_horizontal:
return (lgd.cell_width + 1) / lgd.central.width
return (lgd.cell_height + 1) / lgd.central.height
def apply_bias(self, window_id: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
return False
def remove_all_biases(self) -> bool:
return False
def modify_size_of_window(self, all_windows: WindowList, window_id: int, increment: float, is_horizontal: bool = True) -> bool:
idx = all_windows.group_idx_for_window(window_id)
if idx is None:
return False
return self.apply_bias(idx, increment, all_windows, is_horizontal)
def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts:
data: Dict[str, str] = {}
if layout_opts:
for x in layout_opts.split(';'):
k, v = x.partition('=')[::2]
if k and v:
data[k] = v
return type(self.layout_opts)(data)
def nth_window(self, all_windows: WindowList, num: int) -> Optional[WindowType]:
return all_windows.active_window_in_nth_group(num, clamp=True)
def activate_nth_window(self, all_windows: WindowList, num: int) -> None:
all_windows.set_active_group_idx(num)
def next_window(self, all_windows: WindowList, delta: int = 1) -> None:
all_windows.activate_next_window_group(delta)
def neighbors(self, all_windows: WindowList) -> NeighborsMap:
w = all_windows.active_window
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
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])
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:
all_windows.add_window(window, group_of=overlay_for)
return
if location == 'neighbor':
location = 'after'
self.add_non_overlay_window(all_windows, window, location)
def add_non_overlay_window(self, all_windows: WindowList, window: WindowType, location: Optional[str]) -> None:
next_to: Optional[WindowType] = None
before = False
next_to = all_windows.active_window
if location is not None:
if location in ('after', 'vsplit', 'hsplit'):
pass
elif location == 'before':
before = True
elif location == 'first':
before = True
next_to = None
elif location == 'last':
next_to = None
all_windows.add_window(window, next_to=next_to, before=before)
def update_visibility(self, all_windows: WindowList) -> None:
active_window = all_windows.active_window
for window, is_group_leader in all_windows.iter_windows_with_visibility():
is_visible = window is active_window or (is_group_leader and not self.only_active_window_visible)
window.set_visible_in_layout(is_visible)
def _set_dimensions(self) -> None:
lgd.central, tab_bar, vw, vh, lgd.cell_width, lgd.cell_height = viewport_for_window(self.os_window_id)
def __call__(self, all_windows: WindowList) -> None:
self._set_dimensions()
self.update_visibility(all_windows)
self.blank_rects = []
self.do_layout(all_windows)
def layout_single_window_group(self, wg: WindowGroup, add_blank_rects: bool = True) -> None:
bw = 1 if self.must_draw_borders else 0
xdecoration_pairs = ((
wg.decoration('left', border_mult=bw, is_single_window=True),
wg.decoration('right', border_mult=bw, is_single_window=True),
),)
ydecoration_pairs = ((
wg.decoration('top', border_mult=bw, is_single_window=True),
wg.decoration('bottom', border_mult=bw, is_single_window=True),
),)
geom = layout_single_window(xdecoration_pairs, ydecoration_pairs, left_align=lgd.align_top_left)
wg.set_geometry(geom)
if add_blank_rects and wg:
self.blank_rects.extend(blank_rects_for_window(geom))
def xlayout(
self,
groups: Iterator[WindowGroup],
bias: Optional[Sequence[float]] = None,
start: Optional[int] = None,
size: Optional[int] = None,
offset: int = 0
) -> LayoutDimension:
decoration_pairs = tuple(
(g.decoration('left'), g.decoration('right')) for i, g in
enumerate(groups) if i >= offset
)
if start is None:
start = lgd.central.left
if size is None:
size = lgd.central.width
return layout_dimension(start, size, lgd.cell_width, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
def ylayout(
self,
groups: Iterator[WindowGroup],
bias: Optional[Sequence[float]] = None,
start: Optional[int] = None,
size: Optional[int] = None,
offset: int = 0
) -> LayoutDimension:
decoration_pairs = tuple(
(g.decoration('top'), g.decoration('bottom')) for i, g in
enumerate(groups) if i >= offset
)
if start is None:
start = lgd.central.top
if size is None:
size = lgd.central.height
return layout_dimension(start, size, lgd.cell_height, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
def set_window_group_geometry(self, wg: WindowGroup, xl: LayoutData, yl: LayoutData) -> WindowGeometry:
geom = window_geometry_from_layouts(xl, yl)
wg.set_geometry(geom)
self.blank_rects.extend(blank_rects_for_window(geom))
return geom
def do_layout(self, windows: WindowList) -> None:
raise NotImplementedError()
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap:
return {'left': [], 'right': [], 'top': [], 'bottom': []}
def compute_needs_borders_map(self, all_windows: WindowList) -> Dict[int, bool]:
return all_windows.compute_needs_borders_map(lgd.draw_active_borders)
def resolve_borders(self, all_windows: WindowList) -> Generator[Borders, None, None]:
if lgd.draw_minimal_borders:
needs_borders_map = self.compute_needs_borders_map(all_windows)
yield from self.minimal_borders(all_windows, needs_borders_map)
else:
for i in range(all_windows.num_groups):
yield all_borders
def window_independent_borders(self, windows: WindowList) -> Generator[Edges, None, None]:
return
yield Edges() # type: ignore
def minimal_borders(self, windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
for needs_border in needs_borders_map.values():
yield all_borders if needs_border else no_borders
def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]:
pass

247
kitty/layout/grid.py Normal file
View File

@@ -0,0 +1,247 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from functools import lru_cache
from itertools import repeat
from typing import Callable, Dict, Generator, List, Optional, Sequence, Tuple
from kitty.constants import Edges
from kitty.typing import WindowType
from kitty.window_list import WindowGroup, WindowList
from .base import (
Layout, LayoutData, LayoutDimension, ListOfWindows, NeighborsMap,
layout_dimension, lgd, variable_bias
)
from .tall import neighbors_for_tall_window
@lru_cache()
def calc_grid_size(n: int) -> Tuple[int, int, int, int]:
if n <= 5:
ncols = 1 if n == 1 else 2
else:
for ncols in range(3, (n // 2) + 1):
if ncols * ncols >= n:
break
nrows = n // ncols
special_rows = n - (nrows * (ncols - 1))
special_col = 0 if special_rows < nrows else ncols - 1
return ncols, nrows, special_rows, special_col
class Grid(Layout):
name = 'grid'
def remove_all_biases(self) -> bool:
self.biased_rows: Dict[int, float] = {}
self.biased_cols: Dict[int, float] = {}
return True
def column_layout(
self,
num: int,
bias: Optional[Sequence[float]] = None,
) -> LayoutDimension:
decoration_pairs = tuple(repeat((0, 0), num))
return layout_dimension(lgd.central.left, lgd.central.width, lgd.cell_width, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
def row_layout(
self,
num: int,
bias: Optional[Sequence[float]] = None,
) -> LayoutDimension:
decoration_pairs = tuple(repeat((0, 0), num))
return layout_dimension(lgd.central.top, lgd.central.height, lgd.cell_height, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
def variable_layout(self, layout_func: Callable[..., LayoutDimension], num_windows: int, biased_map: Dict[int, float]) -> LayoutDimension:
return layout_func(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
b = self.biased_cols if is_horizontal else self.biased_rows
num_windows = all_windows.num_groups
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
def position_for_window_idx(idx: int) -> Tuple[int, int]:
row_num = col_num = 0
def on_col_done(col_windows: List[int]) -> None:
nonlocal col_num, row_num
row_num = 0
col_num += 1
for window_idx, xl, yl in self.layout_windows(
num_windows, nrows, ncols, special_rows, special_col, on_col_done):
if idx == window_idx:
return row_num, col_num
row_num += 1
row_num, col_num = position_for_window_idx(idx)
if is_horizontal:
b = self.biased_cols
if ncols < 2:
return False
bias_idx = col_num
attr = 'biased_cols'
def layout_func(windows: ListOfWindows, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
return self.column_layout(num_windows, bias=bias)
else:
b = self.biased_rows
if max(nrows, special_rows) < 2:
return False
bias_idx = row_num
attr = 'biased_rows'
def layout_func(windows: ListOfWindows, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
return self.row_layout(num_windows, bias=bias)
before_layout = list(self.variable_layout(layout_func, num_windows, b))
candidate = b.copy()
before = candidate.get(bias_idx, 0)
candidate[bias_idx] = before + increment
if before_layout == list(self.variable_layout(layout_func, num_windows, candidate)):
return False
setattr(self, attr, candidate)
return True
def layout_windows(
self,
num_windows: int,
nrows: int, ncols: int,
special_rows: int, special_col: int,
on_col_done: Callable[[List[int]], None] = lambda col_windows: None
) -> Generator[Tuple[int, LayoutData, LayoutData], None, None]:
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
xlayout = self.variable_layout(self.column_layout, ncols, self.biased_cols)
yvals_normal = tuple(self.variable_layout(self.row_layout, nrows, self.biased_rows))
yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.row_layout, special_rows, self.biased_rows))
pos = 0
for col in range(ncols):
rows = special_rows if col == special_col else nrows
yls = yvals_special if col == special_col else yvals_normal
xl = next(xlayout)
col_windows = []
for i, yl in enumerate(yls):
window_idx = pos + i
yield window_idx, xl, yl
col_windows.append(window_idx)
pos += rows
on_col_done(col_windows)
def do_layout(self, all_windows: WindowList) -> None:
n = all_windows.num_groups
if n == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
ncols, nrows, special_rows, special_col = calc_grid_size(n)
groups = tuple(all_windows.iter_all_layoutable_groups())
win_col_map: List[List[WindowGroup]] = []
def on_col_done(col_windows: List[int]) -> None:
col_windows_w = [groups[i] for i in col_windows]
win_col_map.append(col_windows_w)
def extents(ld: LayoutData) -> Tuple[int, int]:
start = ld.content_pos - ld.space_before
size = ld.space_before + ld.space_after + ld.content_size
return start, size
def layout(ld: LayoutData, cell_length: int, before_dec: int, after_dec: int) -> LayoutData:
start, size = extents(ld)
space_needed_for_decorations = before_dec + after_dec
content_size = size - space_needed_for_decorations
number_of_cells = content_size // cell_length
cell_area = number_of_cells * cell_length
extra = content_size - cell_area
if extra > 0 and not lgd.align_top_left:
before_dec += extra // 2
return LayoutData(start + before_dec, number_of_cells, before_dec, size - cell_area - before_dec, cell_area)
def position_window_in_grid_cell(window_idx: int, xl: LayoutData, yl: LayoutData) -> None:
wg = groups[window_idx]
edges = Edges(
wg.decoration('left'), wg.decoration('top'), wg.decoration('right'), wg.decoration('bottom')
)
xl = layout(xl, lgd.cell_width, edges.left, edges.right)
yl = layout(yl, lgd.cell_height, edges.top, edges.bottom)
self.set_window_group_geometry(wg, xl, yl)
for window_idx, xl, yl in self.layout_windows(
n, nrows, ncols, special_rows, special_col, on_col_done):
position_window_in_grid_cell(window_idx, xl, yl)
def window_independent_borders(self, all_windows: WindowList) -> Generator[Edges, None, None]:
n = all_windows.num_groups
if not lgd.draw_minimal_borders or n < 2:
return
ncols, nrows, special_rows, special_col = calc_grid_size(n)
row_borders: List[List[Edges]] = [[]]
col_borders: List[Edges] = []
groups = tuple(all_windows.iter_all_layoutable_groups())
bw = groups[0].effective_border()
xl: LayoutData = LayoutData()
yl: LayoutData = LayoutData()
def on_col_done(col_windows: List[int]) -> None:
left = xl.content_pos + xl.content_size + xl.space_after - bw // 2
col_borders.append(Edges(left, lgd.central.top, left + bw, lgd.central.bottom))
row_borders.append([])
for window_idx, xl, yl in self.layout_windows(n, nrows, ncols, special_rows, special_col, on_col_done):
top = yl.content_pos + yl.content_size + yl.space_after - bw // 2
right = xl.content_pos + xl.content_size + xl.space_after
row_borders[-1].append(Edges(xl.content_pos - xl.space_before, top, right, top + bw))
for border in col_borders[:-1]:
yield border
for rows in row_borders:
for border in rows[:-1]:
yield border
def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
n = all_windows.num_groups
if n < 4:
return neighbors_for_tall_window(1, window, all_windows)
wg = all_windows.group_for_window(window)
assert wg is not None
ncols, nrows, special_rows, special_col = calc_grid_size(n)
blank_row: List[Optional[int]] = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = all_windows.iter_all_layoutable_groups()
pos_map: Dict[int, Tuple[int, int]] = {}
col_counts: List[int] = []
for col in range(ncols):
rows = special_rows if col == special_col else nrows
for row in range(rows):
w = next(wi)
matrix[row][col] = wid = w.id
pos_map[wid] = row, col
col_counts.append(rows)
row, col = pos_map[wg.id]
def neighbors(row: int, col: int) -> List[int]:
try:
ans = matrix[row][col]
except IndexError:
ans = None
return [] if ans is None else [ans]
def side(row: int, col: int, delta: int) -> List[int]:
neighbor_col = col + delta
if col_counts[neighbor_col] == col_counts[col]:
return neighbors(row, neighbor_col)
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
return {
'top': neighbors(row-1, col) if row else [],
'bottom': neighbors(row + 1, col),
'left': side(row, col, -1) if col else [],
'right': side(row, col, 1) if col < ncols - 1 else [],
}

50
kitty/layout/interface.py Normal file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Dict, Tuple, Type
from .base import Layout
from .grid import Grid
from .splits import Splits
from .stack import Stack
from .tall import Fat, Tall
from .vertical import Horizontal, Vertical
all_layouts: Dict[str, Type[Layout]] = {
Stack.name: Stack,
Tall.name: Tall,
Fat.name: Fat,
Vertical.name: Vertical,
Horizontal.name: Horizontal,
Grid.name: Grid,
Splits.name: Splits,
}
class CreateLayoutObjectFor:
cache: Dict[Tuple, Layout] = {}
def __call__(
self,
name: str,
os_window_id: int,
tab_id: int,
layout_opts: str = ''
) -> Layout:
key = name, os_window_id, tab_id, layout_opts
ans = create_layout_object_for.cache.get(key)
if ans is None:
name, layout_opts = name.partition(':')[::2]
ans = create_layout_object_for.cache[key] = all_layouts[name](
os_window_id, tab_id, layout_opts)
return ans
create_layout_object_for = CreateLayoutObjectFor()
def evict_cached_layouts(tab_id: int) -> None:
remove = [key for key in create_layout_object_for.cache if key[2] == tab_id]
for key in remove:
del create_layout_object_for.cache[key]

454
kitty/layout/splits.py Normal file
View File

@@ -0,0 +1,454 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import (
Collection, Dict, Generator, Optional, Sequence, Tuple, Union
)
from kitty.constants import Edges, WindowGeometry
from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList
from .base import (
Layout, LayoutOpts, NeighborsMap, blank_rects_for_window, lgd,
window_geometry_from_layouts
)
class Pair:
def __init__(self, horizontal: bool = True):
self.horizontal = horizontal
self.one: Optional[Union[Pair, int]] = None
self.two: Optional[Union[Pair, int]] = None
self.bias = 0.5
self.between_border: Optional[Edges] = None
def __repr__(self) -> str:
return 'Pair(horizontal={}, bias={:.2f}, one={}, two={}, between_border={})'.format(
self.horizontal, self.bias, self.one, self.two, self.between_border)
def all_window_ids(self) -> Generator[int, None, None]:
if self.one is not None:
if isinstance(self.one, Pair):
yield from self.one.all_window_ids()
else:
yield self.one
if self.two is not None:
if isinstance(self.two, Pair):
yield from self.two.all_window_ids()
else:
yield self.two
def self_and_descendants(self) -> Generator['Pair', None, None]:
yield self
if isinstance(self.one, Pair):
yield from self.one.self_and_descendants()
if isinstance(self.two, Pair):
yield from self.two.self_and_descendants()
def pair_for_window(self, window_id: int) -> Optional['Pair']:
if self.one == window_id or self.two == window_id:
return self
ans = None
if isinstance(self.one, Pair):
ans = self.one.pair_for_window(window_id)
if ans is None and isinstance(self.two, Pair):
ans = self.two.pair_for_window(window_id)
return ans
def swap_windows(self, a: int, b: int) -> None:
pa = self.pair_for_window(a)
pb = self.pair_for_window(b)
if pa is None or pb is None:
return
if pa.one == a:
if pb.one == b:
pa.one, pb.one = pb.one, pa.one
else:
pa.one, pb.two = pb.two, pa.one
else:
if pb.one == b:
pa.two, pb.one = pb.one, pa.two
else:
pa.two, pb.two = pb.two, pa.two
def parent(self, root: 'Pair') -> Optional['Pair']:
for q in root.self_and_descendants():
if q.one is self or q.two is self:
return q
def remove_windows(self, window_ids: Collection[int]) -> None:
if isinstance(self.one, int) and self.one in window_ids:
self.one = None
if isinstance(self.two, int) and self.two in window_ids:
self.two = None
if self.one is None and self.two is not None:
self.one, self.two = self.two, None
@property
def is_redundant(self) -> bool:
return self.one is None or self.two is None
def collapse_redundant_pairs(self) -> None:
while isinstance(self.one, Pair) and self.one.is_redundant:
self.one = self.one.one or self.one.two
while isinstance(self.two, Pair) and self.two.is_redundant:
self.two = self.two.one or self.two.two
if isinstance(self.one, Pair):
self.one.collapse_redundant_pairs()
if isinstance(self.two, Pair):
self.two.collapse_redundant_pairs()
def balanced_add(self, window_id: int) -> 'Pair':
if self.one is None or self.two is None:
if self.one is None:
if self.two is None:
self.one = window_id
return self
self.one, self.two = self.two, self.one
self.two = window_id
return self
if isinstance(self.one, Pair) and isinstance(self.two, Pair):
one_count = sum(1 for _ in self.one.all_window_ids())
two_count = sum(1 for _ in self.two.all_window_ids())
q = self.one if one_count < two_count else self.two
return q.balanced_add(window_id)
if not isinstance(self.one, Pair) and not isinstance(self.two, Pair):
pair = Pair(horizontal=self.horizontal)
pair.balanced_add(self.one)
pair.balanced_add(self.two)
self.one, self.two = pair, window_id
return self
if isinstance(self.one, Pair):
window_to_be_split = self.two
self.two = pair = Pair(horizontal=self.horizontal)
else:
window_to_be_split = self.one
self.one = pair = Pair(horizontal=self.horizontal)
assert isinstance(window_to_be_split, int)
pair.balanced_add(window_to_be_split)
pair.balanced_add(window_id)
return pair
def split_and_add(self, existing_window_id: int, new_window_id: int, horizontal: bool, after: bool) -> 'Pair':
q = (existing_window_id, new_window_id) if after else (new_window_id, existing_window_id)
if self.is_redundant:
pair = self
pair.horizontal = horizontal
self.one, self.two = q
else:
pair = Pair(horizontal=horizontal)
if self.one == existing_window_id:
self.one = pair
else:
self.two = pair
tuple(map(pair.balanced_add, q))
return pair
def apply_window_geometry(
self, window_id: int,
window_geometry: WindowGeometry,
id_window_map: Dict[int, WindowGroup],
layout_object: Layout
) -> None:
wg = id_window_map[window_id]
wg.set_geometry(window_geometry)
layout_object.blank_rects.extend(blank_rects_for_window(window_geometry))
def effective_border(self, id_window_map: Dict[int, WindowGroup]) -> int:
for wid in self.all_window_ids():
return id_window_map[wid].effective_border()
return 0
def layout_pair(
self,
left: int, top: int, width: int, height: int,
id_window_map: Dict[int, WindowGroup],
layout_object: Layout
) -> None:
self.between_border = None
if self.one is None or self.two is None:
q = self.one or self.two
if isinstance(q, Pair):
return q.layout_pair(left, top, width, height, id_window_map, layout_object)
if q is None:
return
wg = id_window_map[q]
xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width))
yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height))
geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(q, geom, id_window_map, layout_object)
return
bw = self.effective_border(id_window_map) if lgd.draw_minimal_borders else 0
b1 = bw // 2
b2 = bw - b1
if self.horizontal:
w1 = max(2*lgd.cell_width + 1, int(self.bias * width) - b1)
w2 = max(2*lgd.cell_width + 1, width - w1 - b1 - b2)
if isinstance(self.one, Pair):
self.one.layout_pair(left, top, w1, height, id_window_map, layout_object)
else:
wg = id_window_map[self.one]
yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height))
xl = next(layout_object.xlayout(iter((wg,)), start=left, size=w1))
geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.one, geom, id_window_map, layout_object)
if b1 + b2:
self.between_border = Edges(left + w1, top, left + w1 + b1 + b2, top + height)
left += b1 + b2
if isinstance(self.two, Pair):
self.two.layout_pair(left + w1, top, w2, height, id_window_map, layout_object)
else:
wg = id_window_map[self.two]
xl = next(layout_object.xlayout(iter((wg,)), start=left + w1, size=w2))
yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height))
geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.two, geom, id_window_map, layout_object)
else:
h1 = max(2*lgd.cell_height + 1, int(self.bias * height) - b1)
h2 = max(2*lgd.cell_height + 1, height - h1 - b1 - b2)
if isinstance(self.one, Pair):
self.one.layout_pair(left, top, width, h1, id_window_map, layout_object)
else:
wg = id_window_map[self.one]
xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width))
yl = next(layout_object.ylayout(iter((wg,)), start=top, size=h1))
geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.one, geom, id_window_map, layout_object)
if b1 + b2:
self.between_border = Edges(left, top + h1, left + width, top + h1 + b1 + b2)
top += b1 + b2
if isinstance(self.two, Pair):
self.two.layout_pair(left, top + h1, width, h2, id_window_map, layout_object)
else:
wg = id_window_map[self.two]
xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width))
yl = next(layout_object.ylayout(iter((wg,)), start=top + h1, size=h2))
geom = window_geometry_from_layouts(xl, yl)
self.apply_window_geometry(self.two, geom, id_window_map, layout_object)
def modify_size_of_child(self, which: int, increment: float, is_horizontal: bool, layout_object: 'Splits') -> bool:
if is_horizontal == self.horizontal and not self.is_redundant:
if which == 2:
increment *= -1
new_bias = max(0.1, min(self.bias + increment, 0.9))
if new_bias != self.bias:
self.bias = new_bias
return True
return False
parent = self.parent(layout_object.pairs_root)
if parent is not None:
which = 1 if parent.one is self else 2
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 quadrant(is_horizontal: bool, is_first: bool) -> Tuple[EdgeLiteral, EdgeLiteral]:
if is_horizontal:
if is_first:
return 'left', 'right'
return 'right', 'left'
if is_first:
return 'top', 'bottom'
return 'bottom', 'top'
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))
else:
ans[which].append(other)
other = self.two if self.one == window_id else self.one
extend(other, *quadrant(self.horizontal, self.one == window_id))
child = self
while True:
parent = child.parent(layout_object.pairs_root)
if parent is None:
break
other = parent.two if child is parent.one else parent.one
extend(other, *quadrant(parent.horizontal, child is parent.one))
child = parent
def edge_windows(self, edge: str) -> Generator[int, None, None]:
if self.is_redundant:
q = self.one or self.two
if q:
if isinstance(q, Pair):
yield from q.edge_windows(edge)
else:
yield q
edges = ('left', 'right') if self.horizontal else ('top', 'bottom')
if edge in edges:
q = self.one if edge in ('left', 'top') else self.two
if q:
if isinstance(q, Pair):
yield from q.edge_windows(edge)
else:
yield q
else:
for q in (self.one, self.two):
if q:
if isinstance(q, Pair):
yield from q.edge_windows(edge)
else:
yield q
class SplitsLayoutOpts(LayoutOpts):
default_axis_is_horizontal: bool = True
def __init__(self, data: Dict[str, str]):
self.default_axis_is_horizontal = data.get('split_axis', 'horizontal') == 'horizontal'
class Splits(Layout):
name = 'splits'
needs_all_windows = True
layout_opts = SplitsLayoutOpts({})
@property
def default_axis_is_horizontal(self) -> bool:
return self.layout_opts.default_axis_is_horizontal
@property
def pairs_root(self) -> Pair:
root: Optional[Pair] = getattr(self, '_pairs_root', None)
if root is None:
self._pairs_root = root = Pair(horizontal=self.default_axis_is_horizontal)
return root
@pairs_root.setter
def pairs_root(self, root: Pair) -> None:
self._pairs_root = root
def do_layout(self, all_windows: WindowList) -> None:
groups = tuple(all_windows.iter_all_layoutable_groups())
window_count = len(groups)
root = self.pairs_root
all_present_window_ids = frozenset(w.id for w in groups)
already_placed_window_ids = frozenset(root.all_window_ids())
windows_to_remove = already_placed_window_ids - all_present_window_ids
if windows_to_remove:
for pair in root.self_and_descendants():
pair.remove_windows(windows_to_remove)
root.collapse_redundant_pairs()
if root.one is None or root.two is None:
q = root.one or root.two
if isinstance(q, Pair):
root = self.pairs_root = q
id_window_map = {w.id: w for w in groups}
id_idx_map = {w.id: i for i, w in enumerate(groups)}
windows_to_add = all_present_window_ids - already_placed_window_ids
if windows_to_add:
for wid in sorted(windows_to_add, key=id_idx_map.__getitem__):
root.balanced_add(wid)
if window_count == 1:
self.layout_single_window_group(groups[0])
else:
root.layout_pair(lgd.central.left, lgd.central.top, lgd.central.width, lgd.central.height, id_window_map, self)
def add_non_overlay_window(
self,
all_windows: WindowList,
window: WindowType,
location: Optional[str]
) -> None:
horizontal = self.default_axis_is_horizontal
after = True
if location is not None:
if location == 'vsplit':
horizontal = True
elif location == 'hsplit':
horizontal = False
if location in ('before', 'first'):
after = False
aw = all_windows.active_window
if aw is not None:
ag = all_windows.active_group
assert ag is not None
group_id = ag.id
pair = self.pairs_root.pair_for_window(group_id)
if pair is not None:
target_group = all_windows.add_window(window, next_to=aw, before=not after)
pair.split_and_add(group_id, target_group.id, horizontal, after)
return
all_windows.add_window(window)
def modify_size_of_window(
self,
all_windows: WindowList,
window_id: int,
increment: float,
is_horizontal: bool = True
) -> bool:
grp = all_windows.group_for_window(window_id)
if grp is None:
return False
pair = self.pairs_root.pair_for_window(grp.id)
if pair is None:
return False
which = 1 if pair.one == grp.id else 2
return pair.modify_size_of_child(which, increment, is_horizontal, self)
def remove_all_biases(self) -> bool:
for pair in self.pairs_root.self_and_descendants():
pair.bias = 0.5
return True
def window_independent_borders(self, all_windows: WindowList) -> Generator[Edges, None, None]:
groups = tuple(all_windows.iter_all_layoutable_groups())
window_count = len(groups)
if not lgd.draw_minimal_borders or window_count < 2:
return
for pair in self.pairs_root.self_and_descendants():
if pair.between_border is not None:
yield pair.between_border
def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
wg = all_windows.group_for_window(window)
assert wg is not None
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)
return ans
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> 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)
after = all_windows.groups[before_idx]
if moved and before.id != after.id:
self.pairs_root.swap_windows(before.id, after.id)
return moved
def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]:
if action_name == 'rotate':
args = args or ('90',)
try:
amt = int(args[0])
except Exception:
amt = 90
if amt not in (90, 180, 270):
amt = 90
rotate = amt in (90, 270)
swap = amt in (180, 270)
wg = all_windows.active_group
if wg is not None:
pair = self.pairs_root.pair_for_window(wg.id)
if pair is not None and not pair.is_redundant:
if rotate:
pair.horizontal = not pair.horizontal
if swap:
pair.one, pair.two = pair.two, pair.one
return True

19
kitty/layout/stack.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.window_list import WindowList
from .base import Layout
class Stack(Layout):
name = 'stack'
needs_window_borders = False
only_active_window_visible = True
def do_layout(self, all_windows: WindowList) -> None:
active_group = all_windows.active_group
for group in all_windows.iter_all_layoutable_groups():
self.layout_single_window_group(group, add_blank_rects=group is active_group)

230
kitty/layout/tall.py Normal file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from itertools import islice, repeat
from typing import Dict, Generator, List, Optional, Sequence, Tuple
from kitty.conf.utils import to_bool
from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowList
from .base import (
Borders, Layout, LayoutDimension, LayoutOpts, NeighborsMap, all_borders,
lgd, no_borders, normalize_biases, safe_increment_bias, variable_bias
)
def neighbors_for_tall_window(
num_full_size_windows: int,
window: WindowType,
all_windows: WindowList,
mirrored: bool = False,
main_is_horizontal: bool = True
) -> NeighborsMap:
wg = all_windows.group_for_window(window)
assert wg is not None
groups = tuple(all_windows.iter_all_layoutable_groups())
idx = groups.index(wg)
prev = None if idx == 0 else groups[idx-1]
nxt = None if idx == len(groups) - 1 else groups[idx+1]
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
main_before: EdgeLiteral = 'left' if main_is_horizontal else 'top'
main_after: EdgeLiteral = 'right' if main_is_horizontal else 'bottom'
cross_before: EdgeLiteral = 'top' if main_is_horizontal else 'left'
cross_after: EdgeLiteral = 'bottom' if main_is_horizontal else 'right'
if mirrored:
main_before, main_after = main_after, main_before
if prev is not None:
ans[main_before] = [prev.id]
if idx < num_full_size_windows - 1:
if nxt is not None:
ans[main_after] = [nxt.id]
elif idx == num_full_size_windows - 1:
ans[main_after] = [w.id for w in groups[idx+1:]]
else:
ans[main_before] = [groups[num_full_size_windows - 1].id]
if idx > num_full_size_windows and prev is not None:
ans[cross_before] = [prev.id]
if nxt is not None:
ans[cross_after] = [nxt.id]
return ans
class TallLayoutOpts(LayoutOpts):
bias: Tuple[float, ...] = ()
full_size: int = 1
mirrored: bool = False
def __init__(self, data: Dict[str, str]):
try:
self.full_size = int(data.get('full_size', 1))
except Exception:
self.full_size = 1
self.full_size = fs = max(1, min(self.full_size, 100))
try:
b = int(data.get('bias', 50)) / 100
except Exception:
b = 0.5
b = max(0.1, min(b, 0.9))
self.bias = tuple(repeat(b / fs, fs)) + (1.0 - b,)
self.mirrored = to_bool(data.get('mirrored', 'false'))
class Tall(Layout):
name = 'tall'
main_is_horizontal = True
only_between_border = Borders(False, False, False, True)
only_main_border = Borders(False, False, True, False)
only_main_border_mirrored = Borders(True, False, False, False)
layout_opts = TallLayoutOpts({})
main_axis_layout = Layout.xlayout
perp_axis_layout = Layout.ylayout
@property
def num_full_size_windows(self) -> int:
return self.layout_opts.full_size
def remove_all_biases(self) -> bool:
self.main_bias: List[float] = list(self.layout_opts.bias)
self.biased_map: Dict[int, float] = {}
return True
def variable_layout(self, all_windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension:
num = all_windows.num_groups - self.num_full_size_windows
bias = variable_bias(num, biased_map) if num > 1 else None
return self.perp_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias, offset=self.num_full_size_windows)
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
num_windows = all_windows.num_groups
if self.main_is_horizontal == is_horizontal:
before_main_bias = self.main_bias
ncols = self.num_full_size_windows + 1
biased_col = idx if idx < self.num_full_size_windows else (ncols - 1)
self.main_bias = [
safe_increment_bias(self.main_bias[i], increment * (1 if i == biased_col else -1)) for i in range(ncols)
]
self.main_bias = normalize_biases(self.main_bias)
return self.main_bias != before_main_bias
num_of_short_windows = num_windows - self.num_full_size_windows
if idx < self.num_full_size_windows or num_of_short_windows < 2:
return False
idx -= self.num_full_size_windows
before_layout = list(self.variable_layout(all_windows, self.biased_map))
before = self.biased_map.get(idx, 0.)
candidate = self.biased_map.copy()
candidate[idx] = after = before + increment
if before_layout == list(self.variable_layout(all_windows, candidate)):
return False
self.biased_map = candidate
return before != after
def do_layout(self, all_windows: WindowList) -> None:
num = all_windows.num_groups
if num == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
is_fat = not self.main_is_horizontal
mirrored = self.layout_opts.mirrored
groups = tuple(all_windows.iter_all_layoutable_groups())
main_bias = self.main_bias[::-1] if mirrored else self.main_bias
if num <= self.num_full_size_windows + 1:
if mirrored:
groups = tuple(reversed(groups))
if num < self.num_full_size_windows + 1:
main_bias = normalize_biases(main_bias[:num])
xlayout = self.main_axis_layout(iter(groups), bias=main_bias)
for wg, xl in zip(groups, xlayout):
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
return
start = lgd.central.top if is_fat else lgd.central.left
size = 0
if mirrored:
fsg = groups[:self.num_full_size_windows + 1]
xlayout = self.main_axis_layout(reversed(fsg), bias=main_bias)
for i, wg in enumerate(reversed(fsg)):
xl = next(xlayout)
if i == 0:
size = xl.content_size + xl.space_before + xl.space_after
continue
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
else:
xlayout = self.main_axis_layout(islice(groups, self.num_full_size_windows + 1), bias=main_bias)
attr: EdgeLiteral = 'bottom' if is_fat else 'right'
for i, wg in enumerate(groups):
if i >= self.num_full_size_windows:
break
xl = next(xlayout)
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
geom = self.set_window_group_geometry(wg, xl, yl)
start = getattr(geom, attr) + wg.decoration(attr)
size = (lgd.central.height if is_fat else lgd.central.width) - start
ylayout = self.variable_layout(all_windows, self.biased_map)
for i, wg in enumerate(all_windows.iter_all_layoutable_groups()):
if i < self.num_full_size_windows:
continue
yl = next(ylayout)
xl = next(self.main_axis_layout(iter((wg,)), start=start, size=size))
if is_fat:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap:
return neighbors_for_tall_window(self.num_full_size_windows, window, windows, self.layout_opts.mirrored, self.main_is_horizontal)
def minimal_borders(self, all_windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
mirrored = self.layout_opts.mirrored
only_main_border = self.only_main_border_mirrored if mirrored else self.only_main_border
num = all_windows.num_groups
last_i = num - 1
groups = tuple(all_windows.iter_all_layoutable_groups())
for i, wg in enumerate(groups):
if needs_borders_map[wg.id]:
yield all_borders
continue
if i < self.num_full_size_windows:
next_window_is_full_sized = last_i == i+1 or i+1 < self.num_full_size_windows
if next_window_is_full_sized and needs_borders_map[groups[i+1].id]:
yield no_borders
else:
yield no_borders if i == last_i else only_main_border
continue
if i == last_i:
yield no_borders
break
if needs_borders_map[groups[i+1].id]:
yield no_borders
else:
yield self.only_between_border
def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]:
if action_name == 'increase_num_full_size_windows':
self.layout_opts.full_size += 1
return True
if action_name == 'decrease_num_full_size_windows':
if self.layout_opts.full_size > 1:
self.layout_opts.full_size -= 1
return True
class Fat(Tall):
name = 'fat'
main_is_horizontal = False
only_between_border = Borders(False, False, True, False)
only_main_border = Borders(False, False, False, True)
only_main_border_mirrored = Borders(False, True, False, False)
main_axis_layout = Layout.ylayout
perp_axis_layout = Layout.xlayout

94
kitty/layout/vertical.py Normal file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Dict, Generator
from kitty.typing import WindowType
from kitty.window_list import WindowList
from .base import (
Borders, Layout, LayoutDimension, NeighborsMap, all_borders, no_borders,
variable_bias
)
class Vertical(Layout):
name = 'vertical'
main_is_horizontal = False
only_between_border = Borders(False, False, False, True)
main_axis_layout = Layout.ylayout
perp_axis_layout = Layout.xlayout
def variable_layout(self, all_windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension:
num_windows = all_windows.num_groups
bias = variable_bias(num_windows, biased_map) if num_windows else None
return self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias)
def remove_all_biases(self) -> bool:
self.biased_map: Dict[int, float] = {}
return True
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
if self.main_is_horizontal != is_horizontal:
return False
num_windows = all_windows.num_groups
if num_windows < 2:
return False
before_layout = list(self.variable_layout(all_windows, self.biased_map))
candidate = self.biased_map.copy()
before = candidate.get(idx, 0)
candidate[idx] = before + increment
if before_layout == list(self.variable_layout(all_windows, candidate)):
return False
self.biased_map = candidate
return True
def do_layout(self, all_windows: WindowList) -> None:
window_count = all_windows.num_groups
if window_count == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
ylayout = self.variable_layout(all_windows, self.biased_map)
for i, (wg, yl) in enumerate(zip(all_windows.iter_all_layoutable_groups(), ylayout)):
xl = next(self.perp_axis_layout(iter((wg,))))
if self.main_is_horizontal:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
def minimal_borders(self, all_windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
last_i = all_windows.num_groups - 1
groups = tuple(all_windows.iter_all_layoutable_groups())
for i, wg in enumerate(groups):
if needs_borders_map[wg.id]:
yield all_borders
continue
if i == last_i:
yield no_borders
break
if needs_borders_map[groups[i+1].id]:
yield no_borders
else:
yield self.only_between_border
def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
wg = all_windows.group_for_window(window)
assert wg is not None
groups = tuple(all_windows.iter_all_layoutable_groups())
idx = groups.index(wg)
before = [] if wg is groups[0] else [groups[idx-1].id]
after = [] if wg is groups[-1] else [groups[idx+1].id]
if self.main_is_horizontal:
return {'left': before, 'right': after, 'top': [], 'bottom': []}
return {'top': before, 'bottom': after, 'left': [], 'right': []}
class Horizontal(Vertical):
name = 'horizontal'
main_is_horizontal = True
only_between_border = Borders(False, False, True, False)
main_axis_layout = Layout.xlayout
perp_axis_layout = Layout.ylayout

View File

@@ -550,6 +550,13 @@ left_shift(Line *self, PyObject *args) {
Py_RETURN_NONE;
}
char_type
line_get_char(Line *self, index_type at) {
char_type ch = self->cpu_cells[at].ch;
if (!ch && at > 0 && (self->gpu_cells[at-1].attrs & WIDTH_MASK) > 1) ch = self->cpu_cells[at-1].ch;
return ch;
}
void
line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor, bool UNUSED is_second) {
if (cursor == NULL) {

View File

@@ -54,9 +54,22 @@ line_reset_cells(Line *line, index_type start, index_type num, GPUCell *gpu_cell
memcpy(line->cpu_cells + start, cpu_cells + start, sizeof(CPUCell) * num);
}
static inline void
left_shift_line(Line *line, index_type at, index_type num) {
for (index_type i = at; i < line->xnum - num; i++) {
COPY_CELL(line, i + num, line, i);
}
if (at < line->xnum && ((line->gpu_cells[at].attrs) & WIDTH_MASK) != 1) {
line->cpu_cells[at].ch = BLANK_CHAR;
line->gpu_cells[at].attrs = BLANK_CHAR ? 1 : 0;
clear_sprite_position(line->gpu_cells[at]);
}
}
typedef Line*(get_line_func)(void *, int);
void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch);
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char);
char_type line_get_char(Line *self, index_type at);
void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bool);
void line_right_shift(Line *, unsigned int , unsigned int );
void line_add_combining_char(Line *, uint32_t , unsigned int );

View File

@@ -7,7 +7,7 @@ import os
import shutil
import sys
from contextlib import contextmanager, suppress
from typing import Generator, List, Mapping, Optional, Tuple, Sequence
from typing import Generator, List, Mapping, Optional, Sequence, Tuple
from .borders import load_borders_program
from .boss import Boss
@@ -15,7 +15,7 @@ from .child import set_default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .conf.utils import BadLine
from .config import cached_values_for, initial_window_size_func, expandvars
from .config import cached_values_for, initial_window_size_func
from .constants import (
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
is_wayland, kitty_exe, logo_data_file, running_in_kitty
@@ -29,7 +29,7 @@ from .fonts.box_drawing import set_scale
from .fonts.render import set_font_family
from .options_stub import Options as OptionsStub
from .utils import (
detach, log_error, read_shell_environment, single_instance,
detach, expandvars, log_error, read_shell_environment, single_instance,
startup_notification_handler, unix_socket_paths
)
from .window import load_shader_programs

View File

@@ -56,8 +56,8 @@ def marker_from_multiple_regex(regexes: Iterable[Tuple[int, str]], flags: int =
for match in pat.finditer(text):
left.value = match.start()
right.value = match.end() - 1
grp = next(k for k, v in match.groupdict().items())
color.value = color_map[grp]
grp = match.lastgroup
color.value = color_map[grp] if grp is not None else 0
yield
return marker

View File

@@ -52,12 +52,12 @@ extern monotonic_t monotonic_(void);
static inline monotonic_t
monotonic(void) {
return monotonic_() - monotonic_start_time;
return monotonic_() - monotonic_start_time;
}
static inline void
init_monotonic(void) {
monotonic_start_time = monotonic_();
monotonic_start_time = monotonic_();
}
#ifdef MONOTONIC_IMPLEMENTATION

View File

@@ -25,27 +25,43 @@ typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
#define ALT_INDICATOR (1 << 3)
#define CONTROL_INDICATOR (1 << 4)
#define MOTION_INDICATOR (1 << 5)
#define EXTRA_BUTTON_INDICATOR (1 << 6)
#define SCROLL_BUTTON_INDICATOR (1 << 6)
#define EXTRA_BUTTON_INDICATOR (1 << 7)
static inline unsigned int
button_map(int button) {
switch(button) {
case GLFW_MOUSE_BUTTON_LEFT:
return 0;
case GLFW_MOUSE_BUTTON_RIGHT:
return 2;
case GLFW_MOUSE_BUTTON_MIDDLE:
return 1;
case GLFW_MOUSE_BUTTON_RIGHT:
return 3;
case GLFW_MOUSE_BUTTON_MIDDLE:
return 2;
case GLFW_MOUSE_BUTTON_4:
return EXTRA_BUTTON_INDICATOR;
case GLFW_MOUSE_BUTTON_5:
return EXTRA_BUTTON_INDICATOR | 1;
case GLFW_MOUSE_BUTTON_6:
case GLFW_MOUSE_BUTTON_7:
case GLFW_MOUSE_BUTTON_8:
return button + 5;
default:
return UINT_MAX;
}
}
static inline unsigned int
encode_button(unsigned int button) {
if (button >= 8 && button <= 11) {
return (button - 8) | EXTRA_BUTTON_INDICATOR;
} else if (button >= 4 && button <= 7) {
return (button - 4) | SCROLL_BUTTON_INDICATOR;
} else if (button >= 1 && button <= 3) {
return button - 1;
} else {
return UINT_MAX;
}
}
static char mouse_event_buf[64];
static inline int
@@ -54,7 +70,7 @@ encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_proto
if (action == MOVE) {
cb = 3;
} else {
cb = button_map(button);
cb = encode_button(button);
if (cb == UINT_MAX) return 0;
}
if (action == DRAG || action == MOVE) cb |= MOTION_INDICATOR;
@@ -92,7 +108,16 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
unsigned int x = w->mouse_pos.cell_x + 1, y = w->mouse_pos.cell_y + 1; // 1 based indexing
Screen *screen = w->render_data.screen;
return encode_mouse_event_impl(x, y, screen->modes.mouse_tracking_protocol, button, action, mods);
}
static int
encode_mouse_button(Window *w, int button, MouseAction action, int mods) {
return encode_mouse_event(w, button_map(button), action, mods);
}
static int
encode_mouse_scroll(Window *w, bool upwards, int mods) {
return encode_mouse_event(w, upwards ? 4 : 5, PRESS, mods);
}
// }}}
@@ -321,7 +346,7 @@ HANDLER(handle_move_event) {
if (OPT(focus_follows_mouse)) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
if (window_idx != t->active_window) {
call_boss(switch_focus_to, "I", window_idx);
call_boss(switch_focus_to, "K", t->windows[window_idx].id);
}
}
bool in_left_half_of_cell = false;
@@ -341,7 +366,7 @@ HANDLER(handle_move_event) {
handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed);
} else {
if (!mouse_cell_changed) return;
int sz = encode_mouse_event(w, MAX(0, button), button >=0 ? DRAG : MOVE, 0);
int sz = encode_mouse_button(w, MAX(0, button), button >=0 ? DRAG : MOVE, modifiers);
if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
}
}
@@ -438,7 +463,7 @@ HANDLER(handle_button_event) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
bool is_release = !global_state.callback_os_window->mouse_button_pressed[button];
if (window_idx != t->active_window && !is_release) {
call_boss(switch_focus_to, "I", window_idx);
call_boss(switch_focus_to, "K", t->windows[window_idx].id);
}
Screen *screen = w->render_data.screen;
if (!screen) return;
@@ -450,34 +475,25 @@ HANDLER(handle_button_event) {
);
if (handle_in_kitty) handle_button_event_in_kitty(w, button, modifiers, is_release);
else {
int sz = encode_mouse_event(w, button, is_release ? RELEASE : PRESS, modifiers);
int sz = encode_mouse_button(w, button, is_release ? RELEASE : PRESS, modifiers);
if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
}
}
static inline int
currently_pressed_button(void) {
for (int i = 0; i < GLFW_MOUSE_BUTTON_5; i++) {
for (int i = 0; i <= GLFW_MOUSE_BUTTON_8; i++) {
if (global_state.callback_os_window->mouse_button_pressed[i]) return i;
}
return -1;
}
HANDLER(handle_event) {
switch(button) {
case -1:
button = currently_pressed_button();
handle_move_event(w, button, modifiers, window_idx);
break;
case GLFW_MOUSE_BUTTON_LEFT:
case GLFW_MOUSE_BUTTON_RIGHT:
case GLFW_MOUSE_BUTTON_MIDDLE:
case GLFW_MOUSE_BUTTON_4:
case GLFW_MOUSE_BUTTON_5:
handle_button_event(w, button, modifiers, window_idx);
break;
default:
break;
if (button == -1) {
button = currently_pressed_button();
handle_move_event(w, button, modifiers, window_idx);
} else {
handle_button_event(w, button, modifiers, window_idx);
}
}
@@ -514,7 +530,8 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) {
*window_idx = i; return t->windows + i;
*window_idx = i;
return t->windows + i;
}
}
}
@@ -529,8 +546,10 @@ closest_window_for_event(unsigned int *window_idx) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
double d = distance_to_window(w);
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
if (w->visible) {
double d = distance_to_window(w);
if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
}
}
}
return ans;
@@ -610,7 +629,7 @@ mouse_event(int button, int modifiers, int action) {
}
void
scroll_event(double UNUSED xoffset, double yoffset, int flags) {
scroll_event(double UNUSED xoffset, double yoffset, int flags, int modifiers) {
bool in_tab_bar;
static id_type window_for_momentum_scroll = 0;
static bool main_screen_for_momentum_scroll = false;
@@ -687,7 +706,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
screen_history_scroll(screen, abs(s), upwards);
} else {
if (screen->modes.mouse_tracking_mode) {
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
int sz = encode_mouse_scroll(w, upwards, modifiers);
if (sz > 0) {
mouse_event_buf[sz] = 0;
for (s = abs(s); s > 0; s--) {
@@ -695,7 +714,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
}
}
} else {
fake_scroll(abs(s), upwards);
fake_scroll(w, abs(s), upwards);
}
}
}

66
kitty/multiprocessing.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
# Monkeypatch the stdlib multiprocessing module to work with the embedded python
# in kitty, when using the spawn launcher.
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import util # type: ignore
from multiprocessing import context, get_all_start_methods, get_context, spawn
from typing import Any, Callable, List, Optional, Tuple, Union
from .constants import kitty_exe
orig_spawn_passfds = util.spawnv_passfds
orig_executable = spawn.get_executable()
def spawnv_passfds(path: str, args: List[str], passfds: List[int]) -> Any:
idx = args.index('-c')
patched_args = [spawn.get_executable(), '+runpy'] + args[idx + 1:]
return orig_spawn_passfds(kitty_exe(), patched_args, passfds)
def monkey_patch_multiprocessing() -> None:
# Use kitty to run the worker process used by multiprocessing
spawn.set_executable(kitty_exe())
util.spawnv_passfds = spawnv_passfds
def unmonkey_patch_multiprocessing() -> None:
spawn.set_executable(orig_executable)
util.spawnv_passfds = orig_spawn_passfds
def get_process_pool_executor(
prefer_fork: bool = False,
max_workers: Optional[int] = None,
initializer: Optional[Callable] = None,
initargs: Tuple[Any, ...] = ()
) -> ProcessPoolExecutor:
if prefer_fork and 'fork' in get_all_start_methods():
ctx: Union[context.DefaultContext, context.ForkContext] = get_context('fork')
else:
monkey_patch_multiprocessing()
ctx = get_context()
try:
return ProcessPoolExecutor(max_workers=max_workers, initializer=initializer, initargs=initargs, mp_context=ctx)
except TypeError:
return ProcessPoolExecutor(max_workers=max_workers, initializer=initializer, initargs=initargs)
def test_spawn() -> None:
monkey_patch_multiprocessing()
try:
from multiprocessing import get_context
ctx = get_context('spawn')
q = ctx.Queue()
p = ctx.Process(target=q.put, args=('hello',))
p.start()
x = q.get(timeout=2)
assert x == 'hello'
p.join()
finally:
unmonkey_patch_multiprocessing()

Some files were not shown because too many files have changed in this diff Show More