Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a4420f2d3 | ||
|
|
1eb8d6e845 | ||
|
|
e2f97dc44a | ||
|
|
ea9af96d2b | ||
|
|
e4033d997c | ||
|
|
bc38357193 | ||
|
|
62cb035afe | ||
|
|
7a8bc6b5d2 | ||
|
|
0d86ac23bb | ||
|
|
897fd9486e | ||
|
|
39d3d093c4 | ||
|
|
3f0b52bef4 | ||
|
|
4a04f27fde | ||
|
|
5fb02c0439 | ||
|
|
edfb4ef3f1 | ||
|
|
f037923b00 | ||
|
|
424aafb514 | ||
|
|
1b64167a3e | ||
|
|
a9a86e4604 | ||
|
|
1dc7c23b2f | ||
|
|
5bdcfbc2ce | ||
|
|
74e9bf4f31 | ||
|
|
0652529839 | ||
|
|
772d6597a9 | ||
|
|
4e99194022 | ||
|
|
f0ff2f4ea7 | ||
|
|
d583c533c2 | ||
|
|
ec1f219850 | ||
|
|
21d586cc86 | ||
|
|
8f7139d48f | ||
|
|
cfb6dae04d | ||
|
|
bceac3ee0b | ||
|
|
38d6edb0ca | ||
|
|
1b41a3bcee | ||
|
|
c54f934f6e | ||
|
|
94052bb712 | ||
|
|
7eb1dcc7ee | ||
|
|
626fbe827e | ||
|
|
28c154ecf0 | ||
|
|
8176f661ea | ||
|
|
0195e7f841 | ||
|
|
141b8a1481 | ||
|
|
be3ab90cff | ||
|
|
9782f16635 | ||
|
|
6930edfd8a | ||
|
|
6b1bec6550 | ||
|
|
dac0304815 | ||
|
|
cfcf81b5fb | ||
|
|
f072a02215 | ||
|
|
5f6e21da9b | ||
|
|
b9b38a4ec1 | ||
|
|
e5a7ba4f22 | ||
|
|
1dcf3f2afb | ||
|
|
2ec84969dd | ||
|
|
a08b945124 | ||
|
|
16f712fbaa | ||
|
|
1f63ff1d41 | ||
|
|
49d8f4689f | ||
|
|
e1f993938a | ||
|
|
ab65b9b2fb | ||
|
|
f7a621f89f | ||
|
|
b808a05e5e | ||
|
|
72c37c519d | ||
|
|
af4b072b29 | ||
|
|
739ed906f0 | ||
|
|
9fd462077a | ||
|
|
cb3671343e | ||
|
|
b18b705632 | ||
|
|
9963e78636 | ||
|
|
e39635ea16 | ||
|
|
9329b2c202 | ||
|
|
791f70e5a2 | ||
|
|
8272c4a429 | ||
|
|
f0357b061e |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>`_.
|
||||
|
||||
@@ -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
80
docs/kittens/custom.rst
Normal 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``.
|
||||
@@ -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
|
||||
-------------------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
5
glfw/win32_platform.h
vendored
5
glfw/win32_platform.h
vendored
@@ -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
4
glfw/wl_init.c
vendored
@@ -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, ®istryListener, NULL);
|
||||
|
||||
5
glfw/wl_platform.h
vendored
5
glfw/wl_platform.h
vendored
@@ -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
109
glfw/wl_window.c
vendored
@@ -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
4
glfw/xkb_glfw.c
vendored
@@ -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;
|
||||
}
|
||||
|
||||
105
kitty/boss.py
105
kitty/boss.py
@@ -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
|
||||
|
||||
12
kitty/cli.py
12
kitty/cli.py
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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. '''))
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
2
kitty/glfw-wrapper.c
generated
@@ -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
10
kitty/glfw-wrapper.h
generated
@@ -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
|
||||
|
||||
110
kitty/glfw.c
110
kitty/glfw.c
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
103
kitty/layout.py
103
kitty/layout.py
@@ -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))
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user