Compare commits

...

74 Commits

Author SHA1 Message Date
Kovid Goyal
3a4420f2d3 version 0.12.2 2018-09-24 05:26:11 +05:30
Kovid Goyal
1eb8d6e845 Merge branch 'doc-fix' of https://github.com/nicoulaj/kitty 2018-09-23 08:49:15 +05:30
Julien Nicoulaud
e2f97dc44a Fix broken links 2018-09-22 20:22:15 +02:00
Kovid Goyal
ea9af96d2b Fix using focus_follows_mouse causing text selection with the mouse to malfunction when using multiple kitty windows
Fixes #1002
2018-09-22 18:15:03 +05:30
Kovid Goyal
e4033d997c Merge branch 'prev_' of https://github.com/Luflosi/kitty 2018-09-22 17:28:53 +05:30
Luflosi
bc38357193 Add prev_tab and prev_window as aliases for previous_tab and previous_window 2018-09-22 13:24:51 +02:00
Kovid Goyal
62cb035afe Remote control: Fix the `focus_window` command not focusing the top-level OS window of the specified kitty window
Fixes #1003
2018-09-22 12:17:35 +05:30
Kovid Goyal
7a8bc6b5d2 Fix #1001 2018-09-21 12:26:44 +05:30
Kovid Goyal
0d86ac23bb Merge branch 'ime' of https://github.com/martinetd/kitty 2018-09-21 09:28:56 +05:30
Dominique Martinet
897fd9486e glfw/ibus input: use text from ime event instead of global
When a key is pressed, text is generated from xkb and attached to the
global key_event variable.
If another key is pressed before ibus replies, this text in global
variable would be overwritten before the processed reply comes in and
the wrong character would be input, as shown in logs with
--debug-keyboard:

Press scancode: 0x19 clean_sym: w composed_sym: w text: w mods: numlock glfw_key: 87 (W) xkb_key: 119 (w)
↳ to IBUS: keycode: 0x11 keysym: 0x77 (w) mods: numlock
Press scancode: 0x20 clean_sym: o composed_sym: o text: o mods: numlock glfw_key: 79 (O) xkb_key: 111 (o)
↳ to IBUS: keycode: 0x18 keysym: 0x6f (o) mods: numlockIBUS processed scancode: 0x19 release: 0 handled: 0
From IBUS: scancode: 0x19 name: w is_release: 0
↳ to application: glfw_keycode: 0x57 (W) keysym: 0x77 (w) action: PRESS mods: numlock  text: o
on_key_input: glfw key: 87 native_code: 0x77 action: PRESS mods: 0x0 text: 'o' state: 0 sent text to child
IBUS processed scancode: 0x20 release: 0 handled: 0
From IBUS: scancode: 0x20 name: o is_release: 0
↳ to application: glfw_keycode: 0x4f (O) keysym: 0x6f (o) action: PRESS mods: numlock  text: o
on_key_input: glfw key: 79 native_code: 0x6f action: PRESS mods: 0x0 text: 'o' state: 0 sent text to child

Since the whole event is memcpy'd we can just use the text from the
event attached to the reply instead.
2018-09-21 07:34:01 +09:00
Kovid Goyal
39d3d093c4 ... 2018-09-20 09:10:57 +05:30
Kovid Goyal
3f0b52bef4 Workaround for broken editors like nano that cannot handle newlines in pasted text
Fixes #994
2018-09-20 09:08:29 +05:30
Kovid Goyal
4a04f27fde Fix #991 2018-09-19 19:58:23 +05:30
Kovid Goyal
5fb02c0439 When mapping the new_tab action allow specifying that the tab should open next to the current tab instead of at the end of the tabs list
Fixes #979
2018-09-19 19:43:41 +05:30
Kovid Goyal
edfb4ef3f1 Merge branch 'master' of https://github.com/esell/kitty 2018-09-19 18:13:20 +05:30
Kovid Goyal
f037923b00 Document the basic process for creating custom kittens. Fixes #975 2018-09-19 12:55:50 +05:30
Kovid Goyal
424aafb514 Merge branch 'fix_sgr_bold' of https://github.com/martinetd/kitty 2018-09-19 08:07:00 +05:30
Dominique Martinet
1b64167a3e cursor_as_sgr: fix bold/dim
The old code would print "\e[22m" if either bold or dim was unset, which
is the "normal, neither bold nor dim" control sequence.

Print that only if neither are set, and instead print "1" and "2" (bold,
dim) values individually.
2018-09-19 07:04:01 +09:00
esell
a9a86e4604 Add option to enable custom cursor on macos 2018-09-18 10:56:26 -06:00
Kovid Goyal
1dc7c23b2f Merge branch 'bash_completion' of https://github.com/martinetd/kitty 2018-09-18 06:19:24 +05:30
Dominique Martinet
5bdcfbc2ce bash completion: make complete helper start with _
It is standard to start complete helpers with underscore to avoid
clobbering the function namespace ; like other shells do.
It is ironic for a completion helper to make completion less agreable...
2018-09-18 09:10:47 +09:00
Kovid Goyal
74e9bf4f31 Another sentence worth of documentation 2018-09-17 06:42:20 +05:30
Kovid Goyal
0652529839 Remove dead code 2018-09-12 21:44:59 +05:30
Kovid Goyal
772d6597a9 macOS: Add an option :opt:macos_traditional_fullscreen to make full-screening of kitty windows much faster, but less pretty.
See #911
2018-09-12 21:27:49 +05:30
Kovid Goyal
4e99194022 Document platform fullscreen shortcut on macOS 2018-09-12 20:43:22 +05:30
Kovid Goyal
f0ff2f4ea7 Fix a bug causing incorrect line ordering when viewing the scrollback buffer if the scrollback buffer is full
Fixes #960
2018-09-12 20:31:09 +05:30
Kovid Goyal
d583c533c2 Linux: Fix window icon not set on X11 for the first OS window
Fix #961
2018-09-12 18:29:06 +05:30
Kovid Goyal
ec1f219850 macOS: Make full screening of kitty much faster by using the "traditional full screen" mode of cocoa, similar to iTerm and MacVim
Hopefully workaround a bug in glfw that causes crashes when unplugging
monitors with full screen windows.

Fixes #911
Fixes #898
2018-09-12 15:45:08 +05:30
Kovid Goyal
21d586cc86 macOS: Fix resizing semi-transparent windows causing the windows to be invisible during the resize
Fixes #941
Workaround for  https://github.com/glfw/glfw/issues/1251
2018-09-12 10:44:24 +05:30
Kovid Goyal
8f7139d48f Also respond to a terminfo query for "name" in addition to "TN" 2018-09-11 21:41:50 +05:30
Kovid Goyal
cfb6dae04d ... 2018-09-11 17:51:06 +05:30
Kovid Goyal
bceac3ee0b ... 2018-09-11 09:58:26 +05:30
Kovid Goyal
38d6edb0ca Merge branch 'macos-thicken-font' of https://github.com/tbodt/kitty 2018-09-11 09:57:28 +05:30
Kovid Goyal
1b41a3bcee ... 2018-09-11 08:26:31 +05:30
Kovid Goyal
c54f934f6e Merge branch 'patch-1' of https://github.com/tanrax/kitty 2018-09-11 08:25:22 +05:30
Theodore Dubois
94052bb712 Add option to increase font thickness on macOS 2018-09-10 19:30:29 -07:00
Andros Fenollosa
7eb1dcc7ee Fixbug Gnome cache 2018-09-10 18:44:34 +02:00
Andros Fenollosa
626fbe827e Add local icon 2018-09-10 18:41:07 +02:00
Kovid Goyal
28c154ecf0 typo 2018-09-10 21:58:15 +05:30
Kovid Goyal
8176f661ea macOS: Fix small leak in glyph rendering code 2018-09-10 21:41:26 +05:30
Kovid Goyal
0195e7f841 Wayland: Add support for animated cursors 2018-09-10 11:54:22 +05:30
Kovid Goyal
141b8a1481 Update glfw from upstream 2018-09-10 09:51:06 +05:30
Kovid Goyal
be3ab90cff Add a reference to piping to the index page 2018-09-10 08:19:14 +05:30
Kovid Goyal
9782f16635 Support os_window as a destination for the pipe function 2018-09-10 08:10:50 +05:30
Kovid Goyal
6930edfd8a ... 2018-09-10 08:08:16 +05:30
Kovid Goyal
6b1bec6550 Add a FAQ entry about global hotkey access to kitty 2018-09-10 07:57:33 +05:30
Andros Fenollosa
dac0304815 Update binary.rst 2018-09-09 20:08:12 +02:00
Andros Fenollosa
cfcf81b5fb Update binary.rst 2018-09-09 20:06:56 +02:00
Kovid Goyal
f072a02215 ... 2018-09-09 21:08:11 +05:30
Kovid Goyal
5f6e21da9b Fix kitty @set-colors not working for tab backgrounds when using the fade tabbar style
Fixes #937
2018-09-09 19:41:32 +05:30
Kovid Goyal
b9b38a4ec1 A new pipe function that can be used to pipe the contents of the screen and scrollback buffer to any desired program running in a new window, tab or overlay window. 2018-09-09 18:29:59 +05:30
Kovid Goyal
e5a7ba4f22 Allow viewing the current contents of the alternate screen in a new window.
Fixes #933
2018-09-09 15:32:05 +05:30
Kovid Goyal
1dcf3f2afb ... 2018-09-09 14:32:07 +05:30
Kovid Goyal
2ec84969dd Fix drag-scrolling not working when the mouse leaves the window confines
Fixes #917
2018-09-09 14:30:32 +05:30
Kovid Goyal
a08b945124 Forgot to revert test 2018-09-09 14:02:46 +05:30
Kovid Goyal
16f712fbaa Revert the wide-character backspace patch
Fixes #875
2018-09-09 13:51:15 +05:30
Kovid Goyal
1f63ff1d41 Have the kitty --title flag apply to all windows created using kitty --session
Fixes #921
2018-09-09 10:49:20 +05:30
Kovid Goyal
49d8f4689f ... 2018-09-09 10:24:01 +05:30
Kovid Goyal
e1f993938a Add a new kitty --start-as command line flag to start kitty fullscreen/maximized/minimized.
This obsoletes the --start-in-fullscreen flag introduced in the previous release
which has been removed since it has only existed for a few days.

