Compare commits
144 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 | ||
|
|
7438e0e997 | ||
|
|
cca672e536 | ||
|
|
0fa12a687d | ||
|
|
d2b900c77f | ||
|
|
21242d9144 | ||
|
|
03fd138b63 | ||
|
|
1eeaef6dbb | ||
|
|
b9a93b68c4 | ||
|
|
e3728942dc | ||
|
|
7f0674ac27 | ||
|
|
60fcf7ab3d | ||
|
|
ee82a6ff18 | ||
|
|
bac5e402b7 | ||
|
|
732108109e | ||
|
|
ab3b86d65b | ||
|
|
d4d63506e1 | ||
|
|
3ad69f8ba3 | ||
|
|
9fd50a6ac0 | ||
|
|
9b5c8a18d4 | ||
|
|
9f3112ca56 | ||
|
|
aa9de47da3 | ||
|
|
5eaeda4402 | ||
|
|
ecb03d7dba | ||
|
|
88612d3691 | ||
|
|
2c0e70a423 | ||
|
|
d6b70f10d3 | ||
|
|
40b01fb702 | ||
|
|
89222d93f4 | ||
|
|
b3db3e5732 | ||
|
|
b45161ecc3 | ||
|
|
d4fc0af7de | ||
|
|
5ef8cce1d7 | ||
|
|
c98a147f94 | ||
|
|
e9975cb7eb | ||
|
|
33f0ac83c1 | ||
|
|
3bd45966c6 | ||
|
|
1b7244f3c8 | ||
|
|
1949f3c3d5 | ||
|
|
6a51ce5dc4 | ||
|
|
c0c8e1ce5e | ||
|
|
6a3d6191bd | ||
|
|
b2aa07d29f | ||
|
|
c84fb68bf8 | ||
|
|
128b9e1cd0 | ||
|
|
bbe8bcb28d | ||
|
|
6dc9c22543 | ||
|
|
59f5781f19 | ||
|
|
d61fb9cc42 | ||
|
|
ace052ee70 | ||
|
|
1451a8ccc0 | ||
|
|
547959c1ef | ||
|
|
b1a5e98630 | ||
|
|
f3a1ee7b9f | ||
|
|
b0a39b127d | ||
|
|
e36f1157cc | ||
|
|
053b9a786c | ||
|
|
2561d937da | ||
|
|
55f457caf4 | ||
|
|
f5cc2fd8ad | ||
|
|
e630b76fac | ||
|
|
0b6948cb4f | ||
|
|
b0a2dea747 | ||
|
|
f8395e36a1 | ||
|
|
6ec58f58ce | ||
|
|
96cbfe946c | ||
|
|
ac686a47c1 | ||
|
|
e05aaf77ce | ||
|
|
6b371eb8a8 | ||
|
|
f6660a0d55 | ||
|
|
3b9f213ea2 |
@@ -10,9 +10,9 @@ config to reproduce the issue with).
|
|||||||
|
|
||||||
### Contributing code
|
### 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
|
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
|
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
|
beforehand to discuss it, so that you don't waste your time making a pull
|
||||||
|
|||||||
13
__main__.py
13
__main__.py
@@ -7,8 +7,9 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def icat(args):
|
def icat(args):
|
||||||
from kittens.icat.main import main
|
from kittens.runner import run_kitten
|
||||||
main(args)
|
sys.argv = args
|
||||||
|
run_kitten('icat')
|
||||||
|
|
||||||
|
|
||||||
def list_fonts(args):
|
def list_fonts(args):
|
||||||
@@ -41,7 +42,13 @@ def complete(args):
|
|||||||
def launch(args):
|
def launch(args):
|
||||||
import runpy
|
import runpy
|
||||||
sys.argv = args[1:]
|
sys.argv = args[1:]
|
||||||
runpy.run_path(args[1], run_name='__main__')
|
exe = args[1]
|
||||||
|
if exe.startswith(':'):
|
||||||
|
import shutil
|
||||||
|
exe = shutil.which(exe[1:])
|
||||||
|
if not exe:
|
||||||
|
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||||
|
runpy.run_path(exe, run_name='__main__')
|
||||||
|
|
||||||
|
|
||||||
def run_kitten(args):
|
def run_kitten(args):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS = -T $(FAIL_WARN)
|
SPHINXOPTS = -T $(FAIL_WARN)
|
||||||
SPHINXBUILD = python3 .. +launch $(shell which sphinx-build)
|
SPHINXBUILD = python3 .. +launch :sphinx-build
|
||||||
SPHINXPROJ = kitty
|
SPHINXPROJ = kitty
|
||||||
SOURCEDIR = .
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ the following simple command:
|
|||||||
|
|
||||||
The binaries will be installed in the standard location for your OS,
|
The binaries will be installed in the standard location for your OS,
|
||||||
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
|
: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
|
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
|
and extract it into a directory. The |kitty| executable will be in the
|
||||||
:file:`bin` sub-directory.
|
: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
|
Customizing the installation
|
||||||
--------------------------------
|
--------------------------------
|
||||||
@@ -58,6 +78,12 @@ Customizing the installation
|
|||||||
|ins| \\
|
|ins| \\
|
||||||
installer=/path/to/dmg or tarball
|
installer=/path/to/dmg or tarball
|
||||||
|
|
||||||
|
Uninstalling
|
||||||
|
----------------
|
||||||
|
|
||||||
|
All the installer does is copy the kitty files into the install directory. To
|
||||||
|
uninstall, simply delete that directory.
|
||||||
|
|
||||||
|
|
||||||
Building from source
|
Building from source
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -3,6 +3,116 @@ Changelog
|
|||||||
|
|
||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|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 ``--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
|
||||||
|
font fallback for all subsequent characters that cannot be rendered in the
|
||||||
|
main font to fail (:iss:`799`)
|
||||||
|
|
||||||
|
- Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus``
|
||||||
|
environment variable is set. IME causes key processing latency and even
|
||||||
|
missed keystrokes for many people, so it is now off by default.
|
||||||
|
|
||||||
|
- Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`)
|
||||||
|
|
||||||
|
- Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`)
|
||||||
|
|
||||||
|
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
|
||||||
|
|
||||||
|
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
|
||||||
|
|
||||||
|
- Allow mapping of individual key-presses without modifiers as shortcuts
|
||||||
|
|
||||||
|
- Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`)
|
||||||
|
|
||||||
|
- Improve rendering of wavy underline at small font sizes (:iss:`853`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
||||||
|
|
||||||
|
- Wayland: Allow using the :option:`kitty --class` command line flag
|
||||||
|
to set the app id (:iss:`862`)
|
||||||
|
|
||||||
|
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||||
|
|
||||||
|
- Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`)
|
||||||
|
|
||||||
|
- Linux: Fix crash with some Nvidia drivers when creating tabs in the first
|
||||||
|
top level-window after creating a second top-level window. (:iss:`873`)
|
||||||
|
|
||||||
|
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||||
|
a bug in the 0.12.0 macOS package
|
||||||
|
|
||||||
0.12.0 [2018-09-01]
|
0.12.0 [2018-09-01]
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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::
|
monospace. On Linux you can list all monospace fonts with::
|
||||||
|
|
||||||
fc-list : family spacing | grep spacing=100
|
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>`_.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ For some discussion regarding the design choices, see `#33
|
|||||||
|
|
||||||
To see a quick demo, inside a |kitty| terminal run::
|
To see a quick demo, inside a |kitty| terminal run::
|
||||||
|
|
||||||
kitty icat path/to/some/image.png
|
kitty +kitten icat path/to/some/image.png
|
||||||
|
|
||||||
You can also see a screenshot with more sophisticated features such as
|
You can also see a screenshot with more sophisticated features such as
|
||||||
alpha-blending and text over graphics.
|
alpha-blending and text over graphics.
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ Quickstart
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||||
See the :doc:`binary install instructions </binary>`.
|
See the :doc:`binary install instructions </binary>`. You can also
|
||||||
|
:doc:`build from source </build>`.
|
||||||
|
|
||||||
If you are on Linux, you can also use your distribution's |kitty| package.
|
If you are on Linux, you can also use your distribution's |kitty| package.
|
||||||
|kitty| packages are available for:
|
|kitty| packages are available for:
|
||||||
@@ -60,7 +61,8 @@ If you are on Linux, you can also use your distribution's |kitty| package.
|
|||||||
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
||||||
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
||||||
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
||||||
`Fedora <https://copr.fedorainfracloud.org/coprs/oleastre/kitty-terminal/>`_.
|
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
|
||||||
|
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_.
|
||||||
|
|
||||||
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
||||||
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
||||||
@@ -123,7 +125,7 @@ Action Shortcut
|
|||||||
New tab :sc:`new_tab`
|
New tab :sc:`new_tab`
|
||||||
Close tab :sc:`close_tab`
|
Close tab :sc:`close_tab`
|
||||||
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||||
Previous tab :sc:`previous_tab`
|
Previous tab :sc:`previous_tab` (also :kbd:`control+shift+tab` on macOS)
|
||||||
Next layout :sc:`next_layout`
|
Next layout :sc:`next_layout`
|
||||||
Move tab forward :sc:`move_tab_forward`
|
Move tab forward :sc:`move_tab_forward`
|
||||||
Move tab backward :sc:`move_tab_backward`
|
Move tab backward :sc:`move_tab_backward`
|
||||||
@@ -149,6 +151,15 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
|
|||||||
(clockwise from the top-left)
|
(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
|
Other keyboard shortcuts
|
||||||
----------------------------------
|
----------------------------------
|
||||||
@@ -162,7 +173,8 @@ Paste from selection :sc:`paste_from_selection`
|
|||||||
Increase font size :sc:`increase_font_size`
|
Increase font size :sc:`increase_font_size`
|
||||||
Decrease font size :sc:`decrease_font_size`
|
Decrease font size :sc:`decrease_font_size`
|
||||||
Restore font size :sc:`reset_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`
|
Input unicode character :sc:`input_unicode_character`
|
||||||
Click URL using the keyboard :sc:`open_url`
|
Click URL using the keyboard :sc:`open_url`
|
||||||
Reset the terminal :sc:`reset_terminal`
|
Reset the terminal :sc:`reset_terminal`
|
||||||
@@ -181,6 +193,9 @@ Reset background opacity :sc:`reset_background_opacity`
|
|||||||
Layouts
|
Layouts
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
A layout is an arrangement of multiple *windows*. You can create a new window
|
||||||
|
using the :sc:`new_window` key combination.
|
||||||
|
|
||||||
Currently, there are five layouts available,
|
Currently, there are five layouts available,
|
||||||
|
|
||||||
* **Stack** -- Only a single maximized window is shown at a time
|
* **Stack** -- Only a single maximized window is shown at a time
|
||||||
@@ -250,6 +265,8 @@ Some prominent kittens:
|
|||||||
:doc:`Clipboard <kittens/clipboard>`
|
:doc:`Clipboard <kittens/clipboard>`
|
||||||
Copy/paste to the clipboard from shell scripts, even over SSH.
|
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
|
Configuring kitty
|
||||||
-------------------
|
-------------------
|
||||||
@@ -258,7 +275,6 @@ Configuring kitty
|
|||||||
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
||||||
see the :doc:`configuration docs <conf>`.
|
see the :doc:`configuration docs <conf>`.
|
||||||
|
|
||||||
.. _sessions:
|
|
||||||
|
|
||||||
Remote control
|
Remote control
|
||||||
------------------
|
------------------
|
||||||
@@ -269,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
|
from one window and send text to another, etc, etc. The possibilities are
|
||||||
endless. See the :doc:`tutorial <remote-control>` to get started.
|
endless. See the :doc:`tutorial <remote-control>` to get started.
|
||||||
|
|
||||||
|
.. _sessions:
|
||||||
|
|
||||||
Startup Sessions
|
Startup Sessions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@@ -344,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
|
Colors and text formatting are preserved. You can explore the scrollback buffer
|
||||||
comfortably within the pager.
|
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
|
Frequently Asked Questions
|
||||||
---------------------------------
|
---------------------------------
|
||||||
@@ -384,6 +410,16 @@ The important thing above is to make sure the call to |kitty| to load the zsh
|
|||||||
completions happens after the call to :file:`compinit`.
|
completions happens after the call to :file:`compinit`.
|
||||||
|
|
||||||
|
|
||||||
|
fish
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Add the following to your :file:`~/.config/fish/config.fish`
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
kitty + complete setup fish | source
|
||||||
|
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|||||||
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.
|
adding them to the command line for the next command.
|
||||||
|
|
||||||
The hints kitten is very powerful to see more detailed help on its various
|
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
|
Command Line Interface
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ It supports all image types supported by `ImageMagick
|
|||||||
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
||||||
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
||||||
|
|
||||||
|
You might want to create an alias in your shell's configuration files::
|
||||||
|
|
||||||
|
alias icat="kitty +kitten icat"
|
||||||
|
|
||||||
|
Then you can simply use ``icat image.png`` to view images.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction
|
|||||||
with child programs takes place in a separate thread from rendering, to improve
|
with child programs takes place in a separate thread from rendering, to improve
|
||||||
smoothness.
|
smoothness.
|
||||||
|
|
||||||
There are two parameters you can tune to adjust the performance. ``repaint_delay``
|
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
|
||||||
and ``input_delay``. These control the artificial delays introduced into the
|
and :opt:`input_delay`. These control the artificial delays introduced into the
|
||||||
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
||||||
See also the ``sync_to_monitor`` option to further decrease latency at the cost
|
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
|
||||||
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
||||||
|
|
||||||
You can generate detailed per-function performance data using
|
You can generate detailed per-function performance data using
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Let's walk through a few examples of controlling |kitty|.
|
|||||||
|
|
||||||
Start by running |kitty| as::
|
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
|
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.
|
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
||||||
|
|||||||
51
glfw/backend_utils.c
vendored
51
glfw/backend_utils.c
vendored
@@ -261,3 +261,54 @@ closeFds(int *fds, size_t count) {
|
|||||||
fds++;
|
fds++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Splits and translates a text/uri-list into separate file paths
|
||||||
|
// NOTE: This function destroys the provided string
|
||||||
|
//
|
||||||
|
char** parseUriList(char* text, int* count)
|
||||||
|
{
|
||||||
|
const char* prefix = "file://";
|
||||||
|
char** paths = NULL;
|
||||||
|
char* line;
|
||||||
|
|
||||||
|
*count = 0;
|
||||||
|
|
||||||
|
while ((line = strtok(text, "\r\n")))
|
||||||
|
{
|
||||||
|
text = NULL;
|
||||||
|
|
||||||
|
if (line[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||||
|
{
|
||||||
|
line += strlen(prefix);
|
||||||
|
// TODO: Validate hostname
|
||||||
|
while (*line != '/')
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*count)++;
|
||||||
|
|
||||||
|
char* path = calloc(strlen(line) + 1, 1);
|
||||||
|
paths = realloc(paths, *count * sizeof(char*));
|
||||||
|
paths[*count - 1] = path;
|
||||||
|
|
||||||
|
while (*line)
|
||||||
|
{
|
||||||
|
if (line[0] == '%' && line[1] && line[2])
|
||||||
|
{
|
||||||
|
const char digits[3] = { line[1], line[2], '\0' };
|
||||||
|
*path = strtol(digits, NULL, 16);
|
||||||
|
line += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*path = *line;
|
||||||
|
|
||||||
|
path++;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|||||||
1
glfw/backend_utils.h
vendored
1
glfw/backend_utils.h
vendored
@@ -71,3 +71,4 @@ int pollForEvents(EventLoopData *eld, double timeout);
|
|||||||
unsigned dispatchTimers(EventLoopData *eld);
|
unsigned dispatchTimers(EventLoopData *eld);
|
||||||
void closeFds(int *fds, size_t count);
|
void closeFds(int *fds, size_t count);
|
||||||
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
||||||
|
char** parseUriList(char* text, int* count);
|
||||||
|
|||||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@@ -39,6 +39,7 @@ typedef void* id;
|
|||||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||||
|
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||||
|
|
||||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||||
{
|
{
|
||||||
@@ -102,6 +103,8 @@ typedef struct _GLFWwindowNS
|
|||||||
|
|
||||||
// The text input filter callback
|
// The text input filter callback
|
||||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||||
|
// The toggle fullscreen intercept callback
|
||||||
|
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
|
||||||
// Dead key state
|
// Dead key state
|
||||||
UInt32 deadKeyState;
|
UInt32 deadKeyState;
|
||||||
} _GLFWwindowNS;
|
} _GLFWwindowNS;
|
||||||
|
|||||||
@@ -563,6 +563,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (_GLFWwindow*)glfwWindow {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)isOpaque
|
- (BOOL)isOpaque
|
||||||
{
|
{
|
||||||
return [window->ns.object isOpaque];
|
return [window->ns.object isOpaque];
|
||||||
@@ -1040,6 +1044,18 @@ is_ascii_control_char(char x) {
|
|||||||
return YES;
|
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
|
@end
|
||||||
|
|
||||||
|
|
||||||
@@ -2114,6 +2130,14 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
|||||||
return previous;
|
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) {
|
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||||
handle_reopen_callback = callback;
|
handle_reopen_callback = callback;
|
||||||
|
|||||||
19
glfw/glfw.py
19
glfw/glfw.py
@@ -128,16 +128,6 @@ def collect_source_information():
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def patch_in_file(path, pfunc):
|
|
||||||
with open(path, 'r+') as f:
|
|
||||||
raw = f.read()
|
|
||||||
nraw = pfunc(raw)
|
|
||||||
if raw == nraw:
|
|
||||||
raise SystemExit('Patching of {} failed'.format(path))
|
|
||||||
f.seek(0), f.truncate()
|
|
||||||
f.write(nraw)
|
|
||||||
|
|
||||||
|
|
||||||
class Arg:
|
class Arg:
|
||||||
|
|
||||||
def __init__(self, decl):
|
def __init__(self, decl):
|
||||||
@@ -204,6 +194,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
|||||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||||
|
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||||
void* glfwGetX11Display(void)
|
void* glfwGetX11Display(void)
|
||||||
@@ -223,11 +214,13 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.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);
|
const char* load_glfw(const char* path);
|
||||||
@@ -283,8 +276,6 @@ def from_glfw(glfw_dir):
|
|||||||
for src in files_to_copy:
|
for src in files_to_copy:
|
||||||
shutil.copy2(src, '.')
|
shutil.copy2(src, '.')
|
||||||
shutil.copy2(glfw_header, '.')
|
shutil.copy2(glfw_header, '.')
|
||||||
patch_in_file('cocoa_window.m', lambda x: re.sub(
|
|
||||||
r'[(]void[)]loadMainMenu.+?}', '(void)loadMainMenu\n{ // removed by Kovid as it generated compiler warnings \n}\n', x, flags=re.DOTALL))
|
|
||||||
json.dump(
|
json.dump(
|
||||||
sinfo,
|
sinfo,
|
||||||
open('source-info.json', 'w'),
|
open('source-info.json', 'w'),
|
||||||
|
|||||||
6
glfw/glfw3.h
vendored
6
glfw/glfw3.h
vendored
@@ -973,6 +973,10 @@ extern "C" {
|
|||||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||||
|
/*! @brief Window content area scaling window
|
||||||
|
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||||
|
*/
|
||||||
|
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||||
|
|
||||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||||
@@ -980,6 +984,8 @@ extern "C" {
|
|||||||
|
|
||||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||||
|
|
||||||
|
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||||
/*! @} */
|
/*! @} */
|
||||||
|
|
||||||
#define GLFW_NO_API 0
|
#define GLFW_NO_API 0
|
||||||
|
|||||||
3
glfw/ibus_glfw.c
vendored
3
glfw/ibus_glfw.c
vendored
@@ -280,8 +280,7 @@ setup_connection(_GLFWIBUSData *ibus) {
|
|||||||
void
|
void
|
||||||
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||||
if (ibus->inited) return;
|
if (ibus->inited) return;
|
||||||
if (!test_env_var("XMODIFIERS", "@im=ibus") && !test_env_var("GTK_IM_MODULE", "ibus") && !test_env_var("QT_IM_MODULE", "ibus") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||||
if (getenv("GLFW_IM_MODULE") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
|
||||||
ibus->inited = GLFW_TRUE;
|
ibus->inited = GLFW_TRUE;
|
||||||
setup_connection(ibus);
|
setup_connection(ibus);
|
||||||
}
|
}
|
||||||
|
|||||||
6
glfw/internal.h
vendored
6
glfw/internal.h
vendored
@@ -31,6 +31,8 @@
|
|||||||
#include "glfw_config.h"
|
#include "glfw_config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||||
|
|
||||||
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
||||||
defined(GLFW_INCLUDE_ES1) || \
|
defined(GLFW_INCLUDE_ES1) || \
|
||||||
defined(GLFW_INCLUDE_ES2) || \
|
defined(GLFW_INCLUDE_ES2) || \
|
||||||
@@ -271,6 +273,7 @@ struct _GLFWwndconfig
|
|||||||
GLFWbool maximized;
|
GLFWbool maximized;
|
||||||
GLFWbool centerCursor;
|
GLFWbool centerCursor;
|
||||||
GLFWbool focusOnShow;
|
GLFWbool focusOnShow;
|
||||||
|
GLFWbool scaleToMonitor;
|
||||||
struct {
|
struct {
|
||||||
GLFWbool retina;
|
GLFWbool retina;
|
||||||
char frameName[256];
|
char frameName[256];
|
||||||
@@ -279,6 +282,9 @@ struct _GLFWwndconfig
|
|||||||
char className[256];
|
char className[256];
|
||||||
char instanceName[256];
|
char instanceName[256];
|
||||||
} x11;
|
} x11;
|
||||||
|
struct {
|
||||||
|
char appId[256];
|
||||||
|
} wl;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context configuration
|
// Context configuration
|
||||||
|
|||||||
34
glfw/wgl_context.c
vendored
34
glfw/wgl_context.c
vendored
@@ -256,12 +256,20 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
|
|||||||
|
|
||||||
static void swapBuffersWGL(_GLFWwindow* window)
|
static void swapBuffersWGL(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
// HACK: Use DwmFlush when desktop composition is enabled
|
if (!window->monitor)
|
||||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
|
||||||
{
|
{
|
||||||
int count = abs(window->context.wgl.interval);
|
if (IsWindowsVistaOrGreater())
|
||||||
while (count--)
|
{
|
||||||
DwmFlush();
|
BOOL enabled;
|
||||||
|
|
||||||
|
// HACK: Use DwmFlush when desktop composition is enabled
|
||||||
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
|
{
|
||||||
|
int count = abs(window->context.wgl.interval);
|
||||||
|
while (count--)
|
||||||
|
DwmFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapBuffers(window->context.wgl.dc);
|
SwapBuffers(window->context.wgl.dc);
|
||||||
@@ -273,10 +281,18 @@ static void swapIntervalWGL(int interval)
|
|||||||
|
|
||||||
window->context.wgl.interval = interval;
|
window->context.wgl.interval = interval;
|
||||||
|
|
||||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
if (!window->monitor)
|
||||||
// avoid interfering with DWM vsync
|
{
|
||||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
if (IsWindowsVistaOrGreater())
|
||||||
interval = 0;
|
{
|
||||||
|
BOOL enabled;
|
||||||
|
|
||||||
|
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||||
|
// avoid interfering with DWM vsync
|
||||||
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
|
interval = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_glfw.wgl.EXT_swap_control)
|
if (_glfw.wgl.EXT_swap_control)
|
||||||
_glfw.wgl.SwapIntervalEXT(interval);
|
_glfw.wgl.SwapIntervalEXT(interval);
|
||||||
|
|||||||
68
glfw/win32_init.c
vendored
68
glfw/win32_init.c
vendored
@@ -62,17 +62,6 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
|
|||||||
|
|
||||||
#endif // _GLFW_BUILD_DLL
|
#endif // _GLFW_BUILD_DLL
|
||||||
|
|
||||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
|
||||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp)
|
|
||||||
{
|
|
||||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
|
||||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
|
||||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
|
||||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
|
||||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
|
||||||
return VerifyVersionInfoW(&osvi, mask, cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load necessary libraries (DLLs)
|
// Load necessary libraries (DLLs)
|
||||||
//
|
//
|
||||||
static GLFWbool loadLibraries(void)
|
static GLFWbool loadLibraries(void)
|
||||||
@@ -100,6 +89,14 @@ static GLFWbool loadLibraries(void)
|
|||||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
||||||
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
||||||
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
||||||
|
_glfw.win32.user32.EnableNonClientDpiScaling_ = (PFN_EnableNonClientDpiScaling)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "EnableNonClientDpiScaling");
|
||||||
|
_glfw.win32.user32.SetProcessDpiAwarenessContext_ = (PFN_SetProcessDpiAwarenessContext)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDpiAwarenessContext");
|
||||||
|
_glfw.win32.user32.GetDpiForWindow_ = (PFN_GetDpiForWindow)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "GetDpiForWindow");
|
||||||
|
_glfw.win32.user32.AdjustWindowRectExForDpi_ = (PFN_AdjustWindowRectExForDpi)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "AdjustWindowRectExForDpi");
|
||||||
|
|
||||||
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
||||||
if (_glfw.win32.dinput8.instance)
|
if (_glfw.win32.dinput8.instance)
|
||||||
@@ -155,6 +152,13 @@ static GLFWbool loadLibraries(void)
|
|||||||
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_glfw.win32.ntdll.instance = LoadLibraryA("ntdll.dll");
|
||||||
|
if (_glfw.win32.ntdll.instance)
|
||||||
|
{
|
||||||
|
_glfw.win32.ntdll.RtlVerifyVersionInfo_ = (PFN_RtlVerifyVersionInfo)
|
||||||
|
GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
|
||||||
|
}
|
||||||
|
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +183,9 @@ static void freeLibraries(void)
|
|||||||
|
|
||||||
if (_glfw.win32.shcore.instance)
|
if (_glfw.win32.shcore.instance)
|
||||||
FreeLibrary(_glfw.win32.shcore.instance);
|
FreeLibrary(_glfw.win32.shcore.instance);
|
||||||
|
|
||||||
|
if (_glfw.win32.ntdll.instance)
|
||||||
|
FreeLibrary(_glfw.win32.ntdll.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create key code translation tables
|
// Create key code translation tables
|
||||||
@@ -309,6 +316,7 @@ static void createKeyTables(void)
|
|||||||
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
||||||
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
||||||
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
||||||
|
_glfw.win32.keycodes[0x059] = GLFW_KEY_KP_EQUAL;
|
||||||
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
||||||
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
||||||
|
|
||||||
@@ -339,8 +347,8 @@ static HWND createHelperWindow(void)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: The first call to ShowWindow is ignored if the parent process
|
// HACK: The command to the first ShowWindow call is ignored if the parent
|
||||||
// passed along a STARTUPINFO, so clear that flag with a no-op call
|
// process passed along a STARTUPINFO, so clear that with a no-op call
|
||||||
ShowWindow(window, SW_HIDE);
|
ShowWindow(window, SW_HIDE);
|
||||||
|
|
||||||
// Register for HID device notifications
|
// Register for HID device notifications
|
||||||
@@ -502,6 +510,36 @@ void _glfwUpdateKeyNamesWin32(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replacement for IsWindowsVersionOrGreater as MinGW lacks versionhelpers.h
|
||||||
|
//
|
||||||
|
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp)
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||||
|
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||||
|
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||||
|
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||||
|
// latter lies unless the user knew to embedd a non-default manifest
|
||||||
|
// announcing support for Windows 10 via supportedOS GUID
|
||||||
|
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether we are on at least the specified build of Windows 10
|
||||||
|
//
|
||||||
|
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build)
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), 10, 0, build };
|
||||||
|
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
|
||||||
|
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||||
|
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||||
|
// latter lies unless the user knew to embedd a non-default manifest
|
||||||
|
// announcing support for Windows 10 via supportedOS GUID
|
||||||
|
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
@@ -523,7 +561,9 @@ int _glfwPlatformInit(void)
|
|||||||
createKeyTables();
|
createKeyTables();
|
||||||
_glfwUpdateKeyNamesWin32();
|
_glfwUpdateKeyNamesWin32();
|
||||||
|
|
||||||
if (IsWindows8Point1OrGreater())
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
else if (IsWindows8Point1OrGreater())
|
||||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||||
else if (IsWindowsVistaOrGreater())
|
else if (IsWindowsVistaOrGreater())
|
||||||
SetProcessDPIAware();
|
SetProcessDPIAware();
|
||||||
|
|||||||
4
glfw/win32_monitor.c
vendored
4
glfw/win32_monitor.c
vendored
@@ -324,9 +324,9 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (xscale)
|
if (xscale)
|
||||||
*xscale = xdpi / 96.f;
|
*xscale = xdpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
if (yscale)
|
if (yscale)
|
||||||
*yscale = ydpi / 96.f;
|
*yscale = ydpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
71
glfw/win32_platform.h
vendored
71
glfw/win32_platform.h
vendored
@@ -98,6 +98,12 @@
|
|||||||
#ifndef _WIN32_WINNT_WINBLUE
|
#ifndef _WIN32_WINNT_WINBLUE
|
||||||
#define _WIN32_WINNT_WINBLUE 0x0602
|
#define _WIN32_WINNT_WINBLUE 0x0602
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef WM_GETDPISCALEDSIZE
|
||||||
|
#define WM_GETDPISCALEDSIZE 0x02e4
|
||||||
|
#endif
|
||||||
|
#ifndef USER_DEFAULT_SCREEN_DPI
|
||||||
|
#define USER_DEFAULT_SCREEN_DPI 96
|
||||||
|
#endif
|
||||||
|
|
||||||
#if WINVER < 0x0601
|
#if WINVER < 0x0601
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -140,23 +146,32 @@ typedef enum
|
|||||||
} MONITOR_DPI_TYPE;
|
} MONITOR_DPI_TYPE;
|
||||||
#endif /*DPI_ENUMS_DECLARED*/
|
#endif /*DPI_ENUMS_DECLARED*/
|
||||||
|
|
||||||
|
#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_CONTEXT_PER_MONITOR_AWARE_V2*/
|
||||||
|
|
||||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
|
|
||||||
#define IsWindowsXPOrGreater() \
|
#define IsWindowsXPOrGreater() \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||||
#define IsWindowsVistaOrGreater() \
|
#define IsWindowsVistaOrGreater() \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||||
#define IsWindows7OrGreater() \
|
#define IsWindows7OrGreater() \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||||
#define IsWindows8OrGreater() \
|
#define IsWindows8OrGreater() \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||||
#define IsWindows8Point1OrGreater() \
|
#define IsWindows8Point1OrGreater() \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||||
|
|
||||||
|
#define _glfwIsWindows10AnniversaryUpdateOrGreaterWin32() \
|
||||||
|
_glfwIsWindows10BuildOrGreaterWin32(14393)
|
||||||
|
#define _glfwIsWindows10CreatorsUpdateOrGreaterWin32() \
|
||||||
|
_glfwIsWindows10BuildOrGreaterWin32(15063)
|
||||||
|
|
||||||
// HACK: Define macros that some xinput.h variants don't
|
// HACK: Define macros that some xinput.h variants don't
|
||||||
#ifndef XINPUT_CAPS_WIRELESS
|
#ifndef XINPUT_CAPS_WIRELESS
|
||||||
@@ -209,8 +224,16 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
|
|||||||
// user32.dll function pointer typedefs
|
// user32.dll function pointer typedefs
|
||||||
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
||||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
||||||
|
typedef BOOL (WINAPI * PFN_EnableNonClientDpiScaling)(HWND);
|
||||||
|
typedef BOOL (WINAPI * PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
|
||||||
|
typedef UINT (WINAPI * PFN_GetDpiForWindow)(HWND);
|
||||||
|
typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
|
||||||
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
||||||
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
||||||
|
#define EnableNonClientDpiScaling _glfw.win32.user32.EnableNonClientDpiScaling_
|
||||||
|
#define SetProcessDpiAwarenessContext _glfw.win32.user32.SetProcessDpiAwarenessContext_
|
||||||
|
#define GetDpiForWindow _glfw.win32.user32.GetDpiForWindow_
|
||||||
|
#define AdjustWindowRectExForDpi _glfw.win32.user32.AdjustWindowRectExForDpi_
|
||||||
|
|
||||||
// dwmapi.dll function pointer typedefs
|
// dwmapi.dll function pointer typedefs
|
||||||
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
||||||
@@ -226,6 +249,10 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
|
|||||||
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
||||||
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
||||||
|
|
||||||
|
// ntdll.dll function pointer typedefs
|
||||||
|
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
|
||||||
|
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
|
||||||
|
|
||||||
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
||||||
|
|
||||||
typedef struct VkWin32SurfaceCreateInfoKHR
|
typedef struct VkWin32SurfaceCreateInfoKHR
|
||||||
@@ -279,6 +306,7 @@ typedef struct _GLFWwindowWin32
|
|||||||
GLFWbool maximized;
|
GLFWbool maximized;
|
||||||
// Whether to enable framebuffer transparency on DWM
|
// Whether to enable framebuffer transparency on DWM
|
||||||
GLFWbool transparent;
|
GLFWbool transparent;
|
||||||
|
GLFWbool scaleToMonitor;
|
||||||
|
|
||||||
// The last received cursor position, regardless of source
|
// The last received cursor position, regardless of source
|
||||||
int lastCursorPosX, lastCursorPosY;
|
int lastCursorPosX, lastCursorPosY;
|
||||||
@@ -326,6 +354,10 @@ typedef struct _GLFWlibraryWin32
|
|||||||
HINSTANCE instance;
|
HINSTANCE instance;
|
||||||
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
||||||
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
||||||
|
PFN_EnableNonClientDpiScaling EnableNonClientDpiScaling_;
|
||||||
|
PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContext_;
|
||||||
|
PFN_GetDpiForWindow GetDpiForWindow_;
|
||||||
|
PFN_AdjustWindowRectExForDpi AdjustWindowRectExForDpi_;
|
||||||
} user32;
|
} user32;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -341,6 +373,11 @@ typedef struct _GLFWlibraryWin32
|
|||||||
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
||||||
} shcore;
|
} shcore;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
HINSTANCE instance;
|
||||||
|
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
|
||||||
|
} ntdll;
|
||||||
|
|
||||||
} _GLFWlibraryWin32;
|
} _GLFWlibraryWin32;
|
||||||
|
|
||||||
// Win32-specific per-monitor data
|
// Win32-specific per-monitor data
|
||||||
@@ -396,10 +433,11 @@ typedef struct _GLFWmutexWin32
|
|||||||
|
|
||||||
GLFWbool _glfwRegisterWindowClassWin32(void);
|
GLFWbool _glfwRegisterWindowClassWin32(void);
|
||||||
void _glfwUnregisterWindowClassWin32(void);
|
void _glfwUnregisterWindowClassWin32(void);
|
||||||
GLFWbool _glfwIsCompositionEnabledWin32(void);
|
|
||||||
|
|
||||||
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
||||||
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
||||||
|
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp);
|
||||||
|
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build);
|
||||||
void _glfwInputErrorWin32(int error, const char* description);
|
void _glfwInputErrorWin32(int error, const char* description);
|
||||||
void _glfwUpdateKeyNamesWin32(void);
|
void _glfwUpdateKeyNamesWin32(void);
|
||||||
|
|
||||||
@@ -409,4 +447,3 @@ void _glfwPollMonitorsWin32(void);
|
|||||||
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||||
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
||||||
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
||||||
|
|
||||||
|
|||||||
265
glfw/win32_window.c
vendored
265
glfw/win32_window.c
vendored
@@ -186,14 +186,20 @@ static HICON createIcon(const GLFWimage* image,
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate client window size to full window size according to styles
|
// Translate client window size to full window size according to styles and DPI
|
||||||
//
|
//
|
||||||
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
||||||
int clientWidth, int clientHeight,
|
int clientWidth, int clientHeight,
|
||||||
int* fullWidth, int* fullHeight)
|
int* fullWidth, int* fullHeight,
|
||||||
|
UINT dpi)
|
||||||
{
|
{
|
||||||
RECT rect = { 0, 0, clientWidth, clientHeight };
|
RECT rect = { 0, 0, clientWidth, clientHeight };
|
||||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||||
|
|
||||||
*fullWidth = rect.right - rect.left;
|
*fullWidth = rect.right - rect.left;
|
||||||
*fullHeight = rect.bottom - rect.top;
|
*fullHeight = rect.bottom - rect.top;
|
||||||
}
|
}
|
||||||
@@ -206,7 +212,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
|
|||||||
const float ratio = (float) window->numer / (float) window->denom;
|
const float ratio = (float) window->numer / (float) window->denom;
|
||||||
|
|
||||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||||
0, 0, &xoff, &yoff);
|
0, 0, &xoff, &yoff,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
|
||||||
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
||||||
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
||||||
@@ -337,7 +344,16 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
|||||||
style |= getWindowStyle(window);
|
style |= getWindowStyle(window);
|
||||||
|
|
||||||
GetClientRect(window->win32.handle, &rect);
|
GetClientRect(window->win32.handle, &rect);
|
||||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE,
|
||||||
|
getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||||
|
|
||||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||||
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
||||||
@@ -351,10 +367,12 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
|||||||
//
|
//
|
||||||
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||||
{
|
{
|
||||||
|
BOOL enabled;
|
||||||
|
|
||||||
if (!IsWindowsVistaOrGreater())
|
if (!IsWindowsVistaOrGreater())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_glfwIsCompositionEnabledWin32())
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
{
|
{
|
||||||
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
||||||
DWM_BLURBEHIND bb = {0};
|
DWM_BLURBEHIND bb = {0};
|
||||||
@@ -396,29 +414,6 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translates a GLFW standard cursor to a resource ID
|
|
||||||
//
|
|
||||||
static LPWSTR translateCursorShape(int shape)
|
|
||||||
{
|
|
||||||
switch (shape)
|
|
||||||
{
|
|
||||||
case GLFW_ARROW_CURSOR:
|
|
||||||
return IDC_ARROW;
|
|
||||||
case GLFW_IBEAM_CURSOR:
|
|
||||||
return IDC_IBEAM;
|
|
||||||
case GLFW_CROSSHAIR_CURSOR:
|
|
||||||
return IDC_CROSS;
|
|
||||||
case GLFW_HAND_CURSOR:
|
|
||||||
return IDC_HAND;
|
|
||||||
case GLFW_HRESIZE_CURSOR:
|
|
||||||
return IDC_SIZEWE;
|
|
||||||
case GLFW_VRESIZE_CURSOR:
|
|
||||||
return IDC_SIZENS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves and translates modifier keys
|
// Retrieves and translates modifier keys
|
||||||
//
|
//
|
||||||
static int getKeyMods(void)
|
static int getKeyMods(void)
|
||||||
@@ -578,9 +573,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
if (!window)
|
if (!window)
|
||||||
{
|
{
|
||||||
// This is the message handling for the hidden helper window
|
// This is the message handling for the hidden helper window
|
||||||
|
// and for a regular window during its initial creation
|
||||||
|
|
||||||
switch (uMsg)
|
switch (uMsg)
|
||||||
{
|
{
|
||||||
|
case WM_NCCREATE:
|
||||||
|
{
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
EnableNonClientDpiScaling(hWnd);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_DISPLAYCHANGE:
|
case WM_DISPLAYCHANGE:
|
||||||
_glfwPollMonitorsWin32();
|
_glfwPollMonitorsWin32();
|
||||||
break;
|
break;
|
||||||
@@ -1003,7 +1007,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||||
0, 0, &xoff, &yoff);
|
0, 0, &xoff, &yoff,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
|
||||||
if (window->minwidth != GLFW_DONT_CARE &&
|
if (window->minwidth != GLFW_DONT_CARE &&
|
||||||
window->minheight != GLFW_DONT_CARE)
|
window->minheight != GLFW_DONT_CARE)
|
||||||
@@ -1056,10 +1061,52 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WM_GETDPISCALEDSIZE:
|
||||||
|
{
|
||||||
|
if (window->win32.scaleToMonitor)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Adjust the window size to keep the client area size constant
|
||||||
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
RECT source = {0}, target = {0};
|
||||||
|
SIZE* size = (SIZE*) lParam;
|
||||||
|
|
||||||
|
AdjustWindowRectExForDpi(&source, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
AdjustWindowRectExForDpi(&target, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
LOWORD(wParam));
|
||||||
|
|
||||||
|
size->cx += (target.right - target.left) -
|
||||||
|
(source.right - source.left);
|
||||||
|
size->cy += (target.bottom - target.top) -
|
||||||
|
(source.bottom - source.top);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_DPICHANGED:
|
case WM_DPICHANGED:
|
||||||
{
|
{
|
||||||
const float xscale = HIWORD(wParam) / 96.f;
|
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
const float yscale = LOWORD(wParam) / 96.f;
|
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
|
|
||||||
|
// Only apply the suggested size if the OS is new enough to have
|
||||||
|
// sent a WM_GETDPISCALEDSIZE before this
|
||||||
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
RECT* suggested = (RECT*) lParam;
|
||||||
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
|
suggested->left,
|
||||||
|
suggested->top,
|
||||||
|
suggested->right - suggested->left,
|
||||||
|
suggested->bottom - suggested->top,
|
||||||
|
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||||
|
}
|
||||||
|
|
||||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1146,7 +1193,8 @@ static int createNativeWindow(_GLFWwindow* window,
|
|||||||
|
|
||||||
getFullWindowSize(style, exStyle,
|
getFullWindowSize(style, exStyle,
|
||||||
wndconfig->width, wndconfig->height,
|
wndconfig->width, wndconfig->height,
|
||||||
&fullWidth, &fullHeight);
|
&fullWidth, &fullHeight,
|
||||||
|
USER_DEFAULT_SCREEN_DPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
||||||
@@ -1185,6 +1233,40 @@ static int createNativeWindow(_GLFWwindow* window,
|
|||||||
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window->win32.scaleToMonitor = wndconfig->scaleToMonitor;
|
||||||
|
|
||||||
|
// Adjust window size to account for DPI scaling of the window frame and
|
||||||
|
// optionally DPI scaling of the client area
|
||||||
|
// This cannot be done until we know what monitor it was placed on
|
||||||
|
if (!window->monitor)
|
||||||
|
{
|
||||||
|
RECT rect = { 0, 0, wndconfig->width, wndconfig->height };
|
||||||
|
|
||||||
|
if (wndconfig->scaleToMonitor)
|
||||||
|
{
|
||||||
|
float xscale, yscale;
|
||||||
|
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||||
|
rect.right = (int) (rect.right * xscale);
|
||||||
|
rect.bottom = (int) (rect.bottom * yscale);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||||
|
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||||
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||||
|
|
||||||
|
SetWindowPos(window->win32.handle, NULL,
|
||||||
|
rect.left, rect.top,
|
||||||
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
|
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||||
|
}
|
||||||
|
|
||||||
DragAcceptFiles(window->win32.handle, TRUE);
|
DragAcceptFiles(window->win32.handle, TRUE);
|
||||||
|
|
||||||
if (fbconfig->transparent)
|
if (fbconfig->transparent)
|
||||||
@@ -1244,20 +1326,6 @@ void _glfwUnregisterWindowClassWin32(void)
|
|||||||
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether desktop compositing is enabled
|
|
||||||
//
|
|
||||||
GLFWbool _glfwIsCompositionEnabledWin32(void)
|
|
||||||
{
|
|
||||||
if (IsWindowsVistaOrGreater())
|
|
||||||
{
|
|
||||||
BOOL enabled;
|
|
||||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)))
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
@@ -1395,8 +1463,19 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
|||||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
||||||
{
|
{
|
||||||
RECT rect = { xpos, ypos, xpos, ypos };
|
RECT rect = { xpos, ypos, xpos, ypos };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
||||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
||||||
}
|
}
|
||||||
@@ -1425,8 +1504,19 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
RECT rect = { 0, 0, width, height };
|
RECT rect = { 0, 0, width, height };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
||||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
||||||
@@ -1481,8 +1571,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
|
|
||||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||||
SetRect(&rect, 0, 0, width, height);
|
SetRect(&rect, 0, 0, width, height);
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
if (left)
|
if (left)
|
||||||
*left = -rect.left;
|
*left = -rect.left;
|
||||||
@@ -1563,8 +1663,19 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
rect.left, rect.top,
|
rect.left, rect.top,
|
||||||
rect.right - rect.left, rect.bottom - rect.top,
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
@@ -1624,8 +1735,18 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|||||||
else
|
else
|
||||||
after = HWND_NOTOPMOST;
|
after = HWND_NOTOPMOST;
|
||||||
|
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
FALSE, getWindowExStyle(window));
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, after,
|
SetWindowPos(window->win32.handle, after,
|
||||||
rect.left, rect.top,
|
rect.left, rect.top,
|
||||||
rect.right - rect.left, rect.bottom - rect.top,
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
@@ -1660,7 +1781,15 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
|||||||
|
|
||||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
|
BOOL enabled;
|
||||||
|
|
||||||
|
if (!window->win32.transparent)
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
if (!IsWindowsVistaOrGreater())
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
||||||
@@ -1867,8 +1996,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
|
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||||
{
|
{
|
||||||
cursor->win32.handle =
|
LPCWSTR name = NULL;
|
||||||
CopyCursor(LoadCursorW(NULL, translateCursorShape(shape)));
|
|
||||||
|
if (shape == GLFW_ARROW_CURSOR)
|
||||||
|
name = IDC_ARROW;
|
||||||
|
else if (shape == GLFW_IBEAM_CURSOR)
|
||||||
|
name = IDC_IBEAM;
|
||||||
|
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||||
|
name = IDC_CROSS;
|
||||||
|
else if (shape == GLFW_HAND_CURSOR)
|
||||||
|
name = IDC_HAND;
|
||||||
|
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||||
|
name = IDC_SIZEWE;
|
||||||
|
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||||
|
name = IDC_SIZENS;
|
||||||
|
else
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
cursor->win32.handle = CopyCursor(LoadCursorW(NULL, name));
|
||||||
if (!cursor->win32.handle)
|
if (!cursor->win32.handle)
|
||||||
{
|
{
|
||||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
7
glfw/window.c
vendored
7
glfw/window.c
vendored
@@ -393,6 +393,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
|||||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||||
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
return;
|
return;
|
||||||
|
case GLFW_SCALE_TO_MONITOR:
|
||||||
|
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
|
return;
|
||||||
case GLFW_CENTER_CURSOR:
|
case GLFW_CENTER_CURSOR:
|
||||||
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
return;
|
return;
|
||||||
@@ -457,6 +460,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
|
|||||||
strncpy(_glfw.hints.window.x11.instanceName, value,
|
strncpy(_glfw.hints.window.x11.instanceName, value,
|
||||||
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
||||||
return;
|
return;
|
||||||
|
case GLFW_WAYLAND_APP_ID:
|
||||||
|
strncpy(_glfw.hints.window.wl.appId, value,
|
||||||
|
sizeof(_glfw.hints.window.wl.appId) - 1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
||||||
|
|||||||
32
glfw/wl_init.c
vendored
32
glfw/wl_init.c
vendored
@@ -139,6 +139,7 @@ static void setCursor(const char* name)
|
|||||||
"Wayland: Standard cursor not found");
|
"Wayland: Standard cursor not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: handle animated cursors too.
|
||||||
image = cursor->images[0];
|
image = cursor->images[0];
|
||||||
|
|
||||||
if (!image)
|
if (!image)
|
||||||
@@ -560,6 +561,9 @@ static void registryHandleGlobal(void* data,
|
|||||||
_glfw.wl.seatVersion);
|
_glfw.wl.seatVersion);
|
||||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||||
}
|
}
|
||||||
|
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||||
|
_glfwSetupWaylandDataDevice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||||
{
|
{
|
||||||
@@ -593,6 +597,17 @@ static void registryHandleGlobal(void* data,
|
|||||||
&zwp_idle_inhibit_manager_v1_interface,
|
&zwp_idle_inhibit_manager_v1_interface,
|
||||||
1);
|
1);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(interface, "wl_data_device_manager") == 0)
|
||||||
|
{
|
||||||
|
_glfw.wl.dataDeviceManager =
|
||||||
|
wl_registry_bind(registry, name,
|
||||||
|
&wl_data_device_manager_interface,
|
||||||
|
1);
|
||||||
|
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||||
|
_glfwSetupWaylandDataDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registryHandleGlobalRemove(void *data,
|
static void registryHandleGlobalRemove(void *data,
|
||||||
@@ -674,7 +689,8 @@ int _glfwPlatformInit(void)
|
|||||||
}
|
}
|
||||||
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
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_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);
|
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
||||||
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
||||||
@@ -761,6 +777,17 @@ void _glfwPlatformTerminate(void)
|
|||||||
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
||||||
if (_glfw.wl.idleInhibitManager)
|
if (_glfw.wl.idleInhibitManager)
|
||||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||||
|
if (_glfw.wl.dataSourceForClipboard)
|
||||||
|
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||||
|
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||||
|
if (_glfw.wl.dataOffers[doi].id) {
|
||||||
|
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_glfw.wl.dataDevice)
|
||||||
|
wl_data_device_destroy(_glfw.wl.dataDevice);
|
||||||
|
if (_glfw.wl.dataDeviceManager)
|
||||||
|
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
|
||||||
if (_glfw.wl.registry)
|
if (_glfw.wl.registry)
|
||||||
wl_registry_destroy(_glfw.wl.registry);
|
wl_registry_destroy(_glfw.wl.registry);
|
||||||
if (_glfw.wl.display)
|
if (_glfw.wl.display)
|
||||||
@@ -769,6 +796,9 @@ void _glfwPlatformTerminate(void)
|
|||||||
wl_display_disconnect(_glfw.wl.display);
|
wl_display_disconnect(_glfw.wl.display);
|
||||||
}
|
}
|
||||||
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
||||||
|
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
||||||
|
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetVersionString(void)
|
const char* _glfwPlatformGetVersionString(void)
|
||||||
|
|||||||
29
glfw/wl_platform.h
vendored
29
glfw/wl_platform.h
vendored
@@ -149,6 +149,7 @@ typedef struct _GLFWwindowWayland
|
|||||||
double cursorPosX, cursorPosY;
|
double cursorPosX, cursorPosY;
|
||||||
|
|
||||||
char* title;
|
char* title;
|
||||||
|
char appId[256];
|
||||||
|
|
||||||
// We need to track the monitors the window spans on to calculate the
|
// We need to track the monitors the window spans on to calculate the
|
||||||
// optimal scaling factor.
|
// optimal scaling factor.
|
||||||
@@ -175,6 +176,19 @@ typedef struct _GLFWwindowWayland
|
|||||||
|
|
||||||
} _GLFWwindowWayland;
|
} _GLFWwindowWayland;
|
||||||
|
|
||||||
|
typedef struct _GLFWWaylandDataOffer
|
||||||
|
{
|
||||||
|
struct wl_data_offer *id;
|
||||||
|
const char *mime;
|
||||||
|
int offer_type;
|
||||||
|
size_t idx;
|
||||||
|
int is_self_offer;
|
||||||
|
int has_uri_list;
|
||||||
|
uint32_t source_actions;
|
||||||
|
uint32_t dnd_action;
|
||||||
|
struct wl_surface *surface;
|
||||||
|
} _GLFWWaylandDataOffer;
|
||||||
|
|
||||||
// Wayland-specific global data
|
// Wayland-specific global data
|
||||||
//
|
//
|
||||||
typedef struct _GLFWlibraryWayland
|
typedef struct _GLFWlibraryWayland
|
||||||
@@ -193,6 +207,9 @@ typedef struct _GLFWlibraryWayland
|
|||||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||||
|
struct wl_data_device_manager* dataDeviceManager;
|
||||||
|
struct wl_data_device* dataDevice;
|
||||||
|
struct wl_data_source* dataSourceForClipboard;
|
||||||
|
|
||||||
int compositorVersion;
|
int compositorVersion;
|
||||||
int seatVersion;
|
int seatVersion;
|
||||||
@@ -208,6 +225,7 @@ typedef struct _GLFWlibraryWayland
|
|||||||
id_type keyRepeatTimer;
|
id_type keyRepeatTimer;
|
||||||
_GLFWwindow* keyboardFocus;
|
_GLFWwindow* keyboardFocus;
|
||||||
} keyRepeatInfo;
|
} keyRepeatInfo;
|
||||||
|
id_type cursorAnimationTimer;
|
||||||
_GLFWXKBData xkb;
|
_GLFWXKBData xkb;
|
||||||
_GLFWDBUSData dbus;
|
_GLFWDBUSData dbus;
|
||||||
|
|
||||||
@@ -232,7 +250,11 @@ typedef struct _GLFWlibraryWayland
|
|||||||
} egl;
|
} egl;
|
||||||
|
|
||||||
EventLoopData eventLoopData;
|
EventLoopData eventLoopData;
|
||||||
|
char* clipboardString;
|
||||||
|
char* clipboardSourceString;
|
||||||
|
struct wl_data_offer* clipboardSourceOffer;
|
||||||
|
size_t dataOffersCounter;
|
||||||
|
_GLFWWaylandDataOffer dataOffers[8];
|
||||||
} _GLFWlibraryWayland;
|
} _GLFWlibraryWayland;
|
||||||
|
|
||||||
// Wayland-specific per-monitor data
|
// Wayland-specific per-monitor data
|
||||||
@@ -253,11 +275,14 @@ typedef struct _GLFWmonitorWayland
|
|||||||
//
|
//
|
||||||
typedef struct _GLFWcursorWayland
|
typedef struct _GLFWcursorWayland
|
||||||
{
|
{
|
||||||
struct wl_cursor_image* image;
|
struct wl_cursor* cursor;
|
||||||
struct wl_buffer* buffer;
|
struct wl_buffer* buffer;
|
||||||
int width, height;
|
int width, height;
|
||||||
int xhot, yhot;
|
int xhot, yhot;
|
||||||
|
int currentImage;
|
||||||
} _GLFWcursorWayland;
|
} _GLFWcursorWayland;
|
||||||
|
|
||||||
|
|
||||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||||
|
void _glfwSetupWaylandDataDevice();
|
||||||
|
void animateCursorImage(id_type timer_id, void *data);
|
||||||
|
|||||||
448
glfw/wl_window.c
vendored
448
glfw/wl_window.c
vendored
@@ -37,6 +37,18 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define URI_LIST_MIME "text/uri-list"
|
||||||
|
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
clipboard_mime() {
|
||||||
|
static char buf[128] = {0};
|
||||||
|
if (buf[0] == 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "application/glfw+clipboard-%d", getpid());
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
static void handlePing(void* data,
|
static void handlePing(void* data,
|
||||||
struct wl_shell_surface* shellSurface,
|
struct wl_shell_surface* shellSurface,
|
||||||
uint32_t serial)
|
uint32_t serial)
|
||||||
@@ -681,6 +693,8 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
|||||||
{
|
{
|
||||||
setIdleInhibitor(window, GLFW_FALSE);
|
setIdleInhibitor(window, GLFW_FALSE);
|
||||||
}
|
}
|
||||||
|
if (strlen(window->wl.appId))
|
||||||
|
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
|
||||||
|
|
||||||
wl_surface_commit(window->wl.surface);
|
wl_surface_commit(window->wl.surface);
|
||||||
wl_display_roundtrip(_glfw.wl.display);
|
wl_display_roundtrip(_glfw.wl.display);
|
||||||
@@ -688,6 +702,68 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
|||||||
return GLFW_TRUE;
|
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
|
static void
|
||||||
handleEvents(double timeout)
|
handleEvents(double timeout)
|
||||||
{
|
{
|
||||||
@@ -757,6 +833,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
|||||||
{
|
{
|
||||||
window->wl.justCreated = GLFW_TRUE;
|
window->wl.justCreated = GLFW_TRUE;
|
||||||
window->wl.transparent = fbconfig->transparent;
|
window->wl.transparent = fbconfig->transparent;
|
||||||
|
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
||||||
|
|
||||||
if (!createSurface(window, wndconfig))
|
if (!createSurface(window, wndconfig))
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
@@ -1248,14 +1325,15 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
|||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor->wl.image = standardCursor->images[0];
|
cursor->wl.cursor = standardCursor;
|
||||||
|
cursor->wl.currentImage = 0;
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
||||||
{
|
{
|
||||||
// If it's a standard cursor we don't need to do anything here
|
// If it's a standard cursor we don't need to do anything here
|
||||||
if (cursor->wl.image)
|
if (cursor->wl.cursor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (cursor->wl.buffer)
|
if (cursor->wl.buffer)
|
||||||
@@ -1361,10 +1439,7 @@ static GLFWbool isPointerLocked(_GLFWwindow* window)
|
|||||||
|
|
||||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||||
{
|
{
|
||||||
struct wl_buffer* buffer;
|
|
||||||
struct wl_cursor* defaultCursor;
|
struct wl_cursor* defaultCursor;
|
||||||
struct wl_cursor_image* image;
|
|
||||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
|
||||||
|
|
||||||
if (!_glfw.wl.pointer)
|
if (!_glfw.wl.pointer)
|
||||||
return;
|
return;
|
||||||
@@ -1373,7 +1448,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
|
|
||||||
// If we're not in the correct window just save the 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
|
// 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;
|
return;
|
||||||
|
|
||||||
// Unlock possible pointer lock if no longer disabled.
|
// Unlock possible pointer lock if no longer disabled.
|
||||||
@@ -1383,7 +1458,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
if (window->cursorMode == GLFW_CURSOR_NORMAL)
|
if (window->cursorMode == GLFW_CURSOR_NORMAL)
|
||||||
{
|
{
|
||||||
if (cursor)
|
if (cursor)
|
||||||
image = cursor->wl.image;
|
setCursorImage(&cursor->wl);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
|
defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
|
||||||
@@ -1394,33 +1469,14 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
"Wayland: Standard cursor not found");
|
"Wayland: Standard cursor not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
image = defaultCursor->images[0];
|
_GLFWcursorWayland cursorWayland = {
|
||||||
}
|
defaultCursor,
|
||||||
|
NULL,
|
||||||
if (image)
|
0, 0,
|
||||||
{
|
0, 0,
|
||||||
buffer = wl_cursor_image_get_buffer(image);
|
0
|
||||||
if (!buffer)
|
};
|
||||||
return;
|
setCursorImage(&cursorWayland);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||||
@@ -1435,18 +1491,330 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd)
|
||||||
|
{
|
||||||
|
if (_glfw.wl.clipboardString) {
|
||||||
|
size_t len = strlen(_glfw.wl.clipboardString), pos = 0;
|
||||||
|
double start = glfwGetTime();
|
||||||
|
while (pos < len && glfwGetTime() - start < 2.0) {
|
||||||
|
ssize_t ret = write(fd, _glfw.wl.clipboardString + pos, len - pos);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EINTR) continue;
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret > 0) {
|
||||||
|
start = glfwGetTime();
|
||||||
|
pos += ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||||
|
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
wl_display_flush(_glfw.wl.display);
|
||||||
|
size_t sz = 0, capacity = 0;
|
||||||
|
char *buf = NULL;
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.fd = pipefd[0];
|
||||||
|
fds.events = POLLIN;
|
||||||
|
double start = glfwGetTime();
|
||||||
|
#define bail(...) { \
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||||
|
free(buf); buf = NULL; \
|
||||||
|
close(pipefd[0]); \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
while (glfwGetTime() - start < 2) {
|
||||||
|
int ret = poll(&fds, 1, 2000);
|
||||||
|
if (ret == -1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if (!ret) {
|
||||||
|
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||||
|
}
|
||||||
|
if (capacity <= sz || capacity - sz <= 64) {
|
||||||
|
capacity += 4096;
|
||||||
|
buf = realloc(buf, capacity);
|
||||||
|
if (!buf) {
|
||||||
|
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = read(pipefd[0], buf + sz, capacity - sz - 1);
|
||||||
|
if (ret == -1) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||||
|
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if (ret == 0) { close(pipefd[0]); buf[sz] = 0; return buf; }
|
||||||
|
sz += ret;
|
||||||
|
start = glfwGetTime();
|
||||||
|
}
|
||||||
|
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||||
|
#undef bail
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime)
|
||||||
|
{
|
||||||
|
if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString)
|
||||||
|
return _glfw.wl.clipboardSourceString;
|
||||||
|
free(_glfw.wl.clipboardSourceString);
|
||||||
|
_glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime);
|
||||||
|
return _glfw.wl.clipboardSourceString;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
|
||||||
|
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
|
||||||
|
_glfw.wl.dataSourceForClipboard = NULL;
|
||||||
|
wl_data_source_destroy(wl_data_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const static struct wl_data_source_listener data_source_listener = {
|
||||||
|
.send = _glfwSendClipboardText,
|
||||||
|
.cancelled = data_source_canceled,
|
||||||
|
.target = data_source_target,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void prune_unclaimed_data_offers() {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||||
|
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||||
|
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id == data_offer) {
|
||||||
|
_glfw.wl.dataOffers[i].offer_type = 1;
|
||||||
|
} else if (_glfw.wl.dataOffers[i].offer_type == 1) {
|
||||||
|
_glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prune_unclaimed_data_offers();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id == id) {
|
||||||
|
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||||
|
_glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8";
|
||||||
|
else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0)
|
||||||
|
_glfw.wl.dataOffers[i].mime = "text/plain";
|
||||||
|
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||||
|
_glfw.wl.dataOffers[i].is_self_offer = 1;
|
||||||
|
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||||
|
_glfw.wl.dataOffers[i].has_uri_list = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_offer_source_actions(void *data, struct wl_data_offer* id, uint32_t actions) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id == id) {
|
||||||
|
_glfw.wl.dataOffers[i].source_actions = actions;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_offer_action(void *data, struct wl_data_offer* id, uint32_t action) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id == id) {
|
||||||
|
_glfw.wl.dataOffers[i].dnd_action = action;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct wl_data_offer_listener data_offer_listener = {
|
||||||
|
.offer = handle_offer_mimetype,
|
||||||
|
.source_actions = data_offer_source_actions,
|
||||||
|
.action = data_offer_action,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||||
|
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
|
||||||
|
smallest_idx = _glfw.wl.dataOffers[i].idx;
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
if (_glfw.wl.dataOffers[i].id == NULL) {
|
||||||
|
_glfw.wl.dataOffers[i].id = id;
|
||||||
|
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id);
|
||||||
|
memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||||
|
_glfw.wl.dataOffers[pos].id = id;
|
||||||
|
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
|
||||||
|
end:
|
||||||
|
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].id == id) {
|
||||||
|
_glfw.wl.dataOffers[i].offer_type = 2;
|
||||||
|
_glfw.wl.dataOffers[i].surface = surface;
|
||||||
|
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
|
||||||
|
wl_data_offer_accept(id, serial, mime);
|
||||||
|
} else if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||||
|
_glfw.wl.dataOffers[i].offer_type = 0; // previous drag offer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prune_unclaimed_data_offers();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||||
|
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||||
|
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void drop(void *data, struct wl_data_device *wl_data_device) {
|
||||||
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
|
if (_glfw.wl.dataOffers[i].offer_type == 2) {
|
||||||
|
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
|
||||||
|
if (uri_list) {
|
||||||
|
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||||
|
int count;
|
||||||
|
char** paths = parseUriList(data, &count);
|
||||||
|
|
||||||
|
_GLFWwindow* window = _glfw.windowListHead;
|
||||||
|
while (window)
|
||||||
|
{
|
||||||
|
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
|
||||||
|
_glfwInputDrop(window, count, (const char**) paths);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
window = window->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int k = 0; k < count; k++)
|
||||||
|
free(paths[k]);
|
||||||
|
free(paths);
|
||||||
|
free(uri_list);
|
||||||
|
}
|
||||||
|
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||||
|
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const static struct wl_data_device_listener data_device_listener = {
|
||||||
|
.data_offer = handle_data_offer,
|
||||||
|
.selection = mark_selection_offer,
|
||||||
|
.enter = drag_enter,
|
||||||
|
.motion = motion,
|
||||||
|
.drop = drop,
|
||||||
|
.leave = drag_leave,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||||
|
if (!_glfw.wl.dataDevice) return;
|
||||||
|
if (data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||||
|
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const static struct wl_callback_listener copy_callback_listener = {
|
||||||
|
.done = copy_callback_done
|
||||||
|
};
|
||||||
|
|
||||||
|
void _glfwSetupWaylandDataDevice() {
|
||||||
|
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
|
||||||
|
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline GLFWbool _glfwEnsureDataDevice() {
|
||||||
|
if (!_glfw.wl.dataDeviceManager)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot use clipboard, data device manager is not ready");
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_glfw.wl.dataDevice)
|
||||||
|
{
|
||||||
|
if (!_glfw.wl.seat)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot use clipboard, seat is not ready");
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
if (!_glfw.wl.dataDevice)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot use clipboard, failed to create data device");
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string)
|
void _glfwPlatformSetClipboardString(const char* string)
|
||||||
{
|
{
|
||||||
// TODO
|
if (!_glfwEnsureDataDevice()) return;
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
free(_glfw.wl.clipboardString);
|
||||||
"Wayland: Clipboard setting not implemented yet");
|
_glfw.wl.clipboardString = _glfw_strdup(string);
|
||||||
|
if (_glfw.wl.dataSourceForClipboard)
|
||||||
|
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||||
|
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
|
||||||
|
if (!_glfw.wl.dataSourceForClipboard)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot copy failed to create data source");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
|
||||||
|
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
|
||||||
|
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||||
|
wl_callback_add_listener(callback, ©_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetClipboardString(void)
|
const char* _glfwPlatformGetClipboardString(void)
|
||||||
{
|
{
|
||||||
// TODO
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == 1) {
|
||||||
"Wayland: Clipboard getting not implemented yet");
|
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||||
|
return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
129
glfw/x11_window.c
vendored
129
glfw/x11_window.c
vendored
@@ -161,29 +161,6 @@ static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer po
|
|||||||
event->xproperty.atom == notification->xselection.property;
|
event->xproperty.atom == notification->xselection.property;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translates a GLFW standard cursor to a font cursor shape
|
|
||||||
//
|
|
||||||
static int translateCursorShape(int shape)
|
|
||||||
{
|
|
||||||
switch (shape)
|
|
||||||
{
|
|
||||||
case GLFW_ARROW_CURSOR:
|
|
||||||
return XC_left_ptr;
|
|
||||||
case GLFW_IBEAM_CURSOR:
|
|
||||||
return XC_xterm;
|
|
||||||
case GLFW_CROSSHAIR_CURSOR:
|
|
||||||
return XC_crosshair;
|
|
||||||
case GLFW_HAND_CURSOR:
|
|
||||||
return XC_hand1;
|
|
||||||
case GLFW_HRESIZE_CURSOR:
|
|
||||||
return XC_sb_h_double_arrow;
|
|
||||||
case GLFW_VRESIZE_CURSOR:
|
|
||||||
return XC_sb_v_double_arrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translates an X event modifier state mask
|
// Translates an X event modifier state mask
|
||||||
//
|
//
|
||||||
static int translateState(int state)
|
static int translateState(int state)
|
||||||
@@ -206,23 +183,6 @@ static int translateState(int state)
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the GLFW window corresponding to the specified X11 window
|
|
||||||
//
|
|
||||||
static _GLFWwindow* findWindowByHandle(Window handle)
|
|
||||||
{
|
|
||||||
_GLFWwindow* window;
|
|
||||||
|
|
||||||
if (XFindContext(_glfw.x11.display,
|
|
||||||
handle,
|
|
||||||
_glfw.x11.context,
|
|
||||||
(XPointer*) &window) != 0)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends an EWMH or ICCCM event to the window manager
|
// Sends an EWMH or ICCCM event to the window manager
|
||||||
//
|
//
|
||||||
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
||||||
@@ -390,56 +350,6 @@ static void updateWindowMode(_GLFWwindow* window)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits and translates a text/uri-list into separate file paths
|
|
||||||
// NOTE: This function destroys the provided string
|
|
||||||
//
|
|
||||||
static char** parseUriList(char* text, int* count)
|
|
||||||
{
|
|
||||||
const char* prefix = "file://";
|
|
||||||
char** paths = NULL;
|
|
||||||
char* line;
|
|
||||||
|
|
||||||
*count = 0;
|
|
||||||
|
|
||||||
while ((line = strtok(text, "\r\n")))
|
|
||||||
{
|
|
||||||
text = NULL;
|
|
||||||
|
|
||||||
if (line[0] == '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
|
||||||
{
|
|
||||||
line += strlen(prefix);
|
|
||||||
// TODO: Validate hostname
|
|
||||||
while (*line != '/')
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
(*count)++;
|
|
||||||
|
|
||||||
char* path = calloc(strlen(line) + 1, 1);
|
|
||||||
paths = realloc(paths, *count * sizeof(char*));
|
|
||||||
paths[*count - 1] = path;
|
|
||||||
|
|
||||||
while (*line)
|
|
||||||
{
|
|
||||||
if (line[0] == '%' && line[1] && line[2])
|
|
||||||
{
|
|
||||||
const char digits[3] = { line[1], line[2], '\0' };
|
|
||||||
*path = strtol(digits, NULL, 16);
|
|
||||||
line += 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*path = *line;
|
|
||||||
|
|
||||||
path++;
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode a Unicode code point to a UTF-8 stream
|
// Encode a Unicode code point to a UTF-8 stream
|
||||||
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
||||||
@@ -582,6 +492,15 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
const _GLFWwndconfig* wndconfig,
|
const _GLFWwndconfig* wndconfig,
|
||||||
Visual* visual, int depth)
|
Visual* visual, int depth)
|
||||||
{
|
{
|
||||||
|
int width = wndconfig->width;
|
||||||
|
int height = wndconfig->height;
|
||||||
|
|
||||||
|
if (wndconfig->scaleToMonitor)
|
||||||
|
{
|
||||||
|
width *= _glfw.x11.contentScaleX;
|
||||||
|
height *= _glfw.x11.contentScaleY;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a colormap based on the visual used by the current context
|
// Create a colormap based on the visual used by the current context
|
||||||
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
||||||
_glfw.x11.root,
|
_glfw.x11.root,
|
||||||
@@ -607,7 +526,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||||
_glfw.x11.root,
|
_glfw.x11.root,
|
||||||
0, 0,
|
0, 0,
|
||||||
wndconfig->width, wndconfig->height,
|
width, height,
|
||||||
0, // Border width
|
0, // Border width
|
||||||
depth, // Color depth
|
depth, // Color depth
|
||||||
InputOutput,
|
InputOutput,
|
||||||
@@ -710,7 +629,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
XFree(hints);
|
XFree(hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNormalHints(window, wndconfig->width, wndconfig->height);
|
updateNormalHints(window, width, height);
|
||||||
|
|
||||||
// Set ICCCM WM_CLASS property
|
// Set ICCCM WM_CLASS property
|
||||||
{
|
{
|
||||||
@@ -1243,8 +1162,10 @@ static void processEvent(XEvent *event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window = findWindowByHandle(event->xany.window);
|
if (XFindContext(_glfw.x11.display,
|
||||||
if (window == NULL)
|
event->xany.window,
|
||||||
|
_glfw.x11.context,
|
||||||
|
(XPointer*) &window) != 0)
|
||||||
{
|
{
|
||||||
// This is an event for a window that has already been destroyed
|
// This is an event for a window that has already been destroyed
|
||||||
return;
|
return;
|
||||||
@@ -2680,8 +2601,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
|
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||||
{
|
{
|
||||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display,
|
int native = 0;
|
||||||
translateCursorShape(shape));
|
|
||||||
|
if (shape == GLFW_ARROW_CURSOR)
|
||||||
|
native = XC_left_ptr;
|
||||||
|
else if (shape == GLFW_IBEAM_CURSOR)
|
||||||
|
native = XC_xterm;
|
||||||
|
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||||
|
native = XC_crosshair;
|
||||||
|
else if (shape == GLFW_HAND_CURSOR)
|
||||||
|
native = XC_hand1;
|
||||||
|
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||||
|
native = XC_sb_h_double_arrow;
|
||||||
|
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||||
|
native = XC_sb_v_double_arrow;
|
||||||
|
else
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
|
||||||
if (!cursor->x11.handle)
|
if (!cursor->x11.handle)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
29
glfw/xkb_glfw.c
vendored
29
glfw/xkb_glfw.c
vendored
@@ -33,7 +33,7 @@
|
|||||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||||
|
|
||||||
|
|
||||||
#define map_key(key) { \
|
#define map_key(key) \
|
||||||
switch(key) { \
|
switch(key) { \
|
||||||
S(space, SPACE); \
|
S(space, SPACE); \
|
||||||
S(apostrophe, APOSTROPHE); \
|
S(apostrophe, APOSTROPHE); \
|
||||||
@@ -93,10 +93,6 @@
|
|||||||
D(A, Z, A, Z); \
|
D(A, Z, A, Z); \
|
||||||
R(F1, F25, F1, F25); \
|
R(F1, F25, F1, F25); \
|
||||||
R(KP_0, KP_9, KP_0, KP_9); \
|
R(KP_0, KP_9, KP_0, KP_9); \
|
||||||
default: \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
glfw_key_for_sym(xkb_keysym_t key) {
|
glfw_key_for_sym(xkb_keysym_t key) {
|
||||||
@@ -105,6 +101,13 @@ glfw_key_for_sym(xkb_keysym_t key) {
|
|||||||
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
||||||
#define D(s, e, gs, ...) R(s, e, gs)
|
#define D(s, e, gs, ...) R(s, e, gs)
|
||||||
map_key(key)
|
map_key(key)
|
||||||
|
S(KP_Up, UP);
|
||||||
|
S(KP_Down, DOWN);
|
||||||
|
S(KP_Left, LEFT);
|
||||||
|
S(KP_Right, RIGHT);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return GLFW_KEY_UNKNOWN;
|
return GLFW_KEY_UNKNOWN;
|
||||||
#undef F
|
#undef F
|
||||||
#undef D
|
#undef D
|
||||||
@@ -119,6 +122,9 @@ glfw_xkb_sym_for_key(int key) {
|
|||||||
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
||||||
#define D(...)
|
#define D(...)
|
||||||
map_key(key)
|
map_key(key)
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return GLFW_KEY_UNKNOWN;
|
return GLFW_KEY_UNKNOWN;
|
||||||
#undef F
|
#undef F
|
||||||
#undef D
|
#undef D
|
||||||
@@ -329,12 +335,14 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
|||||||
static KeyEvent key_event = {};
|
static KeyEvent key_event = {};
|
||||||
|
|
||||||
static inline xkb_keysym_t
|
static inline xkb_keysym_t
|
||||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym) {
|
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
|
||||||
|
*compose_completed = 0;
|
||||||
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
|
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
|
||||||
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||||
switch (xkb_compose_state_get_status(composeState)) {
|
switch (xkb_compose_state_get_status(composeState)) {
|
||||||
case XKB_COMPOSE_COMPOSED:
|
case XKB_COMPOSE_COMPOSED:
|
||||||
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
||||||
|
*compose_completed = 1;
|
||||||
return xkb_compose_state_get_one_sym(composeState);
|
return xkb_compose_state_get_one_sym(composeState);
|
||||||
case XKB_COMPOSE_COMPOSING:
|
case XKB_COMPOSE_COMPOSING:
|
||||||
case XKB_COMPOSE_CANCELLED:
|
case XKB_COMPOSE_CANCELLED:
|
||||||
@@ -432,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",
|
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->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")),
|
(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");
|
} else debug("↳ discarded\n");
|
||||||
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
|
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
|
||||||
}
|
}
|
||||||
@@ -465,8 +473,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
|||||||
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
||||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||||
const char *text_type = "composed_text";
|
const char *text_type = "composed_text";
|
||||||
glfw_sym = compose_symbol(sg->composeState, syms[0]);
|
int compose_completed;
|
||||||
if (glfw_sym == XKB_KEY_NoSymbol) {
|
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
|
||||||
|
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
|
||||||
debug("compose not complete, ignoring.\n");
|
debug("compose not complete, ignoring.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from .search import BadRegex, Search
|
|||||||
try:
|
try:
|
||||||
from .highlight import initialize_highlighter, highlight_collection
|
from .highlight import initialize_highlighter, highlight_collection
|
||||||
except ImportError:
|
except ImportError:
|
||||||
initialize_highlighter = None
|
initialize_highlighter = highlight_collection = None
|
||||||
|
|
||||||
|
|
||||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ Horizontal alignment for the displayed image.
|
|||||||
--place
|
--place
|
||||||
Choose where on the screen to display the image. The image will
|
Choose where on the screen to display the image. The image will
|
||||||
be scaled to fit into the specified rectangle. The syntax for
|
be scaled to fit into the specified rectangle. The syntax for
|
||||||
specifying rectanges is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements
|
specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||||
are in cells (i.e. cursor positions) with the origin :italic:`(0, 0)` at
|
All measurements are in cells (i.e. cursor positions) with the
|
||||||
the top-left corner of the screen.
|
origin :italic:`(0, 0)` at the top-left corner of the screen.
|
||||||
|
|
||||||
|
|
||||||
--scale-up
|
--scale-up
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Resize(Handler):
|
|||||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||||
payload = resize_window(global_opts, opts, items)
|
payload = resize_window(global_opts, opts, items)
|
||||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
|
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||||
self.write(encode_send(send))
|
self.write(encode_send(send))
|
||||||
|
|
||||||
def on_kitty_cmd_response(self, response):
|
def on_kitty_cmd_response(self, response):
|
||||||
|
|||||||
103
kitty/boss.py
103
kitty/boss.py
@@ -19,10 +19,11 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
ChildMonitor, background_opacity_of, change_background_opacity,
|
ChildMonitor, background_opacity_of, change_background_opacity,
|
||||||
create_os_window, current_os_window, destroy_global_data,
|
change_os_window_state, create_os_window, current_os_window,
|
||||||
get_clipboard_string, glfw_post_empty_event, global_font_size,
|
destroy_global_data, get_clipboard_string, glfw_post_empty_event,
|
||||||
mark_os_window_for_close, os_window_font_size, patch_global_colors,
|
global_font_size, mark_os_window_for_close, os_window_font_size,
|
||||||
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
|
patch_global_colors, set_clipboard_string, set_in_sequence_mode,
|
||||||
|
toggle_fullscreen
|
||||||
)
|
)
|
||||||
from .keys import get_shortcut, shortcut_matches
|
from .keys import get_shortcut, shortcut_matches
|
||||||
from .layout import set_draw_minimal_borders
|
from .layout import set_draw_minimal_borders
|
||||||
@@ -47,6 +48,23 @@ def listen_on(spec):
|
|||||||
return s.fileno()
|
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: # {{{
|
class DumpCommands: # {{{
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
@@ -103,6 +121,11 @@ class Boss:
|
|||||||
if new_os_window_trigger is not None:
|
if new_os_window_trigger is not None:
|
||||||
self.keymap.pop(new_os_window_trigger, None)
|
self.keymap.pop(new_os_window_trigger, None)
|
||||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||||
|
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):
|
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:
|
if os_window_id is None:
|
||||||
@@ -768,25 +791,54 @@ class Boss:
|
|||||||
if tm is not None:
|
if tm is not None:
|
||||||
tm.next_tab(-1)
|
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):
|
def args_to_special_window(self, args, cwd_from=None):
|
||||||
args = list(args)
|
args = list(args)
|
||||||
stdin = None
|
stdin = None
|
||||||
w = self.active_window
|
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] != '@':
|
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:
|
if stdin is not None:
|
||||||
stdin = stdin.encode('utf-8')
|
stdin = stdin.encode('utf-8')
|
||||||
del args[0]
|
del args[0]
|
||||||
@@ -794,13 +846,13 @@ class Boss:
|
|||||||
cmd = []
|
cmd = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if arg == '@selection':
|
if arg == '@selection':
|
||||||
arg = data_for_at(arg)
|
arg = data_for_at(w, arg)
|
||||||
if not arg:
|
if not arg:
|
||||||
continue
|
continue
|
||||||
cmd.append(arg)
|
cmd.append(arg)
|
||||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
|
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
|
special_window = None
|
||||||
if args:
|
if args:
|
||||||
if isinstance(args, SpecialWindowInstance):
|
if isinstance(args, SpecialWindowInstance):
|
||||||
@@ -809,15 +861,22 @@ class Boss:
|
|||||||
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
||||||
tm = self.active_tab_manager
|
tm = self.active_tab_manager
|
||||||
if tm is not None:
|
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):
|
def new_tab(self, *args):
|
||||||
self._new_tab(args)
|
self._create_tab(args)
|
||||||
|
|
||||||
def new_tab_with_cwd(self, *args):
|
def new_tab_with_cwd(self, *args):
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
cwd_from = w.child.pid if w is not None else None
|
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):
|
def _new_window(self, args, cwd_from=None):
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ vec4 calculate_foreground() {
|
|||||||
// Since strike and text are the same color, we simply add the alpha values
|
// Since strike and text are the same color, we simply add the alpha values
|
||||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||||
// Underline color might be different, so alpha blend
|
// Underline color might be different, so alpha blend
|
||||||
vec4 ans = alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha);
|
||||||
return mix(ans, cursor_color_vec, cursor_alpha);
|
return mix(ans, cursor_color_vec, cursor_alpha);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
static inline char**
|
static inline char**
|
||||||
serialize_string_tuple(PyObject *src) {
|
serialize_string_tuple(PyObject *src) {
|
||||||
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
||||||
|
|
||||||
char **ans = calloc(sz + 1, sizeof(char*));
|
char **ans = calloc(sz + 1, sizeof(char*));
|
||||||
if (!ans) fatal("Out of memory");
|
if (!ans) fatal("Out of memory");
|
||||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||||
@@ -28,6 +29,13 @@ serialize_string_tuple(PyObject *src) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
free_string_tuple(char** data) {
|
||||||
|
size_t i = 0;
|
||||||
|
while(data[i]) free(data[i++]);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
extern char **environ;
|
extern char **environ;
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -125,8 +133,8 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#undef exit_on_err
|
#undef exit_on_err
|
||||||
free(argv);
|
free_string_tuple(argv);
|
||||||
free(env);
|
free_string_tuple(env);
|
||||||
if (PyErr_Occurred()) return NULL;
|
if (PyErr_Occurred()) return NULL;
|
||||||
return PyLong_FromLong(pid);
|
return PyLong_FromLong(pid);
|
||||||
}
|
}
|
||||||
|
|||||||
13
kitty/cli.py
13
kitty/cli.py
@@ -464,7 +464,7 @@ def options_spec():
|
|||||||
dest=cls
|
dest=cls
|
||||||
default={appname}
|
default={appname}
|
||||||
condition=not is_macos
|
condition=not is_macos
|
||||||
Set the class part of the :italic:`WM_CLASS` window property
|
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
||||||
|
|
||||||
|
|
||||||
--name
|
--name
|
||||||
@@ -474,7 +474,9 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
|
|||||||
|
|
||||||
--title -T
|
--title -T
|
||||||
Set the window title. This will override any title set by the program running inside kitty. So
|
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
|
--config -c
|
||||||
@@ -546,6 +548,13 @@ specify this address. This option will be ignored, unless you set
|
|||||||
option as it is read automatically from the environment.
|
option as it is read automatically from the environment.
|
||||||
|
|
||||||
|
|
||||||
|
--start-as
|
||||||
|
type=choices
|
||||||
|
default=normal
|
||||||
|
choices=normal,fullscreen,maximized,minimized
|
||||||
|
Control how the initial kitty window is created.
|
||||||
|
|
||||||
|
|
||||||
# Debugging options
|
# Debugging options
|
||||||
|
|
||||||
--version -v
|
--version -v
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class UnknownLayout(ValueError):
|
|||||||
hide_traceback = True
|
hide_traceback = True
|
||||||
|
|
||||||
|
|
||||||
|
cmap = {}
|
||||||
|
|
||||||
|
|
||||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
||||||
|
|
||||||
def w(func):
|
def w(func):
|
||||||
@@ -45,6 +48,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
|||||||
func.no_response = no_response
|
func.no_response = no_response
|
||||||
func.string_return_is_error = string_return_is_error
|
func.string_return_is_error = string_return_is_error
|
||||||
func.args_count = 0 if not argspec else args_count
|
func.args_count = 0 if not argspec else args_count
|
||||||
|
cmap[func.name] = func
|
||||||
return func
|
return func
|
||||||
return w
|
return w
|
||||||
|
|
||||||
@@ -278,7 +282,7 @@ def set_tab_title(boss, window, payload):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
# set_layout {{{
|
# goto_layout {{{
|
||||||
@cmd(
|
@cmd(
|
||||||
'Set the window layout',
|
'Set the window layout',
|
||||||
'Set the window layout in the specified tab (or the active tab if not specified).'
|
'Set the window layout in the specified tab (or the active tab if not specified).'
|
||||||
@@ -313,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 {{{
|
# close_window {{{
|
||||||
@cmd(
|
@cmd(
|
||||||
'Close the specified window(s)',
|
'Close the specified window(s)',
|
||||||
@@ -363,7 +395,7 @@ reset the layout to its default configuration.
|
|||||||
|
|
||||||
--self
|
--self
|
||||||
type=bool-set
|
type=bool-set
|
||||||
If specified close the window this command is run in, rather than the active window.
|
If specified resize the window this command is run in, rather than the active window.
|
||||||
''',
|
''',
|
||||||
argspec='',
|
argspec='',
|
||||||
string_return_is_error=True
|
string_return_is_error=True
|
||||||
@@ -541,7 +573,9 @@ def focus_window(boss, window, payload):
|
|||||||
raise MatchError(match)
|
raise MatchError(match)
|
||||||
for window in windows:
|
for window in windows:
|
||||||
if window:
|
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
|
break
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@@ -550,10 +584,20 @@ def focus_window(boss, window, payload):
|
|||||||
@cmd(
|
@cmd(
|
||||||
'Focus the specified tab',
|
'Focus the specified tab',
|
||||||
'The active window in the specified tab will be focused.',
|
'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='',
|
argspec='',
|
||||||
|
no_response=True,
|
||||||
)
|
)
|
||||||
def cmd_focus_tab(global_opts, opts, args):
|
def cmd_focus_tab(global_opts, opts, args):
|
||||||
|
if opts.no_response:
|
||||||
|
global_opts.no_command_response = True
|
||||||
return {'match': opts.match}
|
return {'match': opts.match}
|
||||||
|
|
||||||
|
|
||||||
@@ -787,9 +831,6 @@ def kitten(boss, window, payload):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
|
||||||
|
|
||||||
|
|
||||||
def cli_params_for(func):
|
def cli_params_for(func):
|
||||||
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)
|
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,29 @@ cocoa_focus_window(void *w) {
|
|||||||
[window makeKeyWindow];
|
[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*
|
static PyObject*
|
||||||
cocoa_get_lang(PyObject UNUSED *self) {
|
cocoa_get_lang(PyObject UNUSED *self) {
|
||||||
NSString* locale = nil;
|
NSString* locale = nil;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ completion_scripts = {
|
|||||||
'zsh': '''
|
'zsh': '''
|
||||||
_kitty() {
|
_kitty() {
|
||||||
local src
|
local src
|
||||||
# Send all words upto the word the cursor is currently on
|
# Send all words up to the word the cursor is currently on
|
||||||
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
eval ${src}
|
eval ${src}
|
||||||
@@ -44,7 +44,7 @@ _kitty() {
|
|||||||
compdef _kitty kitty
|
compdef _kitty kitty
|
||||||
''',
|
''',
|
||||||
'bash': '''
|
'bash': '''
|
||||||
kitty_completions() {
|
_kitty_completions() {
|
||||||
local src
|
local src
|
||||||
local limit
|
local limit
|
||||||
# Send all words up to the word the cursor is currently on
|
# Send all words up to the word the cursor is currently on
|
||||||
@@ -55,7 +55,15 @@ kitty_completions() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
complete -o nospace -F kitty_completions kitty
|
complete -o nospace -F _kitty_completions kitty
|
||||||
|
''',
|
||||||
|
'fish': '''
|
||||||
|
function __kitty_completions
|
||||||
|
# Send all words up to the one before the cursor
|
||||||
|
commandline -cop | kitty +complete fish
|
||||||
|
end
|
||||||
|
|
||||||
|
complete -f -c kitty -a "(__kitty_completions)"
|
||||||
''',
|
''',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +94,11 @@ def bash_input_parser(data):
|
|||||||
return words, new_word
|
return words, new_word
|
||||||
|
|
||||||
|
|
||||||
|
@input_parser
|
||||||
|
def fish_input_parser(data):
|
||||||
|
return data.rstrip().splitlines(), True
|
||||||
|
|
||||||
|
|
||||||
@output_serializer
|
@output_serializer
|
||||||
def zsh_output_serializer(ans):
|
def zsh_output_serializer(ans):
|
||||||
lines = []
|
lines = []
|
||||||
@@ -95,6 +108,10 @@ def zsh_output_serializer(ans):
|
|||||||
cmd += ['-S', '""']
|
cmd += ['-S', '""']
|
||||||
if description in ans.files_groups:
|
if description in ans.files_groups:
|
||||||
cmd.append('-f')
|
cmd.append('-f')
|
||||||
|
common_prefix = os.path.commonprefix(tuple(matches))
|
||||||
|
if common_prefix:
|
||||||
|
cmd.extend(('-p', shlex.quote(common_prefix)))
|
||||||
|
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
|
||||||
cmd.append('--')
|
cmd.append('--')
|
||||||
for word, description in matches.items():
|
for word, description in matches.items():
|
||||||
cmd.append(shlex.quote(word))
|
cmd.append(shlex.quote(word))
|
||||||
@@ -117,6 +134,16 @@ def bash_output_serializer(ans):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
@output_serializer
|
||||||
|
def fish_output_serializer(ans):
|
||||||
|
lines = []
|
||||||
|
for matches in ans.match_groups.values():
|
||||||
|
for word in matches:
|
||||||
|
lines.append(shlex.quote(word))
|
||||||
|
# debug('\n'.join(lines))
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||||
cmds = ['@' + c for c in cmap]
|
cmds = ['@' + c for c in cmap]
|
||||||
ans.match_groups['Entry points'] = {
|
ans.match_groups['Entry points'] = {
|
||||||
@@ -197,21 +224,40 @@ def complete_remote_command(ans, cmd_name, words, new_word):
|
|||||||
complete_alias_map(ans, words, new_word, alias_map)
|
complete_alias_map(ans, words, new_word, alias_map)
|
||||||
|
|
||||||
|
|
||||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=lambda filename: True):
|
def path_completion(prefix=''):
|
||||||
|
prefix = prefix.replace(r'\ ', ' ')
|
||||||
dirs, files = [], []
|
dirs, files = [], []
|
||||||
base = '.'
|
base = '.'
|
||||||
if prefix.endswith('/'):
|
if prefix.endswith('/'):
|
||||||
base = prefix
|
base = prefix
|
||||||
elif '/' in prefix:
|
elif '/' in prefix:
|
||||||
base = os.path.dirname(prefix)
|
base = os.path.dirname(prefix)
|
||||||
for x in os.scandir(base):
|
src = os.path.expandvars(os.path.expanduser(base))
|
||||||
q = os.path.relpath(x.path)
|
src_prefix = os.path.abspath(os.path.expandvars(os.path.expanduser(prefix))) if prefix else ''
|
||||||
if x.is_dir():
|
try:
|
||||||
if q.startswith(prefix):
|
items = os.scandir(src)
|
||||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
except FileNotFoundError:
|
||||||
|
items = ()
|
||||||
|
for x in items:
|
||||||
|
abspath = os.path.abspath(x.path)
|
||||||
|
if prefix and not abspath.startswith(src_prefix):
|
||||||
|
continue
|
||||||
|
if prefix:
|
||||||
|
q = prefix + abspath[len(src_prefix):].lstrip(os.sep)
|
||||||
|
q = os.path.expandvars(os.path.expanduser(q))
|
||||||
else:
|
else:
|
||||||
if q.startswith(prefix) and predicate(q):
|
q = os.path.relpath(abspath)
|
||||||
files.append(q)
|
if x.is_dir():
|
||||||
|
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||||
|
else:
|
||||||
|
files.append(q)
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None):
|
||||||
|
dirs, files = path_completion(prefix or '')
|
||||||
|
files = filter(predicate, files)
|
||||||
|
|
||||||
if dirs:
|
if dirs:
|
||||||
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||||
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||||
@@ -232,6 +278,17 @@ def complete_icat_args(ans, opt, prefix):
|
|||||||
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||||
|
|
||||||
|
|
||||||
|
def config_file_predicate(filename):
|
||||||
|
return filename.endswith('.conf')
|
||||||
|
|
||||||
|
|
||||||
|
def complete_diff_args(ans, opt, prefix):
|
||||||
|
if opt is None:
|
||||||
|
complete_files_and_dirs(ans, prefix, 'Files')
|
||||||
|
elif opt['dest'] == 'config':
|
||||||
|
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
|
||||||
|
|
||||||
|
|
||||||
def complete_kitten(ans, kitten, words, new_word):
|
def complete_kitten(ans, kitten, words, new_word):
|
||||||
try:
|
try:
|
||||||
cd = get_kitten_cli_docs(kitten)
|
cd = get_kitten_cli_docs(kitten)
|
||||||
@@ -247,7 +304,8 @@ def complete_kitten(ans, kitten, words, new_word):
|
|||||||
for alias in opt['aliases']:
|
for alias in opt['aliases']:
|
||||||
option_map[alias] = opt
|
option_map[alias] = opt
|
||||||
complete_alias_map(ans, words, new_word, option_map, {
|
complete_alias_map(ans, words, new_word, option_map, {
|
||||||
'icat': complete_icat_args
|
'icat': complete_icat_args,
|
||||||
|
'diff': complete_diff_args,
|
||||||
}.get(kitten))
|
}.get(kitten))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,22 @@ named_keys = {
|
|||||||
|
|
||||||
def parse_shortcut(sc):
|
def parse_shortcut(sc):
|
||||||
parts = sc.split('+')
|
parts = sc.split('+')
|
||||||
mods = parse_mods(parts[:-1], sc)
|
mods = 0
|
||||||
if mods is None:
|
if len(parts) > 1:
|
||||||
return None, None, None
|
mods = parse_mods(parts[:-1], sc)
|
||||||
|
if mods is None:
|
||||||
|
return None, None, None
|
||||||
key = parts[-1].upper()
|
key = parts[-1].upper()
|
||||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||||
is_native = False
|
is_native = False
|
||||||
if key is None:
|
if key is None:
|
||||||
key = defines.key_for_native_key_name(parts[-1])
|
if parts[-1].startswith('0x'):
|
||||||
|
try:
|
||||||
|
key = int(parts[-1], 16)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
key = defines.key_for_native_key_name(parts[-1])
|
||||||
is_native = key is not None
|
is_native = key is not None
|
||||||
return mods, is_native, key
|
return mods, is_native, key
|
||||||
|
|
||||||
@@ -132,6 +140,39 @@ def clear_terminal(func, rest):
|
|||||||
return func, args
|
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):
|
def parse_key_action(action):
|
||||||
parts = action.split(' ', 1)
|
parts = action.split(' ', 1)
|
||||||
func = parts[0]
|
func = parts[0]
|
||||||
|
|||||||
@@ -95,6 +95,21 @@ for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
|||||||
prefix. Note that you should only use an XKB key name for keys that are not present
|
prefix. Note that you should only use an XKB key name for keys that are not present
|
||||||
in the list of GLFW keys.
|
in the list of GLFW keys.
|
||||||
|
|
||||||
|
Finally, you can use raw system key codes to map keys. To see the system key code
|
||||||
|
for a key, start kitty with the :option:`kitty --debug-keyboard` option. Then kitty will
|
||||||
|
output some debug text for every key event. In that text look for ``native_code``
|
||||||
|
the value of that becomes the key name in the shortcut. For example:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
|
||||||
|
|
||||||
|
Here, the key name for the :kbd:`A` key is :kbd:`0x61` and you can use it with::
|
||||||
|
|
||||||
|
map ctrl+0x61 something
|
||||||
|
|
||||||
|
to map :kbd:`ctrl+a` to something.
|
||||||
|
|
||||||
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
||||||
assigned in the default configuration.
|
assigned in the default configuration.
|
||||||
|
|
||||||
@@ -131,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
|
map ctrl+alt+2 goto_tab 2
|
||||||
|
|
||||||
Just as with :code:`new_window` above, you can also pass the name of arbitrary
|
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': [
|
'shortcuts.layout': [
|
||||||
_('Layout management'), '',
|
_('Layout management'), '',
|
||||||
@@ -140,6 +160,11 @@ You can also create shortcuts to switch to specific layouts::
|
|||||||
|
|
||||||
map ctrl+alt+t goto_layout tall
|
map ctrl+alt+t goto_layout tall
|
||||||
map ctrl+alt+s goto_layout stack
|
map ctrl+alt+s goto_layout stack
|
||||||
|
|
||||||
|
Similarly, to switch back to the previous layout::
|
||||||
|
|
||||||
|
map ctrl+alt+p last_used_layout
|
||||||
|
|
||||||
''')],
|
''')],
|
||||||
'shortcuts.fonts': [
|
'shortcuts.fonts': [
|
||||||
_('Font sizes'), _('''\
|
_('Font sizes'), _('''\
|
||||||
@@ -290,9 +315,19 @@ o('cursor_stop_blinking_after', 15.0, option_type=positive_float)
|
|||||||
|
|
||||||
g('scrollback') # {{{
|
g('scrollback') # {{{
|
||||||
|
|
||||||
o('scrollback_lines', 2000, option_type=positive_int, long_text=_('''
|
|
||||||
|
def scrollback_lines(x):
|
||||||
|
x = int(x)
|
||||||
|
if x < 0:
|
||||||
|
x = 2 ** 32 - 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||||
on demand.'''))
|
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||||
|
very large scrollback is not recommended a it can slow down resizing of the terminal
|
||||||
|
and also use large amounts of RAM.'''))
|
||||||
|
|
||||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||||
Program with which to view scrollback in a new window. The scrollback buffer is
|
Program with which to view scrollback in a new window. The scrollback buffer is
|
||||||
@@ -432,7 +467,7 @@ def to_layout_names(raw):
|
|||||||
parts = [x.strip().lower() for x in raw.split(',')]
|
parts = [x.strip().lower() for x in raw.split(',')]
|
||||||
ans = []
|
ans = []
|
||||||
for p in parts:
|
for p in parts:
|
||||||
if p == '*':
|
if p in ('*', 'all'):
|
||||||
ans.extend(sorted(all_layouts))
|
ans.extend(sorted(all_layouts))
|
||||||
continue
|
continue
|
||||||
name = p.partition(':')[0]
|
name = p.partition(':')[0]
|
||||||
@@ -444,7 +479,7 @@ def to_layout_names(raw):
|
|||||||
|
|
||||||
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
||||||
The enabled window layouts. A comma separated list of layout names. The special
|
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`.
|
startup layout. For a list of available layouts, see the :ref:`layouts`.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
@@ -749,6 +784,23 @@ o('macos_window_resizable', True, long_text=_('''
|
|||||||
Disable this if you want kitty top-level (OS) windows to not be resizable
|
Disable this if you want kitty top-level (OS) windows to not be resizable
|
||||||
on macOS.
|
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') # {{{
|
g('shortcuts') # {{{
|
||||||
@@ -795,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_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
|
||||||
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
|
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=_('''
|
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. '''))
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
@@ -850,6 +914,8 @@ g('shortcuts.tab') # {{{
|
|||||||
if is_macos:
|
if is_macos:
|
||||||
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
|
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
|
||||||
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
||||||
|
if is_macos:
|
||||||
|
k('previous_tab', 'ctrl+shift+tab', 'previous_tab', _('Previous tab'))
|
||||||
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
||||||
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
||||||
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from collections import namedtuple
|
|||||||
|
|
||||||
|
|
||||||
appname = 'kitty'
|
appname = 'kitty'
|
||||||
version = (0, 12, 0)
|
version = (0, 12, 2)
|
||||||
str_version = '.'.join(map(str, version))
|
str_version = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos = 'darwin' in _plat
|
is_macos = 'darwin' in _plat
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
// *Bell*: Beeps.
|
// *Bell*: Beeps.
|
||||||
#define BEL 0x07
|
#define BEL 0x07
|
||||||
|
|
||||||
// *Backspace*: Backspace one column, but not past the begining of the
|
// *Backspace*: Backspace one column, but not past the beginning of the
|
||||||
// line.
|
// line.
|
||||||
#define BS 0x08
|
#define BS 0x08
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,14 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
|
|||||||
else start++;
|
else start++;
|
||||||
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; }
|
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; }
|
||||||
if (new_font == old_font) { CFRelease(new_font); continue; }
|
if (new_font == old_font) { CFRelease(new_font); continue; }
|
||||||
|
CFStringRef name = CTFontCopyPostScriptName(new_font);
|
||||||
|
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
|
||||||
|
CFRelease(name);
|
||||||
|
if (cr == kCFCompareEqualTo) {
|
||||||
|
CFRelease(new_font);
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
return new_font;
|
return new_font;
|
||||||
}
|
}
|
||||||
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
||||||
@@ -364,10 +372,14 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
|
|||||||
CGContextSetShouldAntialias(render_ctx, true);
|
CGContextSetShouldAntialias(render_ctx, true);
|
||||||
CGContextSetShouldSmoothFonts(render_ctx, true);
|
CGContextSetShouldSmoothFonts(render_ctx, true);
|
||||||
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
|
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);
|
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
|
||||||
CGContextSetTextPosition(render_ctx, 0, height - baseline);
|
CGContextSetTextPosition(render_ctx, 0, height - baseline);
|
||||||
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
|
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
|
||||||
|
CGContextRelease(render_ctx);
|
||||||
|
CGColorSpaceRelease(gray_color_space);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
|
|||||||
char *p = buf;
|
char *p = buf;
|
||||||
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
|
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
|
||||||
if (intensity_differs) {
|
if (intensity_differs) {
|
||||||
if (!self->bold || !self->dim) { P("%d", 22); }
|
if (!self->bold && !self->dim) { P("%d", 22); }
|
||||||
else P("%d;%d", 1, 2);
|
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->italic != prev->italic) P("%d", self->italic ? 3 : 23);
|
||||||
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||||
static char ctty[L_ctermid+1];
|
static char ctty[L_ctermid+1];
|
||||||
int fd = open(ctermid(ctty), flags);
|
int fd = open(ctermid(ctty), flags);
|
||||||
|
if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
||||||
if (!termios_p) return PyErr_NoMemory();
|
if (!termios_p) return PyErr_NoMemory();
|
||||||
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ void colorprofile_pop_dynamic_colors(ColorProfile*);
|
|||||||
|
|
||||||
void set_mouse_cursor(MouseShape);
|
void set_mouse_cursor(MouseShape);
|
||||||
void enter_event();
|
void enter_event();
|
||||||
void mouse_event(int, int);
|
void mouse_event(int, int, int);
|
||||||
void focus_in_event();
|
void focus_in_event();
|
||||||
void wakeup_io_loop(bool);
|
void wakeup_io_loop(bool);
|
||||||
void scroll_event(double, double, int);
|
void scroll_event(double, double, int);
|
||||||
|
|||||||
@@ -1077,10 +1077,10 @@ send_prerendered_sprites(FontGroup *fg) {
|
|||||||
do_increment(fg, &error);
|
do_increment(fg, &error);
|
||||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
|
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
|
||||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
||||||
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
||||||
if (y > 0) { fatal("too many prerendered sprites for your GPU"); }
|
if (y > 0) { fatal("too many pre-rendered sprites for your GPU or the font size is too large"); }
|
||||||
do_increment(fg, &error);
|
do_increment(fg, &error);
|
||||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||||
@@ -1280,6 +1280,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
|||||||
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
|
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
|
||||||
FontGroup *fg = font_groups;
|
FontGroup *fg = font_groups;
|
||||||
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
|
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
|
||||||
|
if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; }
|
||||||
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
|
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
|
||||||
return fg->fonts[ans].face;
|
return fg->fonts[ans].face;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import ctypes
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import ceil, floor, pi, sin, sqrt
|
from math import ceil, pi, cos, sqrt
|
||||||
|
|
||||||
from kitty.config import defaults
|
from kitty.config import defaults
|
||||||
from kitty.constants import is_macos
|
from kitty.constants import is_macos
|
||||||
@@ -116,7 +116,7 @@ def add_dline(buf, cell_width, position, thickness, cell_height):
|
|||||||
|
|
||||||
def add_curl(buf, cell_width, position, thickness, cell_height):
|
def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||||
xfactor = 2.0 * pi / cell_width
|
xfactor = 2.0 * pi / cell_width
|
||||||
yfactor = thickness
|
yfactor = max(thickness, 2)
|
||||||
|
|
||||||
def clamp_y(y):
|
def clamp_y(y):
|
||||||
return max(0, min(int(y), cell_height - 1))
|
return max(0, min(int(y), cell_height - 1))
|
||||||
@@ -125,19 +125,18 @@ def add_curl(buf, cell_width, position, thickness, cell_height):
|
|||||||
return max(0, min(int(x), cell_width - 1))
|
return max(0, min(int(x), cell_width - 1))
|
||||||
|
|
||||||
def add_intensity(x, y, distance):
|
def add_intensity(x, y, distance):
|
||||||
buf[cell_width * y + x] = min(
|
idx = cell_width * y + x
|
||||||
255, buf[cell_width * y + x] + int(255 * (1 - distance))
|
buf[idx] = min(
|
||||||
|
255, buf[idx] + int(255 * (1 - distance))
|
||||||
)
|
)
|
||||||
|
|
||||||
for x_exact in range(cell_width):
|
for x_exact in range(cell_width):
|
||||||
y_exact = yfactor * sin(x_exact * xfactor) + position
|
y_exact = yfactor * cos(x_exact * xfactor) + position
|
||||||
y_below = clamp_y(floor(y_exact))
|
y = clamp_y(ceil(y_exact))
|
||||||
y_above = clamp_y(ceil(y_exact))
|
|
||||||
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
|
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
|
||||||
for x in {x_before, x_exact, x_after}:
|
for x in {x_before, x_exact, x_after}:
|
||||||
for y in {y_below, y_above}:
|
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
add_intensity(x, y, dist)
|
||||||
add_intensity(x, y, dist)
|
|
||||||
|
|
||||||
|
|
||||||
def render_special(
|
def render_special(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
|
#include FT_BITMAP_H
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
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 flags = get_load_flags(self->hinting, self->hintstyle, load_type);
|
||||||
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
||||||
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
|
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;
|
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 **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||||
|
|
||||||
|
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
|
||||||
|
|
||||||
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
|
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
|
||||||
|
|
||||||
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");
|
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");
|
||||||
|
|||||||
16
kitty/glfw-wrapper.h
generated
16
kitty/glfw-wrapper.h
generated
@@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
|
||||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -734,6 +732,10 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
|||||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||||
|
/*! @brief Window content area scaling window
|
||||||
|
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||||
|
*/
|
||||||
|
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||||
|
|
||||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||||
@@ -741,6 +743,8 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
|||||||
|
|
||||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||||
|
|
||||||
|
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||||
/*! @} */
|
/*! @} */
|
||||||
|
|
||||||
#define GLFW_NO_API 0
|
#define GLFW_NO_API 0
|
||||||
@@ -1380,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)();
|
typedef int (*glfwInit_func)();
|
||||||
glfwInit_func glfwInit_impl;
|
glfwInit_func glfwInit_impl;
|
||||||
#define glfwInit glfwInit_impl
|
#define glfwInit glfwInit_impl
|
||||||
@@ -1848,6 +1856,10 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind
|
|||||||
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
|
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
|
||||||
#define glfwSetCocoaTextInputFilter 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);
|
typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReopen_func)(GLFWapplicationshouldhandlereopenfun);
|
||||||
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
|
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
|
||||||
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl
|
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl
|
||||||
|
|||||||
118
kitty/glfw.c
118
kitty/glfw.c
@@ -10,6 +10,7 @@
|
|||||||
#include "glfw-wrapper.h"
|
#include "glfw-wrapper.h"
|
||||||
extern bool cocoa_make_window_resizable(void *w, bool);
|
extern bool cocoa_make_window_resizable(void *w, bool);
|
||||||
extern void cocoa_focus_window(void *w);
|
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_create_global_menu(void);
|
||||||
extern void cocoa_set_hide_from_tasks(void);
|
extern void cocoa_set_hide_from_tasks(void);
|
||||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
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->has_pending_resizes = true; global_state.has_pending_resizes = true;
|
||||||
window->last_resize_event_at = monotonic();
|
window->last_resize_event_at = monotonic();
|
||||||
unjam_event_loop();
|
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);
|
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
|
||||||
global_state.callback_os_window = NULL;
|
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);
|
show_mouse_cursor(w);
|
||||||
double now = monotonic();
|
double now = monotonic();
|
||||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
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;
|
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;
|
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->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_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;
|
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;
|
global_state.callback_os_window = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,12 +290,11 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLFWwindow *current_os_window_ctx = NULL;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
make_os_window_context_current(OSWindow *w) {
|
make_os_window_context_current(OSWindow *w) {
|
||||||
if (w->handle != current_os_window_ctx) {
|
GLFWwindow *current_context = glfwGetCurrentContext();
|
||||||
current_os_window_ctx = w->handle;
|
if (w->handle != current_context) {
|
||||||
glfwMakeContextCurrent(w->handle);
|
glfwMakeContextCurrent(w->handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +339,7 @@ static inline void
|
|||||||
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
||||||
GLFWmonitor *monitor = NULL;
|
GLFWmonitor *monitor = NULL;
|
||||||
if (w) monitor = current_monitor(w);
|
if (w) monitor = current_monitor(w);
|
||||||
if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
|
if (monitor == NULL) { PyErr_Print(); monitor = glfwGetPrimaryMonitor(); }
|
||||||
float xscale = 1, yscale = 1;
|
float xscale = 1, yscale = 1;
|
||||||
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@@ -345,11 +356,56 @@ set_os_window_dpi(OSWindow *w) {
|
|||||||
get_window_dpi(w->handle, &w->logical_dpi_x, &w->logical_dpi_y);
|
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__
|
#ifdef __APPLE__
|
||||||
static int
|
static int
|
||||||
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
|
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;
|
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLFWwindow *application_quit_canary = NULL;
|
static GLFWwindow *application_quit_canary = NULL;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -360,6 +416,14 @@ on_application_reopen(int has_visible_windows) {
|
|||||||
unjam_event_loop();
|
unjam_event_loop();
|
||||||
return false;
|
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
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -409,6 +473,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
|||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
||||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
||||||
|
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||||
if (OPT(x11_hide_window_decorations)) {
|
if (OPT(x11_hide_window_decorations)) {
|
||||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||||
}
|
}
|
||||||
@@ -488,12 +553,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
w->logical_dpi_x = dpi_x; w->logical_dpi_y = dpi_y;
|
w->logical_dpi_x = dpi_x; w->logical_dpi_y = dpi_y;
|
||||||
w->fonts_data = fonts_data;
|
w->fonts_data = fonts_data;
|
||||||
current_os_window_ctx = glfw_window;
|
|
||||||
w->shown_once = true;
|
w->shown_once = true;
|
||||||
push_focus_history(w);
|
push_focus_history(w);
|
||||||
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
|
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
||||||
|
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
|
||||||
#endif
|
#endif
|
||||||
send_prerendered_sprites_for_window(w);
|
send_prerendered_sprites_for_window(w);
|
||||||
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
|
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
|
||||||
@@ -535,7 +600,6 @@ destroy_os_window(OSWindow *w) {
|
|||||||
show_mouse_cursor(w->handle);
|
show_mouse_cursor(w->handle);
|
||||||
glfwSetCursor(w->handle, NULL);
|
glfwSetCursor(w->handle, NULL);
|
||||||
glfwDestroyWindow(w->handle);
|
glfwDestroyWindow(w->handle);
|
||||||
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
|
|
||||||
}
|
}
|
||||||
w->handle = NULL;
|
w->handle = NULL;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
@@ -693,29 +757,22 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
|
|||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
toggle_fullscreen(PYNOARG) {
|
toggle_fullscreen(PYNOARG) {
|
||||||
GLFWmonitor *monitor;
|
|
||||||
OSWindow *w = current_os_window();
|
OSWindow *w = current_os_window();
|
||||||
if (!w) Py_RETURN_NONE;
|
if (!w) Py_RETURN_NONE;
|
||||||
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
|
if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; }
|
||||||
// make fullscreen
|
Py_RETURN_FALSE;
|
||||||
monitor = current_monitor(w->handle);
|
}
|
||||||
if (monitor == NULL) return NULL;
|
|
||||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
static PyObject*
|
||||||
w->before_fullscreen.is_set = true;
|
change_os_window_state(PyObject *self UNUSED, PyObject *args) {
|
||||||
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
|
char *state;
|
||||||
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
|
if (!PyArg_ParseTuple(args, "s", &state)) return NULL;
|
||||||
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
OSWindow *w = current_os_window();
|
||||||
Py_RETURN_TRUE;
|
if (!w || !w->handle) Py_RETURN_NONE;
|
||||||
} else {
|
if (strcmp(state, "maximized") == 0) glfwMaximizeWindow(w->handle);
|
||||||
// make windowed
|
else if (strcmp(state, "minimized") == 0) glfwIconifyWindow(w->handle);
|
||||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
else { PyErr_SetString(PyExc_ValueError, "Unknown window state"); return NULL; }
|
||||||
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);
|
Py_RETURN_NONE;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -926,6 +983,7 @@ static PyMethodDef module_methods[] = {
|
|||||||
METHODB(get_content_scale_for_window, METH_NOARGS),
|
METHODB(get_content_scale_for_window, METH_NOARGS),
|
||||||
METHODB(set_clipboard_string, METH_VARARGS),
|
METHODB(set_clipboard_string, METH_VARARGS),
|
||||||
METHODB(toggle_fullscreen, METH_NOARGS),
|
METHODB(toggle_fullscreen, METH_NOARGS),
|
||||||
|
METHODB(change_os_window_state, METH_VARARGS),
|
||||||
METHODB(glfw_window_hint, METH_VARARGS),
|
METHODB(glfw_window_hint, METH_VARARGS),
|
||||||
METHODB(os_window_should_close, METH_VARARGS),
|
METHODB(os_window_should_close, METH_VARARGS),
|
||||||
METHODB(os_window_swap_buffers, METH_VARARGS),
|
METHODB(os_window_swap_buffers, METH_VARARGS),
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
||||||
|
|
||||||
if (xnum * ynum == 0) {
|
if (xnum == 0 || ynum == 0) {
|
||||||
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
|
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -74,9 +74,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
add_segment(self);
|
add_segment(self);
|
||||||
self->line = alloc_line();
|
self->line = alloc_line();
|
||||||
self->line->xnum = xnum;
|
self->line->xnum = xnum;
|
||||||
for(index_type y = 0; y < self->ynum; y++) {
|
|
||||||
clear_chars_in_line(cpu_lineptr(self, y), gpu_lineptr(self, y), self->xnum, BLANK_CHAR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
@@ -164,7 +161,7 @@ line(HistoryBuf *self, PyObject *val) {
|
|||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
__str__(HistoryBuf *self) {
|
__str__(HistoryBuf *self) {
|
||||||
PyObject *lines = PyTuple_New(self->ynum);
|
PyObject *lines = PyTuple_New(self->count);
|
||||||
if (lines == NULL) return PyErr_NoMemory();
|
if (lines == NULL) return PyErr_NoMemory();
|
||||||
for (index_type i = 0; i < self->count; i++) {
|
for (index_type i = 0; i < self->count; i++) {
|
||||||
init_line(self, index_of(self, i), self->line);
|
init_line(self, index_of(self, i), self->line);
|
||||||
@@ -210,7 +207,7 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline Line*
|
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*
|
static PyObject*
|
||||||
as_text(HistoryBuf *self, PyObject *args) {
|
as_text(HistoryBuf *self, PyObject *args) {
|
||||||
@@ -225,7 +222,7 @@ static PyObject*
|
|||||||
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||||
PyObject *ans = PyList_New(0);
|
PyObject *ans = PyList_New(0);
|
||||||
for (index_type i = 0; i < self->ynum; i++) {
|
for (index_type i = 0; i < self->count; i++) {
|
||||||
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
|
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
|
||||||
PyList_Append(ans, PyLong_FromUnsignedLong(i));
|
PyList_Append(ans, PyLong_FromUnsignedLong(i));
|
||||||
}
|
}
|
||||||
|
|||||||
18
kitty/keys.c
18
kitty/keys.c
@@ -103,13 +103,9 @@ check_if_special(int key, int mods, int scancode) {
|
|||||||
qkey = SPECIAL_INDEX(qkey);
|
qkey = SPECIAL_INDEX(qkey);
|
||||||
special = needs_special_handling[qkey];
|
special = needs_special_handling[qkey];
|
||||||
}
|
}
|
||||||
#ifdef __APPLE__
|
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||||
(void)scancode;
|
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||||
#else
|
}
|
||||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
|
||||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return special;
|
return special;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +123,7 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
|
|||||||
void
|
void
|
||||||
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
|
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
|
||||||
Window *w = active_window();
|
Window *w = active_window();
|
||||||
debug("on_key_input: glfw key: %d native_code: %d action: %s mods: 0x%x text: '%s' state: %d ",
|
debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ",
|
||||||
key, scancode,
|
key, scancode,
|
||||||
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||||
mods, text, state);
|
mods, text, state);
|
||||||
@@ -222,15 +218,13 @@ PYWRAP1(key_for_native_key_name) {
|
|||||||
const char *name;
|
const char *name;
|
||||||
int case_sensitive = 0;
|
int case_sensitive = 0;
|
||||||
PA("s|p", &name, case_sensitive);
|
PA("s|p", &name, case_sensitive);
|
||||||
#ifdef __APPLE__
|
#ifndef __APPLE__
|
||||||
Py_RETURN_NONE;
|
|
||||||
#else
|
|
||||||
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
|
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
|
||||||
int scancode = glfwGetXKBScancode(name, case_sensitive);
|
int scancode = glfwGetXKBScancode(name, case_sensitive);
|
||||||
if (scancode) return Py_BuildValue("i", scancode);
|
if (scancode) return Py_BuildValue("i", scancode);
|
||||||
}
|
}
|
||||||
Py_RETURN_NONE;
|
|
||||||
#endif
|
#endif
|
||||||
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from . import fast_data_types as defines
|
|||||||
from .key_encoding import KEY_MAP
|
from .key_encoding import KEY_MAP
|
||||||
from .terminfo import key_as_bytes
|
from .terminfo import key_as_bytes
|
||||||
from .utils import base64_encode
|
from .utils import base64_encode
|
||||||
from .constants import is_macos
|
|
||||||
|
|
||||||
|
|
||||||
def modify_key_bytes(keybytes, amt):
|
def modify_key_bytes(keybytes, amt):
|
||||||
@@ -276,7 +275,7 @@ def interpret_key_event(key, scancode, mods, window, action):
|
|||||||
def get_shortcut(keymap, mods, key, scancode):
|
def get_shortcut(keymap, mods, key, scancode):
|
||||||
mods &= 0b1111
|
mods &= 0b1111
|
||||||
ans = keymap.get((mods, False, key))
|
ans = keymap.get((mods, False, key))
|
||||||
if ans is None and not is_macos:
|
if ans is None:
|
||||||
ans = keymap.get((mods, True, scancode))
|
ans = keymap.get((mods, True, scancode))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|||||||
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)
|
active_window_idx = idx_for_id(windows[active_window_idx].id, all_windows)
|
||||||
return self.set_active_window(all_windows, active_window_idx)
|
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]
|
w = all_windows[active_window_idx]
|
||||||
windows = process_overlaid_windows(all_windows)[1]
|
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
|
return active_window_idx
|
||||||
idx = idx_for_id(w.id, windows)
|
idx = idx_for_id(w.id, windows)
|
||||||
if idx is None:
|
if idx is None:
|
||||||
idx = idx_for_id(w.overlay_window_id, windows)
|
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]
|
nw = windows[nidx]
|
||||||
nidx = idx_for_id(nw.id, all_windows)
|
nidx = idx_for_id(nw.id, all_windows)
|
||||||
idx = active_window_idx
|
idx = active_window_idx
|
||||||
@@ -368,6 +387,9 @@ class Layout: # {{{
|
|||||||
def do_layout(self, windows, active_window_idx):
|
def do_layout(self, windows, active_window_idx):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def neighbors_for_window(self, window, windows):
|
||||||
|
return {'left': [], 'right': [], 'top': [], 'bottom': []}
|
||||||
|
|
||||||
def resolve_borders(self, windows, active_window):
|
def resolve_borders(self, windows, active_window):
|
||||||
if draw_minimal_borders:
|
if draw_minimal_borders:
|
||||||
needs_borders_map = {w.id: (w is active_window or w.needs_attention) for w in windows}
|
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
|
# left bottom blank rect
|
||||||
self.bottom_blank_rect(windows[0])
|
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):
|
def minimal_borders(self, windows, active_window, needs_borders_map):
|
||||||
last_i = len(windows) - 1
|
last_i = len(windows) - 1
|
||||||
for i, w in enumerate(windows):
|
for i, w in enumerate(windows):
|
||||||
@@ -520,6 +549,14 @@ class Fat(Tall): # {{{
|
|||||||
self.bottom_blank_rect(windows[0])
|
self.bottom_blank_rect(windows[0])
|
||||||
# bottom blank rect
|
# bottom blank rect
|
||||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
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,
|
(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]
|
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
|
yield no_borders
|
||||||
else:
|
else:
|
||||||
yield self.only_between_border
|
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])
|
self.simple_blank_rects(windows[0], windows[-1])
|
||||||
# bottom blank rect
|
# bottom blank rect
|
||||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
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):
|
def _run_app(opts, args):
|
||||||
new_os_window_trigger = get_new_os_window_trigger(opts)
|
new_os_window_trigger = get_new_os_window_trigger(opts)
|
||||||
if False and is_macos:
|
if is_macos and opts.macos_custom_beam_cursor:
|
||||||
# This is disabled because using custom cursors fails
|
|
||||||
# on dual GPU machines: https://github.com/kovidgoyal/kitty/issues/794
|
|
||||||
set_custom_ibeam_cursor()
|
set_custom_ibeam_cursor()
|
||||||
load_all_shaders.cursor_text_color = opts.cursor_text_color
|
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 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:
|
with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback:
|
||||||
window_id = create_os_window(
|
window_id = create_os_window(
|
||||||
@@ -132,9 +133,6 @@ def _run_app(opts, args):
|
|||||||
pre_show_callback,
|
pre_show_callback,
|
||||||
appname, args.name or args.cls or appname,
|
appname, args.name or args.cls or appname,
|
||||||
args.cls or appname, load_all_shaders)
|
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 = Boss(window_id, opts, args, cached_values, new_os_window_trigger)
|
||||||
boss.start()
|
boss.start()
|
||||||
try:
|
try:
|
||||||
@@ -191,12 +189,12 @@ def setup_profiling(args):
|
|||||||
print('To view the graphical call data, use: kcachegrind', cg)
|
print('To view the graphical call data, use: kcachegrind', cg)
|
||||||
|
|
||||||
|
|
||||||
def macos_cmdline():
|
def macos_cmdline(argv_args):
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f:
|
with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return []
|
return argv_args
|
||||||
import shlex
|
import shlex
|
||||||
raw = raw.strip()
|
raw = raw.strip()
|
||||||
ans = shlex.split(raw)
|
ans = shlex.split(raw)
|
||||||
@@ -242,7 +240,7 @@ def _main():
|
|||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
|
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
|
||||||
os.chdir(os.path.expanduser('~'))
|
os.chdir(os.path.expanduser('~'))
|
||||||
args = macos_cmdline()
|
args = macos_cmdline(args)
|
||||||
try:
|
try:
|
||||||
cwd_ok = os.path.isdir(os.getcwd())
|
cwd_ok = os.path.isdir(os.getcwd())
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -179,10 +179,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
|||||||
bool
|
bool
|
||||||
drag_scroll(Window *w, OSWindow *frame) {
|
drag_scroll(Window *w, OSWindow *frame) {
|
||||||
unsigned int margin = frame->fonts_data->cell_height / 2;
|
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 y = frame->mouse_y;
|
||||||
double x = frame->mouse_x, y = frame->mouse_y;
|
|
||||||
if (y < top || y > bottom) return false;
|
|
||||||
if (x < left || x > right) return false;
|
|
||||||
bool upwards = y <= (w->geometry.top + margin);
|
bool upwards = y <= (w->geometry.top + margin);
|
||||||
if (upwards || y >= w->geometry.bottom - margin) {
|
if (upwards || y >= w->geometry.bottom - margin) {
|
||||||
Screen *screen = w->render_data.screen;
|
Screen *screen = w->render_data.screen;
|
||||||
@@ -488,23 +485,32 @@ enter_event() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mouse_event(int button, int modifiers) {
|
mouse_event(int button, int modifiers, int action) {
|
||||||
MouseShape old_cursor = mouse_cursor_shape;
|
MouseShape old_cursor = mouse_cursor_shape;
|
||||||
bool in_tab_bar;
|
bool in_tab_bar;
|
||||||
unsigned int window_idx = 0;
|
unsigned int window_idx = 0;
|
||||||
Window *w = NULL;
|
Window *w = NULL;
|
||||||
if (button == -1 && global_state.active_drag_in_window) { // drag move
|
if (global_state.active_drag_in_window) {
|
||||||
w = window_for_id(global_state.active_drag_in_window);
|
if (button == -1) { // drag move
|
||||||
if (w) {
|
w = window_for_id(global_state.active_drag_in_window);
|
||||||
button = currently_pressed_button();
|
if (w) {
|
||||||
if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
button = currently_pressed_button();
|
||||||
clamp_to_window = true;
|
if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
||||||
handle_move_event(w, button, modifiers, window_idx);
|
clamp_to_window = true;
|
||||||
clamp_to_window = false;
|
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||||
return;
|
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);
|
w = window_for_event(&window_idx, &in_tab_bar);
|
||||||
if (in_tab_bar) {
|
if (in_tab_bar) {
|
||||||
|
|||||||
@@ -1684,6 +1684,21 @@ as_text_non_visual(Screen *self, PyObject *args) {
|
|||||||
as_text_generic(args, self, range_line_, self->lines, self->columns);
|
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*
|
static PyObject*
|
||||||
screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
|
screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
|
||||||
if (PyUnicode_READY(str) != 0) return NULL;
|
if (PyUnicode_READY(str) != 0) return NULL;
|
||||||
@@ -2169,6 +2184,7 @@ static PyMethodDef methods[] = {
|
|||||||
MND(set_pending_timeout, METH_O)
|
MND(set_pending_timeout, METH_O)
|
||||||
MND(as_text, METH_VARARGS)
|
MND(as_text, METH_VARARGS)
|
||||||
MND(as_text_non_visual, METH_VARARGS)
|
MND(as_text_non_visual, METH_VARARGS)
|
||||||
|
MND(as_text_alternate, METH_VARARGS)
|
||||||
MND(tab, METH_NOARGS)
|
MND(tab, METH_NOARGS)
|
||||||
MND(backspace, METH_NOARGS)
|
MND(backspace, METH_NOARGS)
|
||||||
MND(linefeed, METH_NOARGS)
|
MND(linefeed, METH_NOARGS)
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ class Tab:
|
|||||||
|
|
||||||
class Session:
|
class Session:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, default_title=None):
|
||||||
self.tabs = []
|
self.tabs = []
|
||||||
self.active_tab_idx = 0
|
self.active_tab_idx = 0
|
||||||
|
self.default_title = default_title
|
||||||
|
|
||||||
def add_tab(self, opts, name=''):
|
def add_tab(self, opts, name=''):
|
||||||
if self.tabs and not self.tabs[-1].windows:
|
if self.tabs and not self.tabs[-1].windows:
|
||||||
@@ -48,7 +49,7 @@ class Session:
|
|||||||
cmd = None
|
cmd = None
|
||||||
from .tabs import SpecialWindow
|
from .tabs import SpecialWindow
|
||||||
t = self.tabs[-1]
|
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
|
t.next_title = None
|
||||||
|
|
||||||
def add_special_window(self, sw):
|
def add_special_window(self, sw):
|
||||||
@@ -76,8 +77,8 @@ def resolved_shell(opts):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def parse_session(raw, opts):
|
def parse_session(raw, opts, default_title=None):
|
||||||
ans = Session()
|
ans = Session(default_title)
|
||||||
ans.add_tab(opts)
|
ans.add_tab(opts)
|
||||||
for line in raw.splitlines():
|
for line in raw.splitlines():
|
||||||
line = line.strip()
|
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):
|
def create_session(opts, args=None, special_window=None, cwd_from=None, respect_cwd=False, default_session=None):
|
||||||
if args and args.session:
|
if args and args.session:
|
||||||
with open(args.session) as f:
|
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':
|
if default_session and default_session != 'none':
|
||||||
try:
|
try:
|
||||||
with open(default_session) as f:
|
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:
|
except EnvironmentError:
|
||||||
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
|
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
|
||||||
else:
|
else:
|
||||||
return parse_session(session_data, opts)
|
return parse_session(session_data, opts, getattr(args, 'title', None))
|
||||||
ans = Session()
|
ans = Session()
|
||||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||||
ans.add_tab(opts)
|
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
|
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) {
|
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;
|
bool changed = false;
|
||||||
if (os_window->clear_count < 2) {
|
if (os_window->clear_count < 2) {
|
||||||
os_window->clear_count++;
|
os_window->clear_count++;
|
||||||
#define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f)
|
blank_os_window(os_window);
|
||||||
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);
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (os_window->fonts_data) {
|
if (os_window->fonts_data) {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ add_os_window() {
|
|||||||
static inline id_type
|
static inline id_type
|
||||||
add_tab(id_type os_window_id) {
|
add_tab(id_type os_window_id) {
|
||||||
WITH_OS_WINDOW(os_window_id)
|
WITH_OS_WINDOW(os_window_id)
|
||||||
|
make_os_window_context_current(os_window);
|
||||||
ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true);
|
ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true);
|
||||||
memset(os_window->tabs + os_window->num_tabs, 0, sizeof(Tab));
|
memset(os_window->tabs + os_window->num_tabs, 0, sizeof(Tab));
|
||||||
os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter;
|
os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter;
|
||||||
@@ -385,11 +386,13 @@ PYWRAP1(set_options) {
|
|||||||
S(close_on_child_death, PyObject_IsTrue);
|
S(close_on_child_death, PyObject_IsTrue);
|
||||||
S(window_alert_on_bell, PyObject_IsTrue);
|
S(window_alert_on_bell, PyObject_IsTrue);
|
||||||
S(macos_option_as_alt, PyObject_IsTrue);
|
S(macos_option_as_alt, PyObject_IsTrue);
|
||||||
|
S(macos_traditional_fullscreen, PyObject_IsTrue);
|
||||||
S(macos_hide_titlebar, PyObject_IsTrue);
|
S(macos_hide_titlebar, PyObject_IsTrue);
|
||||||
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
|
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
|
||||||
S(macos_window_resizable, PyObject_IsTrue);
|
S(macos_window_resizable, PyObject_IsTrue);
|
||||||
S(x11_hide_window_decorations, PyObject_IsTrue);
|
S(x11_hide_window_decorations, PyObject_IsTrue);
|
||||||
S(macos_hide_from_tasks, PyObject_IsTrue);
|
S(macos_hide_from_tasks, PyObject_IsTrue);
|
||||||
|
S(macos_thicken_font, PyFloat_AsDouble);
|
||||||
|
|
||||||
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
|
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
|
||||||
if (chars == NULL) return NULL;
|
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;
|
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
|
||||||
double repaint_delay, input_delay;
|
double repaint_delay, input_delay;
|
||||||
bool focus_follows_mouse;
|
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;
|
int adjust_line_height_px, adjust_column_width_px;
|
||||||
float adjust_line_height_frac, adjust_column_width_frac;
|
float adjust_line_height_frac, adjust_column_width_frac;
|
||||||
float background_opacity, dim_opacity;
|
float background_opacity, dim_opacity;
|
||||||
@@ -188,6 +189,7 @@ void update_surface_size(int, int, uint32_t);
|
|||||||
void free_texture(uint32_t*);
|
void free_texture(uint32_t*);
|
||||||
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
|
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 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);
|
void set_titlebar_color(OSWindow *w, color_type color);
|
||||||
FONTS_DATA_HANDLE load_fonts_data(double, double, double);
|
FONTS_DATA_HANDLE load_fonts_data(double, double, double);
|
||||||
void send_prerendered_sprites_for_window(OSWindow *w);
|
void send_prerendered_sprites_for_window(OSWindow *w);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from .fast_data_types import (
|
|||||||
from .layout import Rect
|
from .layout import Rect
|
||||||
from .utils import color_as_int
|
from .utils import color_as_int
|
||||||
from .window import calculate_gl_geometry
|
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')
|
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')
|
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
|
self.active_fg = (spec['active_tab_foreground'] << 8) | 2
|
||||||
if 'active_tab_background' in spec:
|
if 'active_tab_background' in spec:
|
||||||
self.active_bg = (spec['active_tab_background'] << 8) | 2
|
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(
|
self.screen.color_profile.set_configured_colors(
|
||||||
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
|
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))
|
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ class Tab: # {{{
|
|||||||
self.windows = deque()
|
self.windows = deque()
|
||||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
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))
|
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||||
|
self._last_used_layout = self._current_layout_name = None
|
||||||
if session_tab is None:
|
if session_tab is None:
|
||||||
self.cwd = self.args.directory
|
self.cwd = self.args.directory
|
||||||
sl = self.enabled_layouts[0]
|
sl = self.enabled_layouts[0]
|
||||||
self.current_layout = self.create_layout_object(sl)
|
self._set_current_layout(sl)
|
||||||
if special_window is None:
|
if special_window is None:
|
||||||
self.new_window(cwd_from=cwd_from)
|
self.new_window(cwd_from=cwd_from)
|
||||||
else:
|
else:
|
||||||
@@ -57,9 +58,14 @@ class Tab: # {{{
|
|||||||
else:
|
else:
|
||||||
self.cwd = session_tab.cwd or self.args.directory
|
self.cwd = session_tab.cwd or self.args.directory
|
||||||
l0 = session_tab.layout
|
l0 = session_tab.layout
|
||||||
self.current_layout = self.create_layout_object(l0)
|
self._set_current_layout(l0)
|
||||||
self.startup(session_tab)
|
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):
|
def startup(self, session_tab):
|
||||||
for cmd in session_tab.windows:
|
for cmd in session_tab.windows:
|
||||||
if isinstance(cmd, (SpecialWindowInstance,)):
|
if isinstance(cmd, (SpecialWindowInstance,)):
|
||||||
@@ -154,7 +160,12 @@ class Tab: # {{{
|
|||||||
else:
|
else:
|
||||||
idx = -1
|
idx = -1
|
||||||
nl = self.enabled_layouts[(idx + 1) % len(self.enabled_layouts)]
|
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()
|
self.relayout()
|
||||||
|
|
||||||
def goto_layout(self, layout_name, raise_exception=False):
|
def goto_layout(self, layout_name, raise_exception=False):
|
||||||
@@ -164,7 +175,7 @@ class Tab: # {{{
|
|||||||
raise ValueError(layout_name)
|
raise ValueError(layout_name)
|
||||||
log_error('Unknown or disabled layout: {}'.format(layout_name))
|
log_error('Unknown or disabled layout: {}'.format(layout_name))
|
||||||
return
|
return
|
||||||
self.current_layout = self.create_layout_object(layout_name)
|
self._set_current_layout(layout_name)
|
||||||
self.relayout()
|
self.relayout()
|
||||||
|
|
||||||
def resize_window_by(self, window_id, increment, is_horizontal):
|
def resize_window_by(self, window_id, increment, is_horizontal):
|
||||||
@@ -260,6 +271,16 @@ class Tab: # {{{
|
|||||||
def previous_window(self):
|
def previous_window(self):
|
||||||
self._next_window(-1)
|
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):
|
def move_window(self, delta=1):
|
||||||
self.active_window_idx = self.current_layout.move_window(self.windows, self.active_window_idx, delta)
|
self.active_window_idx = self.current_layout.move_window(self.windows, self.active_window_idx, delta)
|
||||||
self.relayout()
|
self.relayout()
|
||||||
@@ -445,10 +466,16 @@ class TabManager: # {{{
|
|||||||
self._set_active_tab(nidx)
|
self._set_active_tab(nidx)
|
||||||
self.mark_tab_bar_dirty()
|
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)
|
idx = len(self.tabs)
|
||||||
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
|
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
|
||||||
self._set_active_tab(idx)
|
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()
|
self.mark_tab_bar_dirty()
|
||||||
return self.tabs[idx]
|
return self.tabs[idx]
|
||||||
|
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ def get_capabilities(query_string):
|
|||||||
try:
|
try:
|
||||||
for q in query_string.split(';'):
|
for q in query_string.split(';'):
|
||||||
name = qname = unhexlify(q).decode('utf-8')
|
name = qname = unhexlify(q).decode('utf-8')
|
||||||
if name == 'TN':
|
if name in ('TN', 'name'):
|
||||||
val = names[0]
|
val = names[0]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -436,10 +436,13 @@ class Window:
|
|||||||
self.screen.reset_callbacks()
|
self.screen.reset_callbacks()
|
||||||
self.screen = None
|
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 = []
|
lines = []
|
||||||
add_history = add_history and not self.screen.is_using_alternate_linebuf()
|
add_history = add_history and not self.screen.is_using_alternate_linebuf() and not alternate_screen
|
||||||
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
|
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)
|
f(lines.append, as_ansi, add_wrap_markers)
|
||||||
if add_history:
|
if add_history:
|
||||||
h = []
|
h = []
|
||||||
@@ -475,6 +478,10 @@ class Window:
|
|||||||
if len(text) == len(new_text):
|
if len(text) == len(new_text):
|
||||||
break
|
break
|
||||||
text = new_text
|
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)
|
self.screen.paste(text)
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
from kitty.constants import is_macos
|
from kitty.constants import is_macos
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
DECAWM, sprite_map_set_layout, sprite_map_set_limits, test_render_line,
|
DECAWM, get_fallback_font, sprite_map_set_layout, sprite_map_set_limits,
|
||||||
test_sprite_position_for, wcwidth
|
test_render_line, test_sprite_position_for, wcwidth
|
||||||
)
|
)
|
||||||
from kitty.fonts.box_drawing import box_chars
|
from kitty.fonts.box_drawing import box_chars
|
||||||
from kitty.fonts.render import render_string, setup_for_testing, shape_string
|
from kitty.fonts.render import render_string, setup_for_testing, shape_string
|
||||||
@@ -106,3 +109,16 @@ class Rendering(BaseTest):
|
|||||||
s.draw('\ufe0f')
|
s.draw('\ufe0f')
|
||||||
self.ae((s.cursor.x, s.cursor.y), (2, 4))
|
self.ae((s.cursor.x, s.cursor.y), (2, 4))
|
||||||
self.ae(str(s.line(s.cursor.y)), '\u2716\ufe0f')
|
self.ae(str(s.line(s.cursor.y)), '\u2716\ufe0f')
|
||||||
|
|
||||||
|
@unittest.skipUnless(is_macos, 'Only macOS has a Last Resort font')
|
||||||
|
def test_fallback_font_not_last_resort(self):
|
||||||
|
# Ensure that the LastResort font is not reported as a fallback font on
|
||||||
|
# macOS. See https://github.com/kovidgoyal/kitty/issues/799
|
||||||
|
from io import StringIO
|
||||||
|
orig, buf = sys.stderr, StringIO()
|
||||||
|
sys.stderr = buf
|
||||||
|
try:
|
||||||
|
self.assertRaises(ValueError, get_fallback_font, '\U0010FFFF', False, False)
|
||||||
|
finally:
|
||||||
|
sys.stderr = orig
|
||||||
|
self.assertIn('LastResort', buf.getvalue())
|
||||||
|
|||||||
@@ -222,6 +222,15 @@ class TestScreen(BaseTest):
|
|||||||
for i in range(1, 5):
|
for i in range(1, 5):
|
||||||
self.ae(str(s.line(i)), '12345')
|
self.ae(str(s.line(i)), '12345')
|
||||||
|
|
||||||
|
def test_backspace_wide_characters(self):
|
||||||
|
s = self.create_screen()
|
||||||
|
s.draw('⛅')
|
||||||
|
self.ae(s.cursor.x, 2)
|
||||||
|
s.backspace()
|
||||||
|
s.draw(' ')
|
||||||
|
s.backspace()
|
||||||
|
self.ae(s.cursor.x, 1)
|
||||||
|
|
||||||
def test_resize(self):
|
def test_resize(self):
|
||||||
s = self.create_screen(scrollback=6)
|
s = self.create_screen(scrollback=6)
|
||||||
s.draw(''.join([str(i) * s.columns for i in range(s.lines)]))
|
s.draw(''.join([str(i) * s.columns for i in range(s.lines)]))
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ int main(int argc, char *argv[]) {
|
|||||||
final_argv[i+1] = argv[i];
|
final_argv[i+1] = argv[i];
|
||||||
num_args++;
|
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++) {
|
for (i = 0; i < num_args; i++) {
|
||||||
argvw[i] = Py_DecodeLocale(final_argv[i], NULL);
|
argvw[i] = Py_DecodeLocale(final_argv[i], NULL);
|
||||||
if (argvw[i] == NULL) {
|
if (argvw[i] == NULL) {
|
||||||
|
|||||||
@@ -331,8 +331,14 @@ def require_git_master(branch='master'):
|
|||||||
raise SystemExit('You must be in the {} git branch'.format(branch))
|
raise SystemExit('You must be in the {} git branch'.format(branch))
|
||||||
|
|
||||||
|
|
||||||
|
def require_penv():
|
||||||
|
if 'PENV' not in os.environ:
|
||||||
|
raise SystemExit('The PENV env var is not present, required for uploading releases')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
require_git_master()
|
require_git_master()
|
||||||
|
require_penv()
|
||||||
parser = argparse.ArgumentParser(description='Publish kitty')
|
parser = argparse.ArgumentParser(description='Publish kitty')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--only',
|
'--only',
|
||||||
|
|||||||
Reference in New Issue
Block a user