mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge branch 'ghostty-org:main' into invertCursor
This commit is contained in:
189
.github/scripts/request_review.py
vendored
189
.github/scripts/request_review.py
vendored
@ -1,189 +0,0 @@
|
|||||||
# /// script
|
|
||||||
# requires-python = ">=3.9"
|
|
||||||
# dependencies = [
|
|
||||||
# "githubkit",
|
|
||||||
# "loguru",
|
|
||||||
# ]
|
|
||||||
# ///
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from collections.abc import Iterator
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from githubkit import GitHub
|
|
||||||
from githubkit.exception import RequestFailed
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
ORG_NAME = "ghostty-org"
|
|
||||||
REPO_NAME = "ghostty"
|
|
||||||
ALLOWED_PARENT_TEAM = "localization"
|
|
||||||
LOCALIZATION_TEAM_NAME_PATTERN = re.compile(r"[a-z]{2}_[A-Z]{2}")
|
|
||||||
LEVEL_MAP = {"DEBUG": "DBG", "WARNING": "WRN", "ERROR": "ERR"}
|
|
||||||
|
|
||||||
logger.remove()
|
|
||||||
logger.add(
|
|
||||||
sys.stderr,
|
|
||||||
format=lambda record: (
|
|
||||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
||||||
f"<level>{LEVEL_MAP[record['level'].name]}</level> | "
|
|
||||||
"<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
|
||||||
"<level>{message}</level>\n"
|
|
||||||
),
|
|
||||||
backtrace=True,
|
|
||||||
diagnose=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def log_fail(message: str, *, die: bool = True) -> Iterator[None]:
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except RequestFailed as exc:
|
|
||||||
logger.error(message)
|
|
||||||
logger.error(exc)
|
|
||||||
logger.error(exc.response.raw_response.json())
|
|
||||||
if die:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
gh = GitHub(os.environ["GITHUB_TOKEN"])
|
|
||||||
|
|
||||||
with log_fail("Invalid token"):
|
|
||||||
# Do the simplest request as a test
|
|
||||||
gh.rest.rate_limit.get()
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_and_parse_codeowners() -> dict[str, str]:
|
|
||||||
logger.debug("Fetching CODEOWNERS file...")
|
|
||||||
with log_fail("Failed to fetch CODEOWNERS file"):
|
|
||||||
content = (
|
|
||||||
await gh.rest.repos.async_get_content(
|
|
||||||
ORG_NAME,
|
|
||||||
REPO_NAME,
|
|
||||||
"CODEOWNERS",
|
|
||||||
headers={"Accept": "application/vnd.github.raw+json"},
|
|
||||||
)
|
|
||||||
).text
|
|
||||||
|
|
||||||
logger.debug("Parsing CODEOWNERS file...")
|
|
||||||
codeowners: dict[str, str] = {}
|
|
||||||
for line in content.splitlines():
|
|
||||||
if not line or line.lstrip().startswith("#"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# This assumes that all entries only list one owner
|
|
||||||
# and that this owner is a team (ghostty-org/foobar)
|
|
||||||
path, owner = line.split()
|
|
||||||
path = path.lstrip("/")
|
|
||||||
owner = owner.removeprefix(f"@{ORG_NAME}/")
|
|
||||||
|
|
||||||
if not is_localization_team(owner):
|
|
||||||
logger.debug(f"Skipping non-l11n codeowner {owner!r} for {path}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
codeowners[path] = owner
|
|
||||||
logger.debug(f"Found codeowner {owner!r} for {path}")
|
|
||||||
return codeowners
|
|
||||||
|
|
||||||
|
|
||||||
async def get_team_members(team_name: str) -> list[str]:
|
|
||||||
logger.debug(f"Fetching team {team_name!r}...")
|
|
||||||
with log_fail(f"Failed to fetch team {team_name!r}"):
|
|
||||||
team = (await gh.rest.teams.async_get_by_name(ORG_NAME, team_name)).parsed_data
|
|
||||||
|
|
||||||
if team.parent and team.parent.slug == ALLOWED_PARENT_TEAM:
|
|
||||||
logger.debug(f"Fetching team {team_name!r} members...")
|
|
||||||
with log_fail(f"Failed to fetch team {team_name!r} members"):
|
|
||||||
resp = await gh.rest.teams.async_list_members_in_org(ORG_NAME, team_name)
|
|
||||||
members = [m.login for m in resp.parsed_data]
|
|
||||||
logger.debug(f"Team {team_name!r} members: {', '.join(members)}")
|
|
||||||
return members
|
|
||||||
|
|
||||||
logger.warning(f"Team {team_name} does not have a {ALLOWED_PARENT_TEAM!r} parent")
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
async def get_changed_files(pr_number: int) -> list[str]:
|
|
||||||
logger.debug("Gathering changed files...")
|
|
||||||
with log_fail("Failed to gather changed files"):
|
|
||||||
diff_entries = (
|
|
||||||
await gh.rest.pulls.async_list_files(
|
|
||||||
ORG_NAME,
|
|
||||||
REPO_NAME,
|
|
||||||
pr_number,
|
|
||||||
per_page=3000,
|
|
||||||
headers={"Accept": "application/vnd.github+json"},
|
|
||||||
)
|
|
||||||
).parsed_data
|
|
||||||
return [d.filename for d in diff_entries]
|
|
||||||
|
|
||||||
|
|
||||||
async def request_review(pr_number: int, user: str, pr_author: str) -> None:
|
|
||||||
if user == pr_author:
|
|
||||||
logger.debug(f"Skipping review request for {user!r} (is PR author)")
|
|
||||||
logger.debug(f"Requesting review from {user!r}...")
|
|
||||||
with log_fail(f"Failed to request review from {user}", die=False):
|
|
||||||
await gh.rest.pulls.async_request_reviewers(
|
|
||||||
ORG_NAME,
|
|
||||||
REPO_NAME,
|
|
||||||
pr_number,
|
|
||||||
headers={"Accept": "application/vnd.github+json"},
|
|
||||||
data={"reviewers": [user]},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_localization_team(team_name: str) -> bool:
|
|
||||||
return LOCALIZATION_TEAM_NAME_PATTERN.fullmatch(team_name) is not None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_pr_author(pr_number: int) -> str:
|
|
||||||
logger.debug("Fetching PR author...")
|
|
||||||
with log_fail("Failed to fetch PR author"):
|
|
||||||
resp = await gh.rest.pulls.async_get(ORG_NAME, REPO_NAME, pr_number)
|
|
||||||
pr_author = resp.parsed_data.user.login
|
|
||||||
logger.debug(f"Found author: {pr_author!r}")
|
|
||||||
return pr_author
|
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
|
||||||
logger.debug("Reading PR number...")
|
|
||||||
pr_number = int(os.environ["PR_NUMBER"])
|
|
||||||
logger.debug(f"Starting review request process for PR #{pr_number}...")
|
|
||||||
|
|
||||||
changed_files = await get_changed_files(pr_number)
|
|
||||||
logger.debug(f"Changed files: {', '.join(map(repr, changed_files))}")
|
|
||||||
|
|
||||||
pr_author = await get_pr_author(pr_number)
|
|
||||||
codeowners = await fetch_and_parse_codeowners()
|
|
||||||
|
|
||||||
found_owners = set[str]()
|
|
||||||
for file in changed_files:
|
|
||||||
logger.debug(f"Finding owner for {file!r}...")
|
|
||||||
for path, owner in codeowners.items():
|
|
||||||
if file.startswith(path):
|
|
||||||
logger.debug(f"Found owner: {owner!r}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.debug("No owner found")
|
|
||||||
continue
|
|
||||||
found_owners.add(owner)
|
|
||||||
|
|
||||||
member_lists = await asyncio.gather(
|
|
||||||
*(get_team_members(owner) for owner in found_owners)
|
|
||||||
)
|
|
||||||
await asyncio.gather(
|
|
||||||
*(
|
|
||||||
request_review(pr_number, user, pr_author)
|
|
||||||
for user in chain.from_iterable(member_lists)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
37
.github/workflows/review.yml
vendored
37
.github/workflows/review.yml
vendored
@ -1,37 +0,0 @@
|
|||||||
name: Request Review
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
|
|
||||||
env:
|
|
||||||
PY_COLORS: 1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
review:
|
|
||||||
runs-on: namespace-profile-ghostty-xsm
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Cache
|
|
||||||
uses: namespacelabs/nscloud-cache-action@v1.2.0
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
/nix
|
|
||||||
/zig
|
|
||||||
|
|
||||||
- uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: ghostty
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
|
|
||||||
- name: Request Localization Review
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_REVIEW_TOKEN }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
run: nix develop -c uv run .github/scripts/request_review.py
|
|
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@ -374,6 +374,11 @@ jobs:
|
|||||||
/zig
|
/zig
|
||||||
- run: sudo apt install -y udev
|
- run: sudo apt install -y udev
|
||||||
- run: sudo systemctl start systemd-udevd
|
- run: sudo systemctl start systemd-udevd
|
||||||
|
# Workaround until this is fixed: https://github.com/canonical/lxd-pkg-snap/pull/789
|
||||||
|
- run: |
|
||||||
|
_LXD_SNAP_DEVCGROUP_CONFIG="/var/lib/snapd/cgroup/snap.lxd.device"
|
||||||
|
sudo mkdir -p /var/lib/snapd/cgroup
|
||||||
|
echo 'self-managed=true' | sudo tee "${_LXD_SNAP_DEVCGROUP_CONFIG}"
|
||||||
- uses: snapcore/action-build@v1
|
- uses: snapcore/action-build@v1
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
|
|
||||||
# Shell
|
# Shell
|
||||||
/src/shell-integration/ @ghostty-org/shell
|
/src/shell-integration/ @ghostty-org/shell
|
||||||
/src/termio/shell-integration.zig @ghostty-org/shell
|
/src/termio/shell_integration.zig @ghostty-org/shell
|
||||||
|
|
||||||
# Terminal
|
# Terminal
|
||||||
/src/simd/ @ghostty-org/terminal
|
/src/simd/ @ghostty-org/terminal
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
app-id: com.mitchellh.ghostty.Devel
|
app-id: com.mitchellh.ghostty.Devel
|
||||||
runtime: org.gnome.Platform
|
runtime: org.gnome.Platform
|
||||||
runtime-version: "47"
|
runtime-version: "48"
|
||||||
sdk: org.gnome.Sdk
|
sdk: org.gnome.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.ziglang
|
- org.freedesktop.Sdk.Extension.ziglang
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
app-id: com.mitchellh.ghostty
|
app-id: com.mitchellh.ghostty
|
||||||
runtime: org.gnome.Platform
|
runtime: org.gnome.Platform
|
||||||
runtime-version: "47"
|
runtime-version: "48"
|
||||||
sdk: org.gnome.Sdk
|
sdk: org.gnome.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.ziglang
|
- org.freedesktop.Sdk.Extension.ziglang
|
||||||
|
@ -429,6 +429,13 @@ typedef enum {
|
|||||||
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
||||||
} ghostty_action_fullscreen_e;
|
} ghostty_action_fullscreen_e;
|
||||||
|
|
||||||
|
// apprt.action.FloatWindow
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_FLOAT_WINDOW_ON,
|
||||||
|
GHOSTTY_FLOAT_WINDOW_OFF,
|
||||||
|
GHOSTTY_FLOAT_WINDOW_TOGGLE,
|
||||||
|
} ghostty_action_float_window_e;
|
||||||
|
|
||||||
// apprt.action.SecureInput
|
// apprt.action.SecureInput
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_SECURE_INPUT_ON,
|
GHOSTTY_SECURE_INPUT_ON,
|
||||||
@ -610,6 +617,7 @@ typedef enum {
|
|||||||
GHOSTTY_ACTION_RENDERER_HEALTH,
|
GHOSTTY_ACTION_RENDERER_HEALTH,
|
||||||
GHOSTTY_ACTION_OPEN_CONFIG,
|
GHOSTTY_ACTION_OPEN_CONFIG,
|
||||||
GHOSTTY_ACTION_QUIT_TIMER,
|
GHOSTTY_ACTION_QUIT_TIMER,
|
||||||
|
GHOSTTY_ACTION_FLOAT_WINDOW,
|
||||||
GHOSTTY_ACTION_SECURE_INPUT,
|
GHOSTTY_ACTION_SECURE_INPUT,
|
||||||
GHOSTTY_ACTION_KEY_SEQUENCE,
|
GHOSTTY_ACTION_KEY_SEQUENCE,
|
||||||
GHOSTTY_ACTION_COLOR_CHANGE,
|
GHOSTTY_ACTION_COLOR_CHANGE,
|
||||||
@ -638,6 +646,7 @@ typedef union {
|
|||||||
ghostty_action_mouse_over_link_s mouse_over_link;
|
ghostty_action_mouse_over_link_s mouse_over_link;
|
||||||
ghostty_action_renderer_health_e renderer_health;
|
ghostty_action_renderer_health_e renderer_health;
|
||||||
ghostty_action_quit_timer_e quit_timer;
|
ghostty_action_quit_timer_e quit_timer;
|
||||||
|
ghostty_action_float_window_e float_window;
|
||||||
ghostty_action_secure_input_e secure_input;
|
ghostty_action_secure_input_e secure_input;
|
||||||
ghostty_action_key_sequence_s key_sequence;
|
ghostty_action_key_sequence_s key_sequence;
|
||||||
ghostty_action_color_change_s color_change;
|
ghostty_action_color_change_s color_change;
|
||||||
|
@ -52,6 +52,8 @@ class AppDelegate: NSObject,
|
|||||||
@IBOutlet private var menuSelectSplitLeft: NSMenuItem?
|
@IBOutlet private var menuSelectSplitLeft: NSMenuItem?
|
||||||
@IBOutlet private var menuSelectSplitRight: NSMenuItem?
|
@IBOutlet private var menuSelectSplitRight: NSMenuItem?
|
||||||
@IBOutlet private var menuReturnToDefaultSize: NSMenuItem?
|
@IBOutlet private var menuReturnToDefaultSize: NSMenuItem?
|
||||||
|
@IBOutlet private var menuFloatOnTop: NSMenuItem?
|
||||||
|
@IBOutlet private var menuUseAsDefault: NSMenuItem?
|
||||||
|
|
||||||
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
|
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
|
||||||
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
|
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
|
||||||
@ -175,6 +177,12 @@ class AppDelegate: NSObject,
|
|||||||
handler: localEventHandler)
|
handler: localEventHandler)
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(windowDidBecomeKey),
|
||||||
|
name: NSWindow.didBecomeKeyNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(quickTerminalDidChangeVisibility),
|
selector: #selector(quickTerminalDidChangeVisibility),
|
||||||
@ -406,6 +414,7 @@ class AppDelegate: NSObject,
|
|||||||
syncMenuShortcut(config, action: "prompt_surface_title", menuItem: self.menuChangeTitle)
|
syncMenuShortcut(config, action: "prompt_surface_title", menuItem: self.menuChangeTitle)
|
||||||
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
|
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
|
||||||
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
|
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
|
||||||
|
syncMenuShortcut(config, action: "toggle_window_float_on_top", menuItem: self.menuFloatOnTop)
|
||||||
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
||||||
syncMenuShortcut(config, action: "toggle_command_palette", menuItem: self.menuCommandPalette)
|
syncMenuShortcut(config, action: "toggle_command_palette", menuItem: self.menuCommandPalette)
|
||||||
|
|
||||||
@ -497,6 +506,10 @@ class AppDelegate: NSObject,
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func windowDidBecomeKey(_ notification: Notification) {
|
||||||
|
syncFloatOnTopMenu(notification.object as? NSWindow)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func quickTerminalDidChangeVisibility(_ notification: Notification) {
|
@objc private func quickTerminalDidChangeVisibility(_ notification: Notification) {
|
||||||
guard let quickController = notification.object as? QuickTerminalController else { return }
|
guard let quickController = notification.object as? QuickTerminalController else { return }
|
||||||
self.menuQuickTerminal?.state = if (quickController.visible) { .on } else { .off }
|
self.menuQuickTerminal?.state = if (quickController.visible) { .on } else { .off }
|
||||||
@ -899,3 +912,50 @@ class AppDelegate: NSObject,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Floating Windows
|
||||||
|
|
||||||
|
extension AppDelegate {
|
||||||
|
func syncFloatOnTopMenu(_ window: NSWindow?) {
|
||||||
|
guard let window = (window ?? NSApp.keyWindow) as? TerminalWindow else {
|
||||||
|
// If some other window became key we always turn this off
|
||||||
|
self.menuFloatOnTop?.state = .off
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.menuFloatOnTop?.state = window.level == .floating ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func floatOnTop(_ menuItem: NSMenuItem) {
|
||||||
|
menuItem.state = menuItem.state == .on ? .off : .on
|
||||||
|
guard let window = NSApp.keyWindow else { return }
|
||||||
|
window.level = menuItem.state == .on ? .floating : .normal
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func useAsDefault(_ sender: NSMenuItem) {
|
||||||
|
let ud = UserDefaults.standard
|
||||||
|
let key = TerminalWindow.defaultLevelKey
|
||||||
|
if (menuFloatOnTop?.state == .on) {
|
||||||
|
ud.set(NSWindow.Level.floating, forKey: key)
|
||||||
|
} else {
|
||||||
|
ud.removeObject(forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: NSMenuItemValidation
|
||||||
|
|
||||||
|
extension AppDelegate: NSMenuItemValidation {
|
||||||
|
func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
||||||
|
switch item.action {
|
||||||
|
case #selector(floatOnTop(_:)),
|
||||||
|
#selector(useAsDefault(_:)):
|
||||||
|
// Float on top items only active if the key window is a primary
|
||||||
|
// terminal window (not quick terminal).
|
||||||
|
return NSApp.keyWindow is TerminalWindow
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/>
|
<outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/>
|
||||||
<outlet property="menuDecreaseFontSize" destination="kzb-SZ-dOA" id="Y1B-Vh-6Z2"/>
|
<outlet property="menuDecreaseFontSize" destination="kzb-SZ-dOA" id="Y1B-Vh-6Z2"/>
|
||||||
<outlet property="menuEqualizeSplits" destination="3gH-VD-vL9" id="SiZ-ce-FOF"/>
|
<outlet property="menuEqualizeSplits" destination="3gH-VD-vL9" id="SiZ-ce-FOF"/>
|
||||||
|
<outlet property="menuFloatOnTop" destination="uRj-7z-1Nh" id="94n-o9-Jol"/>
|
||||||
<outlet property="menuIncreaseFontSize" destination="CIH-ey-Z6x" id="hkc-9C-80E"/>
|
<outlet property="menuIncreaseFontSize" destination="CIH-ey-Z6x" id="hkc-9C-80E"/>
|
||||||
<outlet property="menuMoveSplitDividerDown" destination="Zj7-2W-fdF" id="997-LL-nlN"/>
|
<outlet property="menuMoveSplitDividerDown" destination="Zj7-2W-fdF" id="997-LL-nlN"/>
|
||||||
<outlet property="menuMoveSplitDividerLeft" destination="wSR-ny-j1a" id="HCZ-CI-2ob"/>
|
<outlet property="menuMoveSplitDividerLeft" destination="wSR-ny-j1a" id="HCZ-CI-2ob"/>
|
||||||
@ -56,6 +57,7 @@
|
|||||||
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
|
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
|
||||||
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
|
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
|
||||||
<outlet property="menuToggleVisibility" destination="DOX-wA-ilh" id="iBj-Bc-2bq"/>
|
<outlet property="menuToggleVisibility" destination="DOX-wA-ilh" id="iBj-Bc-2bq"/>
|
||||||
|
<outlet property="menuUseAsDefault" destination="TrB-O8-g8H" id="af4-Jh-2HU"/>
|
||||||
<outlet property="menuZoomSplit" destination="oPd-mn-IEH" id="wTu-jK-egI"/>
|
<outlet property="menuZoomSplit" destination="oPd-mn-IEH" id="wTu-jK-egI"/>
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
@ -402,6 +404,19 @@
|
|||||||
<action selector="returnToDefaultSize:" target="-1" id="Bpt-GO-UU1"/>
|
<action selector="returnToDefaultSize:" target="-1" id="Bpt-GO-UU1"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="Cm3-gn-vtj"/>
|
||||||
|
<menuItem title="Float on Top" id="uRj-7z-1Nh">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="floatOnTop:" target="bbz-4X-AYv" id="N58-PO-7pj"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Use as Default" id="TrB-O8-g8H">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="useAsDefault:" target="bbz-4X-AYv" id="RHA-Nl-L2U"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="CpM-rI-Sc1"/>
|
<menuItem isSeparatorItem="YES" id="CpM-rI-Sc1"/>
|
||||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class TerminalWindow: NSWindow {
|
class TerminalWindow: NSWindow {
|
||||||
|
/// This is the key in UserDefaults to use for the default `level` value.
|
||||||
|
static let defaultLevelKey: String = "TerminalDefaultLevel"
|
||||||
|
|
||||||
@objc dynamic var keyEquivalent: String = ""
|
@objc dynamic var keyEquivalent: String = ""
|
||||||
|
|
||||||
/// This is used to determine if certain elements should be drawn light or dark and should
|
/// This is used to determine if certain elements should be drawn light or dark and should
|
||||||
@ -63,6 +66,8 @@ class TerminalWindow: NSWindow {
|
|||||||
if titlebarTabs {
|
if titlebarTabs {
|
||||||
generateToolbar()
|
generateToolbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
level = UserDefaults.standard.value(forKey: Self.defaultLevelKey) as? NSWindow.Level ?? .normal
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -496,6 +496,9 @@ extension Ghostty {
|
|||||||
case GHOSTTY_ACTION_OPEN_CONFIG:
|
case GHOSTTY_ACTION_OPEN_CONFIG:
|
||||||
ghostty_config_open()
|
ghostty_config_open()
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_FLOAT_WINDOW:
|
||||||
|
toggleFloatWindow(app, target: target, mode: action.action.float_window)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_SECURE_INPUT:
|
case GHOSTTY_ACTION_SECURE_INPUT:
|
||||||
toggleSecureInput(app, target: target, mode: action.action.secure_input)
|
toggleSecureInput(app, target: target, mode: action.action.secure_input)
|
||||||
|
|
||||||
@ -1026,6 +1029,43 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func toggleFloatWindow(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
mode mode_raw: ghostty_action_float_window_e
|
||||||
|
) {
|
||||||
|
guard let mode = SetFloatWIndow.from(mode_raw) else { return }
|
||||||
|
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("toggle float window does nothing with an app target")
|
||||||
|
return
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
guard let window = surfaceView.window as? TerminalWindow else { return }
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case .on:
|
||||||
|
window.level = .floating
|
||||||
|
|
||||||
|
case .off:
|
||||||
|
window.level = .normal
|
||||||
|
|
||||||
|
case .toggle:
|
||||||
|
window.level = window.level == .floating ? .normal : .floating
|
||||||
|
}
|
||||||
|
|
||||||
|
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
|
||||||
|
appDelegate.syncFloatOnTopMenu(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func toggleSecureInput(
|
private static func toggleSecureInput(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s,
|
target: ghostty_target_s,
|
||||||
|
@ -42,6 +42,28 @@ extension Ghostty {
|
|||||||
// MARK: Swift Types for C Types
|
// MARK: Swift Types for C Types
|
||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
|
enum SetFloatWIndow {
|
||||||
|
case on
|
||||||
|
case off
|
||||||
|
case toggle
|
||||||
|
|
||||||
|
static func from(_ c: ghostty_action_float_window_e) -> Self? {
|
||||||
|
switch (c) {
|
||||||
|
case GHOSTTY_FLOAT_WINDOW_ON:
|
||||||
|
return .on
|
||||||
|
|
||||||
|
case GHOSTTY_FLOAT_WINDOW_OFF:
|
||||||
|
return .off
|
||||||
|
|
||||||
|
case GHOSTTY_FLOAT_WINDOW_TOGGLE:
|
||||||
|
return .toggle
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum SetSecureInput {
|
enum SetSecureInput {
|
||||||
case on
|
case on
|
||||||
case off
|
case off
|
||||||
|
@ -975,7 +975,14 @@ extension Ghostty {
|
|||||||
event: event,
|
event: event,
|
||||||
translationEvent: translationEvent,
|
translationEvent: translationEvent,
|
||||||
text: translationEvent.ghosttyCharacters,
|
text: translationEvent.ghosttyCharacters,
|
||||||
composing: markedText.length > 0
|
|
||||||
|
// We're composing if we have preedit (the obvious case). But we're also
|
||||||
|
// composing if we don't have preedit and we had marked text before,
|
||||||
|
// because this input probably just reset the preedit state. It shouldn't
|
||||||
|
// be encoded. Example: Japanese begin composing, the press backspace.
|
||||||
|
// This should only cancel the composing state but not actually delete
|
||||||
|
// the prior input characters (prior to the composing).
|
||||||
|
composing: markedText.length > 0 || markedTextBefore
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4289,6 +4289,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.toggle_window_float_on_top => return try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.float_window,
|
||||||
|
.toggle,
|
||||||
|
),
|
||||||
|
|
||||||
.toggle_secure_input => return try self.rt_app.performAction(
|
.toggle_secure_input => return try self.rt_app.performAction(
|
||||||
.{ .surface = self },
|
.{ .surface = self },
|
||||||
.secure_input,
|
.secure_input,
|
||||||
|
@ -205,6 +205,10 @@ pub const Action = union(Key) {
|
|||||||
/// happen and can be ignored or cause a restart it isn't that important.
|
/// happen and can be ignored or cause a restart it isn't that important.
|
||||||
quit_timer: QuitTimer,
|
quit_timer: QuitTimer,
|
||||||
|
|
||||||
|
/// Set the window floating state. A floating window is one that is
|
||||||
|
/// always on top of other windows even when not focused.
|
||||||
|
float_window: FloatWindow,
|
||||||
|
|
||||||
/// Set the secure input functionality on or off. "Secure input" means
|
/// Set the secure input functionality on or off. "Secure input" means
|
||||||
/// that the user is currently at some sort of prompt where they may be
|
/// that the user is currently at some sort of prompt where they may be
|
||||||
/// entering a password or other sensitive information. This can be used
|
/// entering a password or other sensitive information. This can be used
|
||||||
@ -289,6 +293,7 @@ pub const Action = union(Key) {
|
|||||||
renderer_health,
|
renderer_health,
|
||||||
open_config,
|
open_config,
|
||||||
quit_timer,
|
quit_timer,
|
||||||
|
float_window,
|
||||||
secure_input,
|
secure_input,
|
||||||
key_sequence,
|
key_sequence,
|
||||||
color_change,
|
color_change,
|
||||||
@ -425,6 +430,12 @@ pub const Fullscreen = enum(c_int) {
|
|||||||
macos_non_native_padded_notch,
|
macos_non_native_padded_notch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const FloatWindow = enum(c_int) {
|
||||||
|
on,
|
||||||
|
off,
|
||||||
|
toggle,
|
||||||
|
};
|
||||||
|
|
||||||
pub const SecureInput = enum(c_int) {
|
pub const SecureInput = enum(c_int) {
|
||||||
on,
|
on,
|
||||||
off,
|
off,
|
||||||
|
@ -235,6 +235,7 @@ pub const App = struct {
|
|||||||
.inspector,
|
.inspector,
|
||||||
.render_inspector,
|
.render_inspector,
|
||||||
.quit_timer,
|
.quit_timer,
|
||||||
|
.float_window,
|
||||||
.secure_input,
|
.secure_input,
|
||||||
.key_sequence,
|
.key_sequence,
|
||||||
.desktop_notification,
|
.desktop_notification,
|
||||||
|
@ -488,6 +488,7 @@ pub fn performAction(
|
|||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
|
.float_window,
|
||||||
.toggle_command_palette,
|
.toggle_command_palette,
|
||||||
.toggle_visibility,
|
.toggle_visibility,
|
||||||
.cell_size,
|
.cell_size,
|
||||||
@ -1291,6 +1292,13 @@ pub fn run(self: *App) !void {
|
|||||||
// Setup our actions
|
// Setup our actions
|
||||||
self.initActions();
|
self.initActions();
|
||||||
|
|
||||||
|
// On startup, we want to check for configuration errors right away
|
||||||
|
// so we can show our error window. We also need to setup other initial
|
||||||
|
// state.
|
||||||
|
self.syncConfigChanges(null) catch |err| {
|
||||||
|
log.warn("error handling configuration changes err={}", .{err});
|
||||||
|
};
|
||||||
|
|
||||||
while (self.running) {
|
while (self.running) {
|
||||||
_ = glib.MainContext.iteration(self.ctx, 1);
|
_ = glib.MainContext.iteration(self.ctx, 1);
|
||||||
|
|
||||||
|
@ -2280,6 +2280,18 @@ keybind: Keybinds = .{},
|
|||||||
|
|
||||||
/// Custom CSS files to be loaded.
|
/// Custom CSS files to be loaded.
|
||||||
///
|
///
|
||||||
|
/// GTK CSS documentation can be found at the following links:
|
||||||
|
///
|
||||||
|
/// * <https://docs.gtk.org/gtk4/css-overview.html> - An overview of GTK CSS.
|
||||||
|
/// * <https://docs.gtk.org/gtk4/css-properties.html> - A comprehensive list
|
||||||
|
/// of supported CSS properties.
|
||||||
|
///
|
||||||
|
/// Launch Ghostty with `env GTK_DEBUG=interactive ghostty` to tweak Ghostty's
|
||||||
|
/// CSS in real time using the GTK Inspector. Errors in your CSS files would
|
||||||
|
/// also be reported in the terminal you started Ghostty from. See
|
||||||
|
/// <https://developer.gnome.org/documentation/tools/inspector.html> for more
|
||||||
|
/// information about the GTK Inspector.
|
||||||
|
///
|
||||||
/// This configuration can be repeated multiple times to load multiple files.
|
/// This configuration can be repeated multiple times to load multiple files.
|
||||||
/// Prepend a ? character to the file path to suppress errors if the file does
|
/// Prepend a ? character to the file path to suppress errors if the file does
|
||||||
/// not exist. If you want to include a file that begins with a literal ?
|
/// not exist. If you want to include a file that begins with a literal ?
|
||||||
|
@ -222,12 +222,12 @@ pub fn lessThan(_: void, lhs: Binding, rhs: Binding) bool {
|
|||||||
pub const Action = union(enum) {
|
pub const Action = union(enum) {
|
||||||
/// Ignore this key combination, don't send it to the child process, just
|
/// Ignore this key combination, don't send it to the child process, just
|
||||||
/// black hole it.
|
/// black hole it.
|
||||||
ignore: void,
|
ignore,
|
||||||
|
|
||||||
/// This action is used to flag that the binding should be removed from
|
/// This action is used to flag that the binding should be removed from
|
||||||
/// the set. This should never exist in an active set and `set.put` has an
|
/// the set. This should never exist in an active set and `set.put` has an
|
||||||
/// assertion to verify this.
|
/// assertion to verify this.
|
||||||
unbind: void,
|
unbind,
|
||||||
|
|
||||||
/// Send a CSI sequence. The value should be the CSI sequence without the
|
/// Send a CSI sequence. The value should be the CSI sequence without the
|
||||||
/// CSI header (`ESC [` or `\x1b[`).
|
/// CSI header (`ESC [` or `\x1b[`).
|
||||||
@ -252,35 +252,35 @@ pub const Action = union(enum) {
|
|||||||
/// If you do this while in a TUI program such as vim, this may break
|
/// If you do this while in a TUI program such as vim, this may break
|
||||||
/// the program. If you do this while in a shell, you may have to press
|
/// the program. If you do this while in a shell, you may have to press
|
||||||
/// enter after to get a new prompt.
|
/// enter after to get a new prompt.
|
||||||
reset: void,
|
reset,
|
||||||
|
|
||||||
/// Copy and paste.
|
/// Copy and paste.
|
||||||
copy_to_clipboard: void,
|
copy_to_clipboard,
|
||||||
paste_from_clipboard: void,
|
paste_from_clipboard,
|
||||||
paste_from_selection: void,
|
paste_from_selection,
|
||||||
|
|
||||||
/// Copy the URL under the cursor to the clipboard. If there is no
|
/// Copy the URL under the cursor to the clipboard. If there is no
|
||||||
/// URL under the cursor, this does nothing.
|
/// URL under the cursor, this does nothing.
|
||||||
copy_url_to_clipboard: void,
|
copy_url_to_clipboard,
|
||||||
|
|
||||||
/// Increase/decrease the font size by a certain amount.
|
/// Increase/decrease the font size by a certain amount.
|
||||||
increase_font_size: f32,
|
increase_font_size: f32,
|
||||||
decrease_font_size: f32,
|
decrease_font_size: f32,
|
||||||
|
|
||||||
/// Reset the font size to the original configured size.
|
/// Reset the font size to the original configured size.
|
||||||
reset_font_size: void,
|
reset_font_size,
|
||||||
|
|
||||||
/// Clear the screen. This also clears all scrollback.
|
/// Clear the screen. This also clears all scrollback.
|
||||||
clear_screen: void,
|
clear_screen,
|
||||||
|
|
||||||
/// Select all text on the screen.
|
/// Select all text on the screen.
|
||||||
select_all: void,
|
select_all,
|
||||||
|
|
||||||
/// Scroll the screen varying amounts.
|
/// Scroll the screen varying amounts.
|
||||||
scroll_to_top: void,
|
scroll_to_top,
|
||||||
scroll_to_bottom: void,
|
scroll_to_bottom,
|
||||||
scroll_page_up: void,
|
scroll_page_up,
|
||||||
scroll_page_down: void,
|
scroll_page_down,
|
||||||
scroll_page_fractional: f32,
|
scroll_page_fractional: f32,
|
||||||
scroll_page_lines: i16,
|
scroll_page_lines: i16,
|
||||||
|
|
||||||
@ -321,19 +321,19 @@ pub const Action = union(enum) {
|
|||||||
|
|
||||||
/// Open a new window. If the application isn't currently focused,
|
/// Open a new window. If the application isn't currently focused,
|
||||||
/// this will bring it to the front.
|
/// this will bring it to the front.
|
||||||
new_window: void,
|
new_window,
|
||||||
|
|
||||||
/// Open a new tab.
|
/// Open a new tab.
|
||||||
new_tab: void,
|
new_tab,
|
||||||
|
|
||||||
/// Go to the previous tab.
|
/// Go to the previous tab.
|
||||||
previous_tab: void,
|
previous_tab,
|
||||||
|
|
||||||
/// Go to the next tab.
|
/// Go to the next tab.
|
||||||
next_tab: void,
|
next_tab,
|
||||||
|
|
||||||
/// Go to the last tab (the one with the highest index)
|
/// Go to the last tab (the one with the highest index)
|
||||||
last_tab: void,
|
last_tab,
|
||||||
|
|
||||||
/// Go to the tab with the specific number, 1-indexed. If the tab number
|
/// Go to the tab with the specific number, 1-indexed. If the tab number
|
||||||
/// is higher than the number of tabs, this will go to the last tab.
|
/// is higher than the number of tabs, this will go to the last tab.
|
||||||
@ -346,10 +346,10 @@ pub const Action = union(enum) {
|
|||||||
|
|
||||||
/// Toggle the tab overview.
|
/// Toggle the tab overview.
|
||||||
/// This only works with libadwaita enabled currently.
|
/// This only works with libadwaita enabled currently.
|
||||||
toggle_tab_overview: void,
|
toggle_tab_overview,
|
||||||
|
|
||||||
/// Change the title of the current focused surface via a prompt.
|
/// Change the title of the current focused surface via a prompt.
|
||||||
prompt_surface_title: void,
|
prompt_surface_title,
|
||||||
|
|
||||||
/// Create a new split in the given direction.
|
/// Create a new split in the given direction.
|
||||||
///
|
///
|
||||||
@ -365,7 +365,7 @@ pub const Action = union(enum) {
|
|||||||
goto_split: SplitFocusDirection,
|
goto_split: SplitFocusDirection,
|
||||||
|
|
||||||
/// zoom/unzoom the current split.
|
/// zoom/unzoom the current split.
|
||||||
toggle_split_zoom: void,
|
toggle_split_zoom,
|
||||||
|
|
||||||
/// Resize the current split in a given direction.
|
/// Resize the current split in a given direction.
|
||||||
///
|
///
|
||||||
@ -378,12 +378,12 @@ pub const Action = union(enum) {
|
|||||||
resize_split: SplitResizeParameter,
|
resize_split: SplitResizeParameter,
|
||||||
|
|
||||||
/// Equalize all splits in the current window
|
/// Equalize all splits in the current window
|
||||||
equalize_splits: void,
|
equalize_splits,
|
||||||
|
|
||||||
/// Reset the window to the default size. The "default size" is the
|
/// Reset the window to the default size. The "default size" is the
|
||||||
/// size that a new window would be created with. This has no effect
|
/// size that a new window would be created with. This has no effect
|
||||||
/// if the window is fullscreen.
|
/// if the window is fullscreen.
|
||||||
reset_window_size: void,
|
reset_window_size,
|
||||||
|
|
||||||
/// Control the terminal inspector visibility.
|
/// Control the terminal inspector visibility.
|
||||||
///
|
///
|
||||||
@ -397,39 +397,46 @@ pub const Action = union(enum) {
|
|||||||
/// Open the configuration file in the default OS editor. If your default OS
|
/// Open the configuration file in the default OS editor. If your default OS
|
||||||
/// editor isn't configured then this will fail. Currently, any failures to
|
/// editor isn't configured then this will fail. Currently, any failures to
|
||||||
/// open the configuration will show up only in the logs.
|
/// open the configuration will show up only in the logs.
|
||||||
open_config: void,
|
open_config,
|
||||||
|
|
||||||
/// Reload the configuration. The exact meaning depends on the app runtime
|
/// Reload the configuration. The exact meaning depends on the app runtime
|
||||||
/// in use but this usually involves re-reading the configuration file
|
/// in use but this usually involves re-reading the configuration file
|
||||||
/// and applying any changes. Note that not all changes can be applied at
|
/// and applying any changes. Note that not all changes can be applied at
|
||||||
/// runtime.
|
/// runtime.
|
||||||
reload_config: void,
|
reload_config,
|
||||||
|
|
||||||
/// Close the current "surface", whether that is a window, tab, split, etc.
|
/// Close the current "surface", whether that is a window, tab, split, etc.
|
||||||
/// This only closes ONE surface. This will trigger close confirmation as
|
/// This only closes ONE surface. This will trigger close confirmation as
|
||||||
/// configured.
|
/// configured.
|
||||||
close_surface: void,
|
close_surface,
|
||||||
|
|
||||||
/// Close the current tab, regardless of how many splits there may be.
|
/// Close the current tab, regardless of how many splits there may be.
|
||||||
/// This will trigger close confirmation as configured.
|
/// This will trigger close confirmation as configured.
|
||||||
close_tab: void,
|
close_tab,
|
||||||
|
|
||||||
/// Close the window, regardless of how many tabs or splits there may be.
|
/// Close the window, regardless of how many tabs or splits there may be.
|
||||||
/// This will trigger close confirmation as configured.
|
/// This will trigger close confirmation as configured.
|
||||||
close_window: void,
|
close_window,
|
||||||
|
|
||||||
/// Close all windows. This will trigger close confirmation as configured.
|
/// Close all windows. This will trigger close confirmation as configured.
|
||||||
/// This only works for macOS currently.
|
/// This only works for macOS currently.
|
||||||
close_all_windows: void,
|
close_all_windows,
|
||||||
|
|
||||||
/// Toggle maximized window state. This only works on Linux.
|
/// Toggle maximized window state. This only works on Linux.
|
||||||
toggle_maximize: void,
|
toggle_maximize,
|
||||||
|
|
||||||
/// Toggle fullscreen mode of window.
|
/// Toggle fullscreen mode of window.
|
||||||
toggle_fullscreen: void,
|
toggle_fullscreen,
|
||||||
|
|
||||||
/// Toggle window decorations on and off. This only works on Linux.
|
/// Toggle window decorations on and off. This only works on Linux.
|
||||||
toggle_window_decorations: void,
|
toggle_window_decorations,
|
||||||
|
|
||||||
|
/// Toggle whether the terminal window is always on top of other
|
||||||
|
/// windows even when it is not focused. Terminal windows always start
|
||||||
|
/// as normal (not always on top) windows.
|
||||||
|
///
|
||||||
|
/// This only works on macOS.
|
||||||
|
toggle_window_float_on_top,
|
||||||
|
|
||||||
/// Toggle secure input mode on or off. This is used to prevent apps
|
/// Toggle secure input mode on or off. This is used to prevent apps
|
||||||
/// that monitor input from seeing what you type. This is useful for
|
/// that monitor input from seeing what you type. This is useful for
|
||||||
@ -439,7 +446,7 @@ pub const Action = union(enum) {
|
|||||||
/// terminal. You must toggle it off to disable it, or quit Ghostty.
|
/// terminal. You must toggle it off to disable it, or quit Ghostty.
|
||||||
///
|
///
|
||||||
/// This only works on macOS, since this is a system API on macOS.
|
/// This only works on macOS, since this is a system API on macOS.
|
||||||
toggle_secure_input: void,
|
toggle_secure_input,
|
||||||
|
|
||||||
/// Toggle the command palette. The command palette is a UI element
|
/// Toggle the command palette. The command palette is a UI element
|
||||||
/// that lets you see what actions you can perform, their associated
|
/// that lets you see what actions you can perform, their associated
|
||||||
@ -488,7 +495,7 @@ pub const Action = union(enum) {
|
|||||||
/// plugin enabled, open System Settings > Apps & Windows > Window
|
/// plugin enabled, open System Settings > Apps & Windows > Window
|
||||||
/// Management > Desktop Effects, and enable the plugin in the plugin list.
|
/// Management > Desktop Effects, and enable the plugin in the plugin list.
|
||||||
/// Ghostty would then need to be restarted for this to take effect.
|
/// Ghostty would then need to be restarted for this to take effect.
|
||||||
toggle_quick_terminal: void,
|
toggle_quick_terminal,
|
||||||
|
|
||||||
/// Show/hide all windows. If all windows become shown, we also ensure
|
/// Show/hide all windows. If all windows become shown, we also ensure
|
||||||
/// Ghostty becomes focused. When hiding all windows, focus is yielded
|
/// Ghostty becomes focused. When hiding all windows, focus is yielded
|
||||||
@ -497,10 +504,10 @@ pub const Action = union(enum) {
|
|||||||
/// Note: When the focused surface is fullscreen, this method does nothing.
|
/// Note: When the focused surface is fullscreen, this method does nothing.
|
||||||
///
|
///
|
||||||
/// This currently only works on macOS.
|
/// This currently only works on macOS.
|
||||||
toggle_visibility: void,
|
toggle_visibility,
|
||||||
|
|
||||||
/// Quit ghostty.
|
/// Quit ghostty.
|
||||||
quit: void,
|
quit,
|
||||||
|
|
||||||
/// Crash ghostty in the desired thread for the focused surface.
|
/// Crash ghostty in the desired thread for the focused surface.
|
||||||
///
|
///
|
||||||
@ -797,6 +804,7 @@ pub const Action = union(enum) {
|
|||||||
.toggle_maximize,
|
.toggle_maximize,
|
||||||
.toggle_fullscreen,
|
.toggle_fullscreen,
|
||||||
.toggle_window_decorations,
|
.toggle_window_decorations,
|
||||||
|
.toggle_window_float_on_top,
|
||||||
.toggle_secure_input,
|
.toggle_secure_input,
|
||||||
.toggle_command_palette,
|
.toggle_command_palette,
|
||||||
.reset_window_size,
|
.reset_window_size,
|
||||||
|
@ -346,6 +346,12 @@ fn actionCommands(action: Action.Key) []const Command {
|
|||||||
.description = "Toggle the window decorations.",
|
.description = "Toggle the window decorations.",
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
.toggle_window_float_on_top => comptime &.{.{
|
||||||
|
.action = .toggle_window_float_on_top,
|
||||||
|
.title = "Toggle Float on Top",
|
||||||
|
.description = "Toggle the float on top state of the current window.",
|
||||||
|
}},
|
||||||
|
|
||||||
.toggle_secure_input => comptime &.{.{
|
.toggle_secure_input => comptime &.{.{
|
||||||
.action = .toggle_secure_input,
|
.action = .toggle_secure_input,
|
||||||
.title = "Toggle Secure Input",
|
.title = "Toggle Secure Input",
|
||||||
|
@ -162,7 +162,12 @@ pub const Handler = struct {
|
|||||||
break :tmux .{ .tmux = .{ .exit = {} } };
|
break :tmux .{ .tmux = .{ .exit = {} } };
|
||||||
},
|
},
|
||||||
|
|
||||||
.xtgettcap => |list| .{ .xtgettcap = .{ .data = list } },
|
.xtgettcap => |list| xtgettcap: {
|
||||||
|
for (list.items, 0..) |b, i| {
|
||||||
|
list.items[i] = std.ascii.toUpper(b);
|
||||||
|
}
|
||||||
|
break :xtgettcap .{ .xtgettcap = .{ .data = list } };
|
||||||
|
},
|
||||||
|
|
||||||
.decrqss => |buffer| .{ .decrqss = switch (buffer.len) {
|
.decrqss => |buffer| .{ .decrqss = switch (buffer.len) {
|
||||||
0 => .none,
|
0 => .none,
|
||||||
@ -306,6 +311,21 @@ test "XTGETTCAP command" {
|
|||||||
try testing.expect(cmd.xtgettcap.next() == null);
|
try testing.expect(cmd.xtgettcap.next() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "XTGETTCAP mixed case" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var h: Handler = .{};
|
||||||
|
defer h.deinit();
|
||||||
|
try testing.expect(h.hook(alloc, .{ .intermediates = "+", .final = 'q' }) == null);
|
||||||
|
for ("536d756C78") |byte| _ = h.put(byte);
|
||||||
|
var cmd = h.unhook().?;
|
||||||
|
defer cmd.deinit();
|
||||||
|
try testing.expect(cmd == .xtgettcap);
|
||||||
|
try testing.expectEqualStrings("536D756C78", cmd.xtgettcap.next().?);
|
||||||
|
try testing.expect(cmd.xtgettcap.next() == null);
|
||||||
|
}
|
||||||
|
|
||||||
test "XTGETTCAP command multiple keys" {
|
test "XTGETTCAP command multiple keys" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -333,7 +353,7 @@ test "XTGETTCAP command invalid data" {
|
|||||||
var cmd = h.unhook().?;
|
var cmd = h.unhook().?;
|
||||||
defer cmd.deinit();
|
defer cmd.deinit();
|
||||||
try testing.expect(cmd == .xtgettcap);
|
try testing.expect(cmd == .xtgettcap);
|
||||||
try testing.expectEqualStrings("who", cmd.xtgettcap.next().?);
|
try testing.expectEqualStrings("WHO", cmd.xtgettcap.next().?);
|
||||||
try testing.expectEqualStrings("536D756C78", cmd.xtgettcap.next().?);
|
try testing.expectEqualStrings("536D756C78", cmd.xtgettcap.next().?);
|
||||||
try testing.expect(cmd.xtgettcap.next() == null);
|
try testing.expect(cmd.xtgettcap.next() == null);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user