Fixes #935
2018-09-09 10:23:48 +05:30
Kovid Goyal
ab65b9b2fb Do not convert bitmaps when loading a glyph to access its metrics 2018-09-09 09:46:19 +05:30
Kovid Goyal
f7a621f89f Use bitmap pitch to iterate over pixels 2018-09-09 09:42:51 +05:30
Kovid Goyal
b808a05e5e Clean up monochrome bitmap support code 2018-09-09 08:33:32 +05:30
Kovid Goyal
72c37c519d ... 2018-09-09 08:23:59 +05:30
Kovid Goyal
af4b072b29 Merge branch 'master' of https://github.com/isaz/kitty 2018-09-09 08:20:32 +05:30
ismed
739ed906f0 Use the old FT_Bitmap_New for backward compatibility 2018-09-08 21:07:38 +01:00
ismed
9fd462077a Add support for embedded bitmap glyphs 2018-09-08 20:22:48 +01:00
Kovid Goyal
cb3671343e Implement directional movement of windows 2018-09-08 23:51:09 +05:30
Kovid Goyal
b18b705632 Fix overlay window handling when witching to neighboring windows 2018-09-08 23:31:16 +05:30
Kovid Goyal
9963e78636 Linux: Ensure that the python embedded in the kitty binary build always uses UTF-8 mode
Fixes #924
2018-09-08 23:03:36 +05:30
Kovid Goyal
e39635ea16 Add a new `neighboring_window` function to switch to neighboring windows in the current layout, similar to window movement in vim
Fixes #916
2018-09-08 08:59:11 +05:30
Kovid Goyal
9329b2c202 Allow using "all" as "*" for enabled_layouts 2018-09-08 08:22:37 +05:30
Kovid Goyal
791f70e5a2 API to get neighbors from the current layout 2018-09-08 08:09:35 +05:30
Kovid Goyal
8272c4a429 Fix #929 2018-09-08 06:28:30 +05:30
Kovid Goyal
f0357b061e Add a new `last_used_layout` function that can be mapped to a shortcut to switch to the previously used window layout
Fixes #870
2018-09-08 05:56:02 +05:30
46 changed files with 909 additions and 184 deletions

View File

