Packages
Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/commands/window_handler.ex
defmodule Raxol.Terminal.Commands.WindowHandler do
@moduledoc """
Handles window-related commands and operations for the terminal.
"""
alias Raxol.Terminal.ScreenBuffer
def default_char_width_px, do: 8
def default_char_height_px, do: 16
def calculate_width_chars(pixel_width) do
div(pixel_width, default_char_width_px())
end
def calculate_height_chars(pixel_height) do
div(pixel_height, default_char_height_px())
end
def handle_t(emulator, params) do
op = Enum.at(params, 0, 0)
handler = get_handler(op)
call_handler(handler, emulator, params)
end
defp call_handler(handler, emulator, params) do
case :erlang.fun_info(handler, :arity) do
{:arity, 1} -> handler.(emulator)
{:arity, 2} -> handler.(emulator, params)
_ -> {:ok, emulator}
end
end
defp get_handler(op) do
Map.get(window_handlers(), op, &default_handler/2)
end
defp default_handler(emulator, _params), do: {:ok, emulator}
defp window_handlers do
%{
0 => &handle_window_title/2,
1 => &handle_deiconify/1,
2 => &handle_iconify/1,
3 => &handle_move/2,
4 => &handle_resize/2,
5 => &handle_raise/1,
6 => &handle_lower/1,
7 => &handle_refresh/1,
8 => &handle_icon_name/2,
9 => &handle_maximize/1,
10 => &handle_restore/1,
11 => &handle_report_state/1,
13 => &handle_report_size_pixels/1,
14 => &handle_report_position/1,
18 => &handle_report_text_area_size/1,
19 => &handle_report_desktop_size/1
}
end
def handle_window_title(emulator, params) do
# Get title from params or emulator.window_title or use empty string
title = Enum.at(params, 1, emulator.window_title || "")
output = "\x1b]0;#{title}\x07"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_icon_name(emulator, params) do
# ESC]1;iconBEL
icon = Enum.at(params, 1, "")
output = "\x1b]1;#{icon}\x07"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_icon_title(emulator, params) do
# ESC]2;titleBEL
title = Enum.at(params, 1, "")
output = "\x1b]2;#{title}\x07"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_deiconify(emulator) do
updated_window_state = %{emulator.window_state | iconified: false}
updated_emulator = %{emulator | window_state: updated_window_state}
{:ok, updated_emulator}
end
def handle_iconify(emulator) do
updated_window_state = %{emulator.window_state | iconified: true}
updated_emulator = %{emulator | window_state: updated_window_state}
{:ok, updated_emulator}
end
def handle_move(emulator, params) do
y = Enum.at(params, 1, 0)
x = Enum.at(params, 2, 0)
# Ensure non-negative values, default to 0 for invalid input
safe_x =
case is_integer(x) and x >= 0 do
true -> x
false -> 0
end
safe_y =
case is_integer(y) and y >= 0 do
true -> y
false -> 0
end
updated_window_state = %{emulator.window_state | position: {safe_x, safe_y}}
updated_emulator = %{emulator | window_state: updated_window_state}
{:ok, updated_emulator}
end
@doc """
Resizes the window with the given parameters.
Parameters can be a list of integers representing width and/or height.
"""
def resize(emulator, params) do
case handle_resize(emulator, params) do
{:ok, updated_emulator} -> updated_emulator
end
end
def handle_resize(emulator, params) do
{width_px, height_px} = parse_resize_params(emulator, params)
{safe_width_px, safe_height_px} =
validate_dimensions(emulator, width_px, height_px)
{char_width, char_height} =
calculate_char_dimensions(safe_width_px, safe_height_px)
updated_emulator =
update_emulator_size(
emulator,
char_width,
char_height,
safe_width_px,
safe_height_px
)
{:ok, updated_emulator}
end
defp validate_dimensions(emulator, width_px, height_px) do
safe_width =
validate_dimension(width_px, elem(emulator.window_state.size_pixels, 0))
safe_height =
validate_dimension(height_px, elem(emulator.window_state.size_pixels, 1))
{safe_width, safe_height}
end
defp calculate_char_dimensions(width_px, height_px) do
{calculate_width_chars(width_px), calculate_height_chars(height_px)}
end
defp update_emulator_size(
emulator,
char_width,
char_height,
width_px,
height_px
) do
size = {char_width, char_height}
size_pixels = {width_px, height_px}
updated_window_state = %{
emulator.window_state
| size: size,
size_pixels: size_pixels
}
updated_main_buffer =
resize_screen_buffer(emulator.main_screen_buffer, char_width, char_height)
%{
emulator
| window_state: updated_window_state,
main_screen_buffer: updated_main_buffer,
width: char_width,
height: char_height
}
end
defp parse_resize_params(emulator, params) do
case params do
[op, h, w] when is_integer(op) and op == 4 ->
{w, h}
[op, h] when is_integer(op) and op == 4 ->
{default_width_px(), h}
[op] when is_integer(op) and op == 4 ->
{default_width_px(), default_height_px()}
[w] when is_integer(w) ->
{w, get_current_height(emulator)}
[w, h] when is_integer(w) and is_integer(h) ->
{w, h}
_ ->
emulator.window_state.size_pixels
end
end
defp default_width_px, do: 80 * default_char_width_px()
defp default_height_px, do: 24 * default_char_height_px()
defp get_current_height(emulator),
do: elem(emulator.window_state.size_pixels, 1)
defp validate_dimension(value, fallback) do
case is_integer(value) and value > 0 do
true -> value
false -> fallback
end
end
defp resize_screen_buffer(buffer, width, height) do
case {buffer, width, height} do
{nil, _, _} ->
ScreenBuffer.new(width, height)
{buf, w, h} when w > 0 and h > 0 ->
case Raxol.Core.ErrorHandling.safe_call(fn ->
ScreenBuffer.resize(buf, w, h)
end) do
{:ok, resized_buffer} -> resized_buffer
{:error, _reason} -> ScreenBuffer.new(w, h)
end
_ ->
buffer
end
end
def handle_raise(emulator) do
updated_window_state = %{emulator.window_state | stacking_order: :above}
updated_emulator = %{emulator | window_state: updated_window_state}
{:ok, updated_emulator}
end
def handle_lower(emulator) do
updated_window_state = %{emulator.window_state | stacking_order: :below}
updated_emulator = %{emulator | window_state: updated_window_state}
{:ok, updated_emulator}
end
def handle_refresh(emulator) do
# Refresh is a no-op
{:ok, emulator}
end
def handle_maximize(emulator) do
# Store current size before maximizing
current_size = emulator.window_state.size
# Default maximized size
maximized_size = {160, 60}
updated_window_state = %{
emulator.window_state
| maximized: true,
previous_size: current_size,
size: maximized_size,
size_pixels:
{elem(maximized_size, 0) * default_char_width_px(),
elem(maximized_size, 1) * default_char_height_px()}
}
# Update screen buffer dimensions
updated_main_buffer =
ScreenBuffer.resize(emulator.main_screen_buffer, 160, 60)
updated_emulator = %{
emulator
| window_state: updated_window_state,
main_screen_buffer: updated_main_buffer,
width: 160,
height: 60
}
{:ok, updated_emulator}
end
def handle_restore(emulator) do
# Restore to previous size, fallback to {80, 24} if missing/invalid
previous_size =
case emulator.window_state.previous_size do
{w, h} when is_integer(w) and is_integer(h) and w > 0 and h > 0 ->
{w, h}
_ ->
{80, 24}
end
char_width = elem(previous_size, 0)
char_height = elem(previous_size, 1)
updated_window_state = %{
emulator.window_state
| maximized: false,
size: previous_size,
size_pixels:
{char_width * default_char_width_px(), char_height * default_char_height_px()}
}
# Update screen buffer dimensions, create new if nil
updated_main_buffer =
case emulator.main_screen_buffer do
nil -> ScreenBuffer.new(char_width, char_height)
buffer -> ScreenBuffer.resize(buffer, char_width, char_height)
end
updated_emulator = %{
emulator
| window_state: updated_window_state,
main_screen_buffer: updated_main_buffer,
width: char_width,
height: char_height
}
{:ok, updated_emulator}
end
def handle_report_state(emulator) do
# Report window state via output buffer
state_code =
case emulator.window_state.iconified do
true -> "2"
false -> "1"
end
output = "\x1b[#{state_code}t"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_report_size_pixels(emulator) do
# Report window size in pixels - use actual pixel size
{width_px, height_px} = emulator.window_state.size_pixels
output = "\x1b[4;#{height_px};#{width_px}t"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_report_position(emulator) do
# Report window position
{x, y} = emulator.window_state.position
output = "\x1b[3;#{y};#{x}t"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_report_text_area_size(emulator) do
# Report text area size in characters
{width, height} = emulator.window_state.size
output = "\x1b[8;#{height};#{width}t"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
def handle_report_desktop_size(emulator) do
# Report desktop size in characters (hardcoded defaults)
output = "\x1b[9;60;160t"
updated_emulator = %{emulator | output_buffer: output}
{:ok, updated_emulator}
end
# Caching support functions
# Originally from WindowHandler module
# FontMetricsCache lives in main raxol; guarded at runtime
@compile {:no_warn_undefined, Raxol.Performance.Caches.FontMetricsCache}
alias Raxol.Terminal.Font.Manager, as: FontManager
@default_font_size 14
@default_line_height 1.143
@doc """
Gets cached font dimensions for performance.
"""
def get_cached_char_dimensions do
font_manager = get_default_font_manager()
if Code.ensure_loaded?(Raxol.Performance.Caches.FontMetricsCache) do
Raxol.Performance.Caches.FontMetricsCache.get_font_dimensions(font_manager)
else
{default_char_width_px(), default_char_height_px()}
end
end
defp get_default_font_manager do
%FontManager{
family: "monospace",
size: @default_font_size,
weight: :normal,
style: :normal,
line_height: @default_line_height,
letter_spacing: 0,
fallback_fonts: [],
custom_fonts: %{}
}
end
@doc """
Cached version of char width calculation.
"""
def cached_char_width_px do
{char_width, _} = get_cached_char_dimensions()
char_width
end
@doc """
Cached version of char height calculation.
"""
def cached_char_height_px do
{_, char_height} = get_cached_char_dimensions()
char_height
end
end