A new remote control command to resize OS Windows

This commit is contained in:
Kovid Goyal 2021-07-25 22:57:00 +05:30
parent aa339a8a9f
commit 419cf78984
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 183 additions and 13 deletions

View File

@ -26,6 +26,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
could cause incorrect parsing if either the pending buffer capacity or the
pending timeout were exceeded (:iss:`3779`)
- A new remote control command to :program:`resize the OS Window <kitty @
resize-os-window>`
- Graphics protocol: Add support for composing rectangles from one animation
frame onto another (:iss:`3809`)

View File

@ -32,11 +32,12 @@ from .fast_data_types import (
background_opacity_of, change_background_opacity, change_os_window_state,
cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_os_window, destroy_global_data,
focus_os_window, get_clipboard_string, get_options, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
safe_pipe, set_application_quit_request, set_background_image, set_boss,
set_clipboard_string, set_in_sequence_mode, set_options, thread_write,
toggle_fullscreen, toggle_maximized
focus_os_window, get_clipboard_string, get_options, get_os_window_size,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, safe_pipe, set_application_quit_request,
set_background_image, set_boss, set_clipboard_string, set_in_sequence_mode,
set_options, set_os_window_size, thread_write, toggle_fullscreen,
toggle_maximized
)
from .keys import get_shortcut, shortcut_matches
from .layout.base import set_layout_options
@ -52,10 +53,11 @@ from .tabs import (
from .types import SingleKey
from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
log_error, open_url, parse_address_spec, parse_uri_list,
platform_window_id, read_shell_environment, remove_socket_file, safe_print,
set_primary_selection, single_instance, startup_notification_handler
func_name, get_editor, get_new_os_window_size, get_primary_selection,
is_path_in_temp_dir, log_error, open_url, parse_address_spec,
parse_uri_list, platform_window_id, read_shell_environment,
remove_socket_file, safe_print, set_primary_selection, single_instance,
startup_notification_handler
)
from .window import MatchPatternType, Window
@ -533,11 +535,11 @@ class Boss:
self.close_window(window)
def toggle_fullscreen(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the fullscreen status of the specified or the active OS Window'
'@ac:win: Toggle the fullscreen status of the active OS Window'
toggle_fullscreen(os_window_id)
def toggle_maximized(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the maximized status of the specified or the active OS Window'
'@ac:win: Toggle the maximized status of the active OS Window'
toggle_maximized(os_window_id)
def start(self, first_os_window_id: int) -> None:
@ -784,6 +786,16 @@ class Boss:
return None
return tab.resize_window_by(window.id, increment, is_horizontal)
def resize_os_window(self, os_window_id: int, width: int, height: int, unit: str, incremental: bool = False) -> None:
if not incremental and (width < 0 or height < 0):
return
metrics = get_os_window_size(os_window_id)
if metrics is None:
return
has_window_scaling = is_macos or is_wayland()
w, h = get_new_os_window_size(metrics, width, height, unit, incremental, has_window_scaling)
set_os_window_size(os_window_id, w, h)
def default_bg_changed_for(self, window_id: int) -> None:
w = self.window_id_map.get(window_id)
if w is not None:

View File

@ -1202,6 +1202,8 @@ class OSWindowSize(TypedDict):
yscale: float
xdpi: float
ydpi: float
cell_width: int
cell_height: int
def get_os_window_size(os_window_id: int) -> Optional[OSWindowSize]:

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import TYPE_CHECKING, Optional
from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType,
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
)
if TYPE_CHECKING:
from kitty.cli_stub import ResizeOSWindowRCOptions as CLIOptions
class ResizeOSWindow(RemoteCommand):
'''
match: Which window to resize
self: Boolean indicating whether to close the window the command is run in
incremental: Boolean indicating whether to adjust the size incrementally
action: One of :code:`resize, toggle-fullscreen` or :code:`toggle-maximized`
unit: One of :code:`cells` or :code:`pixels`
width: Integer indicating desired window width
height: Integer indicating desired window height
'''
short_desc = 'Resize the specified OS Window'
desc = (
'Resize the specified OS Window.'
' Note that some window managers/environments do not allow applications to resize'
' their windows, for example, tiling window managers.'
)
options_spec = MATCH_WINDOW_OPTION + '''\n
--action
default=resize
choices=resize,toggle-fullscreen,toggle-maximized
The action to perform.
--unit
default=cells
choices=cells,pixels
The unit in which to interpret specified sizes
--width
default=0
type=int
Change the width of the window. Zero leaves the width unchanged.
--height
default=0
type=int
Change the height of the window. Zero leaves the height unchanged.
--incremental
type=bool-set
Treat the specified sizes as increments on the existing window size
instead of absolute sizes.
--self
type=bool-set
If specified resize the window this command is run in, rather than the active window.
--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 = ''
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
if opts.no_response:
global_opts.no_command_response = True
return {
'match': opts.match, 'action': opts.action, 'unit': opts.unit,
'width': opts.width, 'height': opts.height, 'self': opts.self,
'incremental': opts.incremental
}
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
windows = self.windows_for_match_payload(boss, window, payload_get)
if windows:
ac = payload_get('action')
for os_window_id in {w.os_window_id for w in windows}:
if ac == 'resize':
boss.resize_os_window(
os_window_id, width=payload_get('width'), height=payload_get('height'),
unit=payload_get('unit'), incremental=payload_get('incremental')
)
elif ac == 'toggle-fullscreen':
boss.toggle_fullscreen(os_window_id)
elif ac == 'toggle-maximized':
boss.toggle_maximized(os_window_id)
resize_os_window = ResizeOSWindow()

View File

@ -891,9 +891,11 @@ PYWRAP1(get_os_window_size) {
int width, height, fw, fh;
get_os_window_size(os_window, &width, &height, &fw, &fh);
get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale);
return Py_BuildValue("{si si si si sf sf sd sd}",
unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
return Py_BuildValue("{si si si si sf sf sd sd sI sI}",
"width", width, "height", height, "framebuffer_width", fw, "framebuffer_height", fh,
"xscale", xscale, "yscale", yscale, "xdpi", xdpi, "ydpi", ydpi);
"xscale", xscale, "yscale", yscale, "xdpi", xdpi, "ydpi", ydpi,
"cell_width", cell_width, "cell_height", cell_height);
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}

View File

@ -28,6 +28,7 @@ from .typing import AddressFamily, PopenType, Socket, StartupCtx
if TYPE_CHECKING:
from .options.types import Options
from .fast_data_types import OSWindowSize
else:
Options = object
@ -692,3 +693,23 @@ class SSHConnectionData(NamedTuple):
binary: str
hostname: str
port: Optional[int] = None
def get_new_os_window_size(
metrics: 'OSWindowSize', width: int, height: int, unit: str, incremental: bool = False, has_window_scaling: bool = True
) -> Tuple[int, int]:
if unit == 'cells':
cw = metrics['cell_width']
ch = metrics['cell_height']
if has_window_scaling:
cw = int(cw / metrics['xscale'])
ch = int(ch / metrics['yscale'])
width *= cw
height *= ch
if incremental:
w = metrics['width'] + width
h = metrics['height'] + height
else:
w = width or metrics['width']
h = height or metrics['height']
return w, h

View File

@ -12,6 +12,34 @@ is_macos = 'darwin' in _plat
class TestGLFW(BaseTest):
def test_os_window_size_calculation(self):
from kitty.utils import get_new_os_window_size
def t(w, h, width=0, height=0, unit='cells', incremental=False):
self.ae((w, h), get_new_os_window_size(metrics, width, height, unit, incremental, has_window_scaling))
with self.subTest(has_window_scaling=False):
has_window_scaling = False
metrics = {
'width': 200, 'height': 100,
'framebuffer_width': 200, 'framebuffer_height': 100,
'xscale': 2.0, 'yscale': 2.0, 'xdpi': 192.0, 'ydpi': 192.0,
'cell_width': 8, 'cell_height': 16
}
t(80 * metrics['cell_width'], 100, 80)
t(80 * metrics['cell_width'] + metrics['width'], 100, 80, incremental=True)
t(1217, 100, 1217, unit='pixels')
t(1217 + metrics['width'], 100, 1217, unit='pixels', incremental=True)
with self.subTest(has_window_scaling=True):
has_window_scaling = True
metrics['framebuffer_width'] = metrics['width'] * 2
metrics['framebuffer_height'] = metrics['height'] * 2
t(80 * metrics['cell_width'] / metrics['xscale'], 100, 80)
t(80 * metrics['cell_width'] / metrics['xscale'] + metrics['width'], 100, 80, incremental=True)
t(1217, 100, 1217, unit='pixels')
t(1217 + metrics['width'], 100, 1217, unit='pixels', incremental=True)
@unittest.skipIf(is_macos, 'Skipping test on macOS because glfw-cocoa.so is not built with backend_utils')
def test_utf_8_strndup(self):
import ctypes