@@ -10,9 +10,9 @@ config to reproduce the issue with).
### Contributing code
Install [the dependencies](https://github.com/kovidgoyal/kitty#dependencies)
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build.html#dependencies)
using your favorite package manager. Build and run kitty [from
source](https://github.com/kovidgoyal/kitty#install-and-run-from-source).
source](https://sw.kovidgoyal.net/kitty/build.html#install-and-run-from-source).
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
beforehand to discuss it, so that you don't waste your time making a pull

View File

@@ -16,7 +16,8 @@ the following simple command:
The binaries will be installed in the standard location for your OS,
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
Linux. The installer only touches files in that directory.
Linux. The installer only touches files in that directory. To update kitty,
simply re-run the command.
Manually installing
@@ -29,6 +30,25 @@ the :file:`.dmg` and install as normal. If you are on Linux, download the tarbal
and extract it into a directory. The |kitty| executable will be in the
:file:`bin` sub-directory.
Desktop integration on Linux
--------------------------------
If you want the kitty icon to appear in the taskbar and an entry for it to be
present in the menus, you will need to install the :file:`kitty.desktop` file.
The details of the following procedure may need to be adjusted for your
particular desktop, but it should work for most major desktop environments.
.. code-block:: sh
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
# your PATH)
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
# Place the kitty.desktop file somewhere it can be found by the OS
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
# Update the path to the kitty icon in the kitty.desktop file
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
Customizing the installation
--------------------------------
@@ -61,9 +81,8 @@ Customizing the installation
Uninstalling
----------------
All the installer does is copy the kitty files into the install directory
(defaults to :file:`~/.local/kitty.app`). To uninstall, simply delete that
directory.
All the installer does is copy the kitty files into the install directory. To
uninstall, simply delete that directory.
Building from source

View File

@@ -3,10 +3,77 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
0.12.2 [2018-09-24]
------------------------------
- A new ``last_used_layout`` function that can be mapped to a shortcut to
switch to the previously used window layout (:iss:`870`)
- New ``neighboring_window`` and ``move_window`` functions to switch to
neighboring windows in the current layout, and move them around, similar to
window movement in vim (:iss:`916`)
- A new ``pipe`` function that can be used to pipe the contents of the screen
and scrollback buffer to any desired program running in a new window, tab or
overlay window. (:iss:`933`)
- Add a new :option:`kitty --start-as` command line flag to start kitty
full-screen/maximized/minimized. This replaces the ``--start-in-fullscreen``
flag introduced in the previous release (:iss:`935`)
- When mapping the ``new_tab`` action allow specifying that the tab should open
next to the current tab instead of at the end of the tabs list (:iss:`979`)
- macOS: Add a new :opt:`macos_thicken_font` to make text rendering
on macs thicker, which makes it similar to the result of
sub-pixel antialiasing (:pull:`950`)
- macOS: Add an option :opt:`macos_traditional_fullscreen` to make
full-screening of kitty windows much faster, but less pretty. (:iss:`911`)
- Fix a bug causing incorrect line ordering when viewing the scrollback buffer
if the scrollback buffer is full (:iss:`960`)
- Fix drag-scrolling not working when the mouse leaves the window confines
(:iss:`917`)
- Workaround for broken editors like nano that cannot handle newlines in pasted text
(:iss:`994`)
- Linux: Ensure that the python embedded in the kitty binary build uses
UTF-8 mode to process command-line arguments (:iss:`924`)
- Linux: Handle fonts that contain monochrome bitmaps (such as the Terminus TTF
font) (:pull:`934`)
- Have the :option:`kitty --title` flag apply to all windows created
using :option:`kitty --session` (:iss:`921`)
- Revert change for backspacing of wide characters in the previous release,
as it breaks backspacing in some wide character aware programs (:iss:`875`)
- Fix kitty @set-colors not working for tab backgrounds when using the `fade` tabbar style
(:iss:`937`)
- macOS: Fix resizing semi-transparent windows causing the windows to be
invisible during the resize (:iss:`941`)
- Linux: Fix window icon not set on X11 for the first OS window (:iss:`961`)
- macOS: Add an :opt:`macos_custom_beam_cursor` option to use a special
mouse cursor image that can be seen on both light and dark backgrounds
(:iss:`359`)
- Remote control: Fix the ``focus_window`` command not focusing the
top-level OS window of the specified kitty window (:iss:`1003`)
- Fix using :opt:`focus_follows_mouse` causing text selection with the
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
0.12.1 [2018-09-08]
------------------------------
- Add a new :option:`kitty --start-in-fullscreen` command line flag to start
- Add a new ``--start-in-fullscreen`` command line flag to start
kitty in full screen mode (:iss:`856`)
- macOS: Fix a character that cannot be rendered in any font causing

View File

@@ -113,3 +113,12 @@ your font is not listed in ``kitty list-fonts`` it means that it is not
monospace. On Linux you can list all monospace fonts with::
fc-list : family spacing | grep spacing=100
How can I assign a single global shortcut to bring up the kitty terminal?
-----------------------------------------------------------------------------
Bringing up applications on a single key press is the job of the window
manager/desktop environment. For ways to do it with kitty (or indeed any
terminal) in different environments,
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.

View File

@@ -151,6 +151,15 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
(clockwise from the top-left)
======================== =======================
Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring
windows and move windows around (similar to window movement in vim)::
map ctrl+left neighboring_window left
map shift+left move_window right
map ctrl+down neighboring_window down
map shift+down move_window up
...
Other keyboard shortcuts
----------------------------------
@@ -164,7 +173,8 @@ Paste from selection :sc:`paste_from_selection`
Increase font size :sc:`increase_font_size`
Decrease font size :sc:`decrease_font_size`
Restore font size :sc:`reset_font_size`
Toggle fullscreen :sc:`toggle_fullscreen`
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`^⌘+f` on macOS)
on
Input unicode character :sc:`input_unicode_character`
Click URL using the keyboard :sc:`open_url`
Reset the terminal :sc:`reset_terminal`
@@ -255,6 +265,8 @@ Some prominent kittens:
:doc:`Clipboard <kittens/clipboard>`
Copy/paste to the clipboard from shell scripts, even over SSH.
You can also :doc:`Learn to create your own kittens <kittens/custom>`.
Configuring kitty
-------------------
@@ -263,7 +275,6 @@ Configuring kitty
painting frames-per-second. For details and a sample :file:`kitty.conf`,
see the :doc:`configuration docs <conf>`.
.. _sessions:
Remote control
------------------
@@ -274,6 +285,8 @@ fonts, open new windows, tabs, set their titles, change window layout, get text
from one window and send text to another, etc, etc. The possibilities are
endless. See the :doc:`tutorial <remote-control>` to get started.
.. _sessions:
Startup Sessions
------------------
@@ -349,6 +362,14 @@ scrollback buffer in your favorite pager program (which is ``less`` by default).
Colors and text formatting are preserved. You can explore the scrollback buffer
comfortably within the pager.
Additionally, you can pipe the contents of the scrollback buffer to an
arbitrary, command running in a new window, tab or overlay, for example::
map f1 pipe @ansi window less +G -R
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
key. See :sc:`show_scrollback` for details.
Frequently Asked Questions
---------------------------------

80
docs/kittens/custom.rst Normal file
View File

@@ -0,0 +1,80 @@
Custom kittens
=================
You can easily create your own kittens to extend kitty. They are just
terminal programs written in Python. When launching a kitten, kitty will
open an overlay window over the current window and optionally pass the
contents of the current window/scrollback to the kitten over its :file:`STDIN`.
The kitten can then perform whatever actions it likes, just as a normal
terminal program. After execution of the kitten is complete, it has access
to the running kitty instance so it can perform arbitrary actions
such as closing windows, pasting text, etc.
Let's see a simple example of creating a kitten. It will ask the user for some
input and paste it into the terminal window.
Create a file in the kitty config folder, :file:`~/.config/kitty/mykitten.py`
(you might need to adjust the path to wherever the kitty config folder is on
your machine).
.. code-block:: python
def main(args):
# this is the main entry point of the kitten, it will be executed in
# the overlay window when the kitten is launched
answer = input('Enter some text: ')
# whatever this function returns will be available in the
# handle_result() function
return answer
def handle_result(args, answer, target_window_id, boss):
# get the kitty window into which to paste answer
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(answer)
Now in :file:`kitty.conf` add the lines::
map ctrl+k kitten mykitten.py
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
The best way to develop your own kittens is to modify one of the built in
kittens. Look in the kittens sub-directory of the kitty source code for those.
Passing arguments to kittens
------------------------------
You can pass arguments to kittens by defining them in the map directive in
:file:`kitty.conf`. For example::
map ctrl+k kitten mykitten.py arg1 arg2
These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
of the kitten is set to the working directory of whatever program is
running in the active kitty window.
Passing the contents of the screen to the kitten
---------------------------------------------------
If you would like your kitten to have access to the contents of the screen
and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()``
function, telling kitty what kind of input your kitten would like. For example:
.. code-block:: py
def handle_result(...):
pass
handle_result.type_of_input = 'text'
This will send the plain text of the active window to the kitten's
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
instead. If you want line wrap markers as well, use ``screen-ansi``
or just ``screen``. For the scrollback buffer as well, use
``history``, ``ansi-history`` or ``screen-history``.

View File

@@ -19,7 +19,9 @@ terminal, very useful for picking files from the output of a ``git`` or ``ls`` c
adding them to the command line for the next command.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below.
options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text
snippets. See :sc:`insert_selected_path` for examples.
Command Line Interface
-------------------------

View File

@@ -14,7 +14,7 @@ Let's walk through a few examples of controlling |kitty|.
Start by running |kitty| as::
kitty -o allow_remote_control=yes --window-layout tall
kitty -o allow_remote_control=yes -o enabled_layouts=tall
In order for control to work, :opt:`allow_remote_control` must be enabled in
:file:`kitty.conf`. Here we turn it on explicitly at the command line.

View File

@@ -39,6 +39,7 @@ typedef void* id;
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef struct VkMacOSSurfaceCreateInfoMVK
{
@@ -102,6 +103,8 @@ typedef struct _GLFWwindowNS
// The text input filter callback
GLFWcocoatextinputfilterfun textInputFilterCallback;
// The toggle fullscreen intercept callback
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
// Dead key state
UInt32 deadKeyState;
} _GLFWwindowNS;

View File

@@ -563,6 +563,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
[super dealloc];
}
- (_GLFWwindow*)glfwWindow {
return window;
}
- (BOOL)isOpaque
{
return [window->ns.object isOpaque];
@@ -1040,6 +1044,18 @@ is_ascii_control_char(char x) {
return YES;
}
- (void)toggleFullScreen:(nullable id)sender
{
GLFWContentView *view = [self contentView];
if (view)
{
_GLFWwindow *window = [view glfwWindow];
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
return;
}
[super toggleFullScreen:sender];
}
@end
@@ -2114,6 +2130,14 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
return previous;
}
GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback;
window->ns.toggleFullscreenCallback = callback;
return previous;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
handle_reopen_callback = callback;

View File

@@ -194,6 +194,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
void* glfwGetCocoaWindow(GLFWwindow* window)
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
void* glfwGetX11Display(void)
@@ -213,11 +214,13 @@ def generate_wrappers(glfw_header, glfw_native_header):
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
{}
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
{}
const char* load_glfw(const char* path);

View File

@@ -146,10 +146,10 @@ typedef enum
} MONITOR_DPI_TYPE;
#endif /*DPI_ENUMS_DECLARED*/
#ifndef _DPI_AWARENESS_CONTEXTS_
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) -4)
#endif /*_DPI_AWARENESS_CONTEXTS_*/
#endif /*DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2*/
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
#define IsWindowsXPOrGreater() \
@@ -447,4 +447,3 @@ void _glfwPollMonitorsWin32(void);
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);

4
glfw/wl_init.c vendored
View File

@@ -139,6 +139,7 @@ static void setCursor(const char* name)
"Wayland: Standard cursor not found");
return;
}
// TODO: handle animated cursors too.
image = cursor->images[0];
if (!image)
@@ -688,7 +689,8 @@ int _glfwPlatformInit(void)
}
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-keyrepeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, animateCursorImage, NULL);
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL);

5
glfw/wl_platform.h vendored
View File

@@ -225,6 +225,7 @@ typedef struct _GLFWlibraryWayland
id_type keyRepeatTimer;
_GLFWwindow* keyboardFocus;
} keyRepeatInfo;
id_type cursorAnimationTimer;
_GLFWXKBData xkb;
_GLFWDBUSData dbus;
@@ -274,12 +275,14 @@ typedef struct _GLFWmonitorWayland
//
typedef struct _GLFWcursorWayland
{
struct wl_cursor_image* image;
struct wl_cursor* cursor;
struct wl_buffer* buffer;
int width, height;
int xhot, yhot;
int currentImage;
} _GLFWcursorWayland;
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice();
void animateCursorImage(id_type timer_id, void *data);

109
glfw/wl_window.c vendored
View File

