Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
644ee646a2 | ||
|
|
8f1ec053ef | ||
|
|
2e3037ce3a | ||
|
|
109a856b91 | ||
|
|
eaec854bfb | ||
|
|
2c6cc6f047 | ||
|
|
f32ae10339 | ||
|
|
c46317f96f | ||
|
|
a67e4e550e | ||
|
|
65a7b19f40 | ||
|
|
1b5daf930a | ||
|
|
748ca81d4b | ||
|
|
0ea7d24ba4 | ||
|
|
6c9861cb76 | ||
|
|
a1125b5cc8 | ||
|
|
b4e189d2bf | ||
|
|
dbc763fca0 | ||
|
|
4c03ffeae2 | ||
|
|
2719ac7d01 | ||
|
|
219b81b5ee | ||
|
|
8e1443360f | ||
|
|
d55242aa82 | ||
|
|
ba531969ec | ||
|
|
c12e7587cb | ||
|
|
3aac0860a2 | ||
|
|
e4fd12001c | ||
|
|
3581ffe04b | ||
|
|
f6901e4a3d | ||
|
|
5a2cc4578e | ||
|
|
6b5acc4d3d | ||
|
|
43326c9bd0 | ||
|
|
96f3253e6d | ||
|
|
f5090c951a | ||
|
|
edacdc5e25 | ||
|
|
c67661af61 | ||
|
|
5e60329adf | ||
|
|
6b4f8408de | ||
|
|
ea1ef709c6 | ||
|
|
bbf247c5c0 | ||
|
|
8243fd505f | ||
|
|
ed9f9db1b0 | ||
|
|
74bed658e1 | ||
|
|
2f7bdc274d | ||
|
|
1347ff27d9 | ||
|
|
5bf4d520f5 | ||
|
|
cf8d665eb7 | ||
|
|
b2eee6df5c | ||
|
|
e8121b39ec | ||
|
|
5359abab76 | ||
|
|
01644ac3f0 | ||
|
|
8fbf552494 | ||
|
|
9c3390c5e6 | ||
|
|
24d45b99e3 | ||
|
|
936f2186cc | ||
|
|
c68b302fa2 | ||
|
|
ca7cab1a2d | ||
|
|
083b294659 | ||
|
|
bc24716476 | ||
|
|
4acab65016 | ||
|
|
f6b03f106c | ||
|
|
8f9bbeabd7 | ||
|
|
ac72ac7af2 | ||
|
|
68e00bc8e1 | ||
|
|
922d6b3b32 | ||
|
|
4dde98305e | ||
|
|
3593189a75 | ||
|
|
4f135be86c | ||
|
|
605b32f78a | ||
|
|
476a8b1429 | ||
|
|
da9165aebf | ||
|
|
455a1e9532 | ||
|
|
f12031e284 | ||
|
|
df551bc992 | ||
|
|
35fb702833 | ||
|
|
072cd29e3c | ||
|
|
624dd78460 | ||
|
|
51c4737a29 | ||
|
|
d63ea11cac | ||
|
|
3d307794be | ||
|
|
d32a05925f | ||
|
|
04006ea81e | ||
|
|
1723616968 | ||
|
|
23bc2171c9 | ||
|
|
0b3602f764 | ||
|
|
0e448347cd | ||
|
|
e0c66ea5ba | ||
|
|
268de9a9d5 | ||
|
|
74eb523178 | ||
|
|
8c540d5a46 | ||
|
|
2045055ae3 | ||
|
|
d8ed135b46 | ||
|
|
3a4ba7e834 | ||
|
|
fab57fbdf1 | ||
|
|
76a93b6cd6 | ||
|
|
f44c3a4606 | ||
|
|
714bee7f45 | ||
|
|
91673642b3 | ||
|
|
fee08d746c | ||
|
|
527ff0238a | ||
|
|
789d649b5c | ||
|
|
8020657d8c | ||
|
|
a15d3ff12d | ||
|
|
fbce95aeda | ||
|
|
9bc1b5a2d9 | ||
|
|
6a8b7bf92f | ||
|
|
48728bdfdc | ||
|
|
b6416cf8a9 | ||
|
|
053de5763a | ||
|
|
fdb915d0c8 | ||
|
|
c2769c9aed | ||
|
|
57deb19d0b | ||
|
|
321771b150 | ||
|
|
61dd2011f5 | ||
|
|
8a5f189213 | ||
|
|
14560b008a | ||
|
|
db85e07d41 | ||
|
|
07c838fe24 | ||
|
|
1583b1b0c6 | ||
|
|
d250555cd0 | ||
|
|
dbd0dab154 | ||
|
|
b479ea410d | ||
|
|
1db613e95b | ||
|
|
70071fe1f6 | ||
|
|
58688dda38 | ||
|
|
756d65c41e | ||
|
|
b9a08eacb0 | ||
|
|
b653711446 | ||
|
|
e725c647ef | ||
|
|
003f77b257 | ||
|
|
10b578e6d2 | ||
|
|
151c6c2c70 | ||
|
|
53e6de9bd9 | ||
|
|
66a455a079 | ||
|
|
f8855410e0 | ||
|
|
93e8074325 | ||
|
|
296f512bfb | ||
|
|
c8f20d0edf | ||
|
|
286f3630d7 | ||
|
|
38423505b1 | ||
|
|
9a3c621eb6 | ||
|
|
bcf847fe98 | ||
|
|
ee7a5eef99 | ||
|
|
7bed66a458 | ||
|
|
b26229c44e | ||
|
|
c497c0f96a | ||
|
|
6df64613f2 | ||
|
|
46dd648380 | ||
|
|
4cd5de78c7 | ||
|
|
32fa46d6eb | ||
|
|
902c2f6c88 | ||
|
|
2a5196873a | ||
|
|
d2067141d3 | ||
|
|
b5229ec73c | ||
|
|
3a2a16f54c | ||
|
|
b2d428618c | ||
|
|
c28ff5259a | ||
|
|
4619259e26 |
@@ -4,6 +4,53 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.16.0 [2020-01-28]
|
||||
--------------------
|
||||
|
||||
- A new :doc:`marks` feature that allows highlighting and scrolling to arbitrary
|
||||
text in the terminal window.
|
||||
|
||||
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
|
||||
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
|
||||
|
||||
- Allow having more than one full height window in the :code:`tall` layout
|
||||
(:iss:`2276`)
|
||||
|
||||
- Allow choosing OpenType features for individual fonts via the
|
||||
:opt:`font_features` option. (:pull:`2248`)
|
||||
|
||||
- Wayland: Fix a freeze in rare circumstances when having multiple OS Windows
|
||||
(:iss:`2307` and :iss:`1722`)
|
||||
|
||||
- Wayland: Fix window titles being set to very long strings on the order of 8KB
|
||||
causing a crash (:iss:`1526`)
|
||||
|
||||
- Add an option :opt:`force_ltr` to turn off the display of text in RTL scripts
|
||||
in right-to-left order (:pull:`2293`)
|
||||
|
||||
- Allow opening new tabs/windows before the current tab/window as well as after
|
||||
it with the :option:`launch --location` option.
|
||||
|
||||
- Add a :opt:`resize_in_steps` option that can be used to resize the OS window
|
||||
in steps as large as character cells (:pull:`2131`)
|
||||
|
||||
- When triple-click+dragging to select multiple lines, extend the selection
|
||||
of the first line to match the rest on the left (:pull:`2284`)
|
||||
|
||||
- macOS: Add a :code:`titlebar-only` setting to
|
||||
:opt:`hide_window_decorations` to only hide the title bar (:pull:`2286`)
|
||||
|
||||
- Fix a segfault when using :option:`kitty --debug-config` with maps (:iss:`2270`)
|
||||
|
||||
- ``goto_tab`` now maps numbers larger than the last tab to the last tab
|
||||
(:iss:`2291`)
|
||||
|
||||
- Fix URL detection not working for urls of the form scheme:///url
|
||||
(:iss:`2292`)
|
||||
|
||||
- When windows are semi-transparent and all contain graphics, correctly render
|
||||
them. (:iss:`2310`)
|
||||
|
||||
0.15.1 [2019-12-21]
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -334,7 +334,9 @@ Finally, you can specify the image *z-index*, i.e. the vertical stacking order.
|
||||
placed in the same location with different z-index values will be blended if
|
||||
they are semi-transparent. You can specify z-index values using the ``z`` key.
|
||||
Negative z-index values mean that the images will be drawn under the text. This
|
||||
allows rendering of text on top of images.
|
||||
allows rendering of text on top of images. Negative z-index values below
|
||||
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
||||
colors.
|
||||
|
||||
Deleting images
|
||||
---------------------
|
||||
@@ -417,7 +419,7 @@ Key Value Default Description
|
||||
``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image
|
||||
``c`` Positive integer ``0`` The number of columns to display the image over
|
||||
``r`` Positive integer ``0`` The number of rows to display the image over
|
||||
``z`` Integer ``0`` The *z-index* vertical stacking order of the image
|
||||
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
||||
**Keys for deleting images**
|
||||
-----------------------------------------------------------
|
||||
``d`` Single character. ``a`` What to delete.
|
||||
|
||||
@@ -53,7 +53,7 @@ Quickstart
|
||||
--------------
|
||||
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||
See the :doc:`binary install instructions </binary>`. You can also
|
||||
See the :doc:`binary install instructions </binary>`. You can
|
||||
:doc:`build from source </build>`.
|
||||
|
||||
You can also use your favorite package manager to install the |kitty| package.
|
||||
@@ -224,11 +224,11 @@ using the :sc:`new_window` key combination.
|
||||
|
||||
Currently, there are five layouts available,
|
||||
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
* **Tall** -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* **Fat** -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* **Grid** -- All windows are shown in a grid
|
||||
* **Horizontal** -- All windows are shown side-by-side
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* **Vertical** -- All windows are shown one below the other
|
||||
|
||||
You can switch between layouts using the :sc:`next_layout` key combination. You can
|
||||
@@ -256,13 +256,15 @@ the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
|
||||
how the available space is split up.
|
||||
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
|
||||
enabled_layouts tall:bias=70
|
||||
enabled_layouts tall:bias=70;full_size=2
|
||||
|
||||
This will make the tall window occupy ``70%`` of available width. ``bias`` can be
|
||||
any number between 10 and 90.
|
||||
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
|
||||
@@ -441,6 +443,14 @@ internal buffer named ``a`` and :kbd:`F2` to paste from that buffer. The buffer
|
||||
names are arbitrary strings, so you can define as many such buffers as you
|
||||
need.
|
||||
|
||||
Marks
|
||||
-------------
|
||||
|
||||
kitty has the ability to mark text on the screen based on regular expressions.
|
||||
This can be useful to highlight words or phrases when browsing output from long
|
||||
running programs or similar. To learn how this feature works, see :doc:`marks`.
|
||||
|
||||
|
||||
Frequently Asked Questions
|
||||
---------------------------------
|
||||
|
||||
|
||||
@@ -18,6 +18,16 @@ select anything that looks like a path or filename and then insert it into the
|
||||
terminal, very useful for picking files from the output of a ``git`` or ``ls`` command and
|
||||
adding them to the command line for the next command.
|
||||
|
||||
You can also press :sc:`goto_file_line` to select anything that looks
|
||||
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}
|
||||
|
||||
will open the selected file in a new tab inside neovim when you press
|
||||
:kbd:`ctrl+g`.
|
||||
|
||||
The hints kitten is very powerful to see more detailed help on its various
|
||||
options and modes of operation, see below. You can use these options to
|
||||
create mappings in :file:`kitty.conf` to select various different text
|
||||
|
||||
122
docs/marks.rst
Normal file
122
docs/marks.rst
Normal file
@@ -0,0 +1,122 @@
|
||||
Marks
|
||||
=================
|
||||
|
||||
|
||||
kitty has the ability to mark text on the screen based on regular expressions.
|
||||
This can be useful to highlight words or phrases when browsing output from long
|
||||
running programs or similar. Lets start with a few examples:
|
||||
|
||||
Suppose we want to be able to highlight the word ERROR in the current window.
|
||||
Add the following to :file:`kitty.conf`::
|
||||
|
||||
map f1 toggle_marker text 1 ERROR
|
||||
|
||||
Now when you press :kbd:`F1` all instances of the word :code:`ERROR` will be
|
||||
highlighted. To turn off the highlighting, press :kbd:`F1` again.
|
||||
If you want to make it case-insensitive, use::
|
||||
|
||||
map f1 toggle_marker itext 1 ERROR
|
||||
|
||||
To make it match only complete words, use::
|
||||
|
||||
map f1 toggle_marker regex 1 \bERROR\b
|
||||
|
||||
Suppose you want to highlight both :code:`ERROR` and :code:`WARNING`, case
|
||||
insensitively::
|
||||
|
||||
map f1 toggle_marker iregex 1 \bERROR\b 2 \bWARNING\b
|
||||
|
||||
kitty supports up to 3 mark groups (the numbers in the commands above). You
|
||||
can control the colors used for these groups in :file:`kitty.conf` with::
|
||||
|
||||
mark1_foreground red
|
||||
mark1_background gray
|
||||
mark2_foreground green
|
||||
...
|
||||
|
||||
|
||||
.. note::
|
||||
For performance reasons, matching is done per line only, and only when that line is
|
||||
altered in any way. So you cannot match text that stretches across multiple
|
||||
lines.
|
||||
|
||||
|
||||
Creating markers dynamically
|
||||
---------------------------------
|
||||
|
||||
If you want to create markers dynamically rather than pre-defining them in
|
||||
:file:`kitty.conf` you can do so as follows::
|
||||
|
||||
map f1 create_marker
|
||||
map f2 remove_marker
|
||||
|
||||
Then pressing :kbd:`F1` will allow you to enter the marker definition and set
|
||||
it and pressing :kbd:`F2` will remove the marker.
|
||||
|
||||
You can also use the facilities for :doc:`remote-control` to dynamically
|
||||
add/remove markers.
|
||||
|
||||
|
||||
Scrolling to marks
|
||||
--------------------
|
||||
|
||||
kitty has an action to scroll to the next line that contains a mark. You can
|
||||
use it by mapping it to some shortcut in :file:`kitty.conf`::
|
||||
|
||||
map ctrl+p scroll_to_mark prev
|
||||
map ctrl+n scroll_to_mark next
|
||||
|
||||
Then pressing :kbd:`ctrl+p` will scroll to the first line in the scrollback
|
||||
buffer above the current top line that contains a mark. Pressing :kbd:`ctrl+n`
|
||||
will scroll to show the first line below the current last line that contains
|
||||
a mark. If you wish to jump to a mark of a specific type, you can add that to
|
||||
the mapping::
|
||||
|
||||
map ctrl+1 scroll_to_mark prev 1
|
||||
|
||||
Which will scroll only to marks of type 1.
|
||||
|
||||
|
||||
The full syntax for creating marks
|
||||
-------------------------------------
|
||||
|
||||
The syntax of the :code:`toggle_marker` command is::
|
||||
|
||||
toggle_marker <marker-type> <specification>
|
||||
|
||||
Here :code:`marker-type` is one of:
|
||||
|
||||
* :code:`text` - simple substring matching
|
||||
* :code:`itext` - case-insensitive substring matching
|
||||
* :code:`regex` - A python regular expression
|
||||
* :code:`iregex` - A case-insensitive python regular expression
|
||||
* :code:`function` - An arbitrary function defined in a python file, see
|
||||
:ref:`marker_funcs`.
|
||||
|
||||
.. _marker_funcs:
|
||||
|
||||
Arbitrary marker functions
|
||||
-----------------------------
|
||||
|
||||
You can create your own marker functions. Create a python file named
|
||||
:file:`mymarker.py` and in it create a :code:`marker` function. This
|
||||
function receives the text of the line as input and must yield three numbers,
|
||||
the starting character position, the ending character position and the mark
|
||||
group (1-3). For example:
|
||||
|
||||
.. code-block::
|
||||
|
||||
def marker(text):
|
||||
# Function to highlight the letter X
|
||||
for i, ch in enumerate(text):
|
||||
if ch.lower() == 'x':
|
||||
yield i, i, 3
|
||||
|
||||
|
||||
Save this file somewhere and in :file:`kitty.conf`, use::
|
||||
|
||||
map f1 toggle_marker function /path/to/mymarker.py
|
||||
|
||||
If you save the file in the kitty config directory, you can use::
|
||||
|
||||
map f1 toggle_marker function mymarker.py
|
||||
4
glfw/backend_utils.c
vendored
4
glfw/backend_utils.c
vendored
@@ -292,7 +292,7 @@ finalizePollData(EventLoopData *eld) {
|
||||
}
|
||||
|
||||
int
|
||||
pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func display_callback) {
|
||||
int read_ok = 0;
|
||||
timeout = prepareForPoll(eld, timeout);
|
||||
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
|
||||
@@ -305,6 +305,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
errno = 0;
|
||||
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
|
||||
int saved_errno = errno;
|
||||
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
@@ -319,6 +320,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
errno = 0;
|
||||
result = poll(eld->fds, eld->watches_count, -1);
|
||||
int saved_errno = errno;
|
||||
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
|
||||
2
glfw/backend_utils.h
vendored
2
glfw/backend_utils.h
vendored
@@ -90,7 +90,7 @@ void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
|
||||
void changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval);
|
||||
monotonic_t prepareForPoll(EventLoopData *eld, monotonic_t timeout);
|
||||
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout);
|
||||
int pollForEvents(EventLoopData *eld, monotonic_t timeout);
|
||||
int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func);
|
||||
unsigned dispatchTimers(EventLoopData *eld);
|
||||
void finalizePollData(EventLoopData *eld);
|
||||
bool initPollData(EventLoopData *eld, int display_fd);
|
||||
|
||||
@@ -529,6 +529,32 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
||||
@end
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void* _glfwLoadLocalVulkanLoaderNS(void)
|
||||
{
|
||||
CFBundleRef bundle = CFBundleGetMainBundle();
|
||||
if (!bundle)
|
||||
return NULL;
|
||||
|
||||
CFURLRef url =
|
||||
CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib"));
|
||||
if (!url)
|
||||
return NULL;
|
||||
|
||||
char path[PATH_MAX];
|
||||
void* handle = NULL;
|
||||
|
||||
if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1))
|
||||
handle = _glfw_dlopen(path);
|
||||
|
||||
CFRelease(url);
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -146,7 +146,7 @@ static bool modeIsGood(CGDisplayModeRef mode)
|
||||
// Convert Core Graphics display mode to GLFW video mode
|
||||
//
|
||||
static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
|
||||
CVDisplayLinkRef link)
|
||||
double fallbackRefreshRate)
|
||||
{
|
||||
GLFWvidmode result;
|
||||
result.width = (int) CGDisplayModeGetWidth(mode);
|
||||
@@ -154,11 +154,7 @@ static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
|
||||
result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
|
||||
|
||||
if (result.refreshRate == 0)
|
||||
{
|
||||
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
|
||||
if (!(time.flags & kCVTimeIsIndefinite))
|
||||
result.refreshRate = (int) (time.timeScale / (double) time.timeValue);
|
||||
}
|
||||
result.refreshRate = (int) round(fallbackRefreshRate);
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
|
||||
CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
|
||||
@@ -240,6 +236,30 @@ static bool refreshMonitorScreen(_GLFWmonitor* monitor)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns a fallback refresh rate for when Core Graphics says it is zero
|
||||
//
|
||||
static double getFallbackRefreshRate(_GLFWmonitor* monitor)
|
||||
{
|
||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
|
||||
double refreshRate = CGDisplayModeGetRefreshRate(mode);
|
||||
CGDisplayModeRelease(mode);
|
||||
|
||||
if (refreshRate == 0.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;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -331,6 +351,7 @@ 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);
|
||||
@@ -360,9 +381,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return;
|
||||
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
|
||||
const CFIndex count = CFArrayGetCount(modes);
|
||||
CGDisplayModeRef native = NULL;
|
||||
@@ -373,7 +391,8 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
if (!modeIsGood(dm))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
|
||||
const GLFWvidmode mode =
|
||||
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
|
||||
if (_glfwCompareVideoModes(best, &mode) == 0)
|
||||
{
|
||||
native = dm;
|
||||
@@ -392,7 +411,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
}
|
||||
|
||||
CFRelease(modes);
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
// Restore the previously saved (original) video mode
|
||||
@@ -469,9 +487,6 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
{
|
||||
*count = 0;
|
||||
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
|
||||
const CFIndex found = CFArrayGetCount(modes);
|
||||
GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
|
||||
@@ -482,7 +497,8 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
if (!modeIsGood(dm))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
|
||||
const GLFWvidmode mode =
|
||||
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
|
||||
|
||||
for (CFIndex j = 0; j < *count; j++)
|
||||
{
|
||||
@@ -499,20 +515,14 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
}
|
||||
|
||||
CFRelease(modes);
|
||||
CVDisplayLinkRelease(link);
|
||||
return result;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
|
||||
{
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
|
||||
*mode = vidmodeFromCGDisplayMode(native, link);
|
||||
*mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
|
||||
CGDisplayModeRelease(native);
|
||||
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
|
||||
16
glfw/cocoa_platform.h
vendored
16
glfw/cocoa_platform.h
vendored
@@ -65,13 +65,15 @@ typedef void* CVDisplayLinkRef;
|
||||
#endif
|
||||
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef VkFlags VkMetalSurfaceCreateFlagsEXT;
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
VkStructureType sType;
|
||||
@@ -80,7 +82,16 @@ typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
const void* pView;
|
||||
} VkMacOSSurfaceCreateInfoMVK;
|
||||
|
||||
typedef struct VkMetalSurfaceCreateInfoEXT
|
||||
{
|
||||
VkStructureType sType;
|
||||
const void* pNext;
|
||||
VkMetalSurfaceCreateFlagsEXT flags;
|
||||
const void* pLayer;
|
||||
} VkMetalSurfaceCreateInfoEXT;
|
||||
|
||||
typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacOSSurfaceCreateInfoMVK*,const VkAllocationCallbacks*,VkSurfaceKHR*);
|
||||
typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMetalSurfaceCreateInfoEXT*,const VkAllocationCallbacks*,VkSurfaceKHR*);
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "cocoa_joystick.h"
|
||||
@@ -201,6 +212,7 @@ typedef struct _GLFWmonitorNS
|
||||
CGDisplayModeRef previousMode;
|
||||
uint32_t unitNumber;
|
||||
id screen;
|
||||
double fallbackRefreshRate;
|
||||
|
||||
} _GLFWmonitorNS;
|
||||
|
||||
@@ -227,6 +239,8 @@ void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
|
||||
|
||||
float _glfwTransformYNS(float y);
|
||||
|
||||
void* _glfwLoadLocalVulkanLoaderNS(void);
|
||||
|
||||
void _glfwClearDisplayLinks(void);
|
||||
void _glfwRestartDisplayLinks(void);
|
||||
void _glfwDispatchTickCallback(void);
|
||||
|
||||
@@ -410,6 +410,7 @@ static int translateKey(unsigned int key, bool apply_keymap)
|
||||
K('[', LEFT_BRACKET);
|
||||
K('\\', BACKSLASH);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('^', CIRCUMFLEX);
|
||||
K('_', UNDERSCORE);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
@@ -1577,10 +1578,18 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
||||
{
|
||||
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
else
|
||||
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
|
||||
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
||||
else
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
||||
{
|
||||
if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE)
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(widthincr, heightincr)];
|
||||
else
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
||||
@@ -2070,11 +2079,16 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface || !_glfw.vk.MVK_macos_surface)
|
||||
return;
|
||||
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_MVK_macos_surface";
|
||||
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
|
||||
{
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_EXT_metal_surface";
|
||||
}
|
||||
else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface)
|
||||
{
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_MVK_macos_surface";
|
||||
}
|
||||
}
|
||||
|
||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED,
|
||||
@@ -2090,19 +2104,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
VkSurfaceKHR* surface)
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
|
||||
VkResult err;
|
||||
VkMacOSSurfaceCreateInfoMVK sci;
|
||||
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
|
||||
|
||||
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
|
||||
if (!vkCreateMacOSSurfaceMVK)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
// HACK: Dynamically load Core Animation to avoid adding an extra
|
||||
// dependency for the majority who don't use MoltenVK
|
||||
NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
|
||||
@@ -2128,11 +2129,49 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
[window->ns.view setLayer:window->ns.layer];
|
||||
[window->ns.view setWantsLayer:YES];
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
sci.pView = window->ns.view;
|
||||
VkResult err;
|
||||
|
||||
if (_glfw.vk.EXT_metal_surface)
|
||||
{
|
||||
VkMetalSurfaceCreateInfoEXT sci;
|
||||
|
||||
PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
|
||||
vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT");
|
||||
if (!vkCreateMetalSurfaceEXT)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_EXT_metal_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||
sci.pLayer = window->ns.layer;
|
||||
|
||||
err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface);
|
||||
}
|
||||
else
|
||||
{
|
||||
VkMacOSSurfaceCreateInfoMVK sci;
|
||||
|
||||
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
|
||||
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
|
||||
if (!vkCreateMacOSSurfaceMVK)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
sci.pView = window->ns.view;
|
||||
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
|
||||
}
|
||||
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
|
||||
if (err)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -2178,9 +2217,9 @@ GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun
|
||||
requestRenderFrame((_GLFWwindow*)w, callback);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
||||
*cocoa_key = 0;
|
||||
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) {
|
||||
*cocoa_mods = 0;
|
||||
memset(cocoa_key, 0, key_sz);
|
||||
|
||||
if (glfw_mods & GLFW_MOD_SHIFT)
|
||||
*cocoa_mods |= NSEventModifierFlagShift;
|
||||
@@ -2193,147 +2232,156 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
|
||||
if (glfw_mods & GLFW_MOD_CAPS_LOCK)
|
||||
*cocoa_mods |= NSEventModifierFlagCapsLock;
|
||||
|
||||
uint32_t utf_8_key = 0;
|
||||
unichar utf_16_key = 0;
|
||||
|
||||
START_ALLOW_CASE_RANGE
|
||||
switch(glfw_key) {
|
||||
#define K(ch, name) case GLFW_KEY_##name: *cocoa_key = ch; break;
|
||||
K('!', EXCLAM);
|
||||
K('"', DOUBLE_QUOTE);
|
||||
K('#', NUMBER_SIGN);
|
||||
K('$', DOLLAR);
|
||||
K('&', AMPERSAND);
|
||||
K('\'', APOSTROPHE);
|
||||
K('(', PARENTHESIS_LEFT);
|
||||
K(')', PARENTHESIS_RIGHT);
|
||||
K('+', PLUS);
|
||||
K(',', COMMA);
|
||||
K('-', MINUS);
|
||||
K('.', PERIOD);
|
||||
K('/', SLASH);
|
||||
K('0', 0);
|
||||
K('1', 1);
|
||||
K('2', 2);
|
||||
K('3', 3);
|
||||
K('5', 5);
|
||||
K('6', 6);
|
||||
K('7', 7);
|
||||
K('8', 8);
|
||||
K('9', 9);
|
||||
K(':', COLON);
|
||||
K(';', SEMICOLON);
|
||||
K('<', LESS);
|
||||
K('=', EQUAL);
|
||||
K('>', GREATER);
|
||||
K('@', AT);
|
||||
K('[', LEFT_BRACKET);
|
||||
K('\\', BACKSLASH);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('_', UNDERSCORE);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('a', A);
|
||||
K('b', B);
|
||||
K('c', C);
|
||||
K('d', D);
|
||||
K('e', E);
|
||||
K('f', F);
|
||||
K('g', G);
|
||||
K('h', H);
|
||||
K('i', I);
|
||||
K('j', J);
|
||||
K('k', K);
|
||||
K('l', L);
|
||||
K('m', M);
|
||||
K('n', N);
|
||||
K('o', O);
|
||||
K('p', P);
|
||||
K('q', Q);
|
||||
K('r', R);
|
||||
K('s', S);
|
||||
K('t', T);
|
||||
K('u', U);
|
||||
K('v', V);
|
||||
K('w', W);
|
||||
K('x', X);
|
||||
K('y', Y);
|
||||
K('z', Z);
|
||||
K(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
K(MASCULINE_UTF_8, MASCULINE);
|
||||
K(S_SHARP_UTF_8, S_SHARP);
|
||||
K(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
|
||||
K(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
|
||||
K(A_RING_LOWER_CASE_UTF_8, A_RING);
|
||||
K(AE_LOWER_CASE_UTF_8, AE);
|
||||
K(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
|
||||
K(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
|
||||
K(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
|
||||
K(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
|
||||
K(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
|
||||
K(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
|
||||
K(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
|
||||
K(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
|
||||
K(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
|
||||
K(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
|
||||
K(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
|
||||
K(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
|
||||
K(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
|
||||
K(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
|
||||
K(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
|
||||
K(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
|
||||
K(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
|
||||
K(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
|
||||
K(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
|
||||
K(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
|
||||
K(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
|
||||
K(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
|
||||
K(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
|
||||
K(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
|
||||
K(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
|
||||
K(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
|
||||
K(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
|
||||
K(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
|
||||
K(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
|
||||
K(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
|
||||
K(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
|
||||
K(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
|
||||
K(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
|
||||
K(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
|
||||
K(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
|
||||
K(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
|
||||
K(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
|
||||
K(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
|
||||
K(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
|
||||
K(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
|
||||
K(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
|
||||
K(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
|
||||
K(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
|
||||
#define K8(ch, name) case GLFW_KEY_##name: utf_8_key = ch; break;
|
||||
#define K16(ch, name) case GLFW_KEY_##name: utf_16_key = ch; break;
|
||||
K8('!', EXCLAM);
|
||||
K8('"', DOUBLE_QUOTE);
|
||||
K8('#', NUMBER_SIGN);
|
||||
K8('$', DOLLAR);
|
||||
K8('&', AMPERSAND);
|
||||
K8('\'', APOSTROPHE);
|
||||
K8('(', PARENTHESIS_LEFT);
|
||||
K8(')', PARENTHESIS_RIGHT);
|
||||
K8('+', PLUS);
|
||||
K8(',', COMMA);
|
||||
K8('-', MINUS);
|
||||
K8('.', PERIOD);
|
||||
K8('/', SLASH);
|
||||
K8('0', 0);
|
||||
K8('1', 1);
|
||||
K8('2', 2);
|
||||
K8('3', 3);
|
||||
K8('5', 5);
|
||||
K8('6', 6);
|
||||
K8('7', 7);
|
||||
K8('8', 8);
|
||||
K8('9', 9);
|
||||
K8(':', COLON);
|
||||
K8(';', SEMICOLON);
|
||||
K8('<', LESS);
|
||||
K8('=', EQUAL);
|
||||
K8('>', GREATER);
|
||||
K8('@', AT);
|
||||
K8('[', LEFT_BRACKET);
|
||||
K8('\\', BACKSLASH);
|
||||
K8(']', RIGHT_BRACKET);
|
||||
K8('^', CIRCUMFLEX);
|
||||
K8('_', UNDERSCORE);
|
||||
K8('`', GRAVE_ACCENT);
|
||||
K8('a', A);
|
||||
K8('b', B);
|
||||
K8('c', C);
|
||||
K8('d', D);
|
||||
K8('e', E);
|
||||
K8('f', F);
|
||||
K8('g', G);
|
||||
K8('h', H);
|
||||
K8('i', I);
|
||||
K8('j', J);
|
||||
K8('k', K);
|
||||
K8('l', L);
|
||||
K8('m', M);
|
||||
K8('n', N);
|
||||
K8('o', O);
|
||||
K8('p', P);
|
||||
K8('q', Q);
|
||||
K8('r', R);
|
||||
K8('s', S);
|
||||
K8('t', T);
|
||||
K8('u', U);
|
||||
K8('v', V);
|
||||
K8('w', W);
|
||||
K8('x', X);
|
||||
K8('y', Y);
|
||||
K8('z', Z);
|
||||
K8(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
K8(MASCULINE_UTF_8, MASCULINE);
|
||||
K8(S_SHARP_UTF_8, S_SHARP);
|
||||
K8(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
|
||||
K8(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
|
||||
K8(A_RING_LOWER_CASE_UTF_8, A_RING);
|
||||
K8(AE_LOWER_CASE_UTF_8, AE);
|
||||
K8(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
|
||||
K8(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
|
||||
K8(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
|
||||
K8(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
|
||||
K8(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
|
||||
K8(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
|
||||
K8(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
|
||||
K8(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
|
||||
K8(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
|
||||
K8(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
|
||||
K8(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
|
||||
K8(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
|
||||
K8(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
|
||||
K8(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
|
||||
K8(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
|
||||
K8(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
|
||||
K8(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
|
||||
K8(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
|
||||
K8(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
|
||||
K8(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
|
||||
K8(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
|
||||
K8(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
|
||||
K8(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
|
||||
K8(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
|
||||
K8(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
|
||||
K8(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
|
||||
K8(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
|
||||
K8(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
|
||||
K8(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
|
||||
K8(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
|
||||
K8(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
|
||||
K8(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
|
||||
K8(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
|
||||
K8(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
|
||||
K8(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
|
||||
K8(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
|
||||
K8(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
|
||||
K8(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
|
||||
K8(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
|
||||
K8(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
|
||||
K8(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
|
||||
K8(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
|
||||
K8(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
|
||||
|
||||
K(0x35, ESCAPE);
|
||||
K('\r', ENTER);
|
||||
K('\t', TAB);
|
||||
K(NSBackspaceCharacter, BACKSPACE);
|
||||
K(NSInsertFunctionKey, INSERT);
|
||||
K(NSDeleteCharacter, DELETE);
|
||||
K(NSLeftArrowFunctionKey, LEFT);
|
||||
K(NSRightArrowFunctionKey, RIGHT);
|
||||
K(NSUpArrowFunctionKey, UP);
|
||||
K(NSDownArrowFunctionKey, DOWN);
|
||||
K(NSPageUpFunctionKey, PAGE_UP);
|
||||
K(NSPageDownFunctionKey, PAGE_DOWN);
|
||||
K(NSHomeFunctionKey, HOME);
|
||||
K(NSEndFunctionKey, END);
|
||||
K(NSPrintFunctionKey, PRINT_SCREEN);
|
||||
K8(0x35, ESCAPE);
|
||||
K8('\r', ENTER);
|
||||
K8('\t', TAB);
|
||||
K16(NSBackspaceCharacter, BACKSPACE);
|
||||
K16(NSInsertFunctionKey, INSERT);
|
||||
K16(NSDeleteCharacter, DELETE);
|
||||
K16(NSLeftArrowFunctionKey, LEFT);
|
||||
K16(NSRightArrowFunctionKey, RIGHT);
|
||||
K16(NSUpArrowFunctionKey, UP);
|
||||
K16(NSDownArrowFunctionKey, DOWN);
|
||||
K16(NSPageUpFunctionKey, PAGE_UP);
|
||||
K16(NSPageDownFunctionKey, PAGE_DOWN);
|
||||
K16(NSHomeFunctionKey, HOME);
|
||||
K16(NSEndFunctionKey, END);
|
||||
K16(NSPrintFunctionKey, PRINT_SCREEN);
|
||||
case GLFW_KEY_F1 ... GLFW_KEY_F24:
|
||||
*cocoa_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
|
||||
case GLFW_KEY_KP_0 ... GLFW_KEY_KP_9:
|
||||
*cocoa_key = NSEventModifierFlagNumericPad | (0x52 + (glfw_key - GLFW_KEY_KP_0)); break;
|
||||
K((unichar)(0x41|NSEventModifierFlagNumericPad), KP_DECIMAL);
|
||||
K((unichar)(0x43|NSEventModifierFlagNumericPad), KP_MULTIPLY);
|
||||
K((unichar)(0x45|NSEventModifierFlagNumericPad), KP_ADD);
|
||||
K((unichar)(0x4B|NSEventModifierFlagNumericPad), KP_DIVIDE);
|
||||
K((unichar)(0x4E|NSEventModifierFlagNumericPad), KP_SUBTRACT);
|
||||
K((unichar)(0x51|NSEventModifierFlagNumericPad), KP_EQUAL);
|
||||
#undef K
|
||||
utf_16_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
|
||||
#undef K8
|
||||
#undef K16
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
if (utf_16_key != 0) {
|
||||
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
||||
} else {
|
||||
unsigned str_pos = 0;
|
||||
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
||||
uint8_t byte = (utf_8_key >> 24) & 0xff;
|
||||
utf_8_key <<= 8;
|
||||
if (byte != 0) cocoa_key[str_pos++] = byte;
|
||||
}
|
||||
cocoa_key[str_pos] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ def generate_wrappers(glfw_header):
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char* cocoa_key, size_t key_sz, int* cocoa_mods)
|
||||
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
|
||||
47
glfw/glfw3.h
vendored
47
glfw/glfw3.h
vendored
@@ -405,6 +405,7 @@ extern "C" {
|
||||
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
|
||||
#define GLFW_KEY_BACKSLASH 92 /* \ */
|
||||
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
|
||||
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
|
||||
#define GLFW_KEY_UNDERSCORE 95 /* _ */
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
@@ -3035,6 +3036,46 @@ GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth, int minhe
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
|
||||
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* window, int widthincr, int heightincr);
|
||||
|
||||
/*! @brief Sets the size increments of the specified window.
|
||||
*
|
||||
* This function sets the size increments of the content area of the specified
|
||||
* window. If the window is full screen, the size limits only take effect
|
||||
* once it is made windowed. If the window is not resizable, this function
|
||||
* does nothing.
|
||||
*
|
||||
* The size increments are applied immediately to a windowed mode window and
|
||||
* may cause it to be resized.
|
||||
*
|
||||
* The dimension increments must be greater than zero.
|
||||
*
|
||||
* @param[in] window The window to set limits for.
|
||||
* @param[in] widthincr The width increments, in screen coordinates, of the
|
||||
* content area, or `GLFW_DONT_CARE`.
|
||||
* @param[in] heightincr The height increments, in screen coordinates, of the
|
||||
* content area, or `GLFW_DONT_CARE`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark If you set size limits and an aspect ratio that conflict, the
|
||||
* results are undefined.
|
||||
*
|
||||
* @remark @wayland The size limits will not be applied until the window is
|
||||
* actually resized, either by the user or by the compositor.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_sizelimits
|
||||
* @sa @ref glfwSetWindowSizeLimits
|
||||
*
|
||||
* @since Added in version 3.2.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
|
||||
GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* window, int numer, int denom);
|
||||
|
||||
/*! @brief Sets the size of the content area of the specified window.
|
||||
@@ -4148,6 +4189,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
|
||||
* - `GLFW_KEY_LEFT_BRACKET`
|
||||
* - `GLFW_KEY_BACKSLASH`
|
||||
* - `GLFW_KEY_RIGHT_BRACKET`
|
||||
* - `GLFW_KEY_CIRCUMFLEX`
|
||||
* - `GLFW_KEY_UNDERSCORE`
|
||||
* - `GLFW_KEY_GRAVE_ACCENT`
|
||||
* - `GLFW_KEY_WORLD_1`
|
||||
@@ -5524,8 +5566,9 @@ GLFWAPI int glfwVulkanSupported(void);
|
||||
* returned array, as it is an error to specify an extension more than once in
|
||||
* the `VkInstanceCreateInfo` struct.
|
||||
*
|
||||
* @remark @macos This function currently only supports the
|
||||
* `VK_MVK_macos_surface` extension from MoltenVK.
|
||||
* @remark @macos This function currently supports either the
|
||||
* `VK_MVK_macos_surface` extension from MoltenVK or `VK_EXT_metal_surface`
|
||||
* extension.
|
||||
*
|
||||
* @pointer_lifetime The returned array is allocated and freed by GLFW. You
|
||||
* should not free it yourself. It is guaranteed to be valid only until the
|
||||
|
||||
1
glfw/input.c
vendored
1
glfw/input.c
vendored
@@ -510,6 +510,7 @@ const char* _glfwGetKeyName(int key)
|
||||
case GLFW_KEY_LEFT_BRACKET: return "LEFT_BRACKET";
|
||||
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
|
||||
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT_BRACKET";
|
||||
case GLFW_KEY_CIRCUMFLEX: return "CIRCUMFLEX";
|
||||
case GLFW_KEY_UNDERSCORE: return "UNDERSCORE";
|
||||
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE_ACCENT";
|
||||
case GLFW_KEY_WORLD_1: return "WORLD_1";
|
||||
|
||||
4
glfw/internal.h
vendored
4
glfw/internal.h
vendored
@@ -132,6 +132,7 @@ typedef enum VkStructureType
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||
VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT = 1000217000,
|
||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} VkStructureType;
|
||||
|
||||
@@ -418,6 +419,7 @@ struct _GLFWwindow
|
||||
int minwidth, minheight;
|
||||
int maxwidth, maxheight;
|
||||
int numer, denom;
|
||||
int widthincr, heightincr;
|
||||
|
||||
bool stickyKeys;
|
||||
bool stickyMouseButtons;
|
||||
@@ -587,6 +589,7 @@ struct _GLFWlibrary
|
||||
bool KHR_win32_surface;
|
||||
#elif defined(_GLFW_COCOA)
|
||||
bool MVK_macos_surface;
|
||||
bool EXT_metal_surface;
|
||||
#elif defined(_GLFW_X11)
|
||||
bool KHR_xlib_surface;
|
||||
bool KHR_xcb_surface;
|
||||
@@ -673,6 +676,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
int minwidth, int minheight,
|
||||
int maxwidth, int maxheight);
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr);
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
|
||||
void _glfwInputLiveResize(_GLFWwindow* window, bool started);
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
|
||||
4
glfw/null_window.c
vendored
4
glfw/null_window.c
vendored
@@ -128,6 +128,10 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED, int n UNUSED,
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
||||
{
|
||||
if (width)
|
||||
|
||||
4
glfw/vulkan.c
vendored
4
glfw/vulkan.c
vendored
@@ -57,6 +57,8 @@ bool _glfwInitVulkan(int mode)
|
||||
_glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
|
||||
#elif defined(_GLFW_COCOA)
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib");
|
||||
if (!_glfw.vk.handle)
|
||||
_glfw.vk.handle = _glfwLoadLocalVulkanLoaderNS();
|
||||
#else
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -130,6 +132,8 @@ bool _glfwInitVulkan(int mode)
|
||||
#elif defined(_GLFW_COCOA)
|
||||
else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0)
|
||||
_glfw.vk.MVK_macos_surface = true;
|
||||
else if (strcmp(ep[i].extensionName, "VK_EXT_metal_surface") == 0)
|
||||
_glfw.vk.EXT_metal_surface = true;
|
||||
#elif defined(_GLFW_X11)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0)
|
||||
_glfw.vk.KHR_xlib_surface = true;
|
||||
|
||||
16
glfw/window.c
vendored
16
glfw/window.c
vendored
@@ -254,6 +254,8 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
window->maxheight = GLFW_DONT_CARE;
|
||||
window->numer = GLFW_DONT_CARE;
|
||||
window->denom = GLFW_DONT_CARE;
|
||||
window->widthincr = GLFW_DONT_CARE;
|
||||
window->heightincr = GLFW_DONT_CARE;
|
||||
|
||||
// Open the actual window and create its context
|
||||
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
|
||||
@@ -690,6 +692,20 @@ GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* handle, int numer, int denom)
|
||||
_glfwPlatformSetWindowAspectRatio(window, numer, denom);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* handle, int widthincr, int heightincr)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
assert(widthincr >= 0);
|
||||
assert(heightincr >= 0);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
window->widthincr = widthincr;
|
||||
window->heightincr = heightincr;
|
||||
_glfwPlatformSetWindowSizeIncrements(window, window->widthincr, window->heightincr);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwGetFramebufferSize(GLFWwindow* handle, int* width, int* height)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
||||
48
glfw/wl_window.c
vendored
48
glfw/wl_window.c
vendored
@@ -754,6 +754,13 @@ abortOnFatalError(int last_error) {
|
||||
_glfw.wl.eventLoopData.wakeup_data_read = true;
|
||||
}
|
||||
|
||||
static void
|
||||
wayland_read_events(int poll_result, int events, void *data UNUSED) {
|
||||
EVDBG("wayland_read_events poll_result: %d events: %d", poll_result, events);
|
||||
if (poll_result > 0 && events) wl_display_read_events(_glfw.wl.display);
|
||||
else wl_display_cancel_read(_glfw.wl.display);
|
||||
}
|
||||
|
||||
static void
|
||||
handleEvents(monotonic_t timeout)
|
||||
{
|
||||
@@ -762,17 +769,9 @@ handleEvents(monotonic_t timeout)
|
||||
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
|
||||
|
||||
while (wl_display_prepare_read(display) != 0) {
|
||||
while(1) {
|
||||
errno = 0;
|
||||
int num_dispatched = wl_display_dispatch_pending(display);
|
||||
if (num_dispatched < 0) {
|
||||
if (errno == EAGAIN) continue;
|
||||
int last_error = wl_display_get_error(display);
|
||||
if (last_error) abortOnFatalError(last_error);
|
||||
wl_display_cancel_read(display);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
if (wl_display_dispatch_pending(display) == -1) {
|
||||
abortOnFatalError(errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,23 +781,22 @@ handleEvents(monotonic_t timeout)
|
||||
errno = 0;
|
||||
if (wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||
{
|
||||
abortOnFatalError(errno);
|
||||
wl_display_cancel_read(display);
|
||||
abortOnFatalError(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout);
|
||||
// we pass in wayland_read_events to ensure that the above wl_display_prepare_read call
|
||||
// is followed by either wl_display_cancel_read or wl_display_read_events
|
||||
// before any events/timers are dispatched. This allows other wayland functions
|
||||
// to be called in the event/timer handlers without causing a deadlock
|
||||
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout, wayland_read_events);
|
||||
EVDBG("display_read_ok: %d", display_read_ok);
|
||||
if (display_read_ok) {
|
||||
wl_display_read_events(display);
|
||||
int num = wl_display_dispatch_pending(display);
|
||||
(void)num;
|
||||
EVDBG("dispatched %d Wayland events", num);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
glfw_ibus_dispatch(&_glfw.wl.xkb.ibus);
|
||||
glfw_dbus_session_bus_dispatch();
|
||||
EVDBG("other dispatch done");
|
||||
@@ -960,8 +958,13 @@ 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
|
||||
// on 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;
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
|
||||
@@ -1028,6 +1031,13 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED,
|
||||
// The actual limits are checked in the xdg_toplevel::configure handler.
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED,
|
||||
int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
// TODO: find out how to trigger a resize.
|
||||
// The actual limits are checked in the xdg_toplevel::configure handler.
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
|
||||
int* width, int* height)
|
||||
{
|
||||
|
||||
1
glfw/x11_platform.h
vendored
1
glfw/x11_platform.h
vendored
@@ -180,6 +180,7 @@ typedef struct _GLFWwindowX11
|
||||
{
|
||||
Colormap colormap;
|
||||
Window handle;
|
||||
Window parent;
|
||||
|
||||
bool iconified;
|
||||
bool maximized;
|
||||
|
||||
127
glfw/x11_window.c
vendored
127
glfw/x11_window.c
vendored
@@ -68,7 +68,7 @@ static unsigned _glfwDispatchX11Events(void);
|
||||
static void
|
||||
handleEvents(monotonic_t timeout) {
|
||||
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout, NULL);
|
||||
EVDBG("display_read_ok: %d", display_read_ok);
|
||||
if (display_read_ok) {
|
||||
unsigned dispatched = _glfwDispatchX11Events();
|
||||
@@ -255,6 +255,14 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
|
||||
hints->min_aspect.x = hints->max_aspect.x = window->numer;
|
||||
hints->min_aspect.y = hints->max_aspect.y = window->denom;
|
||||
}
|
||||
|
||||
if (window->widthincr != GLFW_DONT_CARE &&
|
||||
window->heightincr != GLFW_DONT_CARE)
|
||||
{
|
||||
hints->flags |= PResizeInc;
|
||||
hints->width_inc = window->widthincr;
|
||||
hints->height_inc = window->heightincr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -525,46 +533,41 @@ static bool createNativeWindow(_GLFWwindow* window,
|
||||
|
||||
window->x11.transparent = _glfwIsVisualTransparentX11(visual);
|
||||
|
||||
// Create the actual window
|
||||
XSetWindowAttributes wa = { 0 };
|
||||
wa.colormap = window->x11.colormap;
|
||||
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
|
||||
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
|
||||
ExposureMask | FocusChangeMask | VisibilityChangeMask |
|
||||
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
|
||||
window->x11.parent = _glfw.x11.root;
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0, // Position
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
visual,
|
||||
CWBorderPixel | CWColormap | CWEventMask,
|
||||
&wa);
|
||||
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
if (!window->x11.handle)
|
||||
{
|
||||
XSetWindowAttributes wa;
|
||||
const unsigned long wamask = CWBorderPixel | CWColormap | CWEventMask;
|
||||
|
||||
wa.colormap = window->x11.colormap;
|
||||
wa.border_pixel = 0;
|
||||
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
|
||||
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
|
||||
ExposureMask | FocusChangeMask | VisibilityChangeMask |
|
||||
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0,
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
visual,
|
||||
wamask,
|
||||
&wa);
|
||||
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
if (!window->x11.handle)
|
||||
{
|
||||
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
XSaveContext(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer) window);
|
||||
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
XSaveContext(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer) window);
|
||||
|
||||
if (!wndconfig->decorated)
|
||||
_glfwPlatformSetWindowDecorated(window, false);
|
||||
|
||||
@@ -1182,6 +1185,12 @@ static void processEvent(XEvent *event)
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case ReparentNotify:
|
||||
{
|
||||
window->x11.parent = event->xreparent.parent;
|
||||
return;
|
||||
}
|
||||
|
||||
case KeyPress:
|
||||
{
|
||||
UPDATE_KEYMAP_IF_NEEDED;
|
||||
@@ -1375,18 +1384,28 @@ static void processEvent(XEvent *event)
|
||||
window->x11.height = event->xconfigure.height;
|
||||
}
|
||||
|
||||
if (event->xconfigure.x != window->x11.xpos ||
|
||||
event->xconfigure.y != window->x11.ypos)
|
||||
{
|
||||
if (event->xany.send_event)
|
||||
{
|
||||
_glfwInputWindowPos(window,
|
||||
event->xconfigure.x,
|
||||
event->xconfigure.y);
|
||||
int xpos = event->xconfigure.x;
|
||||
int ypos = event->xconfigure.y;
|
||||
|
||||
window->x11.xpos = event->xconfigure.x;
|
||||
window->x11.ypos = event->xconfigure.y;
|
||||
}
|
||||
// NOTE: ConfigureNotify events from the server are in local
|
||||
// coordinates, so if we are reparented we need to translate
|
||||
// the position into root (screen) coordinates
|
||||
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
|
||||
{
|
||||
Window dummy;
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
window->x11.parent,
|
||||
_glfw.x11.root,
|
||||
xpos, ypos,
|
||||
&xpos, &ypos,
|
||||
&dummy);
|
||||
}
|
||||
|
||||
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
|
||||
{
|
||||
_glfwInputWindowPos(window, xpos, ypos);
|
||||
window->x11.xpos = xpos;
|
||||
window->x11.ypos = ypos;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -2034,6 +2053,14 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer UNUSED, in
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
int width, height;
|
||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||
updateNormalHints(window, width, height);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
||||
{
|
||||
_glfwPlatformGetWindowSize(window, width, height);
|
||||
@@ -2245,7 +2272,7 @@ void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.NET_ACTIVE_WINDOW)
|
||||
sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0);
|
||||
else
|
||||
else if (_glfwPlatformWindowVisible(window))
|
||||
{
|
||||
XRaiseWindow(_glfw.x11.display, window->x11.handle);
|
||||
XSetInputFocus(_glfw.x11.display, window->x11.handle,
|
||||
|
||||
1
glfw/xkb_glfw.c
vendored
1
glfw/xkb_glfw.c
vendored
@@ -61,6 +61,7 @@ START_ALLOW_CASE_RANGE
|
||||
S(bracketleft, LEFT_BRACKET); \
|
||||
S(backslash, BACKSLASH); \
|
||||
S(bracketright, RIGHT_BRACKET); \
|
||||
S(asciicircum, CIRCUMFLEX); \
|
||||
S(underscore, UNDERSCORE); \
|
||||
S(grave, GRAVE_ACCENT); \
|
||||
R(a, z, A, Z); \
|
||||
|
||||
@@ -21,6 +21,7 @@ from ..tui.operations import faint, styled
|
||||
|
||||
URL_PREFIXES = 'http https file ftp'.split()
|
||||
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
|
||||
screen_size = screen_size_function()
|
||||
|
||||
|
||||
@@ -265,7 +266,7 @@ def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
|
||||
return {
|
||||
'match': handler.text_matches, 'programs': args.program,
|
||||
'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing,
|
||||
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args
|
||||
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args, 'linenum_action': args.linenum_action
|
||||
}
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
@@ -323,11 +324,20 @@ def parse_input(text):
|
||||
return convert_text(text, cols)
|
||||
|
||||
|
||||
def linenum_marks(text, args, Mark, extra_cli_args, *a):
|
||||
regex = args.regex
|
||||
if regex == DEFAULT_REGEX:
|
||||
regex = r'(?P<path>(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
yield from mark(regex, [brackets, quotes], text, args)
|
||||
|
||||
|
||||
def load_custom_processor(customize_processing):
|
||||
if customize_processing.startswith('::import::'):
|
||||
import importlib
|
||||
m = importlib.import_module(customize_processing[len('::import::'):])
|
||||
return {k: getattr(m, k) for k in dir(m)}
|
||||
if customize_processing == '::linenum::':
|
||||
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
|
||||
from kitty.constants import config_dir
|
||||
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
|
||||
if os.path.isabs(customize_processing):
|
||||
@@ -342,6 +352,8 @@ def run(args, text, extra_cli_args=()):
|
||||
try:
|
||||
text = parse_input(text)
|
||||
pattern, post_processors = functions_for(args)
|
||||
if args.type == 'linenum':
|
||||
args.customize_processing = '::linenum::'
|
||||
if args.customize_processing:
|
||||
m = load_custom_processor(args.customize_processing)
|
||||
if 'mark' in m:
|
||||
@@ -386,12 +398,15 @@ multiple times to run multiple programs.
|
||||
|
||||
--type
|
||||
default=url
|
||||
choices=url,regex,path,line,hash,word
|
||||
The type of text to search for.
|
||||
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.
|
||||
|
||||
|
||||
--regex
|
||||
default=(?m)^\s*(.+)\s*$
|
||||
default={default_regex}
|
||||
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
|
||||
The regular expression is in python syntax. If you specify a numbered group in
|
||||
the regular expression only the group will be matched. This allow you to match
|
||||
@@ -403,8 +418,21 @@ the program will be passed arguments corresponding to each named group of
|
||||
the form key=value.
|
||||
|
||||
|
||||
--linenum-action
|
||||
default=self
|
||||
type=choice
|
||||
choices=self,window,tab,os_window,background
|
||||
The action to perform on the matched errors. The actual action is whatever
|
||||
arguments are provided to the kitten, for example:
|
||||
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
|
||||
will open the matched path at the matched line number in vim. This option
|
||||
controls where the action is executed: :code:`self` means the current window,
|
||||
:code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window`
|
||||
a new OS window and :code:`background` run in the background.
|
||||
|
||||
|
||||
--url-prefixes
|
||||
default={0}
|
||||
default={url_prefixes}
|
||||
Comma separated list of recognized URL prefixes.
|
||||
|
||||
|
||||
@@ -470,7 +498,10 @@ on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
|
||||
for details. You can also specify absolute paths to load the script from elsewhere.
|
||||
|
||||
|
||||
'''.format(','.join(sorted(URL_PREFIXES))).format
|
||||
'''.format(
|
||||
default_regex=DEFAULT_REGEX, url_prefixes=','.join(sorted(URL_PREFIXES)),
|
||||
line='{{line}}', path='{{path}}'
|
||||
).format
|
||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
usage = ''
|
||||
|
||||
@@ -496,12 +527,39 @@ def main(args):
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
if items and not args.customize_processing:
|
||||
if items and not (args.customize_processing or args.type == 'linenum'):
|
||||
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return run(args, text, items)
|
||||
|
||||
|
||||
def linenum_handle_result(args, data, target_window_id, boss, extra_cli_args, *a):
|
||||
for m, g in zip(data['match'], data['groupdicts']):
|
||||
if m:
|
||||
path, line = g['path'], g['line']
|
||||
path = path.split(':')[-1]
|
||||
line = int(line)
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
action = data['linenum_action']
|
||||
|
||||
if action == 'self':
|
||||
if w is not None:
|
||||
import shlex
|
||||
w.paste_bytes(shlex.join(cmd) + '\r')
|
||||
elif action == 'background':
|
||||
import subprocess
|
||||
subprocess.Popen(cmd)
|
||||
else:
|
||||
getattr(boss, {
|
||||
'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd'
|
||||
}[action])(*cmd)
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
if data['customize_processing']:
|
||||
m = load_custom_processor(data['customize_processing'])
|
||||
|
||||
@@ -93,6 +93,13 @@ but you can turn it off or on explicitly, if needed.
|
||||
--silent
|
||||
type=bool-set
|
||||
Do not print out anything to stdout during operation.
|
||||
|
||||
|
||||
--z-index -z
|
||||
default=0
|
||||
Z-index of the image. When negative, text will be displayed on top of the image. Use
|
||||
a double minus for values under the threshold for drawing images under cell background
|
||||
colors. For example, --1 evaluates as -1,073,741,825.
|
||||
'''
|
||||
|
||||
|
||||
@@ -182,8 +189,8 @@ def write_chunked(cmd, data):
|
||||
cmd.clear()
|
||||
|
||||
|
||||
def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=None):
|
||||
cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height}
|
||||
def show(outfile, width, height, zindex, fmt, transmit_mode='t', align='center', place=None):
|
||||
cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height, 'z': zindex}
|
||||
if place:
|
||||
set_cursor_for_place(place, cmd, width, height, align)
|
||||
else:
|
||||
@@ -201,6 +208,14 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
def parse_z_index(val):
|
||||
origin = 0
|
||||
if val.startswith('--'):
|
||||
val = val[1:]
|
||||
origin = -1073741824
|
||||
return origin + int(val)
|
||||
|
||||
|
||||
def process(path, args, is_tempfile):
|
||||
m = identify(path)
|
||||
ss = get_screen_size()
|
||||
@@ -219,7 +234,7 @@ def process(path, args, is_tempfile):
|
||||
fmt = 24 if m.mode == 'rgb' else 32
|
||||
transmit_mode = 't'
|
||||
outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
|
||||
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
|
||||
show(outfile, width, height, args.z_index, fmt, transmit_mode, align=args.align, place=args.place)
|
||||
if not args.place:
|
||||
print() # ensure cursor is on a new line
|
||||
return file_removed
|
||||
@@ -360,6 +375,11 @@ def main(args=sys.argv):
|
||||
except Exception:
|
||||
raise SystemExit('Not a valid place specification: {}'.format(args.place))
|
||||
|
||||
try:
|
||||
args.z_index = parse_z_index(args.z_index)
|
||||
except Exception:
|
||||
raise SystemExit('Not a valid z-index specification: {}'.format(args.z_index))
|
||||
|
||||
if args.detect_support:
|
||||
if not detect_support(wait_for=args.detection_timeout, silent=True):
|
||||
raise SystemExit(1)
|
||||
|
||||
@@ -7,5 +7,4 @@ out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = texture(image, texcoord);
|
||||
color = vec4(color.rgb, color.a);
|
||||
}
|
||||
|
||||
@@ -657,7 +657,7 @@ class Boss:
|
||||
with suppress(Exception):
|
||||
s.connect(address)
|
||||
s.sendall(b'c')
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
s.shutdown(socket.SHUT_RDWR)
|
||||
s.close()
|
||||
|
||||
@@ -761,9 +761,6 @@ class Boss:
|
||||
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
|
||||
self._run_kitten('ask', args)
|
||||
|
||||
def show_error(self, title, msg):
|
||||
self._run_kitten('show_error', args=['--title', title], input_data=msg)
|
||||
|
||||
def do_set_tab_title(self, title, tab_id):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None and title:
|
||||
@@ -773,6 +770,32 @@ class Boss:
|
||||
tab.set_title(title)
|
||||
break
|
||||
|
||||
def show_error(self, title, msg):
|
||||
self._run_kitten('show_error', args=['--title', title], input_data=msg)
|
||||
|
||||
def create_marker(self):
|
||||
w = self.active_window
|
||||
if w:
|
||||
spec = None
|
||||
|
||||
def done(data, target_window_id, self):
|
||||
nonlocal spec
|
||||
spec = data['response']
|
||||
|
||||
def done2(target_window_id, self):
|
||||
w = self.window_id_map.get(target_window_id)
|
||||
if w is not None and spec:
|
||||
try:
|
||||
w.set_marker(spec)
|
||||
except Exception as err:
|
||||
self.show_error(_('Invalid marker specification'), str(err))
|
||||
|
||||
self._run_kitten('ask', [
|
||||
'--name=create-marker', '--message',
|
||||
_('Create marker, for example:\ntext 1 ERROR\nSee https://sw.kovidgoyal.net/kitty/marks.html\n')
|
||||
],
|
||||
custom_callback=done, action_on_removal=done2)
|
||||
|
||||
def kitty_shell(self, window_type):
|
||||
cmd = ['@', kitty_exe(), '@']
|
||||
if window_type == 'tab':
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#ifdef NEEDS_BACKROUND
|
||||
in vec3 background;
|
||||
in float draw_bg;
|
||||
#if defined(TRANSPARENT) || defined(SPECIAL)
|
||||
in float bg_alpha;
|
||||
#endif
|
||||
@@ -76,11 +77,13 @@ vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) {
|
||||
*
|
||||
* 2a) Opaque bg with images under text
|
||||
* There are multiple passes, each pass is blended onto the previous using the opaque blend func (alpha, 1- alpha):
|
||||
* 1) Draw only the background -- expected output is color with alpha 1
|
||||
* 2) Draw the images that are supposed to be below text. This happens in the graphics shader
|
||||
* 3) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise
|
||||
* 4) Draw the foreground -- expected output is color with alpha which is blended using the opaque blend func
|
||||
* 5) Draw the images that are supposed to be above text again in the graphics shader
|
||||
* 1) Draw background for all cells
|
||||
* 2) Draw the images that are supposed to be below both the background and text, if any. This happens in the graphics shader
|
||||
* 3) Draw the background of cells that don't have the default background if any images were drawn in 2 above
|
||||
4) Draw the images that are supposed to be below text but not background, again in graphics shader.
|
||||
* 5) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise
|
||||
* 6) Draw the foreground -- expected output is color with alpha which is blended using the opaque blend func
|
||||
* 7) Draw the images that are supposed to be above text again in the graphics shader
|
||||
*
|
||||
* 2b) Transparent bg with images
|
||||
* First everything is rendered into a framebuffer, and then the framebauffer is blended onto
|
||||
@@ -126,10 +129,10 @@ void main() {
|
||||
#endif
|
||||
|
||||
#ifdef BACKGROUND
|
||||
#ifdef TRANSPARENT
|
||||
#if defined(TRANSPARENT)
|
||||
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
|
||||
#else
|
||||
final_color = vec4(background.rgb, 1.0f);
|
||||
final_color = vec4(background.rgb, draw_bg);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
||||
#define STRIKE_SHIFT {STRIKE_SHIFT}
|
||||
#define DIM_SHIFT {DIM_SHIFT}
|
||||
#define MARK_SHIFT {MARK_SHIFT}
|
||||
#define MARK_MASK {MARK_MASK}
|
||||
#define USE_SELECTION_FG
|
||||
#define NUM_COLORS 256
|
||||
|
||||
// Inputs {{{
|
||||
layout(std140) uniform CellRenderData {
|
||||
@@ -16,8 +19,11 @@ layout(std140) uniform CellRenderData {
|
||||
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||
float cursor_x, cursor_y, cursor_w;
|
||||
|
||||
uint color_table[256];
|
||||
uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2];
|
||||
};
|
||||
#ifdef BACKGROUND
|
||||
uniform uint draw_bg_bitfield;
|
||||
#endif
|
||||
|
||||
// Have to use fixed locations here as all variants of the cell program share the same VAO
|
||||
layout(location=0) in uvec3 colors;
|
||||
@@ -45,6 +51,7 @@ const uvec2 cell_pos_map[] = uvec2[4](
|
||||
|
||||
#ifdef NEEDS_BACKROUND
|
||||
out vec3 background;
|
||||
out float draw_bg;
|
||||
#if defined(TRANSPARENT) || defined(SPECIAL)
|
||||
out float bg_alpha;
|
||||
#endif
|
||||
@@ -158,7 +165,11 @@ void main() {
|
||||
float cell_has_cursor = is_cursor(c, r);
|
||||
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
|
||||
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
|
||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||
int mark = int(text_attrs >> MARK_SHIFT) & MARK_MASK;
|
||||
uint has_mark = uint(step(1, float(mark)));
|
||||
uint bg_as_uint = resolve_color(colors[bg_index], default_colors[bg_index]);
|
||||
bg_as_uint = has_mark * color_table[NUM_COLORS + mark] + (ONE - has_mark) * bg_as_uint;
|
||||
vec3 bg = color_to_vec(bg_as_uint);
|
||||
// }}}
|
||||
|
||||
// Foreground {{{
|
||||
@@ -169,12 +180,13 @@ void main() {
|
||||
colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14);
|
||||
|
||||
// Foreground
|
||||
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
|
||||
foreground = color_to_vec(resolved_fg);
|
||||
uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]);
|
||||
fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + 1 + mark] + (ONE - has_mark) * fg_as_uint;
|
||||
foreground = color_to_vec(fg_as_uint);
|
||||
float has_dim = float((text_attrs >> DIM_SHIFT) & ONE);
|
||||
effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim);
|
||||
float in_url = float((is_selected & TWO) >> 1);
|
||||
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg));
|
||||
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint));
|
||||
#ifdef USE_SELECTION_FG
|
||||
// Selection
|
||||
vec3 selection_color = color_to_vec(highlight_fg);
|
||||
@@ -196,23 +208,29 @@ void main() {
|
||||
|
||||
// Background {{{
|
||||
#ifdef NEEDS_BACKROUND
|
||||
float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[bg_index])));
|
||||
draw_bg = 1;
|
||||
|
||||
#if defined(BACKGROUND)
|
||||
background = bg;
|
||||
// draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells
|
||||
uint draw_bg_mask = uint(2 * cell_has_non_default_bg + (1 - cell_has_non_default_bg));
|
||||
draw_bg = step(1, float(draw_bg_bitfield & draw_bg_mask));
|
||||
#endif
|
||||
|
||||
#if defined(TRANSPARENT)
|
||||
#ifdef TRANSPARENT
|
||||
// Set bg_alpha to background_opacity on cells that have the default background color
|
||||
// Which means they must not have a block cursor or a selection or reverse video
|
||||
// On other cells it should be 1. For the SPECIAL program it should be 1 on cells with
|
||||
// selections/block cursor and 0 everywhere else.
|
||||
float is_special_cell = cell_has_block_cursor + float(is_selected & ONE);
|
||||
#ifndef SPECIAL
|
||||
is_special_cell += float(colors[bg_index] & BYTE_MASK) + float(is_reversed);
|
||||
is_special_cell += cell_has_non_default_bg + float(is_reversed);
|
||||
#endif
|
||||
bg_alpha = step(0.5, is_special_cell);
|
||||
#ifndef SPECIAL
|
||||
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
|
||||
bg_alpha *= draw_bg;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -633,7 +633,7 @@ draw_resizing_text(OSWindow *w) {
|
||||
snprintf(text, sizeof(text), "%u x %u cells", width / w->fonts_data->cell_width, height / w->fonts_data->cell_height);
|
||||
StringCanvas rendered = render_simple_text(w->fonts_data, text);
|
||||
if (rendered.canvas) {
|
||||
draw_centered_alpha_mask(w->gvao_idx, width, height, rendered.width, rendered.height, rendered.canvas);
|
||||
draw_centered_alpha_mask(w, width, height, rendered.width, rendered.height, rendered.canvas);
|
||||
free(rendered.canvas);
|
||||
}
|
||||
}
|
||||
@@ -641,7 +641,7 @@ 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) log_error("No render frame received in %f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
|
||||
if (ans) 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ else:
|
||||
try:
|
||||
with open('/proc/' + x + '/stat', 'rb') as f:
|
||||
raw = f.read().decode('utf-8')
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
continue
|
||||
try:
|
||||
q = int(raw.split(' ', 5)[4])
|
||||
|
||||
@@ -223,7 +223,7 @@ def get_defaults_from_seq(seq):
|
||||
default_msg = ('''\
|
||||
Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:`program`
|
||||
to run inside :italic:`{appname}` as normal arguments following the :italic:`options`.
|
||||
For example: {appname} /bin/sh
|
||||
For example: {appname} sh -c "echo hello, world. Press ENTER to quit; read"
|
||||
|
||||
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname)
|
||||
|
||||
@@ -233,7 +233,7 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
screen_size = screen_size_function()
|
||||
try:
|
||||
linesz = min(screen_size().cols, 76)
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
linesz = 76
|
||||
blocks = []
|
||||
a = blocks.append
|
||||
|
||||
@@ -381,6 +381,82 @@ def set_tab_title(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# create_marker {{{
|
||||
@cmd(
|
||||
'Create a marker that highlights specified text',
|
||||
'Create a marker which can highlight text in the specified window. For example: '
|
||||
'create_marker text 1 ERROR. For full details see: https://sw.kovidgoyal.net/kitty/marks.html',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified apply marker to the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec='MARKER SPECIFICATION'
|
||||
)
|
||||
def cmd_create_marker(global_opts, opts, args):
|
||||
'''
|
||||
match: Which window to detach
|
||||
self: Boolean indicating whether to detach the window the command is run in
|
||||
marker_spec: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR']
|
||||
'''
|
||||
from .config import parse_marker_spec
|
||||
if len(args) < 2:
|
||||
raise ValueError('Invalid marker specification: {}'.format(' '.join(args)))
|
||||
parse_marker_spec(args[0], args[1:])
|
||||
return {'match': opts.match, 'self': opts.self, 'marker_spec': args}
|
||||
|
||||
|
||||
def create_marker(boss, window, payload):
|
||||
pg = cmd_create_marker.payload_get
|
||||
match = pg(payload, 'match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and pg(payload, 'self') else boss.active_window]
|
||||
args = pg(payload, 'marker_spec')
|
||||
|
||||
for window in windows:
|
||||
window.set_marker(args)
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# remove_marker {{{
|
||||
@cmd(
|
||||
'Remove the currently set marker, if any.',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified apply marker to the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_remove_marker(global_opts, opts, args):
|
||||
'''
|
||||
match: Which window to detach
|
||||
self: Boolean indicating whether to detach the window the command is run in
|
||||
'''
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
|
||||
def remove_marker(boss, window, payload):
|
||||
pg = cmd_create_marker.payload_get
|
||||
match = pg(payload, 'match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and pg(payload, 'self') else boss.active_window]
|
||||
|
||||
for window in windows:
|
||||
window.remove_marker()
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# detach_window {{{
|
||||
@cmd(
|
||||
'Detach a window and place it in a different/new tab',
|
||||
@@ -814,6 +890,7 @@ def cmd_launch(global_opts, opts, args):
|
||||
stdin_add_formatting: Boolean indicating whether to add formatting codes to stdin
|
||||
stdin_add_line_wrap_markers: Boolean indicating whether to add line wrap markers to stdin
|
||||
no_response: Boolean indicating whether to send back the window id
|
||||
marker: Specification for marker for new window, for example: "text 1 ERROR"
|
||||
'''
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
|
||||
@@ -83,6 +83,11 @@ find_app_name(void) {
|
||||
set_cocoa_pending_action(NEW_OS_WINDOW, NULL);
|
||||
}
|
||||
|
||||
- (void)open_kitty_website_url:(id)sender {
|
||||
(void)sender;
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
|
||||
}
|
||||
|
||||
|
||||
+ (GlobalMenuTarget *) shared_instance
|
||||
{
|
||||
@@ -97,7 +102,7 @@ find_app_name(void) {
|
||||
|
||||
@end
|
||||
|
||||
static unichar new_window_key = 0;
|
||||
static char new_window_key[32] = {0};
|
||||
static NSEventModifierFlags new_window_mods = 0;
|
||||
|
||||
static PyObject*
|
||||
@@ -105,9 +110,9 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
|
||||
int mods, key;
|
||||
if (!PyArg_ParseTuple(args, "ii", &mods, &key)) return NULL;
|
||||
int nwm;
|
||||
get_cocoa_key_equivalent(key, mods, &new_window_key, &nwm);
|
||||
get_cocoa_key_equivalent(key, mods, new_window_key, sizeof(new_window_key), &nwm);
|
||||
new_window_mods = nwm;
|
||||
if (new_window_key) Py_RETURN_TRUE;
|
||||
if (new_window_key[0]) Py_RETURN_TRUE;
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
@@ -118,11 +123,10 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
||||
if (!dockMenu) {
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
dockMenu = [[NSMenu alloc] init];
|
||||
NSMenuItem *newWindowItem =
|
||||
[dockMenu addItemWithTitle:@"New OS window"
|
||||
action:@selector(new_os_window:)
|
||||
keyEquivalent:@""];
|
||||
[newWindowItem setTarget:global_menu_target];
|
||||
[[dockMenu addItemWithTitle:@"New OS window"
|
||||
action:@selector(new_os_window:)
|
||||
keyEquivalent:@""]
|
||||
setTarget:global_menu_target];
|
||||
}
|
||||
return dockMenu;
|
||||
}
|
||||
@@ -249,17 +253,17 @@ cocoa_create_global_menu(void) {
|
||||
action:@selector(orderFrontStandardAboutPanel:)
|
||||
keyEquivalent:@""];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","], *new_os_window_menu_item = NULL;
|
||||
[preferences_menu_item setTarget:global_menu_target];
|
||||
[appMenu addItem:preferences_menu_item];
|
||||
if (new_window_key) {
|
||||
NSString *s = [NSString stringWithCharacters:&new_window_key length:1];
|
||||
new_os_window_menu_item = [[NSMenuItem alloc] initWithTitle:@"New OS window" action:@selector(new_os_window:) keyEquivalent:s];
|
||||
[new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods];
|
||||
[new_os_window_menu_item setTarget:global_menu_target];
|
||||
[appMenu addItem:new_os_window_menu_item];
|
||||
[s release];
|
||||
}
|
||||
[[appMenu addItemWithTitle:@"Preferences..."
|
||||
action:@selector(show_preferences:)
|
||||
keyEquivalent:@","]
|
||||
setTarget:global_menu_target];
|
||||
|
||||
NSMenuItem* new_os_window_menu_item =
|
||||
[appMenu addItemWithTitle:@"New OS window"
|
||||
action:@selector(new_os_window:)
|
||||
keyEquivalent:@(new_window_key)];
|
||||
[new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods];
|
||||
[new_os_window_menu_item setTarget:global_menu_target];
|
||||
|
||||
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
|
||||
@@ -313,10 +317,18 @@ cocoa_create_global_menu(void) {
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
||||
[NSApp setWindowsMenu:windowMenu];
|
||||
[windowMenu release];
|
||||
[preferences_menu_item release];
|
||||
if (new_os_window_menu_item) {
|
||||
[new_os_window_menu_item release];
|
||||
}
|
||||
|
||||
NSMenuItem* helpMenuItem =
|
||||
[bar addItemWithTitle:@"Help"
|
||||
action:NULL
|
||||
keyEquivalent:@""];
|
||||
NSMenu* helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
||||
[helpMenuItem setSubmenu:helpMenu];
|
||||
[[helpMenu addItemWithTitle:[NSString stringWithFormat:@"Visit %@ website", app_name]
|
||||
action:@selector(open_kitty_website_url:)
|
||||
keyEquivalent:@"?"]
|
||||
setTarget:global_menu_target];
|
||||
[helpMenu release];
|
||||
|
||||
[bar release];
|
||||
|
||||
@@ -474,7 +486,6 @@ cleanup() {
|
||||
dockMenu = nil;
|
||||
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
||||
notification_activated_callback = NULL;
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
@@ -489,6 +500,30 @@ cocoa_hide_window_title(void *w)
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_hide_titlebar(void *w)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
cocoa_hide_window_title(w);
|
||||
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
NSButton *button;
|
||||
|
||||
button = [window standardWindowButton: NSWindowCloseButton];
|
||||
if (button) button.hidden = true;
|
||||
button = [window standardWindowButton: NSWindowMiniaturizeButton];
|
||||
if (button) button.hidden = true;
|
||||
button = [window standardWindowButton: NSWindowZoomButton];
|
||||
if (button) button.hidden = true;
|
||||
|
||||
[window setTitlebarAppearsTransparent:YES];
|
||||
[window setStyleMask:
|
||||
[window styleMask] | NSWindowStyleMaskFullSizeContentView];
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
#include "data-types.h"
|
||||
#include "state.h"
|
||||
#include <structmember.h>
|
||||
|
||||
PyTypeObject ColorProfile_Type;
|
||||
@@ -69,6 +69,9 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
||||
init_FG_BG_table();
|
||||
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
|
||||
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
|
||||
#define S(which) self->mark_foregrounds[which] = OPT(mark##which##_foreground); self->mark_backgrounds[which] = OPT(mark##which##_background)
|
||||
S(1); S(2); S(3);
|
||||
#undef S
|
||||
self->dirty = true;
|
||||
}
|
||||
return (PyObject*) self;
|
||||
@@ -107,6 +110,32 @@ copy_color_profile(ColorProfile *dest, ColorProfile *src) {
|
||||
dest->dirty = true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) {
|
||||
PyObject *v = PyDict_GetItemString(spec, key);
|
||||
if (v) {
|
||||
color_type color = PyLong_AsUnsignedLong(v);
|
||||
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) {
|
||||
ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j);
|
||||
self->color_table[which] = color;
|
||||
if (change_configured) self->orig_color_table[which] = color;
|
||||
self->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#define patch_mark_color(key, profiles, spec, array, i) { \
|
||||
PyObject *v = PyDict_GetItemString(spec, key); \
|
||||
if (v) { \
|
||||
color_type color = PyLong_AsUnsignedLong(v); \
|
||||
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) { \
|
||||
ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j); \
|
||||
self->array[i] = color; \
|
||||
self->dirty = true; \
|
||||
} } }
|
||||
|
||||
|
||||
static PyObject*
|
||||
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
|
||||
@@ -114,16 +143,12 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
char key[32] = {0};
|
||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
||||
v = PyDict_GetItemString(spec, key);
|
||||
if (v) {
|
||||
color_type color = PyLong_AsUnsignedLong(v);
|
||||
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) {
|
||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j);
|
||||
self->color_table[i] = color;
|
||||
if (change_configured) self->orig_color_table[i] = color;
|
||||
self->dirty = true;
|
||||
}
|
||||
}
|
||||
patch_color_table(key, profiles, spec, i, change_configured);
|
||||
}
|
||||
for (size_t i = 1; i <= MARK_MASK; i++) {
|
||||
#define S(which, i) snprintf(key, sizeof(key) - 1, "mark%zu_" #which, i); patch_mark_color(key, profiles, spec, mark_##which##s, i)
|
||||
S(background, i); S(foreground, i);
|
||||
#undef S
|
||||
}
|
||||
#define S(config_name, profile_name) { \
|
||||
v = PyDict_GetItemString(spec, #config_name); \
|
||||
@@ -282,8 +307,13 @@ void
|
||||
copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) {
|
||||
size_t i;
|
||||
stride = MAX(1u, stride);
|
||||
for (i = 0, buf = buf + offset; i < sizeof(self->color_table)/sizeof(self->color_table[0]); i++, buf += stride) {
|
||||
*buf = self->color_table[i];
|
||||
for (i = 0, buf = buf + offset; i < arraysz(self->color_table); i++, buf += stride) *buf = self->color_table[i];
|
||||
// Copy the mark colors
|
||||
for (i = 0; i < arraysz(self->mark_backgrounds); i++) {
|
||||
*buf = self->mark_backgrounds[i]; buf += stride;
|
||||
}
|
||||
for (i = 0; i < arraysz(self->mark_foregrounds); i++) {
|
||||
*buf = self->mark_foregrounds[i]; buf += stride;
|
||||
}
|
||||
self->dirty = false;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_in
|
||||
_parse(include, type_map, special_handling, ans, all_keys)
|
||||
except FileNotFoundError:
|
||||
log_error('Could not find included config file: {}, ignoring'.format(val))
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
log_error('Could not read from included config file: {}, ignoring'.format(val))
|
||||
return
|
||||
if all_keys is not None and key not in all_keys:
|
||||
|
||||
@@ -241,6 +241,58 @@ def disable_ligatures_in(func, rest):
|
||||
return func, [where, strategy]
|
||||
|
||||
|
||||
def parse_marker_spec(ftype, parts):
|
||||
flags = re.UNICODE
|
||||
if ftype in ('text', 'itext', 'regex', 'iregex'):
|
||||
if ftype.startswith('i'):
|
||||
flags |= re.IGNORECASE
|
||||
if not parts or len(parts) % 2 != 0:
|
||||
raise ValueError('No color specified in marker: {}'.format(' '.join(parts)))
|
||||
ans = []
|
||||
for i in range(0, len(parts), 2):
|
||||
try:
|
||||
color = max(1, min(int(parts[i]), 3))
|
||||
except Exception:
|
||||
raise ValueError('color {} in marker specification is not an integer'.format(parts[i]))
|
||||
spec = parts[i + 1]
|
||||
if 'regex' not in ftype:
|
||||
spec = re.escape(spec)
|
||||
ans.append((color, spec))
|
||||
ftype = 'regex'
|
||||
spec = tuple(ans)
|
||||
elif ftype == 'function':
|
||||
spec = ' '.join(parts)
|
||||
else:
|
||||
raise ValueError('Unknown marker type: {}'.format(ftype))
|
||||
return ftype, spec, flags
|
||||
|
||||
|
||||
@func_with_args('toggle_marker')
|
||||
def toggle_marker(func, rest):
|
||||
parts = rest.split(maxsplit=1)
|
||||
if len(parts) != 2:
|
||||
raise ValueError('{} if not a valid marker specification'.format(rest))
|
||||
ftype, spec = parts
|
||||
parts = spec.split()
|
||||
return func, list(parse_marker_spec(ftype, parts))
|
||||
|
||||
|
||||
@func_with_args('scroll_to_mark')
|
||||
def scroll_to_mark(func, rest):
|
||||
parts = rest.split()
|
||||
if not parts or not rest:
|
||||
return func, [True, 0]
|
||||
if len(parts) == 1:
|
||||
q = parts[0].lower()
|
||||
if q in ('prev', 'previous', 'next'):
|
||||
return func, [q != 'next', 0]
|
||||
try:
|
||||
return func, [True, max(0, min(int(q), 3))]
|
||||
except Exception:
|
||||
raise ValueError('{} is not a valid scroll_to_mark destination'.format(rest))
|
||||
return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))]
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
parts = action.strip().split(maxsplit=1)
|
||||
func = parts[0]
|
||||
@@ -251,8 +303,8 @@ def parse_key_action(action):
|
||||
if parser is not None:
|
||||
try:
|
||||
func, args = parser(func, rest)
|
||||
except Exception:
|
||||
log_error('Ignoring invalid key action: {}'.format(action))
|
||||
except Exception as err:
|
||||
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
|
||||
else:
|
||||
return KeyAction(func, args)
|
||||
|
||||
@@ -410,6 +462,33 @@ def handle_symbol_map(key, val, ans):
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
|
||||
|
||||
class FontFeature(str):
|
||||
|
||||
def __new__(cls, name, parsed):
|
||||
ans = str.__new__(cls, name)
|
||||
ans.parsed = parsed
|
||||
return ans
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_font_features(key, val, ans):
|
||||
if val != 'none':
|
||||
parts = val.split()
|
||||
if len(parts) < 2:
|
||||
log_error("Ignoring invalid font_features {}".format(val))
|
||||
else:
|
||||
features = []
|
||||
for feat in parts[1:]:
|
||||
try:
|
||||
parsed = defines.parse_font_feature(feat)
|
||||
except ValueError:
|
||||
log_error('Ignoring invalid font feature: {}'.format(feat))
|
||||
else:
|
||||
features.append(FontFeature(feat, parsed))
|
||||
if features:
|
||||
ans['font_features'][parts[0]] = tuple(features)
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_kitten_alias(key, val, ans):
|
||||
parts = val.split(maxsplit=2)
|
||||
@@ -495,7 +574,7 @@ def option_names_for_completion():
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True, accumulate_bad_lines=None):
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}}
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_features': {}}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
|
||||
@@ -144,7 +144,8 @@ For example::
|
||||
_('Tab management'), '',
|
||||
_('''\
|
||||
You can also create shortcuts to go to specific tabs, with 1 being the first
|
||||
tab, 2 the second tab and -1 being the previously active tab::
|
||||
tab, 2 the second tab and -1 being the previously active tab, and any number
|
||||
larger than the last tab being the last tab::
|
||||
|
||||
map ctrl+alt+1 goto_tab 1
|
||||
map ctrl+alt+2 goto_tab 2
|
||||
@@ -223,6 +224,23 @@ def to_font_size(x):
|
||||
|
||||
o('font_size', 11.0, long_text=_('Font size (in pts)'), option_type=to_font_size)
|
||||
|
||||
o('force_ltr', False, long_text=_("""
|
||||
kitty does not support BIDI (bidirectional text), however, for RTL scripts,
|
||||
words are automatically displayed in RTL. That is
|
||||
to say, in an RTL script, the words "HELLO WORLD" display in kitty as "WORLD
|
||||
HELLO", and if you try to select a substring of an RTL-shaped string, you will
|
||||
get the character that would be there had the the string been LTR. For example,
|
||||
assuming the Hebrew word ירושלים, selecting the character that on the screen
|
||||
appears to be ם actually writes into the selection buffer the character י.
|
||||
|
||||
kitty's default behavior is useful in conjunction with a filter to reverse the
|
||||
word order, however, if you wish to manipulate RTL glyphs, it can be very
|
||||
challenging to work with, so this option is provided to turn it off.
|
||||
Furthermore, this option can be used with the command line program
|
||||
:link:`GNU FriBidi <https://github.com/fribidi/fribidi#executable>` to get BIDI
|
||||
support, because it will force kitty to always treat the text as LTR, which
|
||||
FriBidi expects for terminals."""))
|
||||
|
||||
|
||||
def adjust_line_height(x):
|
||||
if x.endswith('%'):
|
||||
@@ -276,6 +294,51 @@ or by defining shortcuts for it in kitty.conf, for example::
|
||||
map alt+1 disable_ligatures_in active always
|
||||
map alt+2 disable_ligatures_in all never
|
||||
map alt+3 disable_ligatures_in tab cursor
|
||||
|
||||
'''))
|
||||
|
||||
o('font_features', 'none', long_text=_('''
|
||||
Choose exactly which OpenType features to enable or disable. This is useful as
|
||||
some fonts might have features worthwhile in a terminal. For example, Fira
|
||||
Code Retina includes a discretionary feature, :code:`zero`, which in that font
|
||||
changes the appearance of the zero (0), to make it more easily distinguishable
|
||||
from Ø. Fira Code Retina also includes other discretionary features known as
|
||||
Stylistic Sets which have the tags :code:`ss01` through :code:`ss20`.
|
||||
|
||||
Note that this code is indexed by PostScript name, and not the font
|
||||
family. This allows you to define very precise feature settings; e.g. you can
|
||||
disable a feature in the italic font but not in the regular font.
|
||||
|
||||
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`::
|
||||
|
||||
$ kitty + list-fonts --psnames | grep Fira
|
||||
Fira Code
|
||||
Fira Code Bold (FiraCode-Bold)
|
||||
Fira Code Light (FiraCode-Light)
|
||||
Fira Code Medium (FiraCode-Medium)
|
||||
Fira Code Regular (FiraCode-Regular)
|
||||
Fira Code Retina (FiraCode-Retina)
|
||||
|
||||
The part in brackets is the PostScript name.
|
||||
|
||||
Enable alternate zero and oldstyle numerals::
|
||||
|
||||
font_features FiraCode-Retina +zero +onum
|
||||
|
||||
Enable only alternate zero::
|
||||
|
||||
font_features FiraCode-Retina +zero
|
||||
|
||||
Disable the normal ligatures, but keep the :code:`calt` feature which (in this
|
||||
font) breaks up monotony::
|
||||
|
||||
font_features TT2020StyleB-Regular -liga +calt
|
||||
|
||||
In conjunction with :opt:`force_ltr`, you may want to disable Arabic shaping
|
||||
entirely, and only look at their isolated forms if they show up in a document.
|
||||
You can do this with e.g.::
|
||||
|
||||
font_features UnifontMedium +isol -medi -fina -init
|
||||
'''))
|
||||
|
||||
|
||||
@@ -569,7 +632,8 @@ def to_layout_names(raw):
|
||||
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
||||
The enabled window layouts. A comma separated list of layout names. The special
|
||||
value :code:`all` means all layouts. The first listed layout will be used as the
|
||||
startup layout. For a list of available layouts, see the :ref:`layouts`.
|
||||
startup layout. Default configuration is all layouts in alphabetical order.
|
||||
For a list of available layouts, see the :ref:`layouts`.
|
||||
'''))
|
||||
|
||||
o('window_resize_step_cells', 2, option_type=positive_int, long_text=_('''
|
||||
@@ -623,8 +687,18 @@ Fade the text in inactive windows by the specified amount (a number between
|
||||
zero and one, with zero being fully faded).
|
||||
'''))
|
||||
|
||||
o('hide_window_decorations', False, long_text=_('''
|
||||
Hide the window decorations (title-bar and window borders).
|
||||
|
||||
def hide_window_decorations(x):
|
||||
if x == 'titlebar-only':
|
||||
return 0b10
|
||||
if to_bool(x):
|
||||
return 0b01
|
||||
return 0b00
|
||||
|
||||
|
||||
o('hide_window_decorations', 'no', option_type=hide_window_decorations, long_text=_('''
|
||||
Hide the window decorations (title-bar and window borders) with :code:`yes`.
|
||||
On macOS, :code:`titlebar-only` can be used to only hide the titlebar.
|
||||
Whether this works and exactly what effect it has depends on the
|
||||
window manager/operating system.
|
||||
'''))
|
||||
@@ -649,6 +723,13 @@ A value of :code:`blank` means draw a blank window.
|
||||
A value of :code:`size` means show the window size in cells.
|
||||
'''))
|
||||
|
||||
o('resize_in_steps', False, long_text=_('''
|
||||
Resize the OS window in steps as large as the cells, instead of with the usual pixel accuracy.
|
||||
Combined with an :opt:`initial_window_width` and :opt:`initial_window_height` in number of cells,
|
||||
this option can be used to keep the margins as small as possible when resizing the OS window.
|
||||
Note that this does not currently work on Wayland.
|
||||
'''))
|
||||
|
||||
# }}}
|
||||
|
||||
g('tabbar') # {{{
|
||||
@@ -811,6 +892,12 @@ o('color14', '#14ffff', option_type=to_color)
|
||||
o('color7', '#dddddd', long_text=_('white'), option_type=to_color)
|
||||
o('color15', '#ffffff', option_type=to_color)
|
||||
|
||||
o('mark1_foreground', 'black', long_text=_('Color for marks of type 1'), option_type=to_color)
|
||||
o('mark1_background', '#98d3cb', long_text=_('Color for marks of type 1 (light steel blue)'), option_type=to_color)
|
||||
o('mark2_foreground', 'black', long_text=_('Color for marks of type 2'), option_type=to_color)
|
||||
o('mark2_background', '#f2dcd3', long_text=_('Color for marks of type 1 (beige)'), option_type=to_color)
|
||||
o('mark3_foreground', 'black', long_text=_('Color for marks of type 3'), option_type=to_color)
|
||||
o('mark3_background', '#f274bc', long_text=_('Color for marks of type 1 (violet)'), option_type=to_color)
|
||||
dfctl = defines.default_color_table()
|
||||
for i in range(16, 256):
|
||||
o('color{}'.format(i), color_as_sharp(color_from_int(dfctl[i])), option_type=to_color, add_to_docs=False)
|
||||
@@ -1228,6 +1315,11 @@ k('insert_selected_hash', 'kitty_mod+p>h', 'kitten hints --type hash --program -
|
||||
Select something that looks like a hash and insert it into the terminal.
|
||||
Useful with git, which uses sha1 hashes to identify commits'''))
|
||||
|
||||
k('goto_file_line', 'kitty_mod+p>n', 'kitten hints --type linenum', _('Open the selected file at the selected line'), long_text=_('''
|
||||
Select something that looks like :code:`filename:linenum` and open it in vim at
|
||||
the specified line number.'''))
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
g('shortcuts.misc') # {{{
|
||||
|
||||
@@ -10,7 +10,7 @@ from collections import namedtuple
|
||||
from contextlib import suppress
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 15, 1)
|
||||
version = (0, 16, 0)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
|
||||
@@ -457,11 +457,12 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
|
||||
CTFontGetGlyphsForCharacters(font, chars, glyphs, num_chars);
|
||||
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, advances, num_chars);
|
||||
CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boxes, num_chars);
|
||||
StringCanvas ans = { .width = 0, .height = (size_t)(2 * bounding_box.size.height) };
|
||||
for (size_t i = 0, y = 0; i < num_chars; i++) {
|
||||
positions[i] = CGPointMake(ans.width, y);
|
||||
ans.width += advances[i].width; y += advances[i].height;
|
||||
CGFloat x = 0, y = 0;
|
||||
for (size_t i = 0; i < num_chars; i++) {
|
||||
positions[i] = CGPointMake(x, y);
|
||||
x += advances[i].width; y += advances[i].height;
|
||||
}
|
||||
StringCanvas ans = { .width = (size_t)ceil(x), .height = (size_t)(2 * bounding_box.size.height) };
|
||||
ensure_render_space(ans.width, ans.height);
|
||||
render_glyphs(font, ans.width, ans.height, baseline, num_chars);
|
||||
ans.canvas = malloc(ans.width * ans.height);
|
||||
|
||||
@@ -58,9 +58,9 @@ static PyObject*
|
||||
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
|
||||
char *devnull = NULL;
|
||||
if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
|
||||
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -251,6 +251,8 @@ PyInit_fast_data_types(void) {
|
||||
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);
|
||||
|
||||
@@ -58,7 +58,7 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
|
||||
|
||||
#define MAX_CHILDREN 512
|
||||
#define BLANK_CHAR 0
|
||||
#define ATTRS_MASK_WITHOUT_WIDTH 0xFFC
|
||||
#define ATTRS_MASK_WITHOUT_WIDTH 0xFFFC
|
||||
#define WIDTH_MASK 3
|
||||
#define DECORATION_SHIFT 2
|
||||
#define DECORATION_MASK 3
|
||||
@@ -68,6 +68,9 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
|
||||
#define REVERSE_SHIFT 6
|
||||
#define STRIKE_SHIFT 7
|
||||
#define DIM_SHIFT 8
|
||||
#define MARK_SHIFT 9
|
||||
#define ATTRS_MASK_WITHOUT_MARK 0xf9ff
|
||||
#define MARK_MASK 3
|
||||
#define COL_MASK 0xFFFFFFFF
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
@@ -141,6 +144,7 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
|
||||
#define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop")
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t left, top, right, bottom;
|
||||
} Region;
|
||||
@@ -231,6 +235,7 @@ typedef struct {
|
||||
DynamicColor dynamic_color_stack[10];
|
||||
size_t dynamic_color_stack_idx;
|
||||
DynamicColor configured, overridden;
|
||||
color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1];
|
||||
} ColorProfile;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -62,6 +62,7 @@ static hb_feature_t hb_features[3] = {{0}};
|
||||
static char_type shape_buffer[4096] = {0};
|
||||
static size_t max_texture_size = 1024, max_array_len = 1024;
|
||||
typedef enum { LIGA_FEATURE, DLIG_FEATURE, CALT_FEATURE } HBFeature;
|
||||
static PyObject* font_feature_settings = NULL;
|
||||
|
||||
typedef struct {
|
||||
char_type left, right;
|
||||
@@ -71,14 +72,12 @@ typedef struct {
|
||||
static SymbolMap *symbol_maps = NULL;
|
||||
static size_t num_symbol_maps = 0;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject *face;
|
||||
// Map glyphs to sprite map co-ords
|
||||
SpritePosition sprite_map[1024];
|
||||
hb_feature_t hb_features[8];
|
||||
size_t num_hb_features;
|
||||
hb_feature_t* ffs_hb_features;
|
||||
size_t num_ffs_hb_features;
|
||||
SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE];
|
||||
bool bold, italic, emoji_presentation;
|
||||
} Font;
|
||||
@@ -348,27 +347,47 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
copy_hb_feature(Font *f, HBFeature which) {
|
||||
memcpy(f->hb_features + f->num_hb_features++, hb_features + which, sizeof(hb_features[0]));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) {
|
||||
f->face = face; Py_INCREF(f->face);
|
||||
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
|
||||
f->num_hb_features = 0;
|
||||
f->num_ffs_hb_features = 0;
|
||||
const char *psname = postscript_name_for_face(face);
|
||||
if (strstr(psname, "NimbusMonoPS-") == psname) {
|
||||
copy_hb_feature(f, LIGA_FEATURE); copy_hb_feature(f, DLIG_FEATURE);
|
||||
if (font_feature_settings != NULL){
|
||||
PyObject* o = PyDict_GetItemString(font_feature_settings, psname);
|
||||
if (o != NULL && PyTuple_Check(o)) {
|
||||
Py_ssize_t len = PyTuple_GET_SIZE(o);
|
||||
if (len > 0) {
|
||||
f->num_ffs_hb_features = len + 1;
|
||||
f->ffs_hb_features = calloc(f->num_ffs_hb_features, sizeof(hb_feature_t));
|
||||
if (!f->ffs_hb_features) return false;
|
||||
for (Py_ssize_t i = 0; i < len; i++) {
|
||||
PyObject* parsed = PyObject_GetAttrString(PyTuple_GET_ITEM(o, i), "parsed");
|
||||
if (parsed) {
|
||||
memcpy(f->ffs_hb_features + i, PyBytes_AS_STRING(parsed), sizeof(hb_feature_t));
|
||||
Py_DECREF(parsed);
|
||||
}
|
||||
}
|
||||
memcpy(f->ffs_hb_features + len, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!f->num_ffs_hb_features) {
|
||||
f->ffs_hb_features = calloc(4, sizeof(hb_feature_t));
|
||||
if (!f->ffs_hb_features) return false;
|
||||
if (strstr(psname, "NimbusMonoPS-") == psname) {
|
||||
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[LIGA_FEATURE], sizeof(hb_feature_t));
|
||||
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[DLIG_FEATURE], sizeof(hb_feature_t));
|
||||
}
|
||||
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
|
||||
}
|
||||
copy_hb_feature(f, CALT_FEATURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
del_font(Font *f) {
|
||||
Py_CLEAR(f->face);
|
||||
free(f->ffs_hb_features); f->ffs_hb_features = NULL;
|
||||
free_maps(f);
|
||||
f->bold = false; f->italic = false;
|
||||
}
|
||||
@@ -670,6 +689,7 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_
|
||||
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
|
||||
}
|
||||
hb_buffer_guess_segment_properties(harfbuzz_buffer);
|
||||
if (OPT(force_ltr)) hb_buffer_set_direction(harfbuzz_buffer, HB_DIRECTION_LTR);
|
||||
}
|
||||
|
||||
|
||||
@@ -776,7 +796,9 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb
|
||||
group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0);
|
||||
load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells);
|
||||
|
||||
hb_shape(font, harfbuzz_buffer, fobj->hb_features, fobj->num_hb_features - (disable_ligature ? 0 : 1));
|
||||
size_t num_features = fobj->num_ffs_hb_features;
|
||||
if (num_features && !disable_ligature) num_features--; // the last feature is always -calt
|
||||
hb_shape(font, harfbuzz_buffer, fobj->ffs_hb_features, num_features);
|
||||
|
||||
unsigned int info_length, positions_length;
|
||||
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
|
||||
@@ -1027,8 +1049,6 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
if (face == NULL) return NULL;
|
||||
font = calloc(1, sizeof(Font));
|
||||
font->face = face;
|
||||
font->hb_features[0] = hb_features[CALT_FEATURE];
|
||||
font->num_hb_features = 1;
|
||||
} else {
|
||||
FontGroup *fg = font_groups;
|
||||
font = fg->fonts + fg->medium_font_idx;
|
||||
@@ -1059,7 +1079,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
||||
default:
|
||||
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS);
|
||||
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
|
||||
else if (cursor_offset > -1) {
|
||||
else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER
|
||||
index_type left, right;
|
||||
split_run_at_offset(cursor_offset, &left, &right);
|
||||
if (right > left) {
|
||||
@@ -1183,12 +1203,12 @@ DescriptorIndices descriptor_indices = {0};
|
||||
static PyObject*
|
||||
set_font_data(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *sm;
|
||||
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx);
|
||||
if (!PyArg_ParseTuple(args, "OOOIIIIO!d",
|
||||
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
|
||||
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO",
|
||||
&box_drawing_function, &prerender_function, &descriptor_for_idx,
|
||||
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
|
||||
&PyTuple_Type, &sm, &global_state.font_sz_in_pts)) return NULL;
|
||||
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx);
|
||||
&PyTuple_Type, &sm, &global_state.font_sz_in_pts, &font_feature_settings)) return NULL;
|
||||
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
|
||||
free_font_groups();
|
||||
clear_symbol_maps();
|
||||
num_symbol_maps = PyTuple_GET_SIZE(sm);
|
||||
@@ -1290,6 +1310,7 @@ finalize(void) {
|
||||
Py_CLEAR(box_drawing_function);
|
||||
Py_CLEAR(prerender_function);
|
||||
Py_CLEAR(descriptor_for_idx);
|
||||
Py_CLEAR(font_feature_settings);
|
||||
free_font_groups();
|
||||
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
|
||||
free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0;
|
||||
@@ -1435,9 +1456,26 @@ free_font_data(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
parse_font_feature(PyObject *self UNUSED, PyObject *feature) {
|
||||
if (!PyUnicode_Check(feature)) {
|
||||
PyErr_SetString(PyExc_TypeError, "feature must be a unicode object");
|
||||
return NULL;
|
||||
}
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, sizeof(hb_feature_t));
|
||||
if (!ans) return NULL;
|
||||
if (!hb_feature_from_string(PyUnicode_AsUTF8(feature), -1, (hb_feature_t*)PyBytes_AS_STRING(ans))) {
|
||||
Py_CLEAR(ans);
|
||||
PyErr_Format(PyExc_ValueError, "%U is not a valid font feature", feature);
|
||||
return NULL;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(set_font_data, METH_VARARGS),
|
||||
METHODB(free_font_data, METH_NOARGS),
|
||||
METHODB(parse_font_feature, METH_O),
|
||||
METHODB(create_test_font_group, METH_VARARGS),
|
||||
METHODB(sprite_map_set_layout, METH_VARARGS),
|
||||
METHODB(test_sprite_position_for, METH_VARARGS),
|
||||
|
||||
@@ -38,7 +38,7 @@ def list_fonts():
|
||||
if f:
|
||||
fn = (f + ' ' + (fd['style'] or '')).strip()
|
||||
is_mono = bool(fd['monospace'])
|
||||
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
|
||||
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}
|
||||
|
||||
|
||||
def find_best_match(family, bold=False, italic=False):
|
||||
|
||||
@@ -45,7 +45,7 @@ def list_fonts():
|
||||
if f:
|
||||
fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip()
|
||||
is_mono = fd.get('spacing') in ('MONO', 'DUAL')
|
||||
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
|
||||
yield {'family': f, 'full_name': fn, 'postscript_name': fd.get('postscript_name', ''), 'is_monospace': is_mono}
|
||||
|
||||
|
||||
def family_name_to_key(family):
|
||||
|
||||
@@ -20,6 +20,7 @@ def create_family_groups(monospaced=True):
|
||||
|
||||
|
||||
def main(argv):
|
||||
psnames = '--psnames' in argv
|
||||
isatty = sys.stdout.isatty()
|
||||
groups = create_family_groups()
|
||||
for k in sorted(groups, key=lambda x: x.lower()):
|
||||
@@ -31,5 +32,7 @@ def main(argv):
|
||||
p = f['full_name']
|
||||
if isatty:
|
||||
p = '\033[3m' + p + '\033[m'
|
||||
if psnames:
|
||||
p += ' ({})'.format(f['postscript_name'])
|
||||
print(' ', p)
|
||||
print()
|
||||
|
||||
@@ -103,7 +103,7 @@ def set_font_family(opts=None, override_font_size=None, debug_font_matching=Fals
|
||||
set_font_data(
|
||||
render_box_drawing, prerender_function, descriptor_for_idx,
|
||||
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
|
||||
sm, sz
|
||||
sm, sz, opts.font_features
|
||||
)
|
||||
|
||||
|
||||
|
||||
11
kitty/gl.c
11
kitty/gl.c
@@ -81,6 +81,11 @@ free_texture(GLuint *tex_id) {
|
||||
*tex_id = 0;
|
||||
}
|
||||
|
||||
void
|
||||
free_framebuffer(GLuint *fb_id) {
|
||||
glDeleteFramebuffers(1, fb_id);
|
||||
*fb_id = 0;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
@@ -126,6 +131,12 @@ init_uniforms(int program) {
|
||||
}
|
||||
}
|
||||
|
||||
GLint
|
||||
get_uniform_location(int program, const char *name) {
|
||||
Program *p = programs + program;
|
||||
return glGetUniformLocation(p->id, name);
|
||||
}
|
||||
|
||||
GLint
|
||||
get_uniform_information(int program, const char *name, GLenum information_type) {
|
||||
GLint q; GLuint t;
|
||||
|
||||
@@ -35,12 +35,14 @@ typedef struct {
|
||||
void gl_init(void);
|
||||
void update_surface_size(int w, int h, GLuint offscreen_texture_id);
|
||||
void free_texture(GLuint *tex_id);
|
||||
void free_framebuffer(GLuint *fb_id);
|
||||
void remove_vao(ssize_t vao_idx);
|
||||
void init_uniforms(int program);
|
||||
GLuint program_id(int program);
|
||||
Program* program_ptr(int program);
|
||||
GLuint block_index(int program, const char *name);
|
||||
GLint block_size(int program, GLuint block_index);
|
||||
GLint get_uniform_location(int program, const char *name);
|
||||
GLint get_uniform_information(int program, const char *name, GLenum information_type);
|
||||
GLint attrib_location(int program, const char *name);
|
||||
ssize_t create_vao(void);
|
||||
|
||||
3
kitty/glfw-wrapper.c
generated
3
kitty/glfw-wrapper.c
generated
@@ -140,6 +140,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwSetWindowAspectRatio_impl) = dlsym(handle, "glfwSetWindowAspectRatio");
|
||||
if (glfwSetWindowAspectRatio_impl == NULL) fail("Failed to load glfw function glfwSetWindowAspectRatio with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetWindowSizeIncrements_impl) = dlsym(handle, "glfwSetWindowSizeIncrements");
|
||||
if (glfwSetWindowSizeIncrements_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeIncrements with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetWindowSize_impl) = dlsym(handle, "glfwSetWindowSize");
|
||||
if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror());
|
||||
|
||||
|
||||
7
kitty/glfw-wrapper.h
generated
7
kitty/glfw-wrapper.h
generated
@@ -165,6 +165,7 @@
|
||||
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
|
||||
#define GLFW_KEY_BACKSLASH 92 /* \ */
|
||||
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
|
||||
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
|
||||
#define GLFW_KEY_UNDERSCORE 95 /* _ */
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
@@ -1750,6 +1751,10 @@ typedef void (*glfwSetWindowAspectRatio_func)(GLFWwindow*, int, int);
|
||||
glfwSetWindowAspectRatio_func glfwSetWindowAspectRatio_impl;
|
||||
#define glfwSetWindowAspectRatio glfwSetWindowAspectRatio_impl
|
||||
|
||||
typedef void (*glfwSetWindowSizeIncrements_func)(GLFWwindow*, int, int);
|
||||
glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl;
|
||||
#define glfwSetWindowSizeIncrements glfwSetWindowSizeIncrements_impl
|
||||
|
||||
typedef void (*glfwSetWindowSize_func)(GLFWwindow*, int, int);
|
||||
glfwSetWindowSize_func glfwSetWindowSize_impl;
|
||||
#define glfwSetWindowSize glfwSetWindowSize_impl
|
||||
@@ -2082,7 +2087,7 @@ typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaun
|
||||
glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl;
|
||||
#define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl
|
||||
|
||||
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*);
|
||||
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, char*, size_t, int*);
|
||||
glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl;
|
||||
#define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl
|
||||
|
||||
|
||||
25
kitty/glfw.c
25
kitty/glfw.c
@@ -14,6 +14,7 @@ extern bool cocoa_make_window_resizable(void *w, bool);
|
||||
extern void cocoa_focus_window(void *w);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_hide_window_title(void *w);
|
||||
extern void cocoa_hide_titlebar(void *w);
|
||||
extern void cocoa_set_activation_policy(bool);
|
||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||
extern bool cocoa_alt_option_key_pressed(unsigned long);
|
||||
@@ -513,12 +514,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
// We don't use depth and stencil buffers
|
||||
glfwWindowHint(GLFW_DEPTH_BITS, 0);
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 0);
|
||||
if (OPT(hide_window_decorations) & 1) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#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);
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -527,7 +528,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
if (global_state.num_os_windows >= MAX_CHILDREN) {
|
||||
@@ -625,6 +625,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
w->fonts_data = fonts_data;
|
||||
w->shown_once = true;
|
||||
w->last_focused_counter = ++focus_counter;
|
||||
if (OPT(resize_in_steps)) os_window_update_size_increments(w);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
||||
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
|
||||
@@ -652,7 +653,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwSetDropCallback(glfw_window, drop_callback);
|
||||
#ifdef __APPLE__
|
||||
if (glfwGetCocoaWindow) {
|
||||
if (!(OPT(macos_show_window_title_in) & WINDOW)) {
|
||||
if (OPT(hide_window_decorations) & 2) {
|
||||
cocoa_hide_titlebar(glfwGetCocoaWindow(glfw_window));
|
||||
} else if (!(OPT(macos_show_window_title_in) & WINDOW)) {
|
||||
cocoa_hide_window_title(glfwGetCocoaWindow(glfw_window));
|
||||
}
|
||||
cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable));
|
||||
@@ -673,6 +676,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
return PyLong_FromUnsignedLongLong(w->id);
|
||||
}
|
||||
|
||||
void
|
||||
os_window_update_size_increments(OSWindow *window) {
|
||||
if (window->handle && window->fonts_data) glfwSetWindowSizeIncrements(
|
||||
window->handle, window->fonts_data->cell_width, window->fonts_data->cell_height);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
static inline bool
|
||||
window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) {
|
||||
@@ -834,7 +843,10 @@ static PyObject*
|
||||
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
||||
int key, native_key;
|
||||
if (!PyArg_ParseTuple(args, "ii", &key, &native_key)) return NULL;
|
||||
return Py_BuildValue("s", glfwGetKeyName(key, native_key));
|
||||
if (!glfwGetKeyName) {
|
||||
return PyUnicode_FromFormat("key: %d native_key: %d", key, native_key);
|
||||
}
|
||||
return Py_BuildValue("z", glfwGetKeyName(key, native_key));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -1079,8 +1091,8 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
|
||||
|
||||
#ifdef __APPLE__
|
||||
void
|
||||
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
||||
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
|
||||
get_cocoa_key_equivalent(int key, int mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) {
|
||||
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, key_sz, cocoa_mods);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1296,6 +1308,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_KEY_LEFT_BRACKET);
|
||||
ADDC(GLFW_KEY_BACKSLASH);
|
||||
ADDC(GLFW_KEY_RIGHT_BRACKET);
|
||||
ADDC(GLFW_KEY_CIRCUMFLEX);
|
||||
ADDC(GLFW_KEY_UNDERSCORE);
|
||||
ADDC(GLFW_KEY_GRAVE_ACCENT);
|
||||
ADDC(GLFW_KEY_WORLD_1);
|
||||
|
||||
@@ -544,7 +544,9 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
||||
if (!self->layers_dirty) return false;
|
||||
self->layers_dirty = false;
|
||||
size_t i, j;
|
||||
self->num_of_negative_refs = 0; self->num_of_positive_refs = 0;
|
||||
self->num_of_below_refs = 0;
|
||||
self->num_of_negative_refs = 0;
|
||||
self->num_of_positive_refs = 0;
|
||||
Image *img; ImageRef *ref;
|
||||
ImageRect r;
|
||||
float screen_width = dx * num_cols, screen_height = dy * num_rows;
|
||||
@@ -565,7 +567,12 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
||||
if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx;
|
||||
else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px;
|
||||
|
||||
if (ref->z_index < 0) self->num_of_negative_refs++; else self->num_of_positive_refs++;
|
||||
if (ref->z_index < ((int32_t)INT32_MIN/2))
|
||||
self->num_of_below_refs++;
|
||||
else if (ref->z_index < 0)
|
||||
self->num_of_negative_refs++;
|
||||
else
|
||||
self->num_of_positive_refs++;
|
||||
ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true);
|
||||
ImageRenderData *rd = self->render_data + self->count;
|
||||
zero_at_ptr(rd);
|
||||
|
||||
@@ -72,7 +72,8 @@ typedef struct {
|
||||
size_t count, capacity;
|
||||
ImageRenderData *render_data;
|
||||
bool layers_dirty;
|
||||
size_t num_of_negative_refs, num_of_positive_refs;
|
||||
// The number of images below MIN_ZINDEX / 2, then the number of refs between MIN_ZINDEX / 2 and -1 inclusive, then the number of refs above 0 inclusive.
|
||||
size_t num_of_below_refs, num_of_negative_refs, num_of_positive_refs;
|
||||
unsigned int last_scrolled_by;
|
||||
size_t used_storage;
|
||||
} GraphicsManager;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
uniform sampler2D image;
|
||||
#ifdef ALPHA_MASK
|
||||
uniform uint fg;
|
||||
uniform float alpha_mask_premult;
|
||||
#else
|
||||
uniform float inactive_text_alpha;
|
||||
#endif
|
||||
@@ -28,6 +29,7 @@ void main() {
|
||||
color = texture(image, texcoord);
|
||||
#ifdef ALPHA_MASK
|
||||
color = vec4(color_to_vec(fg), color.r);
|
||||
color = mix(color, vec4(color.rgb * color.a, color.a), alpha_mask_premult);
|
||||
#else
|
||||
color.a *= inactive_text_alpha;
|
||||
#ifdef PREMULT
|
||||
|
||||
4
kitty/key_encoding.py
generated
4
kitty/key_encoding.py
generated
@@ -192,7 +192,8 @@ ENCODING = {
|
||||
'CYRILLIC E': 'CJ',
|
||||
'CYRILLIC YU': 'CK',
|
||||
'CYRILLIC YA': 'CL',
|
||||
'CYRILLIC IO': 'CM'
|
||||
'CYRILLIC IO': 'CM',
|
||||
'CIRCUMFLEX': 'CN'
|
||||
}
|
||||
KEY_MAP = {
|
||||
32: 'A',
|
||||
@@ -254,6 +255,7 @@ KEY_MAP = {
|
||||
91: 's',
|
||||
92: 't',
|
||||
93: 'u',
|
||||
94: 'CN',
|
||||
95: 'Bj',
|
||||
96: 'v',
|
||||
161: 'w',
|
||||
|
||||
@@ -30,6 +30,7 @@ key_name_aliases = {
|
||||
'[': 'LEFT_BRACKET',
|
||||
'\\': 'BACKSLASH',
|
||||
']': 'RIGHT_BRACKET',
|
||||
'^': 'CIRCUMFLEX',
|
||||
'_': 'UNDERSCORE',
|
||||
'`': 'GRAVE_ACCENT',
|
||||
'§': 'PARAGRAPH',
|
||||
|
||||
16395
kitty/keys.h
generated
16395
kitty/keys.h
generated
File diff suppressed because it is too large
Load Diff
@@ -120,16 +120,13 @@ control_codes.update({
|
||||
for i, k in
|
||||
enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
|
||||
})
|
||||
control_codes[defines.GLFW_KEY_GRAVE_ACCENT] = (0,)
|
||||
control_codes[defines.GLFW_KEY_UNDERSCORE] = (0,)
|
||||
control_codes[defines.GLFW_KEY_SPACE] = (0,)
|
||||
control_codes[defines.GLFW_KEY_2] = (0,)
|
||||
control_codes[defines.GLFW_KEY_GRAVE_ACCENT] = control_codes[defines.GLFW_KEY_UNDERSCORE] = \
|
||||
control_codes[defines.GLFW_KEY_SPACE] = control_codes[defines.GLFW_KEY_2] = (0,)
|
||||
control_codes[defines.GLFW_KEY_3] = (27,)
|
||||
control_codes[defines.GLFW_KEY_4] = (28,)
|
||||
control_codes[defines.GLFW_KEY_5] = (29,)
|
||||
control_codes[defines.GLFW_KEY_6] = (30,)
|
||||
control_codes[defines.GLFW_KEY_7] = (31,)
|
||||
control_codes[defines.GLFW_KEY_SLASH] = (31,)
|
||||
control_codes[defines.GLFW_KEY_6] = control_codes[defines.GLFW_KEY_CIRCUMFLEX] = (30,)
|
||||
control_codes[defines.GLFW_KEY_7] = control_codes[defines.GLFW_KEY_SLASH] = (31,)
|
||||
control_codes[defines.GLFW_KEY_8] = (127,)
|
||||
|
||||
rmkx_key_map = smkx_key_map.copy()
|
||||
@@ -387,7 +384,7 @@ def generate_key_table_impl(w):
|
||||
|
||||
|
||||
def generate_key_table():
|
||||
# To run this, use: python3 . +runpy "from kitty.keys import *; generate_key_table()"
|
||||
# To run this, use: ./kitty/launcher/kitty +runpy "from kitty.keys import *; generate_key_table()"
|
||||
import os
|
||||
from functools import partial
|
||||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'keys.h'), 'w') as f:
|
||||
|
||||
@@ -18,7 +18,7 @@ child process.
|
||||
|
||||
--tab-title
|
||||
The title for the new tab if launching in a new tab. By default, the title
|
||||
of the actie window in the tab is used as the tab title.
|
||||
of the active window in the tab is used as the tab title.
|
||||
|
||||
|
||||
--type
|
||||
@@ -73,11 +73,12 @@ newly launched child process.
|
||||
--location
|
||||
type=choices
|
||||
default=last
|
||||
choices=first,neighbor,last
|
||||
choices=first,after,before,neighbor,last
|
||||
Where to place the newly created window when it is added to a tab which
|
||||
already has existing windows in it. Also applies to creating a new tab,
|
||||
where the value of neighbor will cause the new tab to be placed next to
|
||||
the current tab instead of at the end.
|
||||
already has existing windows in it. :code:`after` and :code:`before` place the new
|
||||
window before or after the active window. :code:`neighbor` is a synonym for :code:`after`.
|
||||
Also applies to creating a new tab, where the value of :code:`after`
|
||||
will cause the new tab to be placed next to the current tab instead of at the end.
|
||||
|
||||
|
||||
--allow-remote-control
|
||||
@@ -116,6 +117,9 @@ want to pipe to program that wants to duplicate the screen layout of the
|
||||
screen.
|
||||
|
||||
|
||||
--marker
|
||||
Create a marker that highlights text in the newly created window. The syntax is
|
||||
the same as for the :code:`toggle_marker` map action (see :doc:`/marks`).
|
||||
'''
|
||||
options_spec.ans = OPTIONS
|
||||
return options_spec.ans
|
||||
@@ -144,7 +148,7 @@ def get_env(opts, active_child):
|
||||
def tab_for_window(boss, opts, target_tab=None):
|
||||
if opts.type == 'tab':
|
||||
tm = boss.active_tab_manager
|
||||
tab = tm.new_tab(empty_tab=True, as_neighbor=opts.location == 'neighbor')
|
||||
tab = tm.new_tab(empty_tab=True, location=opts.location)
|
||||
if opts.tab_title:
|
||||
tab.set_title(opts.tab_title)
|
||||
elif opts.type == 'os-window':
|
||||
@@ -178,6 +182,8 @@ def launch(boss, opts, args, target_tab=None):
|
||||
kw['override_title'] = opts.window_title
|
||||
if opts.copy_colors and active:
|
||||
kw['copy_colors_from'] = active
|
||||
if opts.marker:
|
||||
kw['marker'] = opts.marker
|
||||
cmd = args or None
|
||||
if opts.copy_cmdline and active_child:
|
||||
cmd = active_child.foreground_cmdline
|
||||
|
||||
228
kitty/layout.py
228
kitty/layout.py
@@ -3,7 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
from functools import lru_cache, partial
|
||||
from itertools import islice, repeat
|
||||
|
||||
from .constants import WindowGeometry
|
||||
@@ -279,8 +279,12 @@ class Layout: # {{{
|
||||
all_windows[i] = window
|
||||
active_window_idx = i
|
||||
elif location is not None:
|
||||
if location == 'neighbor' and current_active_window_idx is not None and len(all_windows) > 1:
|
||||
if location == 'neighbor':
|
||||
location = 'after'
|
||||
if location == 'after' and current_active_window_idx is not None and len(all_windows) > 1:
|
||||
active_window_idx = min(current_active_window_idx + 1, len(all_windows))
|
||||
elif location == 'before' and current_active_window_idx is not None and len(all_windows) > 1:
|
||||
active_window_idx = current_active_window_idx
|
||||
elif location == 'first':
|
||||
active_window_idx = 0
|
||||
if active_window_idx is not None:
|
||||
@@ -391,8 +395,11 @@ class Layout: # {{{
|
||||
br = self.blank_rects
|
||||
left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
|
||||
|
||||
def between_blank_rect(self, left_window, right_window):
|
||||
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
|
||||
def between_blank_rect(self, left_window, right_window, vertical=True):
|
||||
if vertical:
|
||||
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
|
||||
else:
|
||||
self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom))
|
||||
|
||||
def bottom_blank_rect(self, window):
|
||||
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
|
||||
@@ -445,28 +452,34 @@ class Tall(Layout): # {{{
|
||||
only_between_border = False, False, False, True
|
||||
only_main_border = False, False, True, False
|
||||
|
||||
@property
|
||||
def num_full_size_windows(self):
|
||||
return self.layout_opts['full_size']
|
||||
|
||||
def remove_all_biases(self):
|
||||
self.main_bias = list(self.layout_opts['bias'])
|
||||
self.biased_map = {}
|
||||
return True
|
||||
|
||||
def variable_layout(self, num_windows, biased_map):
|
||||
num_windows -= 1
|
||||
num_windows -= self.num_full_size_windows
|
||||
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||
|
||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||
if self.main_is_horizontal == is_horizontal:
|
||||
before = self.main_bias
|
||||
if idx == 0:
|
||||
self.main_bias = [safe_increment_bias(self.main_bias[0], increment), safe_increment_bias(self.main_bias[1], -increment)]
|
||||
else:
|
||||
self.main_bias = [safe_increment_bias(self.main_bias[0], -increment), safe_increment_bias(self.main_bias[1], increment)]
|
||||
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)
|
||||
after = self.main_bias
|
||||
else:
|
||||
if idx == 0 or num_windows < 3:
|
||||
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 -= 1
|
||||
idx -= self.num_full_size_windows
|
||||
before_layout = list(self.variable_layout(num_windows, self.biased_map))
|
||||
candidate = self.biased_map.copy()
|
||||
before = candidate.get(idx, 0)
|
||||
@@ -480,40 +493,70 @@ class Tall(Layout): # {{{
|
||||
def parse_layout_opts(self, layout_opts):
|
||||
ans = Layout.parse_layout_opts(self, layout_opts)
|
||||
try:
|
||||
ans['bias'] = int(ans.get('bias', 50)) / 100
|
||||
ans['full_size'] = int(ans.get('full_size', 1))
|
||||
except Exception:
|
||||
ans['bias'] = 0.5
|
||||
ans['bias'] = max(0.1, min(ans['bias'], 0.9))
|
||||
ans['bias'] = ans['bias'], 1.0 - ans['bias']
|
||||
ans['full_size'] = 1
|
||||
ans['full_size'] = fs = max(1, min(ans['full_size'], 100))
|
||||
try:
|
||||
b = int(ans.get('bias', 50)) / 100
|
||||
except Exception:
|
||||
b = 0.5
|
||||
b = max(0.1, min(b, 0.9))
|
||||
ans['bias'] = tuple(repeat(b / fs, fs)) + (1.0 - b,)
|
||||
return ans
|
||||
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
if len(windows) == 1:
|
||||
return self.layout_single_window(windows[0])
|
||||
xlayout = self.xlayout(2, bias=self.main_bias)
|
||||
xstart, xnum = next(xlayout)
|
||||
ystart, ynum = next(self.vlayout(1))
|
||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
||||
xstart, xnum = next(xlayout)
|
||||
ylayout = self.variable_layout(len(windows), self.biased_map)
|
||||
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)):
|
||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
||||
# right bottom blank rect
|
||||
self.bottom_blank_rect(windows[i + 1])
|
||||
y, ynum = next(self.vlayout(1))
|
||||
if len(windows) <= self.num_full_size_windows:
|
||||
bias = normalize_biases(self.main_bias[:-1])
|
||||
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
|
||||
for i, (w, (x, xnum)) in enumerate(zip(windows, xlayout)):
|
||||
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||
if i > 0:
|
||||
self.between_blank_rect(windows[i-1], windows[i])
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(windows[i])
|
||||
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
return
|
||||
|
||||
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias)
|
||||
for i in range(self.num_full_size_windows):
|
||||
w = windows[i]
|
||||
x, xnum = next(xlayout)
|
||||
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||
self.between_blank_rect(windows[i], windows[i+1])
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(windows[i])
|
||||
x, xnum = next(xlayout)
|
||||
ylayout = self.variable_layout(len(windows), self.biased_map)
|
||||
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)):
|
||||
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, ystart, ynum))
|
||||
# right bottom blank rect
|
||||
self.bottom_blank_rect(windows[-1])
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
# between blank rect
|
||||
self.between_blank_rect(windows[0], windows[1])
|
||||
# left bottom blank rect
|
||||
self.bottom_blank_rect(windows[0])
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
if window is windows[0]:
|
||||
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
|
||||
idx = windows.index(window)
|
||||
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
|
||||
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
|
||||
prev = None if idx == 0 else windows[idx-1]
|
||||
nxt = None if idx == len(windows) - 1 else windows[idx+1]
|
||||
ans = {'left': [prev] if prev is not None else [], 'right': [], 'top': [], 'bottom': []}
|
||||
if idx < self.num_full_size_windows - 1:
|
||||
if nxt is not None:
|
||||
ans['right'] = [nxt]
|
||||
elif idx == self.num_full_size_windows - 1:
|
||||
ans['right'] = windows[idx+1:]
|
||||
else:
|
||||
ans['left'] = [windows[self.num_full_size_windows - 1]]
|
||||
if idx > self.num_full_size_windows:
|
||||
ans['top'] = [prev]
|
||||
if nxt is not None:
|
||||
ans['bottom'] = [nxt]
|
||||
return ans
|
||||
|
||||
def minimal_borders(self, windows, active_window, needs_borders_map):
|
||||
last_i = len(windows) - 1
|
||||
@@ -521,11 +564,11 @@ class Tall(Layout): # {{{
|
||||
if needs_borders_map[w.id]:
|
||||
yield all_borders
|
||||
continue
|
||||
if i == 0:
|
||||
if last_i == 1 and needs_borders_map[windows[1].id]:
|
||||
if i < self.num_full_size_windows:
|
||||
if (last_i == i+1 or i+1 < self.num_full_size_windows) and needs_borders_map[windows[i+1].id]:
|
||||
yield no_borders
|
||||
else:
|
||||
yield self.only_main_border
|
||||
yield no_borders if i == last_i else self.only_main_border
|
||||
continue
|
||||
if i == last_i:
|
||||
yield no_borders
|
||||
@@ -548,36 +591,73 @@ class Fat(Tall): # {{{
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
if len(windows) == 1:
|
||||
return self.layout_single_window(windows[0])
|
||||
xstart, xnum = next(self.xlayout(1))
|
||||
ylayout = self.ylayout(2, bias=self.main_bias)
|
||||
ystart, ynum = next(ylayout)
|
||||
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
|
||||
x, xnum = next(self.vlayout(1))
|
||||
if len(windows) <= self.num_full_size_windows:
|
||||
bias = normalize_biases(self.main_bias[:-1])
|
||||
ylayout = self.ylayout(self.num_full_size_windows, bias=bias)
|
||||
for i, (w, (y, ynum)) in enumerate(zip(windows, ylayout)):
|
||||
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||
if i > 0:
|
||||
self.between_blank_rect(windows[i-1], windows[i], vertical=False)
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(windows[-1])
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
return
|
||||
|
||||
ylayout = self.ylayout(self.num_full_size_windows + 1, bias=self.main_bias)
|
||||
for i in range(self.num_full_size_windows):
|
||||
w = windows[i]
|
||||
y, ynum = next(ylayout)
|
||||
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
|
||||
self.between_blank_rect(windows[i], windows[i+1], vertical=False)
|
||||
y, ynum = next(ylayout)
|
||||
xlayout = self.variable_layout(len(windows), self.biased_map)
|
||||
ystart, ynum = next(ylayout)
|
||||
for i, (w, (xstart, xnum)) in enumerate(zip(islice(windows, 1, None), xlayout)):
|
||||
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
|
||||
if i > 0:
|
||||
# bottom between blank rect
|
||||
self.between_blank_rect(windows[i - 1], windows[i])
|
||||
for i, (w, (x, xnum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), xlayout)):
|
||||
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, y, ynum))
|
||||
# bottom blank rect
|
||||
self.bottom_blank_rect(windows[i])
|
||||
|
||||
# left, top and right blank rects
|
||||
self.simple_blank_rects(windows[0], windows[-1])
|
||||
# top bottom blank rect
|
||||
self.bottom_blank_rect(windows[0])
|
||||
# bottom blank rect
|
||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
if window is windows[0]:
|
||||
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
|
||||
idx = windows.index(window)
|
||||
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
|
||||
'right': [] if window is windows[-1] else [windows[idx+1]]}
|
||||
prev = None if idx == 0 else windows[idx-1]
|
||||
nxt = None if idx == len(windows) - 1 else windows[idx+1]
|
||||
ans = {'left': [], 'right': [], 'top': [] if prev is None else [prev], 'bottom': []}
|
||||
if idx < self.num_full_size_windows - 1:
|
||||
if nxt is not None:
|
||||
ans['bottom'] = [nxt]
|
||||
elif idx == self.num_full_size_windows - 1:
|
||||
ans['bottom'] = windows[idx+1:]
|
||||
else:
|
||||
ans['top'] = [windows[self.num_full_size_windows - 1]]
|
||||
if idx > self.num_full_size_windows:
|
||||
ans['left'] = [prev]
|
||||
if nxt is not None:
|
||||
ans['right'] = [nxt]
|
||||
return ans
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class Grid(Layout): # {{{
|
||||
# Grid {{{
|
||||
@lru_cache()
|
||||
def calc_grid_size(n):
|
||||
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'
|
||||
|
||||
@@ -591,7 +671,7 @@ class Grid(Layout): # {{{
|
||||
|
||||
def apply_bias(self, idx, increment, num_windows, is_horizontal):
|
||||
b = self.biased_cols if is_horizontal else self.biased_rows
|
||||
ncols, nrows, special_rows, special_col = self.calc_grid_size(num_windows)
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
|
||||
|
||||
def position_for_window_idx(idx):
|
||||
row_num = col_num = 0
|
||||
@@ -633,18 +713,6 @@ class Grid(Layout): # {{{
|
||||
setattr(self, attr, candidate)
|
||||
return True
|
||||
|
||||
def calc_grid_size(self, n):
|
||||
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
|
||||
|
||||
def layout_windows(self, num_windows, nrows, ncols, special_rows, special_col, on_col_done=lambda col_windows: None):
|
||||
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
|
||||
xlayout = self.variable_layout(self.xlayout, ncols, self.biased_cols)
|
||||
@@ -667,10 +735,7 @@ class Grid(Layout): # {{{
|
||||
n = len(windows)
|
||||
if n == 1:
|
||||
return self.layout_single_window(windows[0])
|
||||
ncols, nrows, special_rows, special_col = self.calc_grid_size(n)
|
||||
layout_data = n, ncols, nrows, special_rows, special_col
|
||||
for w in windows:
|
||||
w.layout_data = layout_data
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
|
||||
win_col_map = []
|
||||
|
||||
@@ -693,14 +758,8 @@ class Grid(Layout): # {{{
|
||||
self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0])
|
||||
|
||||
def minimal_borders(self, windows, active_window, needs_borders_map):
|
||||
try:
|
||||
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
|
||||
except Exception:
|
||||
n = -1
|
||||
if n != len(windows):
|
||||
# Something bad happened
|
||||
yield from Layout.minimal_borders(self, windows, active_window, needs_borders_map)
|
||||
return
|
||||
n = len(windows)
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
blank_row = [None for i in range(ncols)]
|
||||
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
|
||||
wi = iter(windows)
|
||||
@@ -739,14 +798,7 @@ class Grid(Layout): # {{{
|
||||
n = len(windows)
|
||||
if n < 4:
|
||||
return Tall.neighbors_for_window(self, window, windows)
|
||||
try:
|
||||
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
|
||||
except Exception:
|
||||
n = -1
|
||||
if n != len(windows):
|
||||
# Something bad happened
|
||||
return Layout.neighbors_for_window(self, window, windows)
|
||||
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
blank_row = [None for i in range(ncols)]
|
||||
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
|
||||
wi = iter(windows)
|
||||
|
||||
80
kitty/line.c
80
kitty/line.c
@@ -50,7 +50,7 @@ cell_text(CPUCell *cell) {
|
||||
// URL detection {{{
|
||||
|
||||
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
|
||||
static size_t url_prefix_lengths[sizeof(url_prefixes)/sizeof(url_prefixes[0])] = {0};
|
||||
static size_t url_prefix_lengths[arraysz(url_prefixes)] = {0};
|
||||
|
||||
static inline index_type
|
||||
find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
@@ -79,7 +79,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
break;
|
||||
case SECOND_SLASH:
|
||||
if (ch == ':') return pos;
|
||||
state = ANY;
|
||||
state = ch == '/' ? SECOND_SLASH : ANY;
|
||||
break;
|
||||
}
|
||||
pos--;
|
||||
@@ -100,9 +100,9 @@ prefix_matches(Line *self, index_type at, const char* prefix, index_type prefix_
|
||||
static inline bool
|
||||
has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) {
|
||||
if (UNLIKELY(!url_prefix_lengths[0])) {
|
||||
for (index_type i = 0; i < sizeof(url_prefixes)/sizeof(url_prefixes[0]); i++) url_prefix_lengths[i] = strlen(url_prefixes[i]);
|
||||
for (index_type i = 0; i < arraysz(url_prefixes); i++) url_prefix_lengths[i] = strlen(url_prefixes[i]);
|
||||
}
|
||||
for (index_type i = 0; i < sizeof(url_prefixes)/sizeof(url_prefixes[0]); i++) {
|
||||
for (index_type i = 0; i < arraysz(url_prefixes); i++) {
|
||||
index_type prefix_len = url_prefix_lengths[i];
|
||||
if (at < prefix_len || prefix_len < min_prefix_len) continue;
|
||||
if (prefix_matches(self, at, url_prefixes[i], prefix_len)) { *ans = at - prefix_len; return true; }
|
||||
@@ -665,6 +665,78 @@ __eq__(Line *a, Line *b) {
|
||||
return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
line_has_mark(Line *line, attrs_type mark) {
|
||||
for (index_type x = 0; x < line->xnum; x++) {
|
||||
attrs_type m = (line->gpu_cells[x].attrs >> MARK_SHIFT) & MARK_MASK;
|
||||
if (m && (!mark || mark == m)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void
|
||||
report_marker_error(PyObject *marker) {
|
||||
if (!PyObject_HasAttrString(marker, "error_reported")) {
|
||||
PyErr_Print();
|
||||
if (PyObject_SetAttrString(marker, "error_reported", Py_True) != 0) PyErr_Clear();
|
||||
} else PyErr_Clear();
|
||||
}
|
||||
|
||||
static inline void
|
||||
apply_marker(PyObject *marker, Line *line, const PyObject *text) {
|
||||
unsigned int l=0, r=0, col=0, match_pos=0;
|
||||
PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col);
|
||||
if (!pl || !pr || !pcol) { PyErr_Clear(); return; }
|
||||
PyObject *iter = PyObject_CallFunctionObjArgs(marker, text, pl, pr, pcol, NULL);
|
||||
Py_DECREF(pl); Py_DECREF(pr); Py_DECREF(pcol);
|
||||
|
||||
if (iter == NULL) { report_marker_error(marker); return; }
|
||||
PyObject *match;
|
||||
index_type x = 0;
|
||||
#define INCREMENT_MATCH_POS { \
|
||||
if (line->cpu_cells[x].ch) { \
|
||||
match_pos++; \
|
||||
for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) { \
|
||||
if (line->cpu_cells[x].cc_idx[i]) match_pos++; \
|
||||
}}}
|
||||
|
||||
while ((match = PyIter_Next(iter)) && x < line->xnum) {
|
||||
Py_DECREF(match);
|
||||
while (match_pos < l && x < line->xnum) {
|
||||
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
|
||||
INCREMENT_MATCH_POS;
|
||||
x++;
|
||||
}
|
||||
attrs_type am = (col & MARK_MASK) << MARK_SHIFT;
|
||||
while(x < line->xnum && match_pos <= r) {
|
||||
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
|
||||
line->gpu_cells[x].attrs |= am;
|
||||
INCREMENT_MATCH_POS;
|
||||
x++;
|
||||
}
|
||||
|
||||
}
|
||||
while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK;
|
||||
Py_DECREF(iter);
|
||||
if (PyErr_Occurred()) report_marker_error(marker);
|
||||
#undef INCREMENT_MATCH_POS
|
||||
}
|
||||
|
||||
void
|
||||
mark_text_in_line(PyObject *marker, Line *line) {
|
||||
if (!marker) {
|
||||
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
|
||||
return;
|
||||
}
|
||||
PyObject *text = line_as_unicode(line);
|
||||
if (PyUnicode_GET_LENGTH(text) > 0) {
|
||||
apply_marker(marker, line, text);
|
||||
} else {
|
||||
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
|
||||
}
|
||||
Py_DECREF(text);
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
static PyObject*
|
||||
copy_char(Line* self, PyObject *args);
|
||||
|
||||
@@ -90,6 +90,8 @@ void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
|
||||
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
|
||||
void historybuf_refresh_sprite_positions(HistoryBuf *self);
|
||||
void historybuf_clear(HistoryBuf *self);
|
||||
void mark_text_in_line(PyObject *marker, Line *line);
|
||||
bool line_has_mark(Line *, attrs_type mark);
|
||||
|
||||
|
||||
#define as_text_generic(args, container, get_line, lines, columns) { \
|
||||
|
||||
@@ -67,14 +67,14 @@ def talk_to_instance(args):
|
||||
|
||||
data = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
||||
single_instance.socket.sendall(data)
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
single_instance.socket.shutdown(socket.SHUT_RDWR)
|
||||
single_instance.socket.close()
|
||||
|
||||
if args.wait_for_single_instance_window_close:
|
||||
conn = notify_socket.accept()[0]
|
||||
conn.recv(1)
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
conn.shutdown(socket.SHUT_RDWR)
|
||||
conn.close()
|
||||
|
||||
@@ -177,7 +177,8 @@ def setup_profiling(args):
|
||||
exe = kitty_exe()
|
||||
cg = '/tmp/kitty-profile.callgrind'
|
||||
print('Post processing profile data for', exe, '...')
|
||||
subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
|
||||
with open(cg, 'wb') as f:
|
||||
subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=f)
|
||||
try:
|
||||
subprocess.Popen(['kcachegrind', cg])
|
||||
except FileNotFoundError:
|
||||
@@ -201,12 +202,17 @@ def macos_cmdline(argv_args):
|
||||
|
||||
def read_shell_environment(opts=None):
|
||||
if not hasattr(read_shell_environment, 'ans'):
|
||||
ans = read_shell_environment.ans = {}
|
||||
import subprocess
|
||||
from .session import resolved_shell
|
||||
shell = resolved_shell(opts)
|
||||
master, slave = openpty()
|
||||
remove_blocking(master)
|
||||
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
|
||||
try:
|
||||
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
|
||||
except FileNotFoundError:
|
||||
log_error('Could not find shell to read environment')
|
||||
return ans
|
||||
with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'):
|
||||
raw = b''
|
||||
from subprocess import TimeoutExpired
|
||||
@@ -234,14 +240,12 @@ def read_shell_environment(opts=None):
|
||||
break
|
||||
raw += x
|
||||
raw = raw.decode('utf-8', 'replace')
|
||||
ans = read_shell_environment.ans = {}
|
||||
for line in raw.splitlines():
|
||||
k, v = line.partition('=')[::2]
|
||||
if k and v:
|
||||
ans[k] = v
|
||||
else:
|
||||
log_error('Failed to run shell to read its environment')
|
||||
read_shell_environment.ans = {}
|
||||
return read_shell_environment.ans
|
||||
|
||||
|
||||
|
||||
93
kitty/marks.py
Normal file
93
kitty/marks.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from ctypes import POINTER, c_uint, c_void_p, cast
|
||||
|
||||
from .constants import config_dir
|
||||
|
||||
pointer_to_uint = POINTER(c_uint)
|
||||
|
||||
|
||||
def null_marker(*a):
|
||||
return iter(())
|
||||
|
||||
|
||||
def get_output_variables(left_address, right_address, color_address):
|
||||
return (
|
||||
cast(c_void_p(left_address), pointer_to_uint).contents,
|
||||
cast(c_void_p(right_address), pointer_to_uint).contents,
|
||||
cast(c_void_p(color_address), pointer_to_uint).contents,
|
||||
)
|
||||
|
||||
|
||||
def marker_from_regex(expression, color, flags=re.UNICODE):
|
||||
color = max(1, min(color, 3))
|
||||
if isinstance(expression, str):
|
||||
pat = re.compile(expression, flags=flags)
|
||||
else:
|
||||
pat = expression
|
||||
|
||||
def marker(text, left_address, right_address, color_address):
|
||||
left, right, colorv = get_output_variables(left_address, right_address, color_address)
|
||||
colorv.value = color
|
||||
for match in pat.finditer(text):
|
||||
left.value = match.start()
|
||||
right.value = match.end() - 1
|
||||
yield
|
||||
|
||||
return marker
|
||||
|
||||
|
||||
def marker_from_multiple_regex(regexes, flags=re.UNICODE):
|
||||
expr = ''
|
||||
color_map = {}
|
||||
for i, (color, spec) in enumerate(regexes):
|
||||
grp = 'mcg{}'.format(i)
|
||||
expr += '|(?P<{}>{})'.format(grp, spec)
|
||||
color_map[grp] = color
|
||||
expr = expr[1:]
|
||||
pat = re.compile(expr, flags=flags)
|
||||
|
||||
def marker(text, left_address, right_address, color_address):
|
||||
left, right, color = get_output_variables(left_address, right_address, color_address)
|
||||
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() if v is not None)
|
||||
color.value = color_map[grp]
|
||||
yield
|
||||
|
||||
return marker
|
||||
|
||||
|
||||
def marker_from_text(expression, color):
|
||||
return marker_from_regex(re.escape(expression), color)
|
||||
|
||||
|
||||
def marker_from_function(func):
|
||||
def marker(text, left_address, right_address, color_address):
|
||||
left, right, colorv = get_output_variables(left_address, right_address, color_address)
|
||||
for (l, r, c) in func(text):
|
||||
left.value = l
|
||||
right.value = r
|
||||
colorv.value = c
|
||||
yield
|
||||
|
||||
return marker
|
||||
|
||||
|
||||
def marker_from_spec(ftype, spec, flags):
|
||||
if ftype == 'regex':
|
||||
if len(spec) == 1:
|
||||
return marker_from_regex(spec[0][1], spec[0][0], flags=flags)
|
||||
return marker_from_multiple_regex(spec, flags=flags)
|
||||
if ftype == 'function':
|
||||
import runpy
|
||||
path = spec
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
return marker_from_function(runpy.run_path(path, run_name='__marker__').marker)
|
||||
raise ValueError('Unknown marker type: {}'.format(ftype))
|
||||
@@ -68,7 +68,7 @@ class SocketIO:
|
||||
|
||||
def __exit__(self, *a):
|
||||
import socket
|
||||
with suppress(EnvironmentError): # on some OSes such as macOS the socket is already closed at this point
|
||||
with suppress(OSError): # on some OSes such as macOS the socket is already closed at this point
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
self.socket.close()
|
||||
|
||||
|
||||
126
kitty/screen.c
126
kitty/screen.c
@@ -115,7 +115,11 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self->pending_mode.wait_time = 2.0;
|
||||
self->disable_ligatures = OPT(disable_ligatures);
|
||||
self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool));
|
||||
if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL || self->alt_grman == NULL || self->color_profile == NULL) {
|
||||
if (
|
||||
self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL ||
|
||||
self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL ||
|
||||
self->alt_grman == NULL || self->color_profile == NULL
|
||||
) {
|
||||
Py_CLEAR(self); return NULL;
|
||||
}
|
||||
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
|
||||
@@ -278,6 +282,7 @@ dealloc(Screen* self) {
|
||||
Py_CLEAR(self->alt_linebuf);
|
||||
Py_CLEAR(self->historybuf);
|
||||
Py_CLEAR(self->color_profile);
|
||||
Py_CLEAR(self->marker);
|
||||
PyMem_Free(self->overlay_line.cpu_cells);
|
||||
PyMem_Free(self->overlay_line.gpu_cells);
|
||||
PyMem_Free(self->main_tabstops);
|
||||
@@ -1503,6 +1508,12 @@ screen_reset_dirty(Screen *self) {
|
||||
self->history_line_added_count = 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
screen_has_marker(Screen *self) {
|
||||
return self->marker != NULL;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) {
|
||||
unsigned int history_line_added_count = self->history_line_added_count;
|
||||
@@ -1516,6 +1527,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
|
||||
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
|
||||
if (self->historybuf->line->has_dirty_text) {
|
||||
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
|
||||
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
|
||||
historybuf_mark_line_clean(self->historybuf, lnum);
|
||||
}
|
||||
update_line_data(self->historybuf->line, y, address);
|
||||
@@ -1526,6 +1538,8 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
|
||||
if (self->linebuf->line->has_dirty_text ||
|
||||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered_cursor_y == lnum))) {
|
||||
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
|
||||
if (self->linebuf->line->has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
|
||||
|
||||
linebuf_mark_line_clean(self->linebuf, lnum);
|
||||
}
|
||||
update_line_data(self->linebuf->line, y, address);
|
||||
@@ -2183,12 +2197,13 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
|
||||
index_type bottom_line = extending_leftwards ? self->selection.start_y : self->selection.end_y;
|
||||
while(top_line > 0 && visual_line_(self, top_line)->continued) top_line--;
|
||||
while(bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) bottom_line++;
|
||||
bool multiline = self->selection.end_y != self->selection.start_y;
|
||||
found = screen_selection_range_for_line(self, top_line, &start, &end);
|
||||
if (found) {
|
||||
if (extending_leftwards) {
|
||||
self->selection.end_x = start; self->selection.end_y = top_line;
|
||||
self->selection.end_x = multiline ? 0 : start; self->selection.end_y = top_line;
|
||||
} else {
|
||||
self->selection.start_x = start; self->selection.start_y = top_line;
|
||||
self->selection.start_x = multiline ? 0 : start; self->selection.start_y = top_line;
|
||||
}
|
||||
}
|
||||
found = screen_selection_range_for_line(self, bottom_line, &start, &end);
|
||||
@@ -2241,14 +2256,115 @@ send_escape_code_to_child(Screen *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline void
|
||||
screen_mark_all(Screen *self) {
|
||||
for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
|
||||
linebuf_init_line(self->main_linebuf, y);
|
||||
mark_text_in_line(self->marker, self->main_linebuf->line);
|
||||
}
|
||||
for (index_type y = 0; y < self->alt_linebuf->ynum; y++) {
|
||||
linebuf_init_line(self->alt_linebuf, y);
|
||||
mark_text_in_line(self->marker, self->alt_linebuf->line);
|
||||
}
|
||||
for (index_type y = 0; y < self->historybuf->count; y++) {
|
||||
historybuf_init_line(self->historybuf, y, self->historybuf->line);
|
||||
mark_text_in_line(self->marker, self->historybuf->line);
|
||||
}
|
||||
self->is_dirty = true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_marker(Screen *self, PyObject *args) {
|
||||
PyObject *marker = NULL;
|
||||
if (!PyArg_ParseTuple(args, "|O", &marker)) return NULL;
|
||||
if (!marker) {
|
||||
if (self->marker) {
|
||||
Py_CLEAR(self->marker);
|
||||
screen_mark_all(self);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
if (!PyCallable_Check(marker)) {
|
||||
PyErr_SetString(PyExc_TypeError, "marker must be a callable");
|
||||
return NULL;
|
||||
}
|
||||
self->marker = marker;
|
||||
Py_INCREF(marker);
|
||||
screen_mark_all(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
scroll_to_next_mark(Screen *self, PyObject *args) {
|
||||
int backwards = 1;
|
||||
unsigned int mark = 0;
|
||||
if (!PyArg_ParseTuple(args, "|Ip", &mark, &backwards)) return NULL;
|
||||
if (!screen_has_marker(self) || self->linebuf == self->alt_linebuf) Py_RETURN_FALSE;
|
||||
if (backwards) {
|
||||
for (unsigned int y = self->scrolled_by; y < self->historybuf->count; y++) {
|
||||
historybuf_init_line(self->historybuf, y, self->historybuf->line);
|
||||
if (line_has_mark(self->historybuf->line, mark)) {
|
||||
screen_history_scroll(self, y - self->scrolled_by + 1, true);
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Line *line;
|
||||
for (unsigned int y = self->scrolled_by; y > 0; y--) {
|
||||
if (y > self->lines) {
|
||||
historybuf_init_line(self->historybuf, y - self->lines, self->historybuf->line);
|
||||
line = self->historybuf->line;
|
||||
} else {
|
||||
linebuf_init_line(self->linebuf, self->lines - y);
|
||||
line = self->linebuf->line;
|
||||
}
|
||||
if (line_has_mark(line, mark)) {
|
||||
screen_history_scroll(self, self->scrolled_by - y + 1, false);
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
marked_cells(Screen *self, PyObject *o UNUSED) {
|
||||
PyObject *ans = PyList_New(0);
|
||||
if (!ans) return ans;
|
||||
for (index_type y = 0; y < self->lines; y++) {
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
for (index_type x = 0; x < self->columns; x++) {
|
||||
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + x;
|
||||
unsigned int mark = (gpu_cell->attrs >> MARK_SHIFT) & MARK_MASK;
|
||||
if (mark) {
|
||||
PyObject *t = Py_BuildValue("III", x, y, mark);
|
||||
if (!t) { Py_DECREF(ans); return NULL; }
|
||||
if (PyList_Append(ans, t) != 0) { Py_DECREF(t); Py_DECREF(ans); return NULL; }
|
||||
Py_DECREF(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
paste(Screen *self, PyObject *bytes) {
|
||||
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
|
||||
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_START);
|
||||
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
|
||||
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_END);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
paste_bytes(Screen *self, PyObject *bytes) {
|
||||
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
|
||||
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
WRAP2(cursor_position, 1, 1)
|
||||
|
||||
#define COUNT_WRAP(name) WRAP1(name, 1)
|
||||
@@ -2329,7 +2445,11 @@ static PyMethodDef methods[] = {
|
||||
MND(toggle_alt_screen, METH_NOARGS)
|
||||
MND(reset_callbacks, METH_NOARGS)
|
||||
MND(paste, METH_O)
|
||||
MND(paste_bytes, METH_O)
|
||||
MND(copy_colors_from, METH_O)
|
||||
MND(set_marker, METH_VARARGS)
|
||||
MND(marked_cells, METH_NOARGS)
|
||||
MND(scroll_to_next_mark, METH_VARARGS)
|
||||
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
|
||||
|
||||
{NULL} /* Sentinel */
|
||||
|
||||
@@ -61,7 +61,6 @@ typedef struct {
|
||||
index_type xstart, ynum, xnum;
|
||||
} OverlayLine;
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
@@ -108,7 +107,7 @@ typedef struct {
|
||||
uint8_t stop_buf[32];
|
||||
} pending_mode;
|
||||
DisableLigature disable_ligatures;
|
||||
|
||||
PyObject *marker;
|
||||
} Screen;
|
||||
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ def create_sessions(opts, args=None, special_window=None, cwd_from=None, respect
|
||||
try:
|
||||
with open(default_session) as f:
|
||||
session_data = f.read()
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
|
||||
else:
|
||||
yield from parse_session(session_data, opts, getattr(args, 'title', None))
|
||||
|
||||
@@ -150,10 +150,10 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh
|
||||
typedef struct {
|
||||
UniformBlock render_data;
|
||||
ArrayInformation color_table;
|
||||
GLint draw_bg_bitfield_location;
|
||||
} CellProgramLayout;
|
||||
|
||||
static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
|
||||
static GLuint offscreen_framebuffer = 0;
|
||||
static ssize_t blit_vertex_array;
|
||||
|
||||
static void
|
||||
@@ -165,13 +165,13 @@ init_cell_program(void) {
|
||||
cell_program_layouts[i].color_table.offset = get_uniform_information(i, "color_table[0]", GL_UNIFORM_OFFSET);
|
||||
cell_program_layouts[i].color_table.stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE);
|
||||
}
|
||||
cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location = get_uniform_location(CELL_BG_PROGRAM, "draw_bg_bitfield");
|
||||
// Sanity check to ensure the attribute location binding worked
|
||||
#define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); }
|
||||
for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) {
|
||||
C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2);
|
||||
}
|
||||
#undef C
|
||||
glGenFramebuffers(1, &offscreen_framebuffer);
|
||||
blit_vertex_array = create_vao();
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ create_graphics_vao() {
|
||||
struct CellUniformData {
|
||||
bool constants_set;
|
||||
bool alpha_mask_fg_set;
|
||||
GLint gploc, gpploc, cploc, cfploc, fg_loc;
|
||||
GLint gploc, gpploc, cploc, cfploc, fg_loc, amask_premult_loc;
|
||||
GLfloat prev_inactive_text_alpha;
|
||||
};
|
||||
|
||||
@@ -347,7 +347,7 @@ draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *d
|
||||
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
|
||||
|
||||
void
|
||||
draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas) {
|
||||
draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas) {
|
||||
static ImageRenderData data = {.group_count=1};
|
||||
gpu_data_for_centered_image(&data, screen_width, screen_height, width, height);
|
||||
if (!data.texture_id) { glGenTextures(1, &data.texture_id); }
|
||||
@@ -364,11 +364,15 @@ draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_he
|
||||
glUniform1i(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "image"), GRAPHICS_UNIT);
|
||||
glUniform1ui(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "fg"), OPT(foreground));
|
||||
}
|
||||
glScissor(0, 0, screen_width, screen_height);
|
||||
send_graphics_data_to_gpu(1, gvao_idx, &data);
|
||||
glUniform1f(cell_uniform_data.amask_premult_loc, os_window->is_semi_transparent ? 1.f : 0.f);
|
||||
send_graphics_data_to_gpu(1, os_window->gvao_idx, &data);
|
||||
glEnable(GL_BLEND);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, gvao_idx, &data, 0, 1);
|
||||
if (os_window->is_semi_transparent) {
|
||||
BLEND_PREMULT;
|
||||
} else {
|
||||
BLEND_ONTO_OPAQUE;
|
||||
}
|
||||
draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, os_window->gvao_idx, &data, 0, 1);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
@@ -386,12 +390,23 @@ draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
|
||||
|
||||
static void
|
||||
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
glEnable(GL_BLEND);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
|
||||
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
// draw background for all cells
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
|
||||
if (screen->grman->num_of_below_refs) {
|
||||
draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
// draw background for non-default bg cells
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
}
|
||||
|
||||
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
|
||||
|
||||
bind_program(CELL_SPECIAL_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
@@ -399,7 +414,7 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
|
||||
bind_program(CELL_FG_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
|
||||
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
|
||||
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
@@ -407,6 +422,7 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
|
||||
static void
|
||||
draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window) {
|
||||
if (!os_window->offscreen_texture_id) {
|
||||
glGenFramebuffers(1, &os_window->offscreen_framebuffer);
|
||||
glGenTextures(1, &os_window->offscreen_texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
@@ -416,16 +432,34 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, offscreen_framebuffer);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, os_window->offscreen_framebuffer);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, os_window->offscreen_texture_id, 0);
|
||||
/* if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) fatal("Offscreen framebuffer not complete"); */
|
||||
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
// draw background for all cells
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
glEnable(GL_BLEND);
|
||||
BLEND_PREMULT;
|
||||
|
||||
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
|
||||
if (screen->grman->num_of_below_refs) {
|
||||
draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
// Draw background for non-default bg cells
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
} else {
|
||||
// Apply background_opacity
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 0);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
}
|
||||
|
||||
bind_program(CELL_BG_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
|
||||
if (screen->grman->num_of_negative_refs) {
|
||||
draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
|
||||
}
|
||||
|
||||
bind_program(CELL_SPECIAL_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
@@ -433,7 +467,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
|
||||
bind_program(CELL_FG_PROGRAM);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
|
||||
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
|
||||
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@@ -459,11 +493,13 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) {
|
||||
cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
|
||||
cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
|
||||
cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
|
||||
cell_uniform_data.amask_premult_loc = glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "alpha_mask_premult");
|
||||
#define S(prog, name, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), val); }
|
||||
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT, 1i);
|
||||
S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT, 1i);
|
||||
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
|
||||
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
|
||||
S(CELL_BG_PROGRAM, defaultbg, OPT(background), 1f);
|
||||
#undef S
|
||||
cell_uniform_data.constants_set = true;
|
||||
}
|
||||
@@ -525,7 +561,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
|
||||
if (screen->grman->count) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window);
|
||||
else draw_cells_simple(vao_idx, gvao_idx, screen);
|
||||
} else {
|
||||
if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
|
||||
if (screen->grman->num_of_negative_refs || screen->grman->num_of_below_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
|
||||
else draw_cells_simple(vao_idx, gvao_idx, screen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
|
||||
def init_readline(readline):
|
||||
try:
|
||||
readline.read_init_file()
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
if not is_macos:
|
||||
raise
|
||||
if 'libedit' in readline.__doc__:
|
||||
|
||||
@@ -255,6 +255,7 @@ destroy_os_window_item(OSWindow *w) {
|
||||
}
|
||||
Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen);
|
||||
if (w->offscreen_texture_id) free_texture(&w->offscreen_texture_id);
|
||||
if (w->offscreen_framebuffer) free_framebuffer(&w->offscreen_framebuffer);
|
||||
remove_vao(w->tab_bar_render_data.vao_idx);
|
||||
remove_vao(w->gvao_idx);
|
||||
free(w->tabs); w->tabs = NULL;
|
||||
@@ -476,7 +477,7 @@ PYWRAP1(set_options) {
|
||||
#define SS(name, dest, convert) { GA(name); dest = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
|
||||
#define S(name, convert) SS(name, OPT(name), convert)
|
||||
SS(kitty_mod, kitty_mod, PyLong_AsLong);
|
||||
S(hide_window_decorations, PyObject_IsTrue);
|
||||
S(hide_window_decorations, PyLong_AsUnsignedLong);
|
||||
S(visual_bell_duration, parse_s_double_to_monotonic_t);
|
||||
S(enable_audio_bell, PyObject_IsTrue);
|
||||
S(focus_follows_mouse, PyObject_IsTrue);
|
||||
@@ -499,6 +500,12 @@ PYWRAP1(set_options) {
|
||||
S(terminal_select_modifiers, convert_mods);
|
||||
S(click_interval, parse_s_double_to_monotonic_t);
|
||||
S(resize_debounce_time, parse_s_double_to_monotonic_t);
|
||||
S(mark1_foreground, color_as_int);
|
||||
S(mark1_background, color_as_int);
|
||||
S(mark2_foreground, color_as_int);
|
||||
S(mark2_background, color_as_int);
|
||||
S(mark3_foreground, color_as_int);
|
||||
S(mark3_background, color_as_int);
|
||||
S(url_color, color_as_int);
|
||||
S(background, color_as_int);
|
||||
S(foreground, color_as_int);
|
||||
@@ -521,7 +528,9 @@ PYWRAP1(set_options) {
|
||||
S(macos_thicken_font, PyFloat_AsFloat);
|
||||
S(tab_bar_min_tabs, PyLong_AsUnsignedLong);
|
||||
S(disable_ligatures, PyLong_AsLong);
|
||||
S(force_ltr, PyObject_IsTrue);
|
||||
S(resize_draw_strategy, PyLong_AsLong);
|
||||
S(resize_in_steps, PyObject_IsTrue);
|
||||
S(pointer_shape_when_grabbed, pointer_shape);
|
||||
|
||||
GA(tab_bar_style);
|
||||
@@ -781,6 +790,7 @@ PYWRAP1(os_window_font_size) {
|
||||
resize_screen(os_window, w->render_data.screen, true);
|
||||
}
|
||||
}
|
||||
if (OPT(resize_in_steps)) os_window_update_size_increments(os_window);
|
||||
}
|
||||
return Py_BuildValue("d", os_window->font_sz_in_pts);
|
||||
END_WITH_OS_WINDOW
|
||||
@@ -807,6 +817,8 @@ PYWRAP1(patch_global_colors) {
|
||||
P(active_border_color); P(inactive_border_color); P(bell_border_color);
|
||||
if (configured) {
|
||||
P(background); P(url_color);
|
||||
P(mark1_background); P(mark1_foreground); P(mark2_background); P(mark2_foreground);
|
||||
P(mark3_background); P(mark3_foreground);
|
||||
}
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
|
||||
@@ -26,8 +26,10 @@ typedef struct {
|
||||
unsigned int scrollback_pager_history_size;
|
||||
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
|
||||
color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color;
|
||||
color_type mark1_foreground, mark1_background, mark2_foreground, mark2_background, mark3_foreground, mark3_background;
|
||||
monotonic_t repaint_delay, input_delay;
|
||||
bool focus_follows_mouse, hide_window_decorations;
|
||||
bool focus_follows_mouse;
|
||||
unsigned int hide_window_decorations;
|
||||
bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
|
||||
unsigned int macos_option_as_alt;
|
||||
float macos_thicken_font;
|
||||
@@ -41,7 +43,9 @@ typedef struct {
|
||||
Edge tab_bar_edge;
|
||||
unsigned long tab_bar_min_tabs;
|
||||
DisableLigature disable_ligatures;
|
||||
bool force_ltr;
|
||||
ResizeDrawStrategy resize_draw_strategy;
|
||||
bool resize_in_steps;
|
||||
bool sync_to_monitor;
|
||||
bool close_on_child_death;
|
||||
bool window_alert_on_bell;
|
||||
@@ -127,6 +131,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
void *handle;
|
||||
id_type id;
|
||||
uint32_t offscreen_framebuffer;
|
||||
OSWindowGeometry before_fullscreen;
|
||||
int viewport_width, viewport_height, window_width, window_height;
|
||||
double viewport_x_ratio, viewport_y_ratio;
|
||||
@@ -214,9 +219,10 @@ ssize_t create_graphics_vao(void);
|
||||
ssize_t create_border_vao(void);
|
||||
bool send_cell_data_to_gpu(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *);
|
||||
void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *, bool, bool);
|
||||
void draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas);
|
||||
void draw_centered_alpha_mask(OSWindow *w, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas);
|
||||
void update_surface_size(int, int, uint32_t);
|
||||
void free_texture(uint32_t*);
|
||||
void free_framebuffer(uint32_t*);
|
||||
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
|
||||
void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
|
||||
void blank_canvas(float, color_type);
|
||||
@@ -225,7 +231,7 @@ void set_titlebar_color(OSWindow *w, color_type color);
|
||||
FONTS_DATA_HANDLE load_fonts_data(double, double, double);
|
||||
void send_prerendered_sprites_for_window(OSWindow *w);
|
||||
#ifdef __APPLE__
|
||||
void get_cocoa_key_equivalent(int, int, unsigned short*, int*);
|
||||
void get_cocoa_key_equivalent(int, int, char *key, size_t key_sz, int*);
|
||||
typedef enum {
|
||||
PREFERENCES_WINDOW = 1,
|
||||
NEW_OS_WINDOW = 2,
|
||||
@@ -245,3 +251,4 @@ void remove_main_loop_timer(id_type timer_id);
|
||||
void update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled);
|
||||
void run_main_loop(tick_callback_fun, void*);
|
||||
void stop_main_loop(void);
|
||||
void os_window_update_size_increments(OSWindow *window);
|
||||
|
||||
@@ -263,7 +263,7 @@ class Tab: # {{{
|
||||
def new_window(
|
||||
self, use_shell=True, cmd=None, stdin=None, override_title=None,
|
||||
cwd_from=None, cwd=None, overlay_for=None, env=None, location=None,
|
||||
copy_colors_from=None, allow_remote_control=False
|
||||
copy_colors_from=None, allow_remote_control=False, marker=None
|
||||
):
|
||||
child = self.launch_child(
|
||||
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control)
|
||||
@@ -275,6 +275,12 @@ class Tab: # {{{
|
||||
# Must add child before laying out so that resize_pty succeeds
|
||||
get_boss().add_child(window)
|
||||
self._add_window(window, location=location)
|
||||
if marker:
|
||||
try:
|
||||
window.set_marker(marker)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return window
|
||||
|
||||
def new_special_window(self, special_window, location=None, copy_colors_from=None, allow_remote_control=False):
|
||||
@@ -554,9 +560,11 @@ class TabManager: # {{{
|
||||
self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs))
|
||||
|
||||
def goto_tab(self, tab_num):
|
||||
if 0 <= tab_num < len(self.tabs):
|
||||
if tab_num >= len(self.tabs):
|
||||
tab_num = max(0, len(self.tabs) - 1)
|
||||
if tab_num >= 0:
|
||||
self.set_active_tab_idx(tab_num)
|
||||
elif tab_num < 0:
|
||||
else:
|
||||
try:
|
||||
old_active_tab_id = self.active_tab_history[tab_num]
|
||||
except IndexError:
|
||||
@@ -609,17 +617,26 @@ class TabManager: # {{{
|
||||
self._set_active_tab(nidx)
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False):
|
||||
nidx = self.active_tab_idx + 1
|
||||
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False, location='last'):
|
||||
idx = len(self.tabs)
|
||||
orig_active_tab_idx = self.active_tab_idx
|
||||
self._add_tab(Tab(self, no_initial_window=True) if empty_tab else Tab(self, special_window=special_window, cwd_from=cwd_from))
|
||||
self._set_active_tab(idx)
|
||||
if len(self.tabs) > 2 and as_neighbor and idx != nidx:
|
||||
for i in range(idx, nidx, -1):
|
||||
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i-1)
|
||||
self._set_active_tab(nidx)
|
||||
idx = nidx
|
||||
if as_neighbor:
|
||||
location = 'after'
|
||||
if location == 'neighbor':
|
||||
location = 'after'
|
||||
if len(self.tabs) > 1 and location != 'last':
|
||||
if location == 'first':
|
||||
desired_idx = 0
|
||||
else:
|
||||
desired_idx = orig_active_tab_idx + (0 if location == 'before' else 1)
|
||||
if idx != desired_idx:
|
||||
for i in range(idx, desired_idx, -1):
|
||||
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i-1)
|
||||
self._set_active_tab(desired_idx)
|
||||
idx = desired_idx
|
||||
self.mark_tab_bar_dirty()
|
||||
return self.tabs[idx]
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ def update_check(timer_id=None):
|
||||
kitty_exe(), '+runpy',
|
||||
'from kitty.update_check import run_worker; run_worker()'
|
||||
], stdout=subprocess.PIPE)
|
||||
except EnvironmentError as e:
|
||||
except OSError as e:
|
||||
log_error('Failed to run kitty for update check, with error: {}'.format(e))
|
||||
return False
|
||||
monitor_pid(p.pid)
|
||||
|
||||
@@ -260,10 +260,10 @@ class startup_notification_handler:
|
||||
|
||||
|
||||
def remove_socket_file(s, path=None):
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
s.close()
|
||||
if path:
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ def single_instance_unix(name):
|
||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_CLOEXEC)
|
||||
try:
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except EnvironmentError as err:
|
||||
except OSError as err:
|
||||
if err.errno in (errno.EAGAIN, errno.EACCES):
|
||||
# Client
|
||||
s = socket.socket(family=socket.AF_UNIX)
|
||||
@@ -298,7 +298,7 @@ def single_instance_unix(name):
|
||||
s = socket.socket(family=socket.AF_UNIX)
|
||||
try:
|
||||
s.bind(socket_path)
|
||||
except EnvironmentError as err:
|
||||
except OSError as err:
|
||||
if err.errno in (errno.EADDRINUSE, errno.EEXIST):
|
||||
os.unlink(socket_path)
|
||||
s.bind(socket_path)
|
||||
@@ -322,7 +322,7 @@ def single_instance(group_id=None):
|
||||
addr = '\0' + name
|
||||
try:
|
||||
s.bind(addr)
|
||||
except EnvironmentError as err:
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENOENT:
|
||||
return single_instance_unix(name)
|
||||
if err.errno == errno.EADDRINUSE:
|
||||
|
||||
@@ -18,11 +18,11 @@ from .fast_data_types import (
|
||||
BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM,
|
||||
CELL_SPECIAL_PROGRAM, CSI, DCS, DECORATION, DIM,
|
||||
GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
|
||||
OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, Screen,
|
||||
add_window, cell_size_for_window, compile_program, get_clipboard_string,
|
||||
init_cell_program, set_clipboard_string, set_titlebar_color,
|
||||
set_window_render_data, update_window_title, update_window_visibility,
|
||||
viewport_for_window
|
||||
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
|
||||
STRIKETHROUGH, Screen, add_window, cell_size_for_window, compile_program,
|
||||
get_clipboard_string, init_cell_program, set_clipboard_string,
|
||||
set_titlebar_color, set_window_render_data, update_window_title,
|
||||
update_window_visibility, viewport_for_window
|
||||
)
|
||||
from .keys import defines, extended_key_event, keyboard_mode_name
|
||||
from .rgb import to_color
|
||||
@@ -72,6 +72,8 @@ def load_shader_programs(semi_transparent=False):
|
||||
'STRIKE_SHIFT': STRIKETHROUGH,
|
||||
'DIM_SHIFT': DIM,
|
||||
'DECORATION_SHIFT': DECORATION,
|
||||
'MARK_SHIFT': MARK,
|
||||
'MARK_MASK': MARK_MASK,
|
||||
}.items():
|
||||
vv = vv.replace('{{{}}}'.format(gln), str(pyn), 1)
|
||||
if semi_transparent:
|
||||
@@ -133,7 +135,7 @@ class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args, override_title=None, copy_colors_from=None):
|
||||
self.action_on_close = self.action_on_removal = None
|
||||
self.layout_data = None
|
||||
self.current_marker_spec = None
|
||||
self.pty_resized_once = False
|
||||
self.needs_attention = False
|
||||
self.override_title = override_title
|
||||
@@ -535,6 +537,12 @@ class Window:
|
||||
cmd = [x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) for x in self.opts.scrollback_pager]
|
||||
get_boss().display_scrollback(self, data['text'], cmd)
|
||||
|
||||
def paste_bytes(self, text):
|
||||
# paste raw bytes without any processing
|
||||
if isinstance(text, str):
|
||||
text = text.encode('utf-8')
|
||||
self.screen.paste_bytes(text)
|
||||
|
||||
def paste(self, text):
|
||||
if text and not self.destroyed:
|
||||
if isinstance(text, str):
|
||||
@@ -597,4 +605,32 @@ class Window:
|
||||
def scroll_end(self):
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_FULL, False)
|
||||
|
||||
def toggle_marker(self, ftype, spec, flags):
|
||||
from .marks import marker_from_spec
|
||||
key = ftype, spec
|
||||
if key == self.current_marker_spec:
|
||||
self.remove_marker()
|
||||
return
|
||||
self.screen.set_marker(marker_from_spec(ftype, spec, flags))
|
||||
self.current_marker_spec = key
|
||||
|
||||
def set_marker(self, spec):
|
||||
from .config import toggle_marker, parse_marker_spec
|
||||
from .marks import marker_from_spec
|
||||
if isinstance(spec, str):
|
||||
func, (ftype, spec, flags) = toggle_marker('toggle_marker', spec)
|
||||
else:
|
||||
ftype, spec, flags = parse_marker_spec(spec[0], spec[1:])
|
||||
key = ftype, spec
|
||||
self.screen.set_marker(marker_from_spec(ftype, spec, flags))
|
||||
self.current_marker_spec = key
|
||||
|
||||
def remove_marker(self):
|
||||
if self.current_marker_spec is not None:
|
||||
self.screen.set_marker()
|
||||
self.current_marker_spec = None
|
||||
|
||||
def scroll_to_mark(self, prev=True, mark=0):
|
||||
self.screen.scroll_to_next_mark(mark, prev)
|
||||
# }}}
|
||||
|
||||
@@ -238,6 +238,9 @@ class TestDataTypes(BaseTest):
|
||||
lf.set_text(t, 0, len(t), C())
|
||||
return lf
|
||||
|
||||
l0 = create('file:///etc/test')
|
||||
self.ae(l0.url_start_at(0), 0)
|
||||
|
||||
for trail in '.,]>)\\':
|
||||
lx = create("http://xyz.com" + trail)
|
||||
self.ae(lx.url_end_at(0), len(lx) - 2)
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from . import BaseTest
|
||||
from kitty.config import defaults
|
||||
from kitty.layout import Stack, Horizontal, idx_for_id
|
||||
from kitty.fast_data_types import pt_to_px
|
||||
from kitty.layout import Horizontal, Stack, Tall, Grid, idx_for_id
|
||||
from kitty.constants import WindowGeometry
|
||||
|
||||
from . import BaseTest
|
||||
|
||||
|
||||
class Window:
|
||||
@@ -15,6 +17,7 @@ class Window:
|
||||
self.overlay_for = overlay_for
|
||||
self.overlay_window_id = overlay_window_id
|
||||
self.is_visible_in_layout = True
|
||||
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
|
||||
|
||||
def set_visible_in_layout(self, idx, val):
|
||||
self.is_visible_in_layout = bool(val)
|
||||
@@ -172,11 +175,11 @@ class TestLayout(BaseTest):
|
||||
check_visible()
|
||||
|
||||
def test_layout_operations(self):
|
||||
for layout_class in Stack, Horizontal:
|
||||
for layout_class in (Stack, Horizontal, Tall, Grid):
|
||||
q = create_layout(layout_class)
|
||||
self.do_ops_test(q)
|
||||
|
||||
def test_overlay_layout_operations(self):
|
||||
for layout_class in Stack, Horizontal:
|
||||
for layout_class in (Stack, Horizontal, Tall, Grid):
|
||||
q = create_layout(layout_class)
|
||||
self.do_overlay_test(q)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
from . import BaseTest
|
||||
from kitty.fast_data_types import DECAWM, IRM, Cursor, DECCOLM, DECOM
|
||||
from kitty.marks import marker_from_regex, marker_from_function
|
||||
|
||||
|
||||
class TestScreen(BaseTest):
|
||||
@@ -453,3 +454,37 @@ class TestScreen(BaseTest):
|
||||
|
||||
self.ae(as_text(), 'ababababab\nc\n\n')
|
||||
self.ae(as_text(True), 'ababababab\nc\n\n')
|
||||
|
||||
def test_user_marking(self):
|
||||
s = self.create_screen()
|
||||
s.draw('abaa')
|
||||
s.carriage_return(), s.linefeed()
|
||||
s.draw('xyxyx')
|
||||
s.set_marker(marker_from_regex('a', 3))
|
||||
self.ae(s.marked_cells(), [(0, 0, 3), (2, 0, 3), (3, 0, 3)])
|
||||
s.set_marker()
|
||||
self.ae(s.marked_cells(), [])
|
||||
|
||||
def mark_x(text):
|
||||
col = 0
|
||||
for i, c in enumerate(text):
|
||||
if c == 'x':
|
||||
col += 1
|
||||
yield i, i, col
|
||||
|
||||
s.set_marker(marker_from_function(mark_x))
|
||||
self.ae(s.marked_cells(), [(0, 1, 1), (2, 1, 2), (4, 1, 3)])
|
||||
s = self.create_screen(lines=5, scrollback=10)
|
||||
for i in range(15):
|
||||
s.draw(str(i))
|
||||
if i != 14:
|
||||
s.carriage_return(), s.linefeed()
|
||||
s.set_marker(marker_from_regex(r'\d+', 3))
|
||||
for i in range(10):
|
||||
self.assertTrue(s.scroll_to_next_mark())
|
||||
self.ae(s.scrolled_by, i + 1)
|
||||
self.ae(s.scrolled_by, 10)
|
||||
for i in range(10):
|
||||
self.assertTrue(s.scroll_to_next_mark(0, False))
|
||||
self.ae(s.scrolled_by, 10 - i - 1)
|
||||
self.ae(s.scrolled_by, 0)
|
||||
|
||||
@@ -17,7 +17,7 @@ def abspath(x):
|
||||
def run(*args):
|
||||
try:
|
||||
subprocess.check_call(args)
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
raise SystemExit('You are missing the {} program needed to generate the kitty logo'.format(args[0]))
|
||||
|
||||
|
||||
|
||||
6
setup.py
6
setup.py
@@ -333,14 +333,12 @@ SPECIAL_SOURCES = {
|
||||
def newer(dest, *sources):
|
||||
try:
|
||||
dtime = os.path.getmtime(dest)
|
||||
except EnvironmentError:
|
||||
except OSError:
|
||||
return True
|
||||
for s in sources:
|
||||
try:
|
||||
with suppress(FileNotFoundError):
|
||||
if os.path.getmtime(s) >= dtime:
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ if False:
|
||||
os.makedirs(dest)
|
||||
os.chdir(dest)
|
||||
dest = os.path.expanduser('~/.local/bin/kitty')
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
os.remove(dest)
|
||||
with suppress(EnvironmentError):
|
||||
with suppress(OSError):
|
||||
os.makedirs(os.path.dirname(dest))
|
||||
subprocess.check_call(['tar', 'xJf', tarball])
|
||||
os.symlink(os.path.abspath('bin/kitty'), dest)
|
||||
|
||||
Reference in New Issue
Block a user