From b6e92dd79d18e3f1e9ce04917699e3f077071377 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 May 2024 19:59:59 +0530 Subject: [PATCH] Query font size and DPI from terminal --- kittens/choose_fonts/ui.go | 50 ++++++++++++++++++++++++++++------ kittens/query_terminal/main.py | 24 +++++++++++++++- kitty/terminfo.py | 2 +- tools/tui/loop/api.go | 15 ++++++++++ tools/tui/loop/run.go | 19 +++++++++++++ 5 files changed, 100 insertions(+), 10 deletions(-) diff --git a/kittens/choose_fonts/ui.go b/kittens/choose_fonts/ui.go index 855b50e4e..25e5792a7 100644 --- a/kittens/choose_fonts/ui.go +++ b/kittens/choose_fonts/ui.go @@ -2,6 +2,7 @@ package choose_fonts import ( "fmt" + "strconv" "strings" "sync" @@ -24,14 +25,16 @@ const ( ) type handler struct { - lp *loop.Loop - fonts map[string][]ListedFont - state State - err_mutex sync.Mutex - err_in_worker_thread error - mouse_state tui.MouseState - render_count uint - render_lines tui.RenderLines + lp *loop.Loop + fonts map[string][]ListedFont + state State + err_mutex sync.Mutex + err_in_worker_thread error + mouse_state tui.MouseState + render_count uint + render_lines tui.RenderLines + font_sz_in_pts float64 + logical_dpi_x, logical_dpi_y float64 // Listing rl *readline.Readline @@ -259,6 +262,8 @@ func (h *handler) handle_listing_text(text string, from_key_event bool, in_brack // Events {{{ func (h *handler) initialize() { h.lp.SetCursorVisible(false) + h.lp.OnQueryResponse = h.on_query_response + h.lp.QueryTerminal("font_size", "dpi_x", "dpi_y") h.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Family: "}) h.variable_data_requested_for = utils.NewSet[string](256) h.draw_screen() @@ -274,6 +279,35 @@ func (h *handler) finalize() { h.lp.SetCursorShape(loop.BLOCK_CURSOR, true) } +func (h *handler) on_query_response(key, val string, valid bool) error { + if !valid { + return fmt.Errorf("Terminal does not support querying the: %s", key) + } + set_float := func(k, v string, dest *float64) error { + if fs, err := strconv.ParseFloat(v, 64); err == nil { + *dest = fs + } else { + return fmt.Errorf("Invalid response from terminal to %s query: %#v", k, v) + } + return nil + } + switch key { + case "font_size": + if err := set_float(key, val, &h.font_sz_in_pts); err != nil { + return err + } + case "logical_dpi_x": + if err := set_float(key, val, &h.logical_dpi_x); err != nil { + return err + } + case "logical_dpi_y": + if err := set_float(key, val, &h.logical_dpi_y); err != nil { + return err + } + } + return nil +} + func (h *handler) draw_screen() (err error) { h.render_count++ h.lp.StartAtomicUpdate() diff --git a/kittens/query_terminal/main.py b/kittens/query_terminal/main.py index 6303f00e1..a57903e38 100644 --- a/kittens/query_terminal/main.py +++ b/kittens/query_terminal/main.py @@ -150,7 +150,29 @@ class FontSize(Query): def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) - return f'{cf['font_sz_in_pts']:g}' + return f'{cf["font_sz_in_pts"]:g}' + +@query +class DpiX(Query): + name: str = 'dpi_x' + help_text: str = 'The current DPI on the x-axis' + + @staticmethod + def get_result(opts: Options, window_id: int, os_window_id: int) -> str: + from kitty.fast_data_types import current_fonts + cf = current_fonts(os_window_id) + return f'{cf["logical_dpi_x"]:g}' + +@query +class DpiY(Query): + name: str = 'dpi_y' + help_text: str = 'The current DPI on the y-axis' + + @staticmethod + def get_result(opts: Options, window_id: int, os_window_id: int) -> str: + from kitty.fast_data_types import current_fonts + cf = current_fonts(os_window_id) + return f'{cf["logical_dpi_y"]:g}' @query diff --git a/kitty/terminfo.py b/kitty/terminfo.py index ddad06019..7d75adc4e 100644 --- a/kitty/terminfo.py +++ b/kitty/terminfo.py @@ -517,7 +517,7 @@ def key_as_bytes(name: str) -> bytes: return ans.encode('ascii') -def get_capabilities(query_string: str, opts: 'Options', window_id: int, os_window_id: int) -> Generator[str, None, None]: +def get_capabilities(query_string: str, opts: 'Options', window_id: int = 0, os_window_id: int = 0) -> Generator[str, None, None]: from .fast_data_types import ERROR_PREFIX def result(encoded_query_name: str, x: Optional[str] = None) -> str: diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index 669ee3e0e..18b98771d 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -4,6 +4,7 @@ package loop import ( "encoding/base64" + "encoding/hex" "fmt" "os" "runtime" @@ -89,6 +90,9 @@ type Loop struct { // Called when a response to an rc command is received OnRCResponse func(data []byte) error + // Called when a response to a query command is received + OnQueryResponse func(key, val string, valid bool) error + // Called when any input from tty is received OnReceivedData func(data []byte) error @@ -481,6 +485,17 @@ func (self *Loop) CopyTextToClipboard(text string) { self.copy_text_to(text, "c") } +func (self *Loop) QueryTerminal(fields ...string) IdType { + if len(fields) == 0 { + return 0 + } + q := make([]string, len(fields)) + for i, x := range fields { + q[i] = hex.EncodeToString(utils.UnsafeStringToBytes("kitty-query-" + x)) + } + return self.QueueWriteString(fmt.Sprintf("\x1bP+q%s\a", strings.Join(q, ";"))) +} + func (self *Loop) PushPointerShape(s PointerShape) { self.pointer_shapes = append(self.pointer_shapes, s) self.QueueWriteString("\x1b]22;" + s.String() + "\x1b\\") diff --git a/tools/tui/loop/run.go b/tools/tui/loop/run.go index a0cda20c4..fb6ec6a45 100644 --- a/tools/tui/loop/run.go +++ b/tools/tui/loop/run.go @@ -4,6 +4,7 @@ package loop import ( "bytes" + "encoding/hex" "errors" "fmt" "io" @@ -172,6 +173,24 @@ func (self *Loop) handle_dcs(raw []byte) error { if self.OnRCResponse != nil && bytes.HasPrefix(raw, utils.UnsafeStringToBytes("@kitty-cmd")) { return self.OnRCResponse(raw[len("@kitty-cmd"):]) } + if self.OnQueryResponse != nil && (bytes.HasPrefix(raw, utils.UnsafeStringToBytes("1+r")) || bytes.HasPrefix(raw, utils.UnsafeStringToBytes("0+r"))) { + valid := raw[0] == '1' + s := utils.NewSeparatorScanner(utils.UnsafeBytesToString(raw[3:]), ";") + for s.Scan() { + key, val, _ := strings.Cut(s.Text(), "=") + if k, err := hex.DecodeString(key); err == nil { + if bytes.HasPrefix(k, utils.UnsafeStringToBytes("kitty-query-")) { + k = k[len("kitty-query-"):] + if v, err := hex.DecodeString(val); err == nil { + if err = self.OnQueryResponse(string(k), string(v), valid); err != nil { + return err + } + } + } + } + } + return nil + } if self.OnEscapeCode != nil { return self.OnEscapeCode(DCS, raw) }