@@ -702,6 +702,68 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
return GLFW_TRUE;
}
static void
setCursorImage(_GLFWcursorWayland* cursorWayland)
{
struct wl_cursor_image* image;
struct wl_buffer* buffer;
struct wl_surface* surface = _glfw.wl.cursorSurface;
if (!cursorWayland->cursor) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
image = cursorWayland->cursor->images[cursorWayland->currentImage];
buffer = wl_cursor_image_get_buffer(image);
if (image->delay) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ((double)image->delay) / 1000.0);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
} else {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
}
if (!buffer)
return;
cursorWayland->width = image->width;
cursorWayland->height = image->height;
cursorWayland->xhot = image->hotspot_x;
cursorWayland->yhot = image->hotspot_y;
}
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursorWayland->xhot,
cursorWayland->yhot);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursorWayland->width, cursorWayland->height);
wl_surface_commit(surface);
}
static void
incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == mainWindow) {
_GLFWcursor* cursor = window->wl.currentCursor;
if (cursor && cursor->wl.cursor)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(&cursor->wl);
return;
}
}
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
}
void
animateCursorImage(id_type timer_id, void *data) {
incrementCursorImage(_glfw.wl.pointerFocus);
}
static void
handleEvents(double timeout)
{
@@ -1263,14 +1325,15 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
return GLFW_FALSE;
}
cursor->wl.image = standardCursor->images[0];
cursor->wl.cursor = standardCursor;
cursor->wl.currentImage = 0;
return GLFW_TRUE;
}
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
// If it's a standard cursor we don't need to do anything here
if (cursor->wl.image)
if (cursor->wl.cursor)
return;
if (cursor->wl.buffer)
@@ -1376,10 +1439,7 @@ static GLFWbool isPointerLocked(_GLFWwindow* window)
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
struct wl_buffer* buffer;
struct wl_cursor* defaultCursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
if (!_glfw.wl.pointer)
return;
@@ -1388,7 +1448,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
// If we're not in the correct window just save the cursor
// the next time the pointer enters the window the cursor will change
if (window != _glfw.wl.pointerFocus)
if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != mainWindow)
return;
// Unlock possible pointer lock if no longer disabled.
@@ -1398,7 +1458,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
if (window->cursorMode == GLFW_CURSOR_NORMAL)
{
if (cursor)
image = cursor->wl.image;
setCursorImage(&cursor->wl);
else
{
defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
@@ -1409,33 +1469,14 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
"Wayland: Standard cursor not found");
return;
}
image = defaultCursor->images[0];
}
if (image)
{
buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
image->hotspot_x,
image->hotspot_y);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
wl_surface_commit(surface);
}
else
{
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursor->wl.xhot,
cursor->wl.yhot);
wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursor->wl.width, cursor->wl.height);
wl_surface_commit(surface);
_GLFWcursorWayland cursorWayland = {
defaultCursor,
NULL,
0, 0,
0, 0,
0
};
setCursorImage(&cursorWayland);
}
}
else if (window->cursorMode == GLFW_CURSOR_DISABLED)

4
glfw/xkb_glfw.c vendored
View File

@@ -440,9 +440,9 @@ glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
format_mods(ev->glfw_modifiers), key_event.text
format_mods(ev->glfw_modifiers), ev->text
);
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, key_event.text, 0);
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0);
} else debug("↳ discarded\n");
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
}

View File

@@ -19,10 +19,11 @@ from .constants import (
)
from .fast_data_types import (
ChildMonitor, background_opacity_of, change_background_opacity,
create_os_window, current_os_window, destroy_global_data,
get_clipboard_string, glfw_post_empty_event, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
change_os_window_state, create_os_window, current_os_window,
destroy_global_data, get_clipboard_string, glfw_post_empty_event,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, set_clipboard_string, set_in_sequence_mode,
toggle_fullscreen
)
from .keys import get_shortcut, shortcut_matches
from .layout import set_draw_minimal_borders
@@ -47,6 +48,23 @@ def listen_on(spec):
return s.fileno()
def data_for_at(w, arg):
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
return w.as_text(as_ansi=True, add_history=True)
if arg == '@text':
return w.as_text(add_history=True)
if arg == '@screen':
return w.as_text()
if arg == '@ansi_screen':
return w.as_text(as_ansi=True)
if arg == '@alternate':
return w.as_text(alternate_screen=True)
if arg == '@ansi_alternate':
return w.as_text(as_ansi=True, alternate_screen=True)
class DumpCommands: # {{{
def __init__(self, args):
@@ -103,8 +121,11 @@ class Boss:
if new_os_window_trigger is not None:
self.keymap.pop(new_os_window_trigger, None)
self.add_os_window(startup_session, os_window_id=os_window_id)
if args.start_in_fullscreen:
self.toggle_fullscreen()
if args.start_as != 'normal':
if args.start_as == 'fullscreen':
self.toggle_fullscreen()
else:
change_os_window_state(args.start_as)
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
if os_window_id is None:
@@ -770,25 +791,54 @@ class Boss:
if tm is not None:
tm.next_tab(-1)
prev_tab = previous_tab
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
w = window or self.active_window
if stdin:
stdin = data_for_at(w, stdin)
if stdin is not None:
stdin = stdin.encode('utf-8')
cmdline = []
for arg in cmd:
if arg == '@selection':
arg = data_for_at(w, arg)
if not arg:
continue
cmdline.append(arg)
overlay_for = w.id if as_overlay and w.overlay_for is None else None
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for)
def pipe(self, source, dest, exe, *args):
cmd = [exe] + list(args)
window = self.active_window
cwd_from = window.child.pid if window else None
def create_window():
return self.special_window_for_cmd(
cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from)
if dest == 'overlay' or dest == 'window':
tab = self.active_tab
if tab is not None:
return tab.new_special_window(create_window())
elif dest == 'tab':
tm = self.active_tab_manager
if tm is not None:
tm.new_tab(special_window=create_window(), cwd_from=cwd_from)
elif dest == 'os_window':
self._new_os_window(create_window(), cwd_from=cwd_from)
else:
import subprocess
subprocess.Popen(cmd)
def args_to_special_window(self, args, cwd_from=None):
args = list(args)
stdin = None
w = self.active_window
def data_for_at(arg):
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
return w.as_text(as_ansi=True, add_history=True)
if arg == '@text':
return w.as_text(add_history=True)
if arg == '@screen':
return w.as_text()
if arg == '@ansi_screen':
return w.as_text(as_ansi=True)
if args[0].startswith('@') and args[0] != '@':
stdin = data_for_at(args[0]) or None
stdin = data_for_at(w, args[0]) or None
if stdin is not None:
stdin = stdin.encode('utf-8')
del args[0]
@@ -796,13 +846,13 @@ class Boss:
cmd = []
for arg in args:
if arg == '@selection':
arg = data_for_at(arg)
arg = data_for_at(w, arg)
if not arg:
continue
cmd.append(arg)
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
def _new_tab(self, args, cwd_from=None):
def _new_tab(self, args, cwd_from=None, as_neighbor=False):
special_window = None
if args:
if isinstance(args, SpecialWindowInstance):
@@ -811,15 +861,22 @@ class Boss:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager
if tm is not None:
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor)
def _create_tab(self, args, cwd_from=None):
as_neighbor = False
if args and args[0].startswith('!'):
as_neighbor = 'neighbor' in args[0][1:].split(',')
args = args[1:]
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
def new_tab(self, *args):
self._new_tab(args)
self._create_tab(args)
def new_tab_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_tab(args, cwd_from=cwd_from)
self._create_tab(args, cwd_from=cwd_from)
def _new_window(self, args, cwd_from=None):
tab = self.active_tab

View File

@@ -474,7 +474,9 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
--title -T
Set the window title. This will override any title set by the program running inside kitty. So
only use this if you are running a program that does not set titles.
only use this if you are running a program that does not set titles. If combined
with :option:`{appname} --session` the title will be used for all windows created by the
session, that do not set their own titles.
--config -c
@@ -546,9 +548,11 @@ specify this address. This option will be ignored, unless you set
option as it is read automatically from the environment.
--start-in-fullscreen
type=bool-set
Make the initial kitty window fullscreen on startup
--start-as
type=choices
default=normal
choices=normal,fullscreen,maximized,minimized
Control how the initial kitty window is created.
# Debugging options

View File

@@ -282,7 +282,7 @@ def set_tab_title(boss, window, payload):
# }}}
# set_layout {{{
# goto_layout {{{
@cmd(
'Set the window layout',
'Set the window layout in the specified tab (or the active tab if not specified).'
@@ -317,6 +317,34 @@ def goto_layout(boss, window, payload):
# }}}
# last_used_layout {{{
@cmd(
'Switch to the last used layout',
'Switch to the last used window layout in the specified tab (or the active tab if not specified).'
' You can use special match value :italic:`all` to set the layout in all tabs.',
options_spec=MATCH_TAB_OPTION,
)
def cmd_last_used_layout(global_opts, opts, args):
return {'match': opts.match}
def last_used_layout(boss, window, payload):
match = payload['match']
if match:
if match == 'all':
tabs = tuple(boss.all_tabs)
else:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.last_used_layout()
# }}}
# close_window {{{
@cmd(
'Close the specified window(s)',
@@ -545,7 +573,9 @@ def focus_window(boss, window, payload):
raise MatchError(match)
for window in windows:
if window:
boss.set_active_window(window)
os_window_id = boss.set_active_window(window)
if os_window_id:
focus_os_window(os_window_id, True)
break
# }}}
@@ -554,10 +584,20 @@ def focus_window(boss, window, payload):
@cmd(
'Focus the specified tab',
'The active window in the specified tab will be focused.',
options_spec=MATCH_TAB_OPTION,
options_spec=MATCH_TAB_OPTION + '''
--no-response
type=bool-set
default=false
Don't wait for a response indicating the success of the action. Note that
using this option means that you will not be notified of failures.
''',
argspec='',
no_response=True,
)
def cmd_focus_tab(global_opts, opts, args):
if opts.no_response:
global_opts.no_command_response = True
return {'match': opts.match}

View File

@@ -224,6 +224,29 @@ cocoa_focus_window(void *w) {
[window makeKeyWindow];
}
bool
cocoa_toggle_fullscreen(void *w, bool traditional) {
NSWindow *window = (NSWindow*)w;
bool made_fullscreen = true;
NSWindowStyleMask sm = [window styleMask];
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
if (traditional) {
if (!(in_fullscreen)) {
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
} else {
made_fullscreen = false;
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
}
[window setStyleMask: sm];
} else {
if (in_fullscreen) made_fullscreen = false;
[window toggleFullScreen: nil];
}
return made_fullscreen;
}
static PyObject*
cocoa_get_lang(PyObject UNUSED *self) {
NSString* locale = nil;

View File

@@ -44,7 +44,7 @@ _kitty() {
compdef _kitty kitty
''',
'bash': '''
kitty_completions() {
_kitty_completions() {
local src
local limit
# Send all words up to the word the cursor is currently on
@@ -55,7 +55,7 @@ kitty_completions() {
fi
}
complete -o nospace -F kitty_completions kitty
complete -o nospace -F _kitty_completions kitty
''',
'fish': '''
function __kitty_completions

View File

@@ -140,6 +140,39 @@ def clear_terminal(func, rest):
return func, args
@func_with_args('neighboring_window')
def neighboring_window(func, rest):
rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
if rest not in ('left', 'right', 'top', 'bottom'):
log_error('Invalid neighbor specification: {}'.format(rest))
rest = 'right'
return func, [rest]
@func_with_args('move_window')
def move_window(func, rest):
rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
try:
rest = int(rest)
except Exception:
if rest not in ('left', 'right', 'top', 'bottom'):
log_error('Invalid move_window specification: {}'.format(rest))
rest = 0
return func, [rest]
@func_with_args('pipe')
def pipe(func, rest):
import shlex
rest = shlex.split(rest)
if len(rest) < 3:
log_error('Too few arguments to pipe function')
rest = ['none', 'none', 'true']
return func, rest
def parse_key_action(action):
parts = action.split(' ', 1)
func = parts[0]

View File

@@ -146,7 +146,12 @@ You can also create shortcuts to go to specific tabs, with 1 being the first tab
map ctrl+alt+2 goto_tab 2
Just as with :code:`new_window` above, you can also pass the name of arbitrary
commands to run when using new_tab and use :code:`new_tab_with_cwd`.
commands to run when using new_tab and use :code:`new_tab_with_cwd`. Finally,
if you want the new tab to open next to the current tab rather than at the
end of the tabs list, use::
map ctrl+t new_tab !neighbor [optional cmd to run]
''')],
'shortcuts.layout': [
_('Layout management'), '',
@@ -155,6 +160,11 @@ You can also create shortcuts to switch to specific layouts::
map ctrl+alt+t goto_layout tall
map ctrl+alt+s goto_layout stack
Similarly, to switch back to the previous layout::
map ctrl+alt+p last_used_layout
''')],
'shortcuts.fonts': [
_('Font sizes'), _('''\
@@ -457,7 +467,7 @@ def to_layout_names(raw):
parts = [x.strip().lower() for x in raw.split(',')]
ans = []
for p in parts:
if p == '*':
if p in ('*', 'all'):
ans.extend(sorted(all_layouts))
continue
name = p.partition(':')[0]
@@ -469,7 +479,7 @@ 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:`*` means all layouts. The first listed layout will be used as the
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`.
'''))
@@ -774,6 +784,23 @@ o('macos_window_resizable', True, long_text=_('''
Disable this if you want kitty top-level (OS) windows to not be resizable
on macOS.
'''))
o('macos_thicken_font', 0, option_type=positive_float, long_text=_('''
Draw an extra border around the font with the given width, to increase
legibility at small font sizes. For example, a value of 0.75 will
result in rendering that looks similar to sub-pixel antialiasing at
common font sizes.
'''))
o('macos_traditional_fullscreen', False, long_text=_('''
Use the traditional full-screen transition, that is faster, but less pretty.
'''))
# Disabled by default because of https://github.com/kovidgoyal/kitty/issues/794
o('macos_custom_beam_cursor', False, long_text=_('''
Enable/disable custom mouse cursor for macOS that is easier to see on both
light and dark backgrounds. WARNING: this might make your mouse cursor
invisible on dual GPU machines.'''))
# }}}
g('shortcuts') # {{{
@@ -820,13 +847,25 @@ k('scroll_page_down', 'kitty_mod+page_down', 'scroll_page_down', _('Scroll page
k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
You can send the contents of the current screen + history buffer as stdin to an arbitrary program using
the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
For only the current screen, use @screen or @ansi_screen.
For example, the following command opens the scrollback buffer in less in a new window::
map kitty_mod+y new_window @ansi less +G -R
'''))
You can pipe the contents of the current screen + history buffer as
:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example,
the following opens the scrollback buffer in less in an overlay window::
map f1 pipe @ansi overlay less +G -R
Placeholders available are: @text (which is plain text) and @ansi (which
includes text styling escape codes). For only the current screen, use @screen
or @ansi_screen. For the secondary screen, use @alternate and @ansi_alternate.
The secondary screen is the screen not currently displayed. For
example if you run a fullscreen terminal application, the secondary screen will
be the screen you return to when quitting the application. You can also use
``none`` for no :file:`STDIN` input.
To open in a new window, tab or new OS window, use ``window``, ``tab``, or
``os_window`` respectively. You can also use ``none`` in which case the data
will be piped into the program without creating any windows, useful if the
program is a GUI program that creates its own windows. '''))
# }}}

View File

@@ -9,7 +9,7 @@ from collections import namedtuple
appname = 'kitty'
version = (0, 12, 1)
version = (0, 12, 2)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat

View File

@@ -372,10 +372,14 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
CGContextSetShouldAntialias(render_ctx, true);
CGContextSetShouldSmoothFonts(render_ctx, true);
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
CGContextSetGrayStrokeColor(render_ctx, 1, 1);
CGContextSetLineWidth(render_ctx, global_state.opts.macos_thicken_font);
CGContextSetTextDrawingMode(render_ctx, kCGTextFillStroke);
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
CGContextSetTextPosition(render_ctx, 0, height - baseline);
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
CGContextRelease(render_ctx);
CGColorSpaceRelease(gray_color_space);
}
static inline bool

View File

@@ -245,8 +245,8 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
char *p = buf;
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
if (intensity_differs) {
if (!self->bold || !self->dim) { P("%d", 22); }
else P("%d;%d", 1, 2);
if (!self->bold && !self->dim) { P("%d", 22); }
else { if (self->bold) P("%d", 1); if (self->dim) P("%d", 2); }
}
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);

View File

@@ -281,7 +281,7 @@ void colorprofile_pop_dynamic_colors(ColorProfile*);
void set_mouse_cursor(MouseShape);
void enter_event();
void mouse_event(int, int);
void mouse_event(int, int, int);
void focus_in_event();
void wakeup_io_loop(bool);
void scroll_event(double, double, int);

View File

@@ -17,6 +17,7 @@
#endif
#include FT_FREETYPE_H
#include FT_BITMAP_H
typedef struct {
PyObject_HEAD
@@ -231,6 +232,27 @@ load_glyph(Face *self, int glyph_index, int load_type) {
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
int error = FT_Load_Glyph(self->face, glyph_index, flags);
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
// Embedded bitmap glyph?
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && load_type != FT_LOAD_DEFAULT) {
FT_Bitmap bitmap;
FT_Bitmap_New(&bitmap);
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
// Normalize gray levels to the range [0..255]
bitmap.num_grays = 256;
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
for (unsigned int i = 0; i < bitmap.rows; ++i) {
// We only have 2 levels
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
}
error = FT_Bitmap_Copy(library, &bitmap, &self->face->glyph->bitmap);
if (error) { set_freetype_error("Failed to copy bitmap, with error:", error); return false; }
FT_Bitmap_Done(library, &bitmap);
}
return true;
}

2
kitty/glfw-wrapper.c generated
View File

@@ -362,6 +362,8 @@ load_glfw(const char* path) {
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");

10
kitty/glfw-wrapper.h generated
View File

@@ -1,8 +1,6 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
@@ -1386,6 +1384,10 @@ typedef struct GLFWgamepadstate
*/
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef int (*glfwInit_func)();
glfwInit_func glfwInit_impl;
#define glfwInit glfwInit_impl
@@ -1854,6 +1856,10 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
typedef GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun);
glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl;
#define glfwSetCocoaToggleFullscreenIntercept glfwSetCocoaToggleFullscreenIntercept_impl
typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReopen_func)(GLFWapplicationshouldhandlereopenfun);
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl

View File

@@ -10,6 +10,7 @@
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w, bool);
extern void cocoa_focus_window(void *w);
extern bool cocoa_toggle_fullscreen(void *w, bool);
extern void cocoa_create_global_menu(void);
extern void cocoa_set_hide_from_tasks(void);
extern void cocoa_set_titlebar_color(void *w, color_type color);
@@ -106,6 +107,17 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) {
window->has_pending_resizes = true; global_state.has_pending_resizes = true;
window->last_resize_event_at = monotonic();
unjam_event_loop();
#ifdef __APPLE__
// Cocoa starts a sub-loop inside wait events which means main_loop
// stays stuck and no rendering happens. This causes the window to be
// blank. This is particularly bad for semi-transparent windows since
// they are rendered as invisible, so for that case we manually render.
if (global_state.callback_os_window->is_semi_transparent) {
make_os_window_context_current(global_state.callback_os_window);
blank_os_window(global_state.callback_os_window);
swap_window_buffers(global_state.callback_os_window);
}
#endif
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
global_state.callback_os_window = NULL;
}
@@ -157,9 +169,9 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
show_mouse_cursor(w);
double now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (button >= 0 && (unsigned int)button < sizeof(global_state.callback_os_window->mouse_button_pressed)/sizeof(global_state.callback_os_window->mouse_button_pressed[0])) {
if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) {
global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false;
if (is_window_ready_for_callbacks()) mouse_event(button, mods);
if (is_window_ready_for_callbacks()) mouse_event(button, mods, action);
}
global_state.callback_os_window = NULL;
}
@@ -173,7 +185,7 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
global_state.callback_os_window->cursor_blink_zero_time = now;
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio;
if (is_window_ready_for_callbacks()) mouse_event(-1, 0);
if (is_window_ready_for_callbacks()) mouse_event(-1, 0, -1);
global_state.callback_os_window = NULL;
}
@@ -327,7 +339,7 @@ static inline void
get_window_dpi(GLFWwindow *w, double *x, double *y) {
GLFWmonitor *monitor = NULL;
if (w) monitor = current_monitor(w);
if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
if (monitor == NULL) { PyErr_Print(); monitor = glfwGetPrimaryMonitor(); }
float xscale = 1, yscale = 1;
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
#ifdef __APPLE__
@@ -344,11 +356,56 @@ set_os_window_dpi(OSWindow *w) {
get_window_dpi(w->handle, &w->logical_dpi_x, &w->logical_dpi_y);
}
static bool
toggle_fullscreen_for_os_window(OSWindow *w) {
int width, height, x, y;
glfwGetWindowSize(w->handle, &width, &height);
glfwGetWindowPos(w->handle, &x, &y);
#ifdef __APPLE__
if (OPT(macos_traditional_fullscreen)) {
if (cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), true)) {
w->before_fullscreen.is_set = true;
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
return true;
}
if (w->before_fullscreen.is_set) {
glfwSetWindowSize(w->handle, w->before_fullscreen.w, w->before_fullscreen.h);
glfwSetWindowPos(w->handle, w->before_fullscreen.x, w->before_fullscreen.y);
}
return false;
} else {
return cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), false);
}
#else
GLFWmonitor *monitor;
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
// make fullscreen
monitor = current_monitor(w->handle);
if (monitor == NULL) { PyErr_Print(); return false; }
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
w->before_fullscreen.is_set = true;
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
return true;
} else {
// make windowed
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
return false;
}
#endif
}
#ifdef __APPLE__
static int
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
}
static GLFWwindow *application_quit_canary = NULL;
static int
@@ -359,6 +416,14 @@ on_application_reopen(int has_visible_windows) {
unjam_event_loop();
return false;
}
static int
intercept_cocoa_fullscreen(GLFWwindow *w) {
if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return 0;
toggle_fullscreen_for_os_window(global_state.callback_os_window);
global_state.callback_os_window = NULL;
return 1;
}
#endif
void
@@ -493,6 +558,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
#ifdef __APPLE__
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
#endif
send_prerendered_sprites_for_window(w);
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
@@ -691,29 +757,22 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
static PyObject*
toggle_fullscreen(PYNOARG) {
GLFWmonitor *monitor;
OSWindow *w = current_os_window();
if (!w) Py_RETURN_NONE;
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
// make fullscreen
monitor = current_monitor(w->handle);
if (monitor == NULL) return NULL;
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
w->before_fullscreen.is_set = true;
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
Py_RETURN_TRUE;
} else {
// make windowed
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
#ifdef __APPLE__
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(w->handle), OPT(macos_window_resizable));
#endif
Py_RETURN_FALSE;
}
if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
static PyObject*
change_os_window_state(PyObject *self UNUSED, PyObject *args) {
char *state;
if (!PyArg_ParseTuple(args, "s", &state)) return NULL;
OSWindow *w = current_os_window();
if (!w || !w->handle) Py_RETURN_NONE;
if (strcmp(state, "maximized") == 0) glfwMaximizeWindow(w->handle);
else if (strcmp(state, "minimized") == 0) glfwIconifyWindow(w->handle);
else { PyErr_SetString(PyExc_ValueError, "Unknown window state"); return NULL; }
Py_RETURN_NONE;
}
void
@@ -924,6 +983,7 @@ static PyMethodDef module_methods[] = {
METHODB(get_content_scale_for_window, METH_NOARGS),
METHODB(set_clipboard_string, METH_VARARGS),
METHODB(toggle_fullscreen, METH_NOARGS),
METHODB(change_os_window_state, METH_VARARGS),
METHODB(glfw_window_hint, METH_VARARGS),
METHODB(os_window_should_close, METH_VARARGS),
METHODB(os_window_swap_buffers, METH_VARARGS),

View File

@@ -207,7 +207,7 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
}
static inline Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, y, l); return l; }
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
static PyObject*
as_text(HistoryBuf *self, PyObject *args) {

View File

@@ -228,15 +228,34 @@ class Layout: # {{{
active_window_idx = idx_for_id(windows[active_window_idx].id, all_windows)
return self.set_active_window(all_windows, active_window_idx)
def move_window(self, all_windows, active_window_idx, delta=1):
def neighbors(self, all_windows, active_window_idx):
w = all_windows[active_window_idx]
windows = process_overlaid_windows(all_windows)[1]
if len(windows) < 2 or abs(delta) == 0:
ans = self.neighbors_for_window(w, windows)
for values in ans.values():
values[:] = [idx_for_id(w.id, all_windows) for w in values]
return ans
def move_window(self, all_windows, active_window_idx, delta=1):
# delta can be either a number or a string such as 'left', 'top', etc
# for neighborhood moves
w = all_windows[active_window_idx]
windows = process_overlaid_windows(all_windows)[1]
if len(windows) < 2 or not delta:
return active_window_idx
idx = idx_for_id(w.id, windows)
if idx is None:
idx = idx_for_id(w.overlay_window_id, windows)
nidx = (idx + len(windows) + delta) % len(windows)
if isinstance(delta, int):
nidx = (idx + len(windows) + delta) % len(windows)
else:
delta = delta.lower()
delta = {'up': 'top', 'down': 'bottom'}.get(delta, delta)
neighbors = self.neighbors_for_window(w, windows)
if not neighbors.get(delta):
return active_window_idx
nidx = idx_for_id(neighbors[delta][0].id, windows)
nw = windows[nidx]
nidx = idx_for_id(nw.id, all_windows)
idx = active_window_idx
@@ -368,6 +387,9 @@ class Layout: # {{{
def do_layout(self, windows, active_window_idx):
raise NotImplementedError()
def neighbors_for_window(self, window, windows):
return {'left': [], 'right': [], 'top': [], 'bottom': []}
def resolve_borders(self, windows, active_window):
if draw_minimal_borders:
needs_borders_map = {w.id: (w is active_window or w.needs_attention) for w in windows}
@@ -469,6 +491,13 @@ class Tall(Layout): # {{{
# 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]]}
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
for i, w in enumerate(windows):
@@ -520,6 +549,14 @@ class Fat(Tall): # {{{
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]]}
# }}}
@@ -680,6 +717,56 @@ class Grid(Layout): # {{{
(right_neighbor_id is not None and not needs_borders_map[right_neighbor_id]) or next_col_has_different_count,
bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id]
)
def neighbors_for_window(self, window, windows):
n = len(windows)
if n < 4:
return Tall.neighbors_for_window(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)
blank_row = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = iter(windows)
pos_map = {}
col_counts = []
id_map = {}
for col in range(ncols):
rows = special_rows if col == special_col else nrows
for row in range(rows):
w = next(wi)
matrix[row][col] = wid = w.id
pos_map[wid] = row, col
id_map[wid] = w
col_counts.append(rows)
row, col = pos_map[window.id]
def neighbors(row, col):
try:
ans = matrix[row][col]
except IndexError:
ans = None
return [] if ans is None else [id_map[ans]]
def side(row, col, delta):
neighbor_col = col + delta
if col_counts[neighbor_col] == col_counts[col]:
return neighbors(row, neighbor_col)
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
return {
'top': neighbors(row-1, col) if row else [],
'bottom': neighbors(row + 1, col),
'left': side(row, col, -1) if col else [],
'right': side(row, col, 1) if col < ncols - 1 else [],
}
# }}}
@@ -740,6 +827,15 @@ class Vertical(Layout): # {{{
yield no_borders
else:
yield self.only_between_border
def neighbors_for_window(self, window, windows):
idx = windows.index(window)
before = [] if window is windows[0] else [windows[idx-1]]
after = [] if window is windows[-1] else [windows[idx+1]]
if self.main_is_horizontal:
return {'left': before, 'right': after, 'top': [], 'bottom': []}
return {'top': before, 'bottom': after, 'left': [], 'right': []}
# }}}
@@ -768,6 +864,7 @@ class Horizontal(Vertical): # {{{
self.simple_blank_rects(windows[0], windows[-1])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
# }}}

View File

@@ -120,11 +120,12 @@ def get_new_os_window_trigger(opts):
def _run_app(opts, args):
new_os_window_trigger = get_new_os_window_trigger(opts)
if False and is_macos:
# This is disabled because using custom cursors fails
# on dual GPU machines: https://github.com/kovidgoyal/kitty/issues/794
if is_macos and opts.macos_custom_beam_cursor:
set_custom_ibeam_cursor()
load_all_shaders.cursor_text_color = opts.cursor_text_color
if not is_wayland and not is_macos: # no window icons on wayland
with open(logo_data_file, 'rb') as f:
set_default_window_icon(f.read(), 256, 256)
with cached_values_for(run_app.cached_values_name) as cached_values:
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
window_id = create_os_window(
@@ -132,9 +133,6 @@ def _run_app(opts, args):
pre_show_callback,
appname, args.name or args.cls or appname,
args.cls or appname, load_all_shaders)
if not is_wayland and not is_macos: # no window icons on wayland
with open(logo_data_file, 'rb') as f:
set_default_window_icon(f.read(), 256, 256)
boss = Boss(window_id, opts, args, cached_values, new_os_window_trigger)
boss.start()
try:

View File

@@ -179,10 +179,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
bool
drag_scroll(Window *w, OSWindow *frame) {
unsigned int margin = frame->fonts_data->cell_height / 2;
double left = window_left(w, frame), top = window_top(w, frame), right = window_right(w, frame), bottom = window_bottom(w, frame);
double x = frame->mouse_x, y = frame->mouse_y;
if (y < top || y > bottom) return false;
if (x < left || x > right) return false;
double y = frame->mouse_y;
bool upwards = y <= (w->geometry.top + margin);
if (upwards || y >= w->geometry.bottom - margin) {
Screen *screen = w->render_data.screen;
@@ -488,23 +485,32 @@ enter_event() {
}
void
mouse_event(int button, int modifiers) {
mouse_event(int button, int modifiers, int action) {
MouseShape old_cursor = mouse_cursor_shape;
bool in_tab_bar;
unsigned int window_idx = 0;
Window *w = NULL;
if (button == -1 && global_state.active_drag_in_window) { // drag move
w = window_for_id(global_state.active_drag_in_window);
if (w) {
button = currently_pressed_button();
if (button == GLFW_MOUSE_BUTTON_LEFT) {
clamp_to_window = true;
handle_move_event(w, button, modifiers, window_idx);
clamp_to_window = false;
return;
if (global_state.active_drag_in_window) {
if (button == -1) { // drag move
w = window_for_id(global_state.active_drag_in_window);
if (w) {
button = currently_pressed_button();
if (button == GLFW_MOUSE_BUTTON_LEFT) {
clamp_to_window = true;
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (window_idx = 0; window_idx < t->num_windows && t->windows[window_idx].id != w->id; window_idx++);
handle_move_event(w, button, modifiers, window_idx);
clamp_to_window = false;
return;
}
}
}
else if (action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_LEFT) {
w = window_for_id(global_state.active_drag_in_window);
if (w) {
update_drag(true, w, true, modifiers);
}
}
}
w = window_for_event(&window_idx, &in_tab_bar);
if (in_tab_bar) {

View File

@@ -708,15 +708,7 @@ screen_is_cursor_visible(Screen *self) {
void
screen_backspace(Screen *self) {
unsigned int amount = 1;
if (self->cursor->x < self->columns && self->cursor->x > 1) {
// check if previous character is a wide character
linebuf_init_line(self->linebuf, self->cursor->y);
unsigned int xpos = self->cursor->x - 2;
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
if ((gpu_cell->attrs & WIDTH_MASK) == 2) amount = 2;
}
screen_cursor_back(self, amount, -1);
screen_cursor_back(self, 1, -1);
}
void
@@ -1692,6 +1684,21 @@ as_text_non_visual(Screen *self, PyObject *args) {
as_text_generic(args, self, range_line_, self->lines, self->columns);
}
static inline PyObject*
as_text_generic_wrapper(Screen *self, PyObject *args, Line*(get_line)(Screen *, int)) {
as_text_generic(args, self, get_line, self->lines, self->columns);
}
static PyObject*
as_text_alternate(Screen *self, PyObject *args) {
LineBuf *original = self->linebuf;
self->linebuf = original == self->main_linebuf ? self->alt_linebuf : self->main_linebuf;
PyObject *ans = as_text_generic_wrapper(self, args, range_line_);
self->linebuf = original;
return ans;
}
static PyObject*
screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
if (PyUnicode_READY(str) != 0) return NULL;
@@ -2177,6 +2184,7 @@ static PyMethodDef methods[] = {
MND(set_pending_timeout, METH_O)
MND(as_text, METH_VARARGS)
MND(as_text_non_visual, METH_VARARGS)
MND(as_text_alternate, METH_VARARGS)
MND(tab, METH_NOARGS)
MND(backspace, METH_NOARGS)
MND(linefeed, METH_NOARGS)

View File

@@ -24,9 +24,10 @@ class Tab:
class Session:
def __init__(self):
def __init__(self, default_title=None):
self.tabs = []
self.active_tab_idx = 0
self.default_title = default_title
def add_tab(self, opts, name=''):
if self.tabs and not self.tabs[-1].windows:
@@ -48,7 +49,7 @@ class Session:
cmd = None
from .tabs import SpecialWindow
t = self.tabs[-1]
t.windows.append(SpecialWindow(cmd, cwd=t.cwd, override_title=t.next_title))
t.windows.append(SpecialWindow(cmd, cwd=t.cwd, override_title=t.next_title or self.default_title))
t.next_title = None
def add_special_window(self, sw):
@@ -76,8 +77,8 @@ def resolved_shell(opts):
return ans
def parse_session(raw, opts):
ans = Session()
def parse_session(raw, opts, default_title=None):
ans = Session(default_title)
ans.add_tab(opts)
for line in raw.splitlines():
line = line.strip()
@@ -109,7 +110,7 @@ def parse_session(raw, opts):
def create_session(opts, args=None, special_window=None, cwd_from=None, respect_cwd=False, default_session=None):
if args and args.session:
with open(args.session) as f:
return parse_session(f.read(), opts)
return parse_session(f.read(), opts, getattr(args, 'title', None))
if default_session and default_session != 'none':
try:
with open(default_session) as f:
@@ -117,7 +118,7 @@ def create_session(opts, args=None, special_window=None, cwd_from=None, respect_
except EnvironmentError:
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
else:
return parse_session(session_data, opts)
return parse_session(session_data, opts, getattr(args, 'title', None))
ans = Session()
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
ans.add_tab(opts)

View File

@@ -426,15 +426,20 @@ set_cell_uniforms(float current_inactive_text_alpha) {
}
}
void
blank_os_window(OSWindow *os_window) {
#define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f)
glClearColor(C(16), C(8), C(0), os_window->is_semi_transparent ? os_window->background_opacity : 1.0f);
#undef C
glClear(GL_COLOR_BUFFER_BIT);
}
bool
send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) {
bool changed = false;
if (os_window->clear_count < 2) {
os_window->clear_count++;
#define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f)
glClearColor(C(16), C(8), C(0), os_window->is_semi_transparent ? os_window->background_opacity : 1.0f);
#undef C
glClear(GL_COLOR_BUFFER_BIT);
blank_os_window(os_window);
changed = true;
}
if (os_window->fonts_data) {

View File

@@ -386,11 +386,13 @@ PYWRAP1(set_options) {
S(close_on_child_death, PyObject_IsTrue);
S(window_alert_on_bell, PyObject_IsTrue);
S(macos_option_as_alt, PyObject_IsTrue);
S(macos_traditional_fullscreen, PyObject_IsTrue);
S(macos_hide_titlebar, PyObject_IsTrue);
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
S(macos_window_resizable, PyObject_IsTrue);
S(x11_hide_window_decorations, PyObject_IsTrue);
S(macos_hide_from_tasks, PyObject_IsTrue);
S(macos_thicken_font, PyFloat_AsDouble);
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
if (chars == NULL) return NULL;

View File

@@ -23,7 +23,8 @@ typedef struct {
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
double repaint_delay, input_delay;
bool focus_follows_mouse;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed, macos_window_resizable;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
float macos_thicken_font;
int adjust_line_height_px, adjust_column_width_px;
float adjust_line_height_frac, adjust_column_width_frac;
float background_opacity, dim_opacity;
@@ -188,6 +189,7 @@ void update_surface_size(int, int, uint32_t);
void free_texture(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_os_window(OSWindow *);
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);

View File

@@ -13,7 +13,7 @@ from .fast_data_types import (
from .layout import Rect
from .utils import color_as_int
from .window import calculate_gl_geometry
from .rgb import alpha_blend
from .rgb import alpha_blend, color_from_int
TabBarData = namedtuple('TabBarData', 'title is_active needs_attention')
DrawData = namedtuple('DrawData', 'leading_spaces sep trailing_spaces bell_on_tab bell_fg alpha active_bg inactive_bg default_bg')
@@ -121,6 +121,11 @@ class TabBar:
self.active_fg = (spec['active_tab_foreground'] << 8) | 2
if 'active_tab_background' in spec:
self.active_bg = (spec['active_tab_background'] << 8) | 2
self.draw_data = self.draw_data._replace(active_bg=color_from_int(spec['active_tab_background']))
if 'inactive_tab_background' in spec:
self.draw_data = self.draw_data._replace(inactive_bg=color_from_int(spec['inactive_tab_background']))
if 'background' in spec:
self.draw_data = self.draw_data._replace(default_bg=color_from_int(spec['background']))
self.screen.color_profile.set_configured_colors(
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))

View File

@@ -46,10 +46,11 @@ class Tab: # {{{
self.windows = deque()
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
self._last_used_layout = self._current_layout_name = None
if session_tab is None:
self.cwd = self.args.directory
sl = self.enabled_layouts[0]
self.current_layout = self.create_layout_object(sl)
self._set_current_layout(sl)
if special_window is None:
self.new_window(cwd_from=cwd_from)
else:
@@ -57,9 +58,14 @@ class Tab: # {{{
else:
self.cwd = session_tab.cwd or self.args.directory
l0 = session_tab.layout
self.current_layout = self.create_layout_object(l0)
self._set_current_layout(l0)
self.startup(session_tab)
def _set_current_layout(self, layout_name):
self._last_used_layout = self._current_layout_name
self.current_layout = self.create_layout_object(layout_name)
self._current_layout_name = layout_name
def startup(self, session_tab):
for cmd in session_tab.windows:
if isinstance(cmd, (SpecialWindowInstance,)):
@@ -154,7 +160,12 @@ class Tab: # {{{
else:
idx = -1
nl = self.enabled_layouts[(idx + 1) % len(self.enabled_layouts)]
self.current_layout = self.create_layout_object(nl)
self._set_current_layout(nl)
self.relayout()
def last_used_layout(self):
if len(self.enabled_layouts) > 1 and self._last_used_layout and self._last_used_layout != self._current_layout_name:
self._set_current_layout(self._last_used_layout)
self.relayout()
def goto_layout(self, layout_name, raise_exception=False):
@@ -164,7 +175,7 @@ class Tab: # {{{
raise ValueError(layout_name)
log_error('Unknown or disabled layout: {}'.format(layout_name))
return
self.current_layout = self.create_layout_object(layout_name)
self._set_current_layout(layout_name)
self.relayout()
def resize_window_by(self, window_id, increment, is_horizontal):
@@ -260,6 +271,16 @@ class Tab: # {{{
def previous_window(self):
self._next_window(-1)
prev_window = previous_window
def neighboring_window(self, which):
neighbors = self.current_layout.neighbors(self.windows, self.active_window_idx)
candidates = neighbors.get(which)
if candidates:
self.active_window_idx = self.current_layout.set_active_window(self.windows, candidates[0])
self.relayout_borders()
glfw_post_empty_event()
def move_window(self, delta=1):
self.active_window_idx = self.current_layout.move_window(self.windows, self.active_window_idx, delta)
self.relayout()
@@ -445,10 +466,16 @@ class TabManager: # {{{
self._set_active_tab(nidx)
self.mark_tab_bar_dirty()
def new_tab(self, special_window=None, cwd_from=None):
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False):
nidx = self.active_tab_idx + 1
idx = len(self.tabs)
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx)
if len(self.tabs) > 2 and as_neighbor and idx != nidx:
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
swap_tabs(self.os_window_id, idx, nidx)
self._set_active_tab(nidx)
idx = nidx
self.mark_tab_bar_dirty()
return self.tabs[idx]

View File

@@ -468,7 +468,7 @@ def get_capabilities(query_string):
try:
for q in query_string.split(';'):
name = qname = unhexlify(q).decode('utf-8')
if name == 'TN':
if name in ('TN', 'name'):
val = names[0]
else:
try:

View File

@@ -436,10 +436,13 @@ class Window:
self.screen.reset_callbacks()
self.screen = None
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False):
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False):
lines = []
add_history = add_history and not self.screen.is_using_alternate_linebuf()
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
add_history = add_history and not self.screen.is_using_alternate_linebuf() and not alternate_screen
if alternate_screen:
f = self.screen.as_text_alternate
else:
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h = []
@@ -475,6 +478,10 @@ class Window:
if len(text) == len(new_text):
break
text = new_text
else:
# Workaround for broken editors like nano that cannot handle
# newlines in pasted text see https://github.com/kovidgoyal/kitty/issues/994
text = b'\r'.join(text.splitlines())
self.screen.paste(text)
def copy_to_clipboard(self):

View File

@@ -229,7 +229,7 @@ class TestScreen(BaseTest):
s.backspace()
s.draw(' ')
s.backspace()
self.ae(s.cursor.x, 0)
self.ae(s.cursor.x, 1)
def test_resize(self):
s = self.create_screen(scrollback=6)

View File

@@ -153,6 +153,10 @@ int main(int argc, char *argv[]) {
final_argv[i+1] = argv[i];
num_args++;
}
#if PY_VERSION_HEX >= 0x03070000
// Always use UTF-8 mode, see https://github.com/kovidgoyal/kitty/issues/924
Py_UTF8Mode = 1;
#endif
for (i = 0; i < num_args; i++) {
argvw[i] = Py_DecodeLocale(final_argv[i], NULL);
if (argvw[i] == NULL) {