From 1c73f757df65c466d6fc57129dee22cdade250f8 Mon Sep 17 00:00:00 2001 From: RubenRME Date: Mon, 31 Mar 2025 03:46:41 +0200 Subject: [PATCH 001/119] lang: added Korean language file --- po/ko_KR.UTF-8.po | 259 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 po/ko_KR.UTF-8.po diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po new file mode 100644 index 000000000..48659c388 --- /dev/null +++ b/po/ko_KR.UTF-8.po @@ -0,0 +1,259 @@ +# Korean translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Ruben Engelbrecht , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"PO-Revision-Date: 2025-03-31 03:08+0200\n" +"Last-Translator: Ruben Engelbrecht \n" +"Language-Team: Korean \n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "터미널 제목 변경" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "기본 제목으로 복원하려면 비워 두세요." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "취소" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "확인" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "설정 오류" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "하나 이상의 설정 오류가 발견되었습니다. 아래 오류를 확인한 후 설정을 다시 로드하거나 무시하세요." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "무시" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "설정을 다시 로드하기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "복사" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "붙여넣기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "지우기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "초기화" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "제목 변경…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "위로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "아래로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "왼쪽으로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "오른쪽으로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "탭" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "새 탭" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "탭 닫기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "창" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "새 창" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "창 닫기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "설정" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "설정 열기" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "터미널 인스펙터" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Ghostty 정보" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "종료" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "클립보드 액세스 권한 부여" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "응용 프로그램이 클립보드에서 읽기를 시도하고 있습니다. 현재 클립보드 내용은 아래와 같습니다." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "거부" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "허용" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "응용 프로그램이 클립보드에 쓰기를 시도하고 있습니다. 현재 클립보드 내용은 아래와 같습니다." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "경고: 잠재적으로 안전하지 않은 붙여넣기" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "이 텍스트를 터미널에 붙여넣는 것은 위험할 수 있습니다. 일부 명령이 실행될 수 있는 것으로 보입니다." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: 터미널 인스펙터" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "클립보드에 복사됨" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "닫기" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Ghostty를 종료하시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "창을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "탭을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "분할을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "이 창의 모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "이 탭의 모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "이 분할에서 현재 실행 중인 프로세스가 종료됩니다." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "메인 메뉴" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "열린 탭 보기" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ 디버그 빌드를 실행 중입니다! 성능이 저하될 수 있습니다." +msgstr "" + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "구성을 다시 로드했습니다" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Ghostty 개발자들" From 62bbad96b11aea5d12c16f33f8cff6a019fd79fc Mon Sep 17 00:00:00 2001 From: RubenRME Date: Mon, 31 Mar 2025 12:48:51 +0200 Subject: [PATCH 002/119] fix: fixed missing translation key at line 250 --- po/ko_KR.UTF-8.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po index 48659c388..708391eef 100644 --- a/po/ko_KR.UTF-8.po +++ b/po/ko_KR.UTF-8.po @@ -247,8 +247,8 @@ msgstr "열린 탭 보기" #: src/apprt/gtk/Window.zig:295 msgid "" -"⚠️ 디버그 빌드를 실행 중입니다! 성능이 저하될 수 있습니다." -msgstr "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ 디버그 빌드를 실행 중입니다! 성능이 저하될 수 있습니다." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From a92e761c09f977f67f155a467b9d73427a00df7f Mon Sep 17 00:00:00 2001 From: RubenRME Date: Mon, 31 Mar 2025 12:51:19 +0200 Subject: [PATCH 003/119] fix: added locale to il8n.zig --- src/os/i18n.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/i18n.zig b/src/os/i18n.zig index baae73e46..4cf8817a5 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -29,6 +29,7 @@ pub const locales = [_][:0]const u8{ "nb_NO.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", + "ko_KR.UTF-8", }; /// Set for faster membership lookup of locales. From cb991620b964e28f7551c4851f3aa16aaa5b812e Mon Sep 17 00:00:00 2001 From: RME Date: Thu, 19 Jun 2025 13:51:34 +0200 Subject: [PATCH 004/119] Apply suggestions from code review Co-authored-by: Hojin You --- po/ko_KR.UTF-8.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po index 708391eef..4b87a9d35 100644 --- a/po/ko_KR.UTF-8.po +++ b/po/ko_KR.UTF-8.po @@ -23,7 +23,7 @@ msgstr "터미널 제목 변경" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." -msgstr "기본 제목으로 복원하려면 비워 두세요." +msgstr "제목란을 비워 두면 기본값으로 복원됩니다." #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 #: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 @@ -42,7 +42,7 @@ msgstr "설정 오류" msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." -msgstr "하나 이상의 설정 오류가 발견되었습니다. 아래 오류를 확인한 후 설정을 다시 로드하거나 무시하세요." +msgstr "설정에 하나 이상의 문제가 발견되었습니다. 아래 오류(를)들을 확인한 후 설정을 다시 불러오거나 무시하세요." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -52,7 +52,7 @@ msgstr "무시" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Reload Configuration" -msgstr "설정을 다시 로드하기" +msgstr "설정 값 다시 불러오기" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 @@ -145,7 +145,7 @@ msgstr "설정 열기" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Terminal Inspector" -msgstr "터미널 인스펙터" +msgstr "터미널 조사 도구" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 @@ -248,11 +248,11 @@ msgstr "열린 탭 보기" #: src/apprt/gtk/Window.zig:295 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ 디버그 빌드를 실행 중입니다! 성능이 저하될 수 있습니다." +msgstr "⚠️ 디버그 빌드 실행 중! 성능이 저하됩니다." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" -msgstr "구성을 다시 로드했습니다" +msgstr "설정값을 다시 불러왔습니다" #: src/apprt/gtk/Window.zig:941 msgid "Ghostty Developers" From ad5ab92333b648333fe9208dca481092e9b663ed Mon Sep 17 00:00:00 2001 From: RME Date: Sun, 29 Jun 2025 15:42:18 +0200 Subject: [PATCH 005/119] Update po/ko_KR.UTF-8.po Co-authored-by: Hojin You --- po/ko_KR.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po index 4b87a9d35..4fccb14f4 100644 --- a/po/ko_KR.UTF-8.po +++ b/po/ko_KR.UTF-8.po @@ -145,7 +145,7 @@ msgstr "설정 열기" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Terminal Inspector" -msgstr "터미널 조사 도구" +msgstr "터미널 인스펙터" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 From e25029eff620f6f80272302caf953d530b3dd522 Mon Sep 17 00:00:00 2001 From: RME Date: Mon, 30 Jun 2025 15:00:15 +0200 Subject: [PATCH 006/119] add ko_KR i18n to CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 343e1dcc1..54600d1ad 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -177,6 +177,7 @@ /po/tr_TR.UTF-8.po @ghostty-org/tr_TR /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN +/po/ko_KR.UTF-8.po @ghostty-org/ko_KR # Packaging - Snap /snap/ @ghostty-org/snap From 6484df913435377ef9aec6b9519d639af57a4ab4 Mon Sep 17 00:00:00 2001 From: RME Date: Mon, 30 Jun 2025 15:01:44 +0200 Subject: [PATCH 007/119] update debug build string, line 251 --- po/ko_KR.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po index 4fccb14f4..be7fd2502 100644 --- a/po/ko_KR.UTF-8.po +++ b/po/ko_KR.UTF-8.po @@ -248,7 +248,7 @@ msgstr "열린 탭 보기" #: src/apprt/gtk/Window.zig:295 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ 디버그 빌드 실행 중! 성능이 저하됩니다." +msgstr "⚠️ Ghostty는 디버그 빌드 실행 중! 성능이 저하됩니다." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From 2850c3b58a30c3f4716695dd1108cbb78b9115a3 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 16:51:11 +0300 Subject: [PATCH 008/119] Adding Bulgarian localization. --- po/bg_BG.UTF-8.po | 275 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 2 files changed, 276 insertions(+) create mode 100644 po/bg_BG.UTF-8.po diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po new file mode 100644 index 000000000..e240fd6e7 --- /dev/null +++ b/po/bg_BG.UTF-8.po @@ -0,0 +1,275 @@ +# Bulgarian translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Damyan Bogoev , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-04-23 16:58+0800\n" +"PO-Revision-Date: 2025-05-19 11:34+0300\n" +"Last-Translator: Damyan Bogoev \n" +"Language-Team: Bulgarian \n" +"Language: bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Промяна на заглавието на терминала" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Оставете празно за възстановяване на заглавието по подразбиране." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Отказ" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "ОК" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Грешки в конфигурацията" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors " +"below, and either reload your configuration or ignore these errors." +msgstr "Открити са една или повече грешки в конфигурацията. Моля, прегледайте грешките по-долу и или презаредете конфигурацията си, или ги игнорирайте." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Игнорирай" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 +msgid "Reload Configuration" +msgstr "Презареди на конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Раздели нагоре" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Раздели надолу" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Раздели наляво" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Раздели надясно" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:16 +msgid "Execute a command…" +msgstr "Изпълнение на команда…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Копирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Постави" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Изчисти" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Нулирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Раздели" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Промяна на заглавие…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:255 +msgid "New Tab" +msgstr "Нов раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Затвори раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Нов прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Затвори прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Конфигурация" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Open Configuration" +msgstr "Отвори на конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Command Palette" +msgstr "Командна палитра" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Terminal Inspector" +msgstr "Инспектор на терминала" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +#: src/apprt/gtk/Window.zig:1024 +msgid "About Ghostty" +msgstr "За Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112 +msgid "Quit" +msgstr "Изход" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Разрешаване на достъп до клипборда" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да чете от клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Откажи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Позволи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да запише в клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Предупреждение: Потенциално опасно поставяне" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "Поставянето на този текст в терминала може да е опасно, тъй като изглежда, че може да бъдат изпълнени някои команди." + +#: src/apprt/gtk/Window.zig:208 +msgid "Main Menu" +msgstr "Главно меню" + +#: src/apprt/gtk/Window.zig:229 +msgid "View Open Tabs" +msgstr "Преглед на отворените раздели" + +#: src/apprt/gtk/Window.zig:256 +msgid "New Split" +msgstr "Ново разделяне" + +#: src/apprt/gtk/Window.zig:319 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Използвате дебъг версия на Ghostty! Производителността ще бъде намалена." + +#: src/apprt/gtk/Window.zig:765 +msgid "Reloaded the configuration" +msgstr "Конфигурацията е презаредена" + +#: src/apprt/gtk/Window.zig:1005 +msgid "Ghostty Developers" +msgstr "Разработчици на Ghostty" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Инспектор на терминала" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Затвори" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Изход от Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Затваряне на прозореца?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Затваряне на раздела?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Затваряне на разделянето?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Всички терминални сесии ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Всички терминални сесии в този прозорец ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Всички терминални сесии в този раздел ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Текущият процес в това разделяне ще бъде прекратен." + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Копирано в клипборда" \ No newline at end of file diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 6981d55a0..cc157611c 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -46,6 +46,7 @@ pub const locales = [_][:0]const u8{ "es_AR.UTF-8", "pt_BR.UTF-8", "ca_ES.UTF-8", + "bg_BG.UTF-8", "ga_IE.UTF-8", }; From 0653bcb16e38ba773d44025b8dc97987d587b4da Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:30 +0300 Subject: [PATCH 009/119] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index e240fd6e7..d0ec4b803 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -52,7 +52,7 @@ msgstr "Игнорирай" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 msgid "Reload Configuration" -msgstr "Презареди на конфигурацията" +msgstr "Презареди конфигурацията" #: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 From 4fe3a01f1bec1295008fadc175bbaa07ecd3a4ab Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:38 +0300 Subject: [PATCH 010/119] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index d0ec4b803..317c37a97 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -80,7 +80,7 @@ msgstr "Раздели надясно" #: src/apprt/gtk/ui/1.5/command-palette.blp:16 msgid "Execute a command…" -msgstr "Изпълнение на команда…" +msgstr "Изпълни команда…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 From 87df0004c99fa2ba4d98755acdb68eb3bd9259b2 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:43 +0300 Subject: [PATCH 011/119] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index 317c37a97..9b2773ca5 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -149,7 +149,7 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Open Configuration" -msgstr "Отвори на конфигурацията" +msgstr "Отвори конфигурацията" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Command Palette" From 9ad4537d03b62a452f44afaa0e65d7e134477b57 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:58 +0300 Subject: [PATCH 012/119] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index 9b2773ca5..b371cb04d 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -111,7 +111,7 @@ msgstr "Раздели" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Промяна на заглавие…" +msgstr "Промени заглавие…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" From 1377e6d22595e78762b7a9887f5d04cba69cfdc9 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 29 Jun 2025 15:33:58 -0600 Subject: [PATCH 013/119] font/sprite: rework sprite font drawing This is a fairly large rework of how we handle the sprite font drawing. Drawing routines are now context-less, provided only a canvas and some metrics. There is now a separate file per unicode block / PUA area. Sprites are now drawn on canvases with an extra quarter-cell of padding on each edge, and automatically cropped when sent to the atlas, this allows sprites to extend past cell boundaries which makes it possible to have, for example, diagonal box drawing characters that connect across cell diagonals instead of being pinched in. Most of the sprites the code is just directly ported from the old code, but I've rewritten a handful. Moving forward, I'd like to rewrite more of these since the way they're currently written isn't ideal. This rework, in addition to improving the packing efficiency of sprites on the atlas, and allowing for out-of-cell drawing, will make it a lot easier to add new sprites in the future, since all it takes now is to add a single function and an import (if it's a new file). I reworked the regression/change testing to be more robust as well, it now covers all sprite glyphs (except non-codepoint ones) and does so at 4 different sizes. Addition/removal of glyphs will no longer create diff noise in the generated diff image, since the position in the image of each glyph is now fixed. --- src/font/Atlas.zig | 31 + src/font/sprite.zig | 6 - src/font/sprite/Box.zig | 3397 ----------------- src/font/sprite/Face.zig | 675 +++- src/font/sprite/Powerline.zig | 564 --- src/font/sprite/canvas.zig | 449 ++- src/font/sprite/cursor.zig | 65 - src/font/sprite/draw/README.md | 50 + src/font/sprite/draw/block.zig | 184 + src/font/sprite/draw/box.zig | 947 +++++ src/font/sprite/draw/braille.zig | 148 + src/font/sprite/draw/branch.zig | 505 +++ src/font/sprite/draw/common.zig | 244 ++ src/font/sprite/draw/geometric_shapes.zig | 200 + src/font/sprite/{ => draw}/octants.txt | 0 src/font/sprite/draw/powerline.zig | 396 ++ src/font/sprite/draw/special.zig | 328 ++ .../draw/symbols_for_legacy_computing.zig | 1431 +++++++ ...ymbols_for_legacy_computing_supplement.zig | 193 + src/font/sprite/testdata/Box.ppm | Bin 1048593 -> 0 bytes .../testdata/U+1CC00...U+1CCFF-11x21+2.png | Bin 0 -> 403 bytes .../testdata/U+1CC00...U+1CCFF-12x24+3.png | Bin 0 -> 534 bytes .../testdata/U+1CC00...U+1CCFF-18x36+4.png | Bin 0 -> 1022 bytes .../testdata/U+1CC00...U+1CCFF-9x17+1.png | Bin 0 -> 316 bytes .../testdata/U+1CD00...U+1CDFF-11x21+2.png | Bin 0 -> 1275 bytes .../testdata/U+1CD00...U+1CDFF-12x24+3.png | Bin 0 -> 1870 bytes .../testdata/U+1CD00...U+1CDFF-18x36+4.png | Bin 0 -> 3404 bytes .../testdata/U+1CD00...U+1CDFF-9x17+1.png | Bin 0 -> 1101 bytes .../testdata/U+1FB00...U+1FBFF-11x21+2.png | Bin 0 -> 5450 bytes .../testdata/U+1FB00...U+1FBFF-12x24+3.png | Bin 0 -> 5724 bytes .../testdata/U+1FB00...U+1FBFF-18x36+4.png | Bin 0 -> 9997 bytes .../testdata/U+1FB00...U+1FBFF-9x17+1.png | Bin 0 -> 4298 bytes .../testdata/U+2500...U+25FF-11x21+2.png | Bin 0 -> 2223 bytes .../testdata/U+2500...U+25FF-12x24+3.png | Bin 0 -> 2638 bytes .../testdata/U+2500...U+25FF-18x36+4.png | Bin 0 -> 4541 bytes .../testdata/U+2500...U+25FF-9x17+1.png | Bin 0 -> 1848 bytes .../testdata/U+2800...U+28FF-11x21+2.png | Bin 0 -> 1022 bytes .../testdata/U+2800...U+28FF-12x24+3.png | Bin 0 -> 1547 bytes .../testdata/U+2800...U+28FF-18x36+4.png | Bin 0 -> 2490 bytes .../testdata/U+2800...U+28FF-9x17+1.png | Bin 0 -> 917 bytes .../testdata/U+E000...U+E0FF-11x21+2.png | Bin 0 -> 1104 bytes .../testdata/U+E000...U+E0FF-12x24+3.png | Bin 0 -> 1251 bytes .../testdata/U+E000...U+E0FF-18x36+4.png | Bin 0 -> 2228 bytes .../testdata/U+E000...U+E0FF-9x17+1.png | Bin 0 -> 895 bytes .../testdata/U+F500...U+F5FF-11x21+2.png | Bin 0 -> 1114 bytes .../testdata/U+F500...U+F5FF-12x24+3.png | Bin 0 -> 1423 bytes .../testdata/U+F500...U+F5FF-18x36+4.png | Bin 0 -> 2470 bytes .../testdata/U+F500...U+F5FF-9x17+1.png | Bin 0 -> 871 bytes .../testdata/U+F600...U+F6FF-11x21+2.png | Bin 0 -> 493 bytes .../testdata/U+F600...U+F6FF-12x24+3.png | Bin 0 -> 636 bytes .../testdata/U+F600...U+F6FF-18x36+4.png | Bin 0 -> 1218 bytes .../testdata/U+F600...U+F6FF-9x17+1.png | Bin 0 -> 394 bytes src/font/sprite/underline.zig | 312 -- typos.toml | 2 + 54 files changed, 5474 insertions(+), 4653 deletions(-) delete mode 100644 src/font/sprite/Box.zig delete mode 100644 src/font/sprite/Powerline.zig delete mode 100644 src/font/sprite/cursor.zig create mode 100644 src/font/sprite/draw/README.md create mode 100644 src/font/sprite/draw/block.zig create mode 100644 src/font/sprite/draw/box.zig create mode 100644 src/font/sprite/draw/braille.zig create mode 100644 src/font/sprite/draw/branch.zig create mode 100644 src/font/sprite/draw/common.zig create mode 100644 src/font/sprite/draw/geometric_shapes.zig rename src/font/sprite/{ => draw}/octants.txt (100%) create mode 100644 src/font/sprite/draw/powerline.zig create mode 100644 src/font/sprite/draw/special.zig create mode 100644 src/font/sprite/draw/symbols_for_legacy_computing.zig create mode 100644 src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig delete mode 100644 src/font/sprite/testdata/Box.ppm create mode 100644 src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png create mode 100644 src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png delete mode 100644 src/font/sprite/underline.zig diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index 969318943..aac2e7e8d 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -251,6 +251,37 @@ pub fn set(self: *Atlas, reg: Region, data: []const u8) void { _ = self.modified.fetchAdd(1, .monotonic); } +/// Like `set` but allows specifying a width for the source data and an +/// offset x and y, so that a section of a larger buffer may be copied +/// in to the atlas. +pub fn setFromLarger( + self: *Atlas, + reg: Region, + src: []const u8, + src_width: u32, + src_x: u32, + src_y: u32, +) void { + assert(reg.x < (self.size - 1)); + assert((reg.x + reg.width) <= (self.size - 1)); + assert(reg.y < (self.size - 1)); + assert((reg.y + reg.height) <= (self.size - 1)); + + const depth = self.format.depth(); + var i: u32 = 0; + while (i < reg.height) : (i += 1) { + const tex_offset = (((reg.y + i) * self.size) + reg.x) * depth; + const src_offset = (((src_y + i) * src_width) + src_x) * depth; + fastmem.copy( + u8, + self.data[tex_offset..], + src[src_offset .. src_offset + (reg.width * depth)], + ); + } + + _ = self.modified.fetchAdd(1, .monotonic); +} + // Grow the texture to the new size, preserving all previously written data. pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void { assert(size_new >= self.size); diff --git a/src/font/sprite.zig b/src/font/sprite.zig index 6485d6008..4be06a918 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -33,12 +33,6 @@ pub const Sprite = enum(u32) { cursor_hollow_rect, cursor_bar, - // Note: we don't currently put the box drawing glyphs in here because - // there are a LOT and I'm lazy. What I want to do is spend more time - // studying the patterns to see if we can programmatically build our - // enum perhaps and comptime generate the drawing code at the same time. - // I'm not sure if that's advisable yet though. - test { const testing = std.testing; try testing.expectEqual(start, @intFromEnum(Sprite.underline)); diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig deleted file mode 100644 index f5140091d..000000000 --- a/src/font/sprite/Box.zig +++ /dev/null @@ -1,3397 +0,0 @@ -//! This file contains functions for drawing the box drawing characters -//! (https://en.wikipedia.org/wiki/Box-drawing_character) and related -//! characters that are provided by the terminal. -//! -//! The box drawing logic is based off similar logic in Kitty and Foot. -//! The primary drawing code was originally ported directly and slightly -//! modified from Foot (https://codeberg.org/dnkl/foot/). Foot is licensed -//! under the MIT license and is copyright 2019 Daniel Eklöf. -//! -//! The modifications made were primarily around spacing, DPI calculations, -//! and adapting the code to our atlas model. Further, more extensive changes -//! were made, refactoring the line characters to all share a single unified -//! function (draw_lines), as well as many of the fractional block characters -//! which now use draw_block instead of dedicated separate functions. -//! -//! Additional characters from Unicode 16.0 and beyond are original work. -const Box = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -const z2d = @import("z2d"); - -const font = @import("../main.zig"); -const Sprite = @import("../sprite.zig").Sprite; - -const log = std.log.scoped(.box_font); - -/// Grid metrics for the rendering. -metrics: font.Metrics, - -/// The thickness of a line. -const Thickness = enum { - super_light, - light, - heavy, - - /// Calculate the real height of a line based on its thickness - /// and a base thickness value. The base thickness value is expected - /// to be in pixels. - fn height(self: Thickness, base: u32) u32 { - return switch (self) { - .super_light => @max(base / 2, 1), - .light => base, - .heavy => base * 2, - }; - } -}; - -/// Specification of a traditional intersection-style line/box-drawing char, -/// which can have a different style of line from each edge to the center. -const Lines = packed struct(u8) { - up: Style = .none, - right: Style = .none, - down: Style = .none, - left: Style = .none, - - const Style = enum(u2) { - none, - light, - heavy, - double, - }; -}; - -/// Specification of a quadrants char, which has each of the -/// 4 quadrants of the character cell either filled or empty. -const Quads = packed struct(u4) { - tl: bool = false, - tr: bool = false, - bl: bool = false, - br: bool = false, -}; - -/// Specification of a branch drawing node, which consists of a -/// circle which is either empty or filled, and lines connecting -/// optionally between the circle and each of the 4 edges. -const BranchNode = packed struct(u5) { - up: bool = false, - right: bool = false, - down: bool = false, - left: bool = false, - filled: bool = false, -}; - -/// Alignment of a figure within a cell -const Alignment = struct { - horizontal: enum { - left, - right, - center, - } = .center, - - vertical: enum { - top, - bottom, - middle, - } = .middle, - - const upper: Alignment = .{ .vertical = .top }; - const lower: Alignment = .{ .vertical = .bottom }; - const left: Alignment = .{ .horizontal = .left }; - const right: Alignment = .{ .horizontal = .right }; - - const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left }; - const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right }; - const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left }; - const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right }; - - const center: Alignment = .{}; - - const upper_center = upper; - const lower_center = lower; - const middle_left = left; - const middle_right = right; - const middle_center: Alignment = center; - - const top = upper; - const bottom = lower; - const center_top = top; - const center_bottom = bottom; - - const top_left = upper_left; - const top_right = upper_right; - const bottom_left = lower_left; - const bottom_right = lower_right; -}; - -const Corner = enum(u2) { - tl, - tr, - bl, - br, -}; - -const Edge = enum(u2) { - top, - left, - bottom, - right, -}; - -const SmoothMosaic = packed struct(u10) { - tl: bool, - ul: bool, - ll: bool, - bl: bool, - bc: bool, - br: bool, - lr: bool, - ur: bool, - tr: bool, - tc: bool, - - fn from(comptime pattern: *const [15:0]u8) SmoothMosaic { - return .{ - .tl = pattern[0] == '#', - - .ul = pattern[4] == '#' and - (pattern[0] != '#' or pattern[8] != '#'), - - .ll = pattern[8] == '#' and - (pattern[4] != '#' or pattern[12] != '#'), - - .bl = pattern[12] == '#', - - .bc = pattern[13] == '#' and - (pattern[12] != '#' or pattern[14] != '#'), - - .br = pattern[14] == '#', - - .lr = pattern[10] == '#' and - (pattern[14] != '#' or pattern[6] != '#'), - - .ur = pattern[6] == '#' and - (pattern[10] != '#' or pattern[2] != '#'), - - .tr = pattern[2] == '#', - - .tc = pattern[1] == '#' and - (pattern[2] != '#' or pattern[0] != '#'), - }; - } -}; - -// Octant range, inclusive -const octant_min = 0x1cd00; -const octant_max = 0x1cde5; - -// Utility names for common fractions -const one_eighth: f64 = 0.125; -const one_quarter: f64 = 0.25; -const one_third: f64 = (1.0 / 3.0); -const three_eighths: f64 = 0.375; -const half: f64 = 0.5; -const five_eighths: f64 = 0.625; -const two_thirds: f64 = (2.0 / 3.0); -const three_quarters: f64 = 0.75; -const seven_eighths: f64 = 0.875; - -/// Shades -const Shade = enum(u8) { - off = 0x00, - light = 0x40, - medium = 0x80, - dark = 0xc0, - on = 0xff, - - _, -}; - -pub fn renderGlyph( - self: Box, - alloc: Allocator, - atlas: *font.Atlas, - cp: u32, -) !font.Glyph { - const metrics = self.metrics; - - // Create the canvas we'll use to draw - var canvas = try font.sprite.Canvas.init( - alloc, - metrics.cell_width, - metrics.cell_height, - ); - defer canvas.deinit(); - - // Perform the actual drawing - try self.draw(alloc, &canvas, cp); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - // Our coordinates start at the BOTTOM for our renderers so we have to - // specify an offset of the full height because we rendered a full size - // cell. - const offset_y = @as(i32, @intCast(metrics.cell_height)); - - return font.Glyph{ - .width = metrics.cell_width, - .height = metrics.cell_height, - .offset_x = 0, - .offset_y = offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(metrics.cell_width), - }; -} - -fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - _ = alloc; - switch (cp) { - // '─' - 0x2500 => self.draw_lines(canvas, .{ .left = .light, .right = .light }), - // '━' - 0x2501 => self.draw_lines(canvas, .{ .left = .heavy, .right = .heavy }), - // '│' - 0x2502 => self.draw_lines(canvas, .{ .up = .light, .down = .light }), - // '┃' - 0x2503 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy }), - // '┄' - 0x2504 => self.draw_light_triple_dash_horizontal(canvas), - // '┅' - 0x2505 => self.draw_heavy_triple_dash_horizontal(canvas), - // '┆' - 0x2506 => self.draw_light_triple_dash_vertical(canvas), - // '┇' - 0x2507 => self.draw_heavy_triple_dash_vertical(canvas), - // '┈' - 0x2508 => self.draw_light_quadruple_dash_horizontal(canvas), - // '┉' - 0x2509 => self.draw_heavy_quadruple_dash_horizontal(canvas), - // '┊' - 0x250a => self.draw_light_quadruple_dash_vertical(canvas), - // '┋' - 0x250b => self.draw_heavy_quadruple_dash_vertical(canvas), - // '┌' - 0x250c => self.draw_lines(canvas, .{ .down = .light, .right = .light }), - // '┍' - 0x250d => self.draw_lines(canvas, .{ .down = .light, .right = .heavy }), - // '┎' - 0x250e => self.draw_lines(canvas, .{ .down = .heavy, .right = .light }), - // '┏' - 0x250f => self.draw_lines(canvas, .{ .down = .heavy, .right = .heavy }), - - // '┐' - 0x2510 => self.draw_lines(canvas, .{ .down = .light, .left = .light }), - // '┑' - 0x2511 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy }), - // '┒' - 0x2512 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light }), - // '┓' - 0x2513 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy }), - // '└' - 0x2514 => self.draw_lines(canvas, .{ .up = .light, .right = .light }), - // '┕' - 0x2515 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy }), - // '┖' - 0x2516 => self.draw_lines(canvas, .{ .up = .heavy, .right = .light }), - // '┗' - 0x2517 => self.draw_lines(canvas, .{ .up = .heavy, .right = .heavy }), - // '┘' - 0x2518 => self.draw_lines(canvas, .{ .up = .light, .left = .light }), - // '┙' - 0x2519 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy }), - // '┚' - 0x251a => self.draw_lines(canvas, .{ .up = .heavy, .left = .light }), - // '┛' - 0x251b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy }), - // '├' - 0x251c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .light }), - // '┝' - 0x251d => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .heavy }), - // '┞' - 0x251e => self.draw_lines(canvas, .{ .up = .heavy, .right = .light, .down = .light }), - // '┟' - 0x251f => self.draw_lines(canvas, .{ .down = .heavy, .right = .light, .up = .light }), - - // '┠' - 0x2520 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .light }), - // '┡' - 0x2521 => self.draw_lines(canvas, .{ .down = .light, .right = .heavy, .up = .heavy }), - // '┢' - 0x2522 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy, .down = .heavy }), - // '┣' - 0x2523 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }), - // '┤' - 0x2524 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light }), - // '┥' - 0x2525 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy }), - // '┦' - 0x2526 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .down = .light }), - // '┧' - 0x2527 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .up = .light }), - // '┨' - 0x2528 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light }), - // '┩' - 0x2529 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .up = .heavy }), - // '┪' - 0x252a => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .down = .heavy }), - // '┫' - 0x252b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }), - // '┬' - 0x252c => self.draw_lines(canvas, .{ .down = .light, .left = .light, .right = .light }), - // '┭' - 0x252d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .down = .light }), - // '┮' - 0x252e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .down = .light }), - // '┯' - 0x252f => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .right = .heavy }), - - // '┰' - 0x2530 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .right = .light }), - // '┱' - 0x2531 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .down = .heavy }), - // '┲' - 0x2532 => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .down = .heavy }), - // '┳' - 0x2533 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }), - // '┴' - 0x2534 => self.draw_lines(canvas, .{ .up = .light, .left = .light, .right = .light }), - // '┵' - 0x2535 => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light }), - // '┶' - 0x2536 => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light }), - // '┷' - 0x2537 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .right = .heavy }), - // '┸' - 0x2538 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .right = .light }), - // '┹' - 0x2539 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy }), - // '┺' - 0x253a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy }), - // '┻' - 0x253b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }), - // '┼' - 0x253c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }), - // '┽' - 0x253d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }), - // '┾' - 0x253e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }), - // '┿' - 0x253f => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }), - - // '╀' - 0x2540 => self.draw_lines(canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }), - // '╁' - 0x2541 => self.draw_lines(canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }), - // '╂' - 0x2542 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), - // '╃' - 0x2543 => self.draw_lines(canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }), - // '╄' - 0x2544 => self.draw_lines(canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }), - // '╅' - 0x2545 => self.draw_lines(canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }), - // '╆' - 0x2546 => self.draw_lines(canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }), - // '╇' - 0x2547 => self.draw_lines(canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }), - // '╈' - 0x2548 => self.draw_lines(canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }), - // '╉' - 0x2549 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }), - // '╊' - 0x254a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }), - // '╋' - 0x254b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }), - // '╌' - 0x254c => self.draw_light_double_dash_horizontal(canvas), - // '╍' - 0x254d => self.draw_heavy_double_dash_horizontal(canvas), - // '╎' - 0x254e => self.draw_light_double_dash_vertical(canvas), - // '╏' - 0x254f => self.draw_heavy_double_dash_vertical(canvas), - - // '═' - 0x2550 => self.draw_lines(canvas, .{ .left = .double, .right = .double }), - // '║' - 0x2551 => self.draw_lines(canvas, .{ .up = .double, .down = .double }), - // '╒' - 0x2552 => self.draw_lines(canvas, .{ .down = .light, .right = .double }), - // '╓' - 0x2553 => self.draw_lines(canvas, .{ .down = .double, .right = .light }), - // '╔' - 0x2554 => self.draw_lines(canvas, .{ .down = .double, .right = .double }), - // '╕' - 0x2555 => self.draw_lines(canvas, .{ .down = .light, .left = .double }), - // '╖' - 0x2556 => self.draw_lines(canvas, .{ .down = .double, .left = .light }), - // '╗' - 0x2557 => self.draw_lines(canvas, .{ .down = .double, .left = .double }), - // '╘' - 0x2558 => self.draw_lines(canvas, .{ .up = .light, .right = .double }), - // '╙' - 0x2559 => self.draw_lines(canvas, .{ .up = .double, .right = .light }), - // '╚' - 0x255a => self.draw_lines(canvas, .{ .up = .double, .right = .double }), - // '╛' - 0x255b => self.draw_lines(canvas, .{ .up = .light, .left = .double }), - // '╜' - 0x255c => self.draw_lines(canvas, .{ .up = .double, .left = .light }), - // '╝' - 0x255d => self.draw_lines(canvas, .{ .up = .double, .left = .double }), - // '╞' - 0x255e => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .double }), - // '╟' - 0x255f => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .light }), - - // '╠' - 0x2560 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .double }), - // '╡' - 0x2561 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double }), - // '╢' - 0x2562 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light }), - // '╣' - 0x2563 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double }), - // '╤' - 0x2564 => self.draw_lines(canvas, .{ .down = .light, .left = .double, .right = .double }), - // '╥' - 0x2565 => self.draw_lines(canvas, .{ .down = .double, .left = .light, .right = .light }), - // '╦' - 0x2566 => self.draw_lines(canvas, .{ .down = .double, .left = .double, .right = .double }), - // '╧' - 0x2567 => self.draw_lines(canvas, .{ .up = .light, .left = .double, .right = .double }), - // '╨' - 0x2568 => self.draw_lines(canvas, .{ .up = .double, .left = .light, .right = .light }), - // '╩' - 0x2569 => self.draw_lines(canvas, .{ .up = .double, .left = .double, .right = .double }), - // '╪' - 0x256a => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }), - // '╫' - 0x256b => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }), - // '╬' - 0x256c => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), - // '╭' - 0x256d => try self.draw_arc(canvas, .br, .light), - // '╮' - 0x256e => try self.draw_arc(canvas, .bl, .light), - // '╯' - 0x256f => try self.draw_arc(canvas, .tl, .light), - - // '╰' - 0x2570 => try self.draw_arc(canvas, .tr, .light), - // '╱' - 0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(canvas), - // '╲' - 0x2572 => self.draw_light_diagonal_upper_left_to_lower_right(canvas), - // '╳' - 0x2573 => self.draw_light_diagonal_cross(canvas), - // '╴' - 0x2574 => self.draw_lines(canvas, .{ .left = .light }), - // '╵' - 0x2575 => self.draw_lines(canvas, .{ .up = .light }), - // '╶' - 0x2576 => self.draw_lines(canvas, .{ .right = .light }), - // '╷' - 0x2577 => self.draw_lines(canvas, .{ .down = .light }), - // '╸' - 0x2578 => self.draw_lines(canvas, .{ .left = .heavy }), - // '╹' - 0x2579 => self.draw_lines(canvas, .{ .up = .heavy }), - // '╺' - 0x257a => self.draw_lines(canvas, .{ .right = .heavy }), - // '╻' - 0x257b => self.draw_lines(canvas, .{ .down = .heavy }), - // '╼' - 0x257c => self.draw_lines(canvas, .{ .left = .light, .right = .heavy }), - // '╽' - 0x257d => self.draw_lines(canvas, .{ .up = .light, .down = .heavy }), - // '╾' - 0x257e => self.draw_lines(canvas, .{ .left = .heavy, .right = .light }), - // '╿' - 0x257f => self.draw_lines(canvas, .{ .up = .heavy, .down = .light }), - - // '▀' UPPER HALF BLOCK - 0x2580 => self.draw_block(canvas, .upper, 1, half), - // '▁' LOWER ONE EIGHTH BLOCK - 0x2581 => self.draw_block(canvas, .lower, 1, one_eighth), - // '▂' LOWER ONE QUARTER BLOCK - 0x2582 => self.draw_block(canvas, .lower, 1, one_quarter), - // '▃' LOWER THREE EIGHTHS BLOCK - 0x2583 => self.draw_block(canvas, .lower, 1, three_eighths), - // '▄' LOWER HALF BLOCK - 0x2584 => self.draw_block(canvas, .lower, 1, half), - // '▅' LOWER FIVE EIGHTHS BLOCK - 0x2585 => self.draw_block(canvas, .lower, 1, five_eighths), - // '▆' LOWER THREE QUARTERS BLOCK - 0x2586 => self.draw_block(canvas, .lower, 1, three_quarters), - // '▇' LOWER SEVEN EIGHTHS BLOCK - 0x2587 => self.draw_block(canvas, .lower, 1, seven_eighths), - // '█' FULL BLOCK - 0x2588 => self.draw_full_block(canvas), - // '▉' LEFT SEVEN EIGHTHS BLOCK - 0x2589 => self.draw_block(canvas, .left, seven_eighths, 1), - // '▊' LEFT THREE QUARTERS BLOCK - 0x258a => self.draw_block(canvas, .left, three_quarters, 1), - // '▋' LEFT FIVE EIGHTHS BLOCK - 0x258b => self.draw_block(canvas, .left, five_eighths, 1), - // '▌' LEFT HALF BLOCK - 0x258c => self.draw_block(canvas, .left, half, 1), - // '▍' LEFT THREE EIGHTHS BLOCK - 0x258d => self.draw_block(canvas, .left, three_eighths, 1), - // '▎' LEFT ONE QUARTER BLOCK - 0x258e => self.draw_block(canvas, .left, one_quarter, 1), - // '▏' LEFT ONE EIGHTH BLOCK - 0x258f => self.draw_block(canvas, .left, one_eighth, 1), - - // '▐' RIGHT HALF BLOCK - 0x2590 => self.draw_block(canvas, .right, half, 1), - // '░' - 0x2591 => self.draw_light_shade(canvas), - // '▒' - 0x2592 => self.draw_medium_shade(canvas), - // '▓' - 0x2593 => self.draw_dark_shade(canvas), - // '▔' UPPER ONE EIGHTH BLOCK - 0x2594 => self.draw_block(canvas, .upper, 1, one_eighth), - // '▕' RIGHT ONE EIGHTH BLOCK - 0x2595 => self.draw_block(canvas, .right, one_eighth, 1), - // '▖' - 0x2596 => self.draw_quadrant(canvas, .{ .bl = true }), - // '▗' - 0x2597 => self.draw_quadrant(canvas, .{ .br = true }), - // '▘' - 0x2598 => self.draw_quadrant(canvas, .{ .tl = true }), - // '▙' - 0x2599 => self.draw_quadrant(canvas, .{ .tl = true, .bl = true, .br = true }), - // '▚' - 0x259a => self.draw_quadrant(canvas, .{ .tl = true, .br = true }), - // '▛' - 0x259b => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .bl = true }), - // '▜' - 0x259c => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .br = true }), - // '▝' - 0x259d => self.draw_quadrant(canvas, .{ .tr = true }), - // '▞' - 0x259e => self.draw_quadrant(canvas, .{ .tr = true, .bl = true }), - // '▟' - 0x259f => self.draw_quadrant(canvas, .{ .tr = true, .bl = true, .br = true }), - - // '◢' - 0x25e2 => self.draw_corner_triangle_shade(canvas, .br, .on), - // '◣' - 0x25e3 => self.draw_corner_triangle_shade(canvas, .bl, .on), - // '◤' - 0x25e4 => self.draw_corner_triangle_shade(canvas, .tl, .on), - // '◥' - 0x25e5 => self.draw_corner_triangle_shade(canvas, .tr, .on), - - // '◸' - 0x25f8 => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // top edge - self.rect( - canvas, - 0, - 0, - self.metrics.cell_width, - thickness_px, - ); - // left edge - self.rect( - canvas, - 0, - 0, - thickness_px, - self.metrics.cell_height -| 1, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_right, - ); - }, - // '◹' - 0x25f9 => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // top edge - self.rect( - canvas, - 0, - 0, - self.metrics.cell_width, - thickness_px, - ); - // right edge - self.rect( - canvas, - self.metrics.cell_width -| thickness_px, - 0, - self.metrics.cell_width, - self.metrics.cell_height -| 1, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_right, - ); - }, - // '◺' - 0x25fa => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // bottom edge - self.rect( - canvas, - 0, - self.metrics.cell_height -| thickness_px, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // left edge - self.rect( - canvas, - 0, - 1, - thickness_px, - self.metrics.cell_height, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_right, - ); - }, - // '◿' - 0x25ff => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // bottom edge - self.rect( - canvas, - 0, - self.metrics.cell_height -| thickness_px, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // right edge - self.rect( - canvas, - self.metrics.cell_width -| thickness_px, - 1, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_right, - ); - }, - - 0x2800...0x28ff => self.draw_braille(canvas, cp), - - 0x1fb00...0x1fb3b => self.draw_sextant(canvas, cp), - - octant_min...octant_max => self.draw_octant(canvas, cp), - - // '🬼' - 0x1fb3c => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\#.. - \\##. - )), - // '🬽' - 0x1fb3d => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\#\. - \\### - )), - // '🬾' - 0x1fb3e => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#.. - \\#\. - \\##. - )), - // '🬿' - 0x1fb3f => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#.. - \\##. - \\### - )), - // '🭀' - 0x1fb40 => try self.draw_smooth_mosaic(canvas, .from( - \\#.. - \\#.. - \\##. - \\##. - )), - - // '🭁' - 0x1fb41 => try self.draw_smooth_mosaic(canvas, .from( - \\/## - \\### - \\### - \\### - )), - // '🭂' - 0x1fb42 => try self.draw_smooth_mosaic(canvas, .from( - \\./# - \\### - \\### - \\### - )), - // '🭃' - 0x1fb43 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\### - \\### - )), - // '🭄' - 0x1fb44 => try self.draw_smooth_mosaic(canvas, .from( - \\..# - \\.## - \\### - \\### - )), - // '🭅' - 0x1fb45 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\.## - \\### - )), - // '🭆' - 0x1fb46 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\./# - \\### - \\### - )), - - // '🭇' - 0x1fb47 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\..# - \\.## - )), - // '🭈' - 0x1fb48 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\./# - \\### - )), - // '🭉' - 0x1fb49 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\..# - \\./# - \\.## - )), - // '🭊' - 0x1fb4a => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\..# - \\.## - \\### - )), - // '🭋' - 0x1fb4b => try self.draw_smooth_mosaic(canvas, .from( - \\..# - \\..# - \\.## - \\.## - )), - - // '🭌' - 0x1fb4c => try self.draw_smooth_mosaic(canvas, .from( - \\##\ - \\### - \\### - \\### - )), - // '🭍' - 0x1fb4d => try self.draw_smooth_mosaic(canvas, .from( - \\#\. - \\### - \\### - \\### - )), - // '🭎' - 0x1fb4e => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\### - \\### - )), - // '🭏' - 0x1fb4f => try self.draw_smooth_mosaic(canvas, .from( - \\#.. - \\##. - \\### - \\### - )), - // '🭐' - 0x1fb50 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\##. - \\### - )), - // '🭑' - 0x1fb51 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#\. - \\### - \\### - )), - - // '🭒' - 0x1fb52 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\\## - )), - // '🭓' - 0x1fb53 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\.\# - )), - // '🭔' - 0x1fb54 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.## - \\.## - )), - // '🭕' - 0x1fb55 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.## - \\..# - )), - // '🭖' - 0x1fb56 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.## - \\.## - \\.## - )), - - // '🭗' - 0x1fb57 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\#.. - \\... - \\... - )), - // '🭘' - 0x1fb58 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\#/. - \\... - \\... - )), - // '🭙' - 0x1fb59 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\#/. - \\#.. - \\... - )), - // '🭚' - 0x1fb5a => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\##. - \\#.. - \\... - )), - // '🭛' - 0x1fb5b => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\#.. - \\#.. - )), - - // '🭜' - 0x1fb5c => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\#/. - \\... - )), - // '🭝' - 0x1fb5d => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\##/ - )), - // '🭞' - 0x1fb5e => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\#/. - )), - // '🭟' - 0x1fb5f => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\##. - \\##. - )), - // '🭠' - 0x1fb60 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\##. - \\#.. - )), - // '🭡' - 0x1fb61 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\##. - \\##. - \\##. - )), - - // '🭢' - 0x1fb62 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\..# - \\... - \\... - )), - // '🭣' - 0x1fb63 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.\# - \\... - \\... - )), - // '🭤' - 0x1fb64 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.\# - \\..# - \\... - )), - // '🭥' - 0x1fb65 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.## - \\..# - \\... - )), - // '🭦' - 0x1fb66 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\..# - \\..# - )), - // '🭧' - 0x1fb67 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.\# - \\... - )), - - // '🭨' - 0x1fb68 => { - try self.draw_edge_triangle(canvas, .left); - canvas.invert(); - }, - // '🭩' - 0x1fb69 => { - try self.draw_edge_triangle(canvas, .top); - canvas.invert(); - }, - // '🭪' - 0x1fb6a => { - try self.draw_edge_triangle(canvas, .right); - canvas.invert(); - }, - // '🭫' - 0x1fb6b => { - try self.draw_edge_triangle(canvas, .bottom); - canvas.invert(); - }, - // '🭬' - 0x1fb6c => try self.draw_edge_triangle(canvas, .left), - // '🭭' - 0x1fb6d => try self.draw_edge_triangle(canvas, .top), - // '🭮' - 0x1fb6e => try self.draw_edge_triangle(canvas, .right), - // '🭯' - 0x1fb6f => try self.draw_edge_triangle(canvas, .bottom), - - // '🭰' - 0x1fb70 => self.draw_vertical_one_eighth_block_n(canvas, 1), - // '🭱' - 0x1fb71 => self.draw_vertical_one_eighth_block_n(canvas, 2), - // '🭲' - 0x1fb72 => self.draw_vertical_one_eighth_block_n(canvas, 3), - // '🭳' - 0x1fb73 => self.draw_vertical_one_eighth_block_n(canvas, 4), - // '🭴' - 0x1fb74 => self.draw_vertical_one_eighth_block_n(canvas, 5), - // '🭵' - 0x1fb75 => self.draw_vertical_one_eighth_block_n(canvas, 6), - - // '🭶' - 0x1fb76 => self.draw_horizontal_one_eighth_block_n(canvas, 1), - // '🭷' - 0x1fb77 => self.draw_horizontal_one_eighth_block_n(canvas, 2), - // '🭸' - 0x1fb78 => self.draw_horizontal_one_eighth_block_n(canvas, 3), - // '🭹' - 0x1fb79 => self.draw_horizontal_one_eighth_block_n(canvas, 4), - // '🭺' - 0x1fb7a => self.draw_horizontal_one_eighth_block_n(canvas, 5), - // '🭻' - 0x1fb7b => self.draw_horizontal_one_eighth_block_n(canvas, 6), - - // '🮂' UPPER ONE QUARTER BLOCK - 0x1fb82 => self.draw_block(canvas, .upper, 1, one_quarter), - // '🮃' UPPER THREE EIGHTHS BLOCK - 0x1fb83 => self.draw_block(canvas, .upper, 1, three_eighths), - // '🮄' UPPER FIVE EIGHTHS BLOCK - 0x1fb84 => self.draw_block(canvas, .upper, 1, five_eighths), - // '🮅' UPPER THREE QUARTERS BLOCK - 0x1fb85 => self.draw_block(canvas, .upper, 1, three_quarters), - // '🮆' UPPER SEVEN EIGHTHS BLOCK - 0x1fb86 => self.draw_block(canvas, .upper, 1, seven_eighths), - - // '🭼' LEFT AND LOWER ONE EIGHTH BLOCK - 0x1fb7c => { - self.draw_block(canvas, .left, one_eighth, 1); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🭽' LEFT AND UPPER ONE EIGHTH BLOCK - 0x1fb7d => { - self.draw_block(canvas, .left, one_eighth, 1); - self.draw_block(canvas, .upper, 1, one_eighth); - }, - // '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK - 0x1fb7e => { - self.draw_block(canvas, .right, one_eighth, 1); - self.draw_block(canvas, .upper, 1, one_eighth); - }, - // '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK - 0x1fb7f => { - self.draw_block(canvas, .right, one_eighth, 1); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🮀' UPPER AND LOWER ONE EIGHTH BLOCK - 0x1fb80 => { - self.draw_block(canvas, .upper, 1, one_eighth); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🮁' - 0x1fb81 => self.draw_horizontal_one_eighth_1358_block(canvas), - - // '🮇' RIGHT ONE QUARTER BLOCK - 0x1fb87 => self.draw_block(canvas, .right, one_quarter, 1), - // '🮈' RIGHT THREE EIGHTHS BLOCK - 0x1fb88 => self.draw_block(canvas, .right, three_eighths, 1), - // '🮉' RIGHT FIVE EIGHTHS BLOCK - 0x1fb89 => self.draw_block(canvas, .right, five_eighths, 1), - // '🮊' RIGHT THREE QUARTERS BLOCK - 0x1fb8a => self.draw_block(canvas, .right, three_quarters, 1), - // '🮋' RIGHT SEVEN EIGHTHS BLOCK - 0x1fb8b => self.draw_block(canvas, .right, seven_eighths, 1), - // '🮌' - 0x1fb8c => self.draw_block_shade(canvas, .left, half, 1, .medium), - // '🮍' - 0x1fb8d => self.draw_block_shade(canvas, .right, half, 1, .medium), - // '🮎' - 0x1fb8e => self.draw_block_shade(canvas, .upper, 1, half, .medium), - // '🮏' - 0x1fb8f => self.draw_block_shade(canvas, .lower, 1, half, .medium), - - // '🮐' - 0x1fb90 => self.draw_medium_shade(canvas), - // '🮑' - 0x1fb91 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .upper, 1, half); - }, - // '🮒' - 0x1fb92 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .lower, 1, half); - }, - // '🮔' - 0x1fb94 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .right, half, 1); - }, - // '🮕' - 0x1fb95 => self.draw_checkerboard_fill(canvas, 0), - // '🮖' - 0x1fb96 => self.draw_checkerboard_fill(canvas, 1), - // '🮗' - 0x1fb97 => { - self.draw_horizontal_one_eighth_block_n(canvas, 2); - self.draw_horizontal_one_eighth_block_n(canvas, 3); - self.draw_horizontal_one_eighth_block_n(canvas, 6); - self.draw_horizontal_one_eighth_block_n(canvas, 7); - }, - // '🮘' - 0x1fb98 => self.draw_upper_left_to_lower_right_fill(canvas), - // '🮙' - 0x1fb99 => self.draw_upper_right_to_lower_left_fill(canvas), - // '🮚' - 0x1fb9a => { - try self.draw_edge_triangle(canvas, .top); - try self.draw_edge_triangle(canvas, .bottom); - }, - // '🮛' - 0x1fb9b => { - try self.draw_edge_triangle(canvas, .left); - try self.draw_edge_triangle(canvas, .right); - }, - // '🮜' - 0x1fb9c => self.draw_corner_triangle_shade(canvas, .tl, .medium), - // '🮝' - 0x1fb9d => self.draw_corner_triangle_shade(canvas, .tr, .medium), - // '🮞' - 0x1fb9e => self.draw_corner_triangle_shade(canvas, .br, .medium), - // '🮟' - 0x1fb9f => self.draw_corner_triangle_shade(canvas, .bl, .medium), - - // '🮠' - 0x1fba0 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true }), - // '🮡' - 0x1fba1 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true }), - // '🮢' - 0x1fba2 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true }), - // '🮣' - 0x1fba3 => self.draw_corner_diagonal_lines(canvas, .{ .br = true }), - // '🮤' - 0x1fba4 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true }), - // '🮥' - 0x1fba5 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .br = true }), - // '🮦' - 0x1fba6 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true, .br = true }), - // '🮧' - 0x1fba7 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true }), - // '🮨' - 0x1fba8 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .br = true }), - // '🮩' - 0x1fba9 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true }), - // '🮪' - 0x1fbaa => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true, .br = true }), - // '🮫' - 0x1fbab => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true, .br = true }), - // '🮬' - 0x1fbac => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .br = true }), - // '🮭' - 0x1fbad => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true }), - // '🮮' - 0x1fbae => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }), - // '🮯' - 0x1fbaf => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), - - // '🮽' - 0x1fbbd => { - self.draw_light_diagonal_cross(canvas); - canvas.invert(); - }, - // '🮾' - 0x1fbbe => { - self.draw_corner_diagonal_lines(canvas, .{ .br = true }); - canvas.invert(); - }, - // '🮿' - 0x1fbbf => { - self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }); - canvas.invert(); - }, - - // '🯎' - 0x1fbce => self.draw_block(canvas, .left, two_thirds, 1), - // '🯏' - 0x1fbcf => self.draw_block(canvas, .left, one_third, 1), - // '🯐' - 0x1fbd0 => self.draw_cell_diagonal( - canvas, - .middle_right, - .lower_left, - ), - // '🯑' - 0x1fbd1 => self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_left, - ), - // '🯒' - 0x1fbd2 => self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_right, - ), - // '🯓' - 0x1fbd3 => self.draw_cell_diagonal( - canvas, - .middle_left, - .lower_right, - ), - // '🯔' - 0x1fbd4 => self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_center, - ), - // '🯕' - 0x1fbd5 => self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_right, - ), - // '🯖' - 0x1fbd6 => self.draw_cell_diagonal( - canvas, - .upper_right, - .lower_center, - ), - // '🯗' - 0x1fbd7 => self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_left, - ), - // '🯘' - 0x1fbd8 => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .upper_right, - ); - }, - // '🯙' - 0x1fbd9 => { - self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_right, - ); - }, - // '🯚' - 0x1fbda => { - self.draw_cell_diagonal( - canvas, - .lower_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_right, - ); - }, - // '🯛' - 0x1fbdb => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_left, - ); - }, - // '🯜' - 0x1fbdc => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_center, - ); - self.draw_cell_diagonal( - canvas, - .lower_center, - .upper_right, - ); - }, - // '🯝' - 0x1fbdd => { - self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_left, - ); - self.draw_cell_diagonal( - canvas, - .middle_left, - .lower_right, - ); - }, - // '🯞' - 0x1fbde => { - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_center, - ); - self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_right, - ); - }, - // '🯟' - 0x1fbdf => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_right, - ); - self.draw_cell_diagonal( - canvas, - .middle_right, - .lower_left, - ); - }, - - // '🯠' - 0x1fbe0 => self.draw_circle(canvas, .top, false), - // '🯡' - 0x1fbe1 => self.draw_circle(canvas, .right, false), - // '🯢' - 0x1fbe2 => self.draw_circle(canvas, .bottom, false), - // '🯣' - 0x1fbe3 => self.draw_circle(canvas, .left, false), - // '🯤' - 0x1fbe4 => self.draw_block(canvas, .upper_center, 0.5, 0.5), - // '🯥' - 0x1fbe5 => self.draw_block(canvas, .lower_center, 0.5, 0.5), - // '🯦' - 0x1fbe6 => self.draw_block(canvas, .middle_left, 0.5, 0.5), - // '🯧' - 0x1fbe7 => self.draw_block(canvas, .middle_right, 0.5, 0.5), - // '🯨' - 0x1fbe8 => self.draw_circle(canvas, .top, true), - // '🯩' - 0x1fbe9 => self.draw_circle(canvas, .right, true), - // '🯪' - 0x1fbea => self.draw_circle(canvas, .bottom, true), - // '🯫' - 0x1fbeb => self.draw_circle(canvas, .left, true), - // '🯬' - 0x1fbec => self.draw_circle(canvas, .top_right, true), - // '🯭' - 0x1fbed => self.draw_circle(canvas, .bottom_left, true), - // '🯮' - 0x1fbee => self.draw_circle(canvas, .bottom_right, true), - // '🯯' - 0x1fbef => self.draw_circle(canvas, .top_left, true), - - // (Below:) - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - // TODO(qwerasd): This should be in another file, but really the - // general organization of the sprite font code - // needs to be reworked eventually. - // - //           - //                     - //                     - //             - - // '' - 0x0f5d0 => self.hline_middle(canvas, .light), - // '' - 0x0f5d1 => self.vline_middle(canvas, .light), - // '' - 0x0f5d2 => self.draw_fading_line(canvas, .right, .light), - // '' - 0x0f5d3 => self.draw_fading_line(canvas, .left, .light), - // '' - 0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light), - // '' - 0x0f5d5 => self.draw_fading_line(canvas, .top, .light), - // '' - 0x0f5d6 => try self.draw_arc(canvas, .br, .light), - // '' - 0x0f5d7 => try self.draw_arc(canvas, .bl, .light), - // '' - 0x0f5d8 => try self.draw_arc(canvas, .tr, .light), - // '' - 0x0f5d9 => try self.draw_arc(canvas, .tl, .light), - // '' - 0x0f5da => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - }, - // '' - 0x0f5db => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5dc => { - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5dd => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5de => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5df => { - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .bl, .light); - }, - - // '' - 0x0f5e0 => { - try self.draw_arc(canvas, .bl, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e1 => { - try self.draw_arc(canvas, .br, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e2 => { - try self.draw_arc(canvas, .br, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5e3 => { - try self.draw_arc(canvas, .tl, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e4 => { - try self.draw_arc(canvas, .tr, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e5 => { - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5e6 => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .tr, .light); - }, - // '' - 0x0f5e7 => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5e8 => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5e9 => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5ea => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5eb => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5ec => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5ed => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light), - // '' - 0x0f5ef => self.draw_branch_node(canvas, .{}, .light), - - // '' - 0x0f5f0 => self.draw_branch_node(canvas, .{ - .right = true, - .filled = true, - }, .light), - // '' - 0x0f5f1 => self.draw_branch_node(canvas, .{ - .right = true, - }, .light), - // '' - 0x0f5f2 => self.draw_branch_node(canvas, .{ - .left = true, - .filled = true, - }, .light), - // '' - 0x0f5f3 => self.draw_branch_node(canvas, .{ - .left = true, - }, .light), - // '' - 0x0f5f4 => self.draw_branch_node(canvas, .{ - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f5f5 => self.draw_branch_node(canvas, .{ - .left = true, - .right = true, - }, .light), - // '' - 0x0f5f6 => self.draw_branch_node(canvas, .{ - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5f7 => self.draw_branch_node(canvas, .{ - .down = true, - }, .light), - // '' - 0x0f5f8 => self.draw_branch_node(canvas, .{ - .up = true, - .filled = true, - }, .light), - // '' - 0x0f5f9 => self.draw_branch_node(canvas, .{ - .up = true, - }, .light), - // '' - 0x0f5fa => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5fb => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - }, .light), - // '' - 0x0f5fc => self.draw_branch_node(canvas, .{ - .right = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5fd => self.draw_branch_node(canvas, .{ - .right = true, - .down = true, - }, .light), - // '' - 0x0f5fe => self.draw_branch_node(canvas, .{ - .left = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5ff => self.draw_branch_node(canvas, .{ - .left = true, - .down = true, - }, .light), - - // '' - 0x0f600 => self.draw_branch_node(canvas, .{ - .up = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f601 => self.draw_branch_node(canvas, .{ - .up = true, - .right = true, - }, .light), - // '' - 0x0f602 => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .filled = true, - }, .light), - // '' - 0x0f603 => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - }, .light), - // '' - 0x0f604 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f605 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .right = true, - }, .light), - // '' - 0x0f606 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .filled = true, - }, .light), - // '' - 0x0f607 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - }, .light), - // '' - 0x0f608 => self.draw_branch_node(canvas, .{ - .down = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f609 => self.draw_branch_node(canvas, .{ - .down = true, - .left = true, - .right = true, - }, .light), - // '' - 0x0f60a => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f60b => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .right = true, - }, .light), - // '' - 0x0f60c => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f60d => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .right = true, - }, .light), - - // '𜰡' - SEPARATED BLOCK QUADRANT-1 - 0x1cc21 => try self.draw_separated_block_quadrant(canvas, "1"), - // '𜰢' - SEPARATED BLOCK QUADRANT-2 - 0x1cc22 => try self.draw_separated_block_quadrant(canvas, "2"), - // '𜰣' - SEPARATED BLOCK QUADRANT-12 - 0x1cc23 => try self.draw_separated_block_quadrant(canvas, "12"), - // '𜰤' - SEPARATED BLOCK QUADRANT-3 - 0x1cc24 => try self.draw_separated_block_quadrant(canvas, "3"), - // '𜰥' - SEPARATED BLOCK QUADRANT-13 - 0x1cc25 => try self.draw_separated_block_quadrant(canvas, "13"), - // '𜰦' - SEPARATED BLOCK QUADRANT-23 - 0x1cc26 => try self.draw_separated_block_quadrant(canvas, "23"), - // '𜰧' - SEPARATED BLOCK QUADRANT-123 - 0x1cc27 => try self.draw_separated_block_quadrant(canvas, "123"), - // '𜰨' - SEPARATED BLOCK QUADRANT-4 - 0x1cc28 => try self.draw_separated_block_quadrant(canvas, "4"), - // '𜰩' - SEPARATED BLOCK QUADRANT-14 - 0x1cc29 => try self.draw_separated_block_quadrant(canvas, "14"), - // '𜰪' - SEPARATED BLOCK QUADRANT-24 - 0x1cc2a => try self.draw_separated_block_quadrant(canvas, "24"), - // '𜰫' - SEPARATED BLOCK QUADRANT-124 - 0x1cc2b => try self.draw_separated_block_quadrant(canvas, "124"), - // '𜰬' - SEPARATED BLOCK QUADRANT-34 - 0x1cc2c => try self.draw_separated_block_quadrant(canvas, "34"), - // '𜰭' - SEPARATED BLOCK QUADRANT-134 - 0x1cc2d => try self.draw_separated_block_quadrant(canvas, "134"), - // '𜰮' - SEPARATED BLOCK QUADRANT-234 - 0x1cc2e => try self.draw_separated_block_quadrant(canvas, "234"), - // '𜰯' - SEPARATED BLOCK QUADRANT-1234 - 0x1cc2f => try self.draw_separated_block_quadrant(canvas, "1234"), - - else => return error.InvalidCodepoint, - } -} - -fn draw_lines( - self: Box, - canvas: *font.sprite.Canvas, - lines: Lines, -) void { - const light_px = Thickness.light.height(self.metrics.box_thickness); - const heavy_px = Thickness.heavy.height(self.metrics.box_thickness); - - // Top of light horizontal strokes - const h_light_top = (self.metrics.cell_height -| light_px) / 2; - // Bottom of light horizontal strokes - const h_light_bottom = h_light_top +| light_px; - - // Top of heavy horizontal strokes - const h_heavy_top = (self.metrics.cell_height -| heavy_px) / 2; - // Bottom of heavy horizontal strokes - const h_heavy_bottom = h_heavy_top +| heavy_px; - - // Top of the top doubled horizontal stroke (bottom is `h_light_top`) - const h_double_top = h_light_top -| light_px; - // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`) - const h_double_bottom = h_light_bottom +| light_px; - - // Left of light vertical strokes - const v_light_left = (self.metrics.cell_width -| light_px) / 2; - // Right of light vertical strokes - const v_light_right = v_light_left +| light_px; - - // Left of heavy vertical strokes - const v_heavy_left = (self.metrics.cell_width -| heavy_px) / 2; - // Right of heavy vertical strokes - const v_heavy_right = v_heavy_left +| heavy_px; - - // Left of the left doubled vertical stroke (right is `v_light_left`) - const v_double_left = v_light_left -| light_px; - // Right of the right doubled vertical stroke (left is `v_light_right`) - const v_double_right = v_light_right +| light_px; - - // The bottom of the up line - const up_bottom = if (lines.left == .heavy or lines.right == .heavy) - h_heavy_bottom - else if (lines.left != lines.right or lines.down == lines.up) - if (lines.left == .double or lines.right == .double) - h_double_bottom - else - h_light_bottom - else if (lines.left == .none and lines.right == .none) - h_light_bottom - else - h_light_top; - - // The top of the down line - const down_top = if (lines.left == .heavy or lines.right == .heavy) - h_heavy_top - else if (lines.left != lines.right or lines.up == lines.down) - if (lines.left == .double or lines.right == .double) - h_double_top - else - h_light_top - else if (lines.left == .none and lines.right == .none) - h_light_top - else - h_light_bottom; - - // The right of the left line - const left_right = if (lines.up == .heavy or lines.down == .heavy) - v_heavy_right - else if (lines.up != lines.down or lines.left == lines.right) - if (lines.up == .double or lines.down == .double) - v_double_right - else - v_light_right - else if (lines.up == .none and lines.down == .none) - v_light_right - else - v_light_left; - - // The left of the right line - const right_left = if (lines.up == .heavy or lines.down == .heavy) - v_heavy_left - else if (lines.up != lines.down or lines.right == lines.left) - if (lines.up == .double or lines.down == .double) - v_double_left - else - v_light_left - else if (lines.up == .none and lines.down == .none) - v_light_left - else - v_light_right; - - switch (lines.up) { - .none => {}, - .light => self.rect(canvas, v_light_left, 0, v_light_right, up_bottom), - .heavy => self.rect(canvas, v_heavy_left, 0, v_heavy_right, up_bottom), - .double => { - const left_bottom = if (lines.left == .double) h_light_top else up_bottom; - const right_bottom = if (lines.right == .double) h_light_top else up_bottom; - - self.rect(canvas, v_double_left, 0, v_light_left, left_bottom); - self.rect(canvas, v_light_right, 0, v_double_right, right_bottom); - }, - } - - switch (lines.right) { - .none => {}, - .light => self.rect(canvas, right_left, h_light_top, self.metrics.cell_width, h_light_bottom), - .heavy => self.rect(canvas, right_left, h_heavy_top, self.metrics.cell_width, h_heavy_bottom), - .double => { - const top_left = if (lines.up == .double) v_light_right else right_left; - const bottom_left = if (lines.down == .double) v_light_right else right_left; - - self.rect(canvas, top_left, h_double_top, self.metrics.cell_width, h_light_top); - self.rect(canvas, bottom_left, h_light_bottom, self.metrics.cell_width, h_double_bottom); - }, - } - - switch (lines.down) { - .none => {}, - .light => self.rect(canvas, v_light_left, down_top, v_light_right, self.metrics.cell_height), - .heavy => self.rect(canvas, v_heavy_left, down_top, v_heavy_right, self.metrics.cell_height), - .double => { - const left_top = if (lines.left == .double) h_light_bottom else down_top; - const right_top = if (lines.right == .double) h_light_bottom else down_top; - - self.rect(canvas, v_double_left, left_top, v_light_left, self.metrics.cell_height); - self.rect(canvas, v_light_right, right_top, v_double_right, self.metrics.cell_height); - }, - } - - switch (lines.left) { - .none => {}, - .light => self.rect(canvas, 0, h_light_top, left_right, h_light_bottom), - .heavy => self.rect(canvas, 0, h_heavy_top, left_right, h_heavy_bottom), - .double => { - const top_right = if (lines.up == .double) v_light_left else left_right; - const bottom_right = if (lines.down == .double) v_light_left else left_right; - - self.rect(canvas, 0, h_double_top, top_right, h_light_top); - self.rect(canvas, 0, h_light_bottom, bottom_right, h_double_bottom); - }, - } -} - -fn draw_light_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 3, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 3, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 3, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 3, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 4, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 4, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 4, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 4, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 2, - Thickness.light.height(self.metrics.box_thickness), - Thickness.light.height(self.metrics.box_thickness), - ); -} - -fn draw_heavy_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 2, - Thickness.heavy.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_light_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 2, - Thickness.light.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_heavy_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 2, - Thickness.heavy.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_light_diagonal_upper_right_to_lower_left(self: Box, canvas: *font.sprite.Canvas) void { - canvas.line(.{ - .p0 = .{ .x = @floatFromInt(self.metrics.cell_width), .y = 0 }, - .p1 = .{ .x = 0, .y = @floatFromInt(self.metrics.cell_height) }, - }, @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), .on) catch {}; -} - -fn draw_light_diagonal_upper_left_to_lower_right(self: Box, canvas: *font.sprite.Canvas) void { - canvas.line(.{ - .p0 = .{ .x = 0, .y = 0 }, - .p1 = .{ - .x = @floatFromInt(self.metrics.cell_width), - .y = @floatFromInt(self.metrics.cell_height), - }, - }, @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), .on) catch {}; -} - -fn draw_light_diagonal_cross(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_light_diagonal_upper_right_to_lower_left(canvas); - self.draw_light_diagonal_upper_left_to_lower_right(canvas); -} - -fn draw_block( - self: Box, - canvas: *font.sprite.Canvas, - comptime alignment: Alignment, - comptime width: f64, - comptime height: f64, -) void { - self.draw_block_shade(canvas, alignment, width, height, .on); -} - -fn draw_block_shade( - self: Box, - canvas: *font.sprite.Canvas, - comptime alignment: Alignment, - comptime width: f64, - comptime height: f64, - comptime shade: Shade, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const w: u32 = @intFromFloat(@round(float_width * width)); - const h: u32 = @intFromFloat(@round(float_height * height)); - - const x = switch (alignment.horizontal) { - .left => 0, - .right => self.metrics.cell_width - w, - .center => (self.metrics.cell_width - w) / 2, - }; - const y = switch (alignment.vertical) { - .top => 0, - .bottom => self.metrics.cell_height - h, - .middle => (self.metrics.cell_height - h) / 2, - }; - - canvas.rect(.{ - .x = x, - .y = y, - .width = w, - .height = h, - }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))); -} - -fn draw_corner_triangle_shade( - self: Box, - canvas: *font.sprite.Canvas, - comptime corner: Corner, - comptime shade: Shade, -) void { - const x0, const y0, const x1, const y1, const x2, const y2 = switch (corner) { - .tl => .{ 0, 0, 0, self.metrics.cell_height, self.metrics.cell_width, 0 }, - .tr => .{ 0, 0, self.metrics.cell_width, self.metrics.cell_height, self.metrics.cell_width, 0 }, - .bl => .{ 0, 0, 0, self.metrics.cell_height, self.metrics.cell_width, self.metrics.cell_height }, - .br => .{ 0, self.metrics.cell_height, self.metrics.cell_width, self.metrics.cell_height, self.metrics.cell_width, 0 }, - }; - - canvas.triangle(.{ - .p0 = .{ .x = @floatFromInt(x0), .y = @floatFromInt(y0) }, - .p1 = .{ .x = @floatFromInt(x1), .y = @floatFromInt(y1) }, - .p2 = .{ .x = @floatFromInt(x2), .y = @floatFromInt(y2) }, - }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))) catch {}; -} - -fn draw_full_block(self: Box, canvas: *font.sprite.Canvas) void { - self.rect(canvas, 0, 0, self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_vertical_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void { - const x = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.metrics.cell_width)) / 8))); - const w = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 8))); - self.rect(canvas, x, 0, x + w, self.metrics.cell_height); -} - -fn draw_checkerboard_fill(self: Box, canvas: *font.sprite.Canvas, parity: u1) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const x_size: usize = 4; - const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width))); - for (0..x_size) |x| { - const x0 = (self.metrics.cell_width * x) / x_size; - const x1 = (self.metrics.cell_width * (x + 1)) / x_size; - for (0..y_size) |y| { - const y0 = (self.metrics.cell_height * y) / y_size; - const y1 = (self.metrics.cell_height * (y + 1)) / y_size; - if ((x + y) % 2 == parity) { - canvas.rect(.{ - .x = @intCast(x0), - .y = @intCast(y0), - .width = @intCast(x1 -| x0), - .height = @intCast(y1 -| y0), - }, .on); - } - } - } -} - -fn draw_upper_left_to_lower_right_fill(self: Box, canvas: *font.sprite.Canvas) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - const line_count = self.metrics.cell_width / (2 * thick_px); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); - - for (0..line_count * 2 + 1) |_i| { - const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); - const top_x = @as(f64, @floatFromInt(i)) * stride; - const bottom_x = float_width + top_x; - canvas.line(.{ - .p0 = .{ .x = top_x, .y = 0 }, - .p1 = .{ .x = bottom_x, .y = float_height }, - }, float_thick, .on) catch {}; - } -} - -fn draw_upper_right_to_lower_left_fill(self: Box, canvas: *font.sprite.Canvas) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - const line_count = self.metrics.cell_width / (2 * thick_px); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); - - for (0..line_count * 2 + 1) |_i| { - const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); - const bottom_x = @as(f64, @floatFromInt(i)) * stride; - const top_x = float_width + bottom_x; - canvas.line(.{ - .p0 = .{ .x = top_x, .y = 0 }, - .p1 = .{ .x = bottom_x, .y = float_height }, - }, float_thick, .on) catch {}; - } -} - -fn draw_corner_diagonal_lines( - self: Box, - canvas: *font.sprite.Canvas, - comptime corners: Quads, -) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const center_x: f64 = @floatFromInt(self.metrics.cell_width / 2 + self.metrics.cell_width % 2); - const center_y: f64 = @floatFromInt(self.metrics.cell_height / 2 + self.metrics.cell_height % 2); - - if (corners.tl) canvas.line(.{ - .p0 = .{ .x = center_x, .y = 0 }, - .p1 = .{ .x = 0, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.tr) canvas.line(.{ - .p0 = .{ .x = center_x, .y = 0 }, - .p1 = .{ .x = float_width, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.bl) canvas.line(.{ - .p0 = .{ .x = center_x, .y = float_height }, - .p1 = .{ .x = 0, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.br) canvas.line(.{ - .p0 = .{ .x = center_x, .y = float_height }, - .p1 = .{ .x = float_width, .y = center_y }, - }, float_thick, .on) catch {}; -} - -fn draw_cell_diagonal( - self: Box, - canvas: *font.sprite.Canvas, - comptime from: Alignment, - comptime to: Alignment, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const x0: f64 = switch (from.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y0: f64 = switch (from.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - const x1: f64 = switch (to.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y1: f64 = switch (to.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - - self.draw_line( - canvas, - .{ .x = x0, .y = y0 }, - .{ .x = x1, .y = y1 }, - .light, - ) catch {}; -} - -fn draw_fading_line( - self: Box, - canvas: *font.sprite.Canvas, - comptime to: Edge, - comptime thickness: Thickness, -) void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - // Top of horizontal strokes - const h_top = (self.metrics.cell_height -| thick_px) / 2; - // Bottom of horizontal strokes - const h_bottom = h_top +| thick_px; - // Left of vertical strokes - const v_left = (self.metrics.cell_width -| thick_px) / 2; - // Right of vertical strokes - const v_right = v_left +| thick_px; - - // If we're fading to the top or left, we start with 0.0 - // and increment up as we progress, otherwise we start - // at 255.0 and increment down (negative). - var color: f64 = switch (to) { - .top, .left => 0.0, - .bottom, .right => 255.0, - }; - const inc: f64 = 255.0 / switch (to) { - .top => float_height, - .bottom => -float_height, - .left => float_width, - .right => -float_width, - }; - - switch (to) { - .top, .bottom => { - for (0..self.metrics.cell_height) |y| { - for (v_left..v_right) |x| { - canvas.pixel( - @intCast(x), - @intCast(y), - @enumFromInt(@as(u8, @intFromFloat(@round(color)))), - ); - } - color += inc; - } - }, - .left, .right => { - for (0..self.metrics.cell_width) |x| { - for (h_top..h_bottom) |y| { - canvas.pixel( - @intCast(x), - @intCast(y), - @enumFromInt(@as(u8, @intFromFloat(@round(color)))), - ); - } - color += inc; - } - }, - } -} - -fn draw_branch_node( - self: Box, - canvas: *font.sprite.Canvas, - node: BranchNode, - comptime thickness: Thickness, -) void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - - // Top of horizontal strokes - const h_top = (self.metrics.cell_height -| thick_px) / 2; - // Bottom of horizontal strokes - const h_bottom = h_top +| thick_px; - // Left of vertical strokes - const v_left = (self.metrics.cell_width -| thick_px) / 2; - // Right of vertical strokes - const v_right = v_left +| thick_px; - - // We calculate the center of the circle this way - // to ensure it aligns with box drawing characters - // since the lines are sometimes off center to - // make sure they aren't split between pixels. - const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; - const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; - // The radius needs to be the smallest distance from the center to an edge. - const r: f64 = @min( - @min(cx, cy), - @min(float_width - cx, float_height - cy), - ); - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth(float_thick); - - // These @intFromFloat casts shouldn't ever fail since r can never - // be greater than cx or cy, so when subtracting it from them the - // result can never be negative. - if (node.up) - self.rect(canvas, v_left, 0, v_right, @intFromFloat(@ceil(cy - r))); - if (node.right) - self.rect(canvas, @intFromFloat(@floor(cx + r)), h_top, self.metrics.cell_width, h_bottom); - if (node.down) - self.rect(canvas, v_left, @intFromFloat(@floor(cy + r)), v_right, self.metrics.cell_height); - if (node.left) - self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom); - - if (node.filled) { - ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.fill() catch return; - } else { - ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.stroke() catch return; - } -} - -fn draw_circle( - self: Box, - canvas: *font.sprite.Canvas, - comptime position: Alignment, - comptime filled: bool, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const x: f64 = switch (position.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y: f64 = switch (position.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - const r: f64 = 0.5 * @min(float_width, float_height); - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth( - @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), - ); - - if (filled) { - ctx.arc(x, y, r, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.fill() catch return; - } else { - ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.stroke() catch return; - } -} - -fn draw_line( - self: Box, - canvas: *font.sprite.Canvas, - p0: font.sprite.Point(f64), - p1: font.sprite.Point(f64), - comptime thickness: Thickness, -) !void { - canvas.line( - .{ .p0 = p0, .p1 = p1 }, - @floatFromInt(thickness.height(self.metrics.box_thickness)), - .on, - ) catch {}; -} - -fn draw_shade(self: Box, canvas: *font.sprite.Canvas, v: u16) void { - canvas.rect((font.sprite.Box(u32){ - .p0 = .{ .x = 0, .y = 0 }, - .p1 = .{ - .x = self.metrics.cell_width, - .y = self.metrics.cell_height, - }, - }).rect(), @as(font.sprite.Color, @enumFromInt(v))); -} - -fn draw_light_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0x40); -} - -fn draw_medium_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0x80); -} - -fn draw_dark_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0xc0); -} - -fn draw_horizontal_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void { - const h = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_height)) / 8))); - const y = @min( - self.metrics.cell_height -| h, - @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.metrics.cell_height)) / 8))), - ); - self.rect(canvas, 0, y, self.metrics.cell_width, y + h); -} - -fn draw_horizontal_one_eighth_1358_block(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_horizontal_one_eighth_block_n(canvas, 0); - self.draw_horizontal_one_eighth_block_n(canvas, 2); - self.draw_horizontal_one_eighth_block_n(canvas, 4); - self.draw_horizontal_one_eighth_block_n(canvas, 7); -} - -fn draw_quadrant(self: Box, canvas: *font.sprite.Canvas, comptime quads: Quads) void { - const center_x = self.metrics.cell_width / 2 + self.metrics.cell_width % 2; - const center_y = self.metrics.cell_height / 2 + self.metrics.cell_height % 2; - - if (quads.tl) self.rect(canvas, 0, 0, center_x, center_y); - if (quads.tr) self.rect(canvas, center_x, 0, self.metrics.cell_width, center_y); - if (quads.bl) self.rect(canvas, 0, center_y, center_x, self.metrics.cell_height); - if (quads.br) self.rect(canvas, center_x, center_y, self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_braille(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - var w: u32 = @min(self.metrics.cell_width / 4, self.metrics.cell_height / 8); - var x_spacing: u32 = self.metrics.cell_width / 4; - var y_spacing: u32 = self.metrics.cell_height / 8; - var x_margin: u32 = x_spacing / 2; - var y_margin: u32 = y_spacing / 2; - - var x_px_left: u32 = self.metrics.cell_width - 2 * x_margin - x_spacing - 2 * w; - var y_px_left: u32 = self.metrics.cell_height - 2 * y_margin - 3 * y_spacing - 4 * w; - - // First, try hard to ensure the DOT width is non-zero - if (x_px_left >= 2 and y_px_left >= 4 and w == 0) { - w += 1; - x_px_left -= 2; - y_px_left -= 4; - } - - // Second, prefer a non-zero margin - if (x_px_left >= 2 and x_margin == 0) { - x_margin = 1; - x_px_left -= 2; - } - if (y_px_left >= 2 and y_margin == 0) { - y_margin = 1; - y_px_left -= 2; - } - - // Third, increase spacing - if (x_px_left >= 1) { - x_spacing += 1; - x_px_left -= 1; - } - if (y_px_left >= 3) { - y_spacing += 1; - y_px_left -= 3; - } - - // Fourth, margins (“spacing”, but on the sides) - if (x_px_left >= 2) { - x_margin += 1; - x_px_left -= 2; - } - if (y_px_left >= 2) { - y_margin += 1; - y_px_left -= 2; - } - - // Last - increase dot width - if (x_px_left >= 2 and y_px_left >= 4) { - w += 1; - x_px_left -= 2; - y_px_left -= 4; - } - - assert(x_px_left <= 1 or y_px_left <= 1); - assert(2 * x_margin + 2 * w + x_spacing <= self.metrics.cell_width); - assert(2 * y_margin + 4 * w + 3 * y_spacing <= self.metrics.cell_height); - - const x = [2]u32{ x_margin, x_margin + w + x_spacing }; - const y = y: { - var y: [4]u32 = undefined; - y[0] = y_margin; - y[1] = y[0] + w + y_spacing; - y[2] = y[1] + w + y_spacing; - y[3] = y[2] + w + y_spacing; - break :y y; - }; - - assert(cp >= 0x2800); - assert(cp <= 0x28ff); - const sym = cp - 0x2800; - - // Left side - if (sym & 1 > 0) - self.rect(canvas, x[0], y[0], x[0] + w, y[0] + w); - if (sym & 2 > 0) - self.rect(canvas, x[0], y[1], x[0] + w, y[1] + w); - if (sym & 4 > 0) - self.rect(canvas, x[0], y[2], x[0] + w, y[2] + w); - - // Right side - if (sym & 8 > 0) - self.rect(canvas, x[1], y[0], x[1] + w, y[0] + w); - if (sym & 16 > 0) - self.rect(canvas, x[1], y[1], x[1] + w, y[1] + w); - if (sym & 32 > 0) - self.rect(canvas, x[1], y[2], x[1] + w, y[2] + w); - - // 8-dot patterns - if (sym & 64 > 0) - self.rect(canvas, x[0], y[3], x[0] + w, y[3] + w); - if (sym & 128 > 0) - self.rect(canvas, x[1], y[3], x[1] + w, y[3] + w); -} - -fn draw_sextant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - const Sextants = packed struct(u6) { - tl: bool, - tr: bool, - ml: bool, - mr: bool, - bl: bool, - br: bool, - }; - - assert(cp >= 0x1fb00 and cp <= 0x1fb3b); - const idx = cp - 0x1fb00; - const sex: Sextants = @bitCast(@as(u6, @intCast( - idx + (idx / 0x14) + 1, - ))); - - const x_halfs = self.xHalfs(); - const y_thirds = self.yThirds(); - - if (sex.tl) self.rect(canvas, 0, 0, x_halfs[0], y_thirds[0]); - if (sex.tr) self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_thirds[0]); - if (sex.ml) self.rect(canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]); - if (sex.mr) self.rect(canvas, x_halfs[1], y_thirds[1], self.metrics.cell_width, y_thirds[2]); - if (sex.bl) self.rect(canvas, 0, y_thirds[3], x_halfs[0], self.metrics.cell_height); - if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[3], self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_octant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - assert(cp >= octant_min and cp <= octant_max); - - // Octant representation. We use the funny numeric string keys - // so its easier to parse the actual name used in the Symbols for - // Legacy Computing spec. - const Octant = packed struct(u8) { - @"1": bool = false, - @"2": bool = false, - @"3": bool = false, - @"4": bool = false, - @"5": bool = false, - @"6": bool = false, - @"7": bool = false, - @"8": bool = false, - }; - - // Parse the octant data. This is all done at comptime so this is - // static data that is embedded in the binary. - const octants_len = octant_max - octant_min + 1; - const octants: [octants_len]Octant = comptime octants: { - @setEvalBranchQuota(10_000); - - var result: [octants_len]Octant = @splat(.{}); - var i: usize = 0; - - const data = @embedFile("octants.txt"); - var it = std.mem.splitScalar(u8, data, '\n'); - while (it.next()) |line| { - // Skip comments - if (line.len == 0 or line[0] == '#') continue; - - const current = &result[i]; - i += 1; - - // Octants are in the format "BLOCK OCTANT-1235". The numbers - // at the end are keys into our packed struct. Since we're - // at comptime we can metaprogram it all. - const idx = std.mem.indexOfScalar(u8, line, '-').?; - for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true; - } - - assert(i == octants_len); - break :octants result; - }; - - const x_halfs = self.xHalfs(); - const y_quads = self.yQuads(); - const oct = octants[cp - octant_min]; - if (oct.@"1") self.rect(canvas, 0, 0, x_halfs[0], y_quads[0]); - if (oct.@"2") self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_quads[0]); - if (oct.@"3") self.rect(canvas, 0, y_quads[1], x_halfs[0], y_quads[2]); - if (oct.@"4") self.rect(canvas, x_halfs[1], y_quads[1], self.metrics.cell_width, y_quads[2]); - if (oct.@"5") self.rect(canvas, 0, y_quads[3], x_halfs[0], y_quads[4]); - if (oct.@"6") self.rect(canvas, x_halfs[1], y_quads[3], self.metrics.cell_width, y_quads[4]); - if (oct.@"7") self.rect(canvas, 0, y_quads[5], x_halfs[0], self.metrics.cell_height); - if (oct.@"8") self.rect(canvas, x_halfs[1], y_quads[5], self.metrics.cell_width, self.metrics.cell_height); -} - -/// xHalfs[0] should be used as the right edge of a left-aligned half. -/// xHalfs[1] should be used as the left edge of a right-aligned half. -fn xHalfs(self: Box) [2]u32 { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const half_width: u32 = @intFromFloat(@round(0.5 * float_width)); - return .{ half_width, self.metrics.cell_width - half_width }; -} - -/// Use these values as such: -/// yThirds[0] bottom edge of the first third. -/// yThirds[1] top edge of the second third. -/// yThirds[2] bottom edge of the second third. -/// yThirds[3] top edge of the final third. -fn yThirds(self: Box) [4]u32 { - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const one_third_height: u32 = @intFromFloat(@round(one_third * float_height)); - const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height)); - return .{ - one_third_height, - self.metrics.cell_height - two_thirds_height, - two_thirds_height, - self.metrics.cell_height - one_third_height, - }; -} - -/// Use these values as such: -/// yQuads[0] bottom edge of first quarter. -/// yQuads[1] top edge of second quarter. -/// yQuads[2] bottom edge of second quarter. -/// yQuads[3] top edge of third quarter. -/// yQuads[4] bottom edge of third quarter -/// yQuads[5] top edge of fourth quarter. -fn yQuads(self: Box) [6]u32 { - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height)); - const half_height: u32 = @intFromFloat(@round(0.50 * float_height)); - const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height)); - return .{ - quarter_height, - self.metrics.cell_height - three_quarters_height, - half_height, - self.metrics.cell_height - half_height, - three_quarters_height, - self.metrics.cell_height - quarter_height, - }; -} - -fn draw_smooth_mosaic( - self: Box, - canvas: *font.sprite.Canvas, - mosaic: SmoothMosaic, -) !void { - const y_thirds = self.yThirds(); - const top: f64 = 0.0; - // We average the edge positions for the y_thirds boundaries here - // rather than having to deal with varying alignments depending on - // the surrounding pieces. The most this will be off by is half of - // a pixel, so hopefully it's not noticeable. - const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1]))); - const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3]))); - const bottom: f64 = @floatFromInt(self.metrics.cell_height); - const left: f64 = 0.0; - const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); - const right: f64 = @floatFromInt(self.metrics.cell_width); - - var path: z2d.StaticPath(12) = .{}; - path.init(); // nodes.len = 0 - - if (mosaic.tl) path.lineTo(left, top); // +1, nodes.len = 1 - if (mosaic.ul) path.lineTo(left, upper); // +1, nodes.len = 2 - if (mosaic.ll) path.lineTo(left, lower); // +1, nodes.len = 3 - if (mosaic.bl) path.lineTo(left, bottom); // +1, nodes.len = 4 - if (mosaic.bc) path.lineTo(center, bottom); // +1, nodes.len = 5 - if (mosaic.br) path.lineTo(right, bottom); // +1, nodes.len = 6 - if (mosaic.lr) path.lineTo(right, lower); // +1, nodes.len = 7 - if (mosaic.ur) path.lineTo(right, upper); // +1, nodes.len = 8 - if (mosaic.tr) path.lineTo(right, top); // +1, nodes.len = 9 - if (mosaic.tc) path.lineTo(center, top); // +1, nodes.len = 10 - path.close(); // +2, nodes.len = 12 - - try z2d.painter.fill( - canvas.alloc, - &canvas.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); -} - -fn draw_edge_triangle( - self: Box, - canvas: *font.sprite.Canvas, - comptime edge: Edge, -) !void { - const upper: f64 = 0.0; - const middle: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_height)) / 2); - const lower: f64 = @floatFromInt(self.metrics.cell_height); - const left: f64 = 0.0; - const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); - const right: f64 = @floatFromInt(self.metrics.cell_width); - - const x0, const y0, const x1, const y1 = switch (edge) { - .top => .{ right, upper, left, upper }, - .left => .{ left, upper, left, lower }, - .bottom => .{ left, lower, right, lower }, - .right => .{ right, lower, right, upper }, - }; - - var path: z2d.StaticPath(5) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(center, middle); // +1, nodes.len = 1 - path.lineTo(x0, y0); // +1, nodes.len = 2 - path.lineTo(x1, y1); // +1, nodes.len = 3 - path.close(); // +2, nodes.len = 5 - - try z2d.painter.fill( - canvas.alloc, - &canvas.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); -} - -fn draw_arc( - self: Box, - canvas: *font.sprite.Canvas, - comptime corner: Corner, - comptime thickness: Thickness, -) !void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const center_x: f64 = @as(f64, @floatFromInt((self.metrics.cell_width -| thick_px) / 2)) + float_thick / 2; - const center_y: f64 = @as(f64, @floatFromInt((self.metrics.cell_height -| thick_px) / 2)) + float_thick / 2; - - const r = @min(float_width, float_height) / 2; - - // Fraction away from the center to place the middle control points, - const s: f64 = 0.25; - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth(float_thick); - ctx.setLineCapMode(.round); - - switch (corner) { - .tl => { - try ctx.moveTo(center_x, 0); - try ctx.lineTo(center_x, center_y - r); - try ctx.curveTo( - center_x, - center_y - s * r, - center_x - s * r, - center_y, - center_x - r, - center_y, - ); - try ctx.lineTo(0, center_y); - }, - .tr => { - try ctx.moveTo(center_x, 0); - try ctx.lineTo(center_x, center_y - r); - try ctx.curveTo( - center_x, - center_y - s * r, - center_x + s * r, - center_y, - center_x + r, - center_y, - ); - try ctx.lineTo(float_width, center_y); - }, - .bl => { - try ctx.moveTo(center_x, float_height); - try ctx.lineTo(center_x, center_y + r); - try ctx.curveTo( - center_x, - center_y + s * r, - center_x - s * r, - center_y, - center_x - r, - center_y, - ); - try ctx.lineTo(0, center_y); - }, - .br => { - try ctx.moveTo(center_x, float_height); - try ctx.lineTo(center_x, center_y + r); - try ctx.curveTo( - center_x, - center_y + s * r, - center_x + s * r, - center_y, - center_x + r, - center_y, - ); - try ctx.lineTo(float_width, center_y); - }, - } - try ctx.stroke(); -} - -fn draw_dash_horizontal( - self: Box, - canvas: *font.sprite.Canvas, - count: u8, - thick_px: u32, - desired_gap: u32, -) void { - assert(count >= 2 and count <= 4); - - // +------------+ - // | | - // | | - // | | - // | | - // | -- -- -- | - // | | - // | | - // | | - // | | - // +------------+ - // Our dashed line should be made such that when tiled horizontally - // it creates one consistent line with no uneven gap or segment sizes. - // In order to make sure this is the case, we should have half-sized - // gaps on the left and right so that it is centered properly. - - // For N dashes, there are N - 1 gaps between them, but we also have - // half-sized gaps on either side, adding up to N total gaps. - const gap_count = count; - - // We need at least 1 pixel for each gap and each dash, if we don't - // have that then we can't draw our dashed line correctly so we just - // draw a solid line and return. - if (self.metrics.cell_width < count + gap_count) { - self.hline_middle(canvas, .light); - return; - } - - // We never want the gaps to take up more than 50% of the space, - // because if they do the dashes are too small and look wrong. - const gap_width = @min(desired_gap, self.metrics.cell_width / (2 * count)); - const total_gap_width = gap_count * gap_width; - const total_dash_width = self.metrics.cell_width - total_gap_width; - const dash_width = total_dash_width / count; - const remaining = total_dash_width % count; - - assert(dash_width * count + gap_width * gap_count + remaining == self.metrics.cell_width); - - // Our dashes should be centered vertically. - const y: u32 = (self.metrics.cell_height -| thick_px) / 2; - - // We start at half a gap from the left edge, in order to center - // our dashes properly. - var x: u32 = gap_width / 2; - - // We'll distribute the extra space in to dash widths, 1px at a - // time. We prefer this to making gaps larger since that is much - // more visually obvious. - var extra: u32 = remaining; - - for (0..count) |_| { - var x1 = x + dash_width; - // We distribute left-over size in to dash widths, - // since it's less obvious there than in the gaps. - if (extra > 0) { - extra -= 1; - x1 += 1; - } - self.hline(canvas, x, x1, y, thick_px); - // Advance by the width of the dash we drew and the width - // of a gap to get the the start of the next dash. - x = x1 + gap_width; - } -} - -fn draw_dash_vertical( - self: Box, - canvas: *font.sprite.Canvas, - comptime count: u8, - thick_px: u32, - desired_gap: u32, -) void { - assert(count >= 2 and count <= 4); - - // +-----------+ - // | | | - // | | | - // | | - // | | | - // | | | - // | | - // | | | - // | | | - // | | - // +-----------+ - // Our dashed line should be made such that when tiled vertically it - // it creates one consistent line with no uneven gap or segment sizes. - // In order to make sure this is the case, we should have an extra gap - // gap at the bottom. - // - // A single full-sized extra gap is preferred to two half-sized ones for - // vertical to allow better joining to solid characters without creating - // visible half-sized gaps. Unlike horizontal, centering is a lot less - // important, visually. - - // Because of the extra gap at the bottom, there are as many gaps as - // there are dashes. - const gap_count = count; - - // We need at least 1 pixel for each gap and each dash, if we don't - // have that then we can't draw our dashed line correctly so we just - // draw a solid line and return. - if (self.metrics.cell_height < count + gap_count) { - self.vline_middle(canvas, .light); - return; - } - - // We never want the gaps to take up more than 50% of the space, - // because if they do the dashes are too small and look wrong. - const gap_height = @min(desired_gap, self.metrics.cell_height / (2 * count)); - const total_gap_height = gap_count * gap_height; - const total_dash_height = self.metrics.cell_height - total_gap_height; - const dash_height = total_dash_height / count; - const remaining = total_dash_height % count; - - assert(dash_height * count + gap_height * gap_count + remaining == self.metrics.cell_height); - - // Our dashes should be centered horizontally. - const x: u32 = (self.metrics.cell_width -| thick_px) / 2; - - // We start at the top of the cell. - var y: u32 = 0; - - // We'll distribute the extra space in to dash heights, 1px at a - // time. We prefer this to making gaps larger since that is much - // more visually obvious. - var extra: u32 = remaining; - - inline for (0..count) |_| { - var y1 = y + dash_height; - // We distribute left-over size in to dash widths, - // since it's less obvious there than in the gaps. - if (extra > 0) { - extra -= 1; - y1 += 1; - } - self.vline(canvas, y, y1, x, thick_px); - // Advance by the height of the dash we drew and the height - // of a gap to get the the start of the next dash. - y = y1 + gap_height; - } -} - -fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { - const thick_px = thickness.height(self.metrics.box_thickness); - self.vline(canvas, 0, self.metrics.cell_height, (self.metrics.cell_width -| thick_px) / 2, thick_px); -} - -fn hline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { - const thick_px = thickness.height(self.metrics.box_thickness); - self.hline(canvas, 0, self.metrics.cell_width, (self.metrics.cell_height -| thick_px) / 2, thick_px); -} - -fn vline( - self: Box, - canvas: *font.sprite.Canvas, - y1: u32, - y2: u32, - x: u32, - thickness_px: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x, 0), self.metrics.cell_width), - .y = @min(@max(y1, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x + thickness_px, 0), self.metrics.cell_width), - .y = @min(@max(y2, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -fn hline( - self: Box, - canvas: *font.sprite.Canvas, - x1: u32, - x2: u32, - y: u32, - thickness_px: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x1, 0), self.metrics.cell_width), - .y = @min(@max(y, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x2, 0), self.metrics.cell_width), - .y = @min(@max(y + thickness_px, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -fn rect( - self: Box, - canvas: *font.sprite.Canvas, - x1: u32, - y1: u32, - x2: u32, - y2: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x1, 0), self.metrics.cell_width), - .y = @min(@max(y1, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x2, 0), self.metrics.cell_width), - .y = @min(@max(y2, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -// Separated Block Quadrants from Symbols for Legacy Computing Supplement -// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 -fn draw_separated_block_quadrant(self: Box, canvas: *font.sprite.Canvas, comptime fmt: []const u8) !void { - comptime { - if (fmt.len > 4) @compileError("cannot have more than four quadrants"); - var seen = [_]bool{false} ** (std.math.maxInt(u8) + 1); - for (fmt) |c| { - if (seen[c]) @compileError("repeated quadrants not allowed"); - seen[c] = true; - switch (c) { - '1'...'4' => {}, - else => @compileError("invalid quadrant"), - } - } - } - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - const gap: f64 = @max(1.0, @as(f64, @floatFromInt(self.metrics.cell_width)) * 0.10) / 2.0; - const left: f64 = gap; - const right = @as(f64, @floatFromInt(self.metrics.cell_width)) - gap; - const top: f64 = gap; - const bottom = @as(f64, @floatFromInt(self.metrics.cell_height)) - gap; - const center_x = @as(f64, @floatFromInt(self.metrics.cell_width)) / 2.0; - const center_left = center_x - gap; - const center_right = center_x + gap; - const center_y = @as(f64, @floatFromInt(self.metrics.cell_height)) / 2.0; - const center_top = center_y - gap; - const center_bottom = center_y + gap; - - inline for (fmt) |c| { - const x1, const y1, const x2, const y2 = switch (c) { - '1' => .{ - left, top, - center_left, center_top, - }, - '2' => .{ - center_right, top, - right, center_top, - }, - '3' => .{ - left, center_bottom, - center_left, bottom, - }, - '4' => .{ - center_right, center_bottom, - right, bottom, - }, - else => unreachable, - }; - try ctx.moveTo(x1, y1); - try ctx.lineTo(x2, y1); - try ctx.lineTo(x2, y2); - try ctx.lineTo(x1, y2); - try ctx.closePath(); - } - - try ctx.fill(); -} - -test "all" { - const testing = std.testing; - const alloc = testing.allocator; - - var cp: u32 = 0x2500; - const end = 0x259f; - while (cp <= end) : (cp += 1) { - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - const face: Box = .{ - .metrics = font.Metrics.calc(.{ - .cell_width = 18.0, - .ascent = 30.0, - .descent = -6.0, - .line_gap = 0.0, - }), - }; - const glyph = try face.renderGlyph( - alloc, - &atlas_grayscale, - cp, - ); - try testing.expectEqual(@as(u32, face.metrics.cell_width), glyph.width); - try testing.expectEqual(@as(u32, face.metrics.cell_height), glyph.height); - } -} - -fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { - // Box Drawing and Block Elements. - var cp: u32 = 0x2500; - while (cp <= 0x259f) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Braille - cp = 0x2800; - while (cp <= 0x28ff) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Symbols for Legacy Computing. - cp = 0x1fb00; - while (cp <= 0x1fbef) : (cp += 1) { - switch (cp) { - // (Block Mosaics / "Sextants") - // 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠 - // 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 - // (Smooth Mosaics) - // 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆 - // 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑 - // 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 - // 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 - // 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 - // (Block Elements) - // 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 - // 🭼 🭽 🭾 🭿 🮀 🮁 - // 🮂 🮃 🮄 🮅 🮆 - // 🮇 🮈 🮉 🮊 🮋 - // (Rectangular Shade Characters) - // 🮌 🮍 🮎 🮏 🮐 🮑 🮒 - 0x1FB00...0x1FB92, - // (Rectangular Shade Characters) - // 🮔 - // (Fill Characters) - // 🮕 🮖 🮗 - // (Diagonal Fill Characters) - // 🮘 🮙 - // (Smooth Mosaics) - // 🮚 🮛 - // (Triangular Shade Characters) - // 🮜 🮝 🮞 🮟 - // (Character Cell Diagonals) - // 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 - // (Light Solid Line With Stroke) - // 🮯 - 0x1FB94...0x1FBAF, - // (Negative Terminal Characters) - // 🮽 🮾 🮿 - 0x1FBBD...0x1FBBF, - // (Block Elements) - // 🯎 🯏 - // (Character Cell Diagonals) - // 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 - // (Geometric Shapes) - // 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 - 0x1FBCE...0x1FBEF, - => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - // TODO(qwerasd): This should be in another file, but really the - // general organization of the sprite font code - // needs to be reworked eventually. - // - //           - //                     - //                     - //             - cp = 0xf5d0; - while (cp <= 0xf60d) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Symbols for Legacy Computing Supplement: Quadrants - // 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 - cp = 0x1cc21; - while (cp <= 0x1cc2f) : (cp += 1) { - switch (cp) { - 0x1cc21...0x1cc2f => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Symbols for Legacy Computing Supplement: Octants - cp = 0x1CD00; - while (cp <= 0x1CDE5) : (cp += 1) { - switch (cp) { - 0x1CD00...0x1CDE5 => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Geometric Shapes: filled and outlined corners - for ([_]u21{ '◢', '◣', '◤', '◥', '◸', '◹', '◺', '◿' }) |char| { - _ = try self.renderGlyph( - alloc, - atlas, - char, - ); - } -} - -test "render all sprites" { - // Renders all sprites to an atlas and compares - // it to a ground truth for regression testing. - - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 1024, .grayscale); - defer atlas_grayscale.deinit(alloc); - - // Even cell size and thickness (18 x 36) - try (Box{ - .metrics = font.Metrics.calc(.{ - .cell_width = 18.0, - .ascent = 30.0, - .descent = -6.0, - .line_gap = 0.0, - .underline_thickness = 2.0, - .strikethrough_thickness = 2.0, - }), - }).testRenderAll(alloc, &atlas_grayscale); - - // Odd cell size and thickness (9 x 15) - try (Box{ - .metrics = font.Metrics.calc(.{ - .cell_width = 9.0, - .ascent = 12.0, - .descent = -3.0, - .line_gap = 0.0, - .underline_thickness = 1.0, - .strikethrough_thickness = 1.0, - }), - }).testRenderAll(alloc, &atlas_grayscale); - - const ground_truth = @embedFile("./testdata/Box.ppm"); - - var stream = std.io.changeDetectionStream(ground_truth, std.io.null_writer); - try atlas_grayscale.dump(stream.writer()); - - if (stream.changeDetected()) { - log.err( - \\ - \\!! [Box.zig] Change detected from ground truth! - \\!! Dumping ./Box_test.ppm and ./Box_test_diff.ppm - \\!! Please check changes and update Box.ppm in testdata if intended. - , - .{}, - ); - - const ppm = try std.fs.cwd().createFile("Box_test.ppm", .{}); - defer ppm.close(); - try atlas_grayscale.dump(ppm.writer()); - - const diff = try std.fs.cwd().createFile("Box_test_diff.ppm", .{}); - defer diff.close(); - var writer = diff.writer(); - try writer.print( - \\P6 - \\{d} {d} - \\255 - \\ - , .{ atlas_grayscale.size, atlas_grayscale.size }); - for (ground_truth[try diff.getPos()..], atlas_grayscale.data) |a, b| { - if (a == b) { - try writer.writeByteNTimes(a / 3, 3); - } else { - try writer.writeByte(a); - try writer.writeByte(b); - try writer.writeByte(0); - } - } - } -} diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index af0c0af6a..25968e865 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -16,25 +16,154 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const wuffs = @import("wuffs"); +const z2d = @import("z2d"); const font = @import("../main.zig"); const Sprite = font.sprite.Sprite; -const Box = @import("Box.zig"); -const Powerline = @import("Powerline.zig"); -const underline = @import("underline.zig"); -const cursor = @import("cursor.zig"); + +const special = @import("draw/special.zig"); const log = std.log.scoped(.font_sprite); /// Grid metrics for rendering sprites. metrics: font.Metrics, +pub const DrawFnError = + Allocator.Error || + z2d.painter.FillError || + z2d.painter.StrokeError || + error{ + /// Something went wrong while doing math. + MathError, + }; + +/// A function that draws a glyph on the provided canvas. +pub const DrawFn = fn ( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) DrawFnError!void; + +const Range = struct { + min: u32, + max: u32, + draw: DrawFn, +}; + +/// Automatically collect ranges for functions with names +/// in the format `draw` or `draw_`. +const ranges = ranges: { + @setEvalBranchQuota(1_000_000); + + // Structs containing drawing functions for codepoint ranges. + const structs = [_]type{ + @import("draw/block.zig"), + @import("draw/box.zig"), + @import("draw/braille.zig"), + @import("draw/branch.zig"), + @import("draw/geometric_shapes.zig"), + @import("draw/powerline.zig"), + @import("draw/symbols_for_legacy_computing.zig"), + @import("draw/symbols_for_legacy_computing_supplement.zig"), + }; + + // Count how many draw fns we have + var range_count = 0; + for (structs) |s| { + for (@typeInfo(s).@"struct".decls) |decl| { + if (!@hasDecl(s, decl.name)) continue; + if (!std.mem.startsWith(u8, decl.name, "draw")) continue; + range_count += 1; + } + } + + // Make an array and collect ranges for each function. + var r: [range_count]Range = undefined; + var names: [range_count][:0]const u8 = undefined; + var i = 0; + for (structs) |s| { + for (@typeInfo(s).@"struct".decls) |decl| { + if (!@hasDecl(s, decl.name)) continue; + if (!std.mem.startsWith(u8, decl.name, "draw")) continue; + + const sep = std.mem.indexOfScalar(u8, decl.name, '_') orelse decl.name.len; + + const min = std.fmt.parseInt(u21, decl.name[4..sep], 16) catch unreachable; + + const max = if (sep == decl.name.len) + min + else + std.fmt.parseInt(u21, decl.name[sep + 1 ..], 16) catch unreachable; + + r[i] = .{ + .min = min, + .max = max, + .draw = @field(s, decl.name), + }; + names[i] = decl.name; + i += 1; + } + } + + // Sort ranges in ascending order + std.mem.sortUnstableContext(0, r.len, struct { + r: []Range, + names: [][:0]const u8, + pub fn lessThan(self: @This(), a: usize, b: usize) bool { + return self.r[a].min < self.r[b].min; + } + pub fn swap(self: @This(), a: usize, b: usize) void { + std.mem.swap(Range, &self.r[a], &self.r[b]); + std.mem.swap([:0]const u8, &self.names[a], &self.names[b]); + } + }{ + .r = &r, + .names = &names, + }); + + // Ensure there's no overlapping ranges + i = 0; + for (r, 0..) |n, k| { + if (n.min <= i) { + @compileError( + std.fmt.comptimePrint( + "Codepoint range for {s}(...) overlaps range for {s}(...), {X} <= {X} <= {X}", + .{ names[k], names[k - 1], r[k - 1].min, n.min, r[k - 1].max }, + ), + ); + } + i = n.max; + } + + break :ranges r; +}; + +fn getDrawFn(cp: u32) ?*const DrawFn { + // For special sprites (cursors, underlines, etc.) all sprites are drawn + // by functions from `Special` that share the name of the enum field. + if (cp >= Sprite.start) switch (@as(Sprite, @enumFromInt(cp))) { + inline else => |sprite| { + return @field(special, @tagName(sprite)); + }, + }; + + // Pray that the compiler is smart enough to + // turn this in to a jump table or something... + inline for (ranges) |range| { + if (cp >= range.min and cp <= range.max) return range.draw; + } + return null; +} + /// Returns true if the codepoint exists in our sprite font. pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool { - // We ignore presentation. No matter what presentation is requested - // we always provide glyphs for our codepoints. + // We ignore presentation. No matter what presentation is + // requested we always provide glyphs for our codepoints. _ = p; _ = self; - return Kind.init(cp) != null; + return getDrawFn(cp) != null; } /// Render the glyph. @@ -52,18 +181,10 @@ pub fn renderGlyph( } } - const metrics = self.metrics; - - // We adjust our sprite width based on the cell width. - const width = switch (opts.cell_width orelse 1) { - 0, 1 => metrics.cell_width, - else => |width| metrics.cell_width * width, - }; - // It should be impossible for this to be null and we assert that // in runtime safety modes but in case it is its not worth memory // corruption so we return a valid, blank glyph. - const kind = Kind.init(cp) orelse return .{ + const draw = getDrawFn(cp) orelse return .{ .width = 0, .height = 0, .offset_x = 0, @@ -73,217 +194,349 @@ pub fn renderGlyph( .advance_x = 0, }; - // Safe to ".?" because of the above assertion. - return switch (kind) { - .box => (Box{ .metrics = metrics }).renderGlyph(alloc, atlas, cp), + const metrics = self.metrics; - .underline => try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - metrics.underline_position, - metrics.underline_thickness, - ), + // We adjust our sprite width based on the cell width. + const width = switch (opts.cell_width orelse 1) { + 0, 1 => metrics.cell_width, + else => |width| metrics.cell_width * width, + }; - .strikethrough => try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - metrics.strikethrough_position, - metrics.strikethrough_thickness, - ), + const height = metrics.cell_height; - .overline => overline: { - var g = try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - 0, - metrics.overline_thickness, - ); + const padding_x = width / 4; + const padding_y = height / 4; - // We have to manually subtract the overline position - // on the rendered glyph since it can be negative. - g.offset_y -= metrics.overline_position; + // Make a canvas of the desired size + var canvas = try font.sprite.Canvas.init(alloc, width, height, padding_x, padding_y); + defer canvas.deinit(); - break :overline g; - }, + try draw(cp, &canvas, width, height, metrics); - .powerline => powerline: { - const f: Powerline = .{ - .width = metrics.cell_width, - .height = metrics.cell_height, - .thickness = metrics.box_thickness, - }; + // Write the drawing to the atlas + const region = try canvas.writeAtlas(alloc, atlas); - break :powerline try f.renderGlyph(alloc, atlas, cp); - }, - - .cursor => cursor: { - var g = try cursor.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cursor_height, - metrics.cursor_thickness, - ); - - // Cursors are drawn at their specified height - // and are centered vertically within the cell. - const cursor_height: i32 = @intCast(metrics.cursor_height); - const cell_height: i32 = @intCast(metrics.cell_height); - g.offset_y += @divTrunc(cell_height - cursor_height, 2); - - break :cursor g; - }, + return font.Glyph{ + .width = region.width, + .height = region.height, + .offset_x = @as(i32, @intCast(canvas.clip_left)) - @as(i32, @intCast(padding_x)), + .offset_y = @as(i32, @intCast(region.height +| canvas.clip_bottom)) - @as(i32, @intCast(padding_y)), + .atlas_x = region.x, + .atlas_y = region.y, + .advance_x = @floatFromInt(width), }; } -/// Kind of sprites we have. Drawing is implemented separately for each kind. -const Kind = enum { - box, - underline, - overline, - strikethrough, - powerline, - cursor, +/// Used in `testDrawRanges`, checks for diff between the provided atlas +/// and the reference file for the range, returns true if there is a diff. +fn testDiffAtlas( + alloc: Allocator, + atlas: *z2d.Surface, + path: []const u8, + i: u32, + width: u32, + height: u32, + thickness: u32, +) !bool { + // Get the file contents, we compare the PNG data first in + // order to ensure that no one smuggles arbitrary binary + // data in to the reference PNGs. + const test_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_only }); + defer test_file.close(); + const test_bytes = try test_file.readToEndAlloc( + alloc, + std.math.maxInt(usize), + ); + defer alloc.free(test_bytes); - pub fn init(cp: u32) ?Kind { - return switch (cp) { - Sprite.start...Sprite.end => switch (@as(Sprite, @enumFromInt(cp))) { - .underline, - .underline_double, - .underline_dotted, - .underline_dashed, - .underline_curly, - => .underline, + const cwd_absolute = try std.fs.cwd().realpathAlloc(alloc, "."); + defer alloc.free(cwd_absolute); - .overline, - => .overline, + // Get the reference file contents to compare. + const ref_path = try std.fmt.allocPrint( + alloc, + "./src/font/sprite/testdata/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(ref_path); + const ref_file = + std.fs.cwd().openFile(ref_path, .{ .mode = .read_only }) catch |err| { + log.err("Can't open reference file {s}: {}\n", .{ + ref_path, + err, + }); - .strikethrough, - => .strikethrough, + // Copy the test PNG in to the CWD so it isn't + // cleaned up with the rest of the tmp dir files. + const test_path = try std.fmt.allocPrint( + alloc, + "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ cwd_absolute, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(test_path); + try std.fs.copyFileAbsolute(path, test_path, .{}); - .cursor_rect, - .cursor_hollow_rect, - .cursor_bar, - => .cursor, - }, - - // == Box fonts == - - // "Box Drawing" block - // ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠ - // ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁ - // ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ - // ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ - 0x2500...0x257F, - - // "Block Elements" block - // ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟ - 0x2580...0x259F, - - // "Geometric Shapes" block - 0x25e2...0x25e5, // ◢◣◤◥ - 0x25f8...0x25fa, // ◸◹◺ - 0x25ff, // ◿ - - // "Braille" block - 0x2800...0x28FF, - - // "Symbols for Legacy Computing" block - // (Block Mosaics / "Sextants") - // 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠 - // 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 - // (Smooth Mosaics) - // 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆 - // 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑 - // 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 - // 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 - // 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 - // (Block Elements) - // 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 - // 🭼 🭽 🭾 🭿 🮀 🮁 - // 🮂 🮃 🮄 🮅 🮆 - // 🮇 🮈 🮉 🮊 🮋 - // (Rectangular Shade Characters) - // 🮌 🮍 🮎 🮏 🮐 🮑 🮒 - 0x1FB00...0x1FB92, - // (Rectangular Shade Characters) - // 🮔 - // (Fill Characters) - // 🮕 🮖 🮗 - // (Diagonal Fill Characters) - // 🮘 🮙 - // (Smooth Mosaics) - // 🮚 🮛 - // (Triangular Shade Characters) - // 🮜 🮝 🮞 🮟 - // (Character Cell Diagonals) - // 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 - // (Light Solid Line With Stroke) - // 🮯 - 0x1FB94...0x1FBAF, - // (Negative Terminal Characters) - // 🮽 🮾 🮿 - 0x1FBBD...0x1FBBF, - // (Block Elements) - // 🯎 🯏 - // (Character Cell Diagonals) - // 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 - // (Geometric Shapes) - // 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 - 0x1FBCE...0x1FBEF, - // (Octants) - 0x1CD00...0x1CDE5, - => .box, - - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - //           - //                     - //                     - //             - 0xF5D0...0xF60D => .box, - - // Separated Block Quadrants from Symbols for Legacy Computing Supplement - // 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 - 0x1CC21...0x1CC2F => .box, - - // Powerline fonts - 0xE0B0, - 0xE0B1, - 0xE0B3, - 0xE0B4, - 0xE0B6, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - 0xE0D2, - 0xE0D4, - => .powerline, - - else => null, + return true; }; + defer ref_file.close(); + const ref_bytes = try ref_file.readToEndAlloc( + alloc, + std.math.maxInt(usize), + ); + defer alloc.free(ref_bytes); + + // Do our PNG bytes comparison, if it's the same then we can + // move on, otherwise we'll decode the reference file and do + // a pixel-for-pixel diff. + if (std.mem.eql(u8, test_bytes, ref_bytes)) return false; + + // Copy the test PNG in to the CWD so it isn't + // cleaned up with the rest of the tmp dir files. + const test_path = try std.fmt.allocPrint( + alloc, + "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ cwd_absolute, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(test_path); + try std.fs.copyFileAbsolute(path, test_path, .{}); + + // Use wuffs to decode the reference PNG to raw pixels. + // These will be RGBA, so when diffing we can just compare + // every fourth byte. + const ref_rgba = try wuffs.png.decode(alloc, ref_bytes); + defer alloc.free(ref_rgba.data); + + assert(ref_rgba.width == atlas.getWidth()); + assert(ref_rgba.height == atlas.getHeight()); + + // We'll make a visual representation of the diff using + // red for removed pixels and green for added. We make + // a z2d surface for that here. + var diff = try z2d.Surface.init( + .image_surface_rgb, + alloc, + atlas.getWidth(), + atlas.getHeight(), + ); + defer diff.deinit(alloc); + const diff_pix = diff.image_surface_rgb.buf; + + const test_gray = std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf); + + var differs: bool = false; + for (0..test_gray.len) |j| { + const t = test_gray[j]; + const r = ref_rgba.data[j * 4]; + if (t == r) { + // If the pixels match, write it as a faded gray. + diff_pix[j].r = t / 3; + diff_pix[j].g = t / 3; + diff_pix[j].b = t / 3; + } else { + differs = true; + // Otherwise put the reference value in the red + // channel and the new value in the green channel. + diff_pix[j].r = r; + diff_pix[j].g = t; + } } -}; + + // If the PNG data differs but not the raw pixels, that's + // a big red flag, since it could mean someone is trying to + // smuggle binary data in to the test files. + if (!differs) { + log.err( + "!!! Test PNG data does not match reference, but pixels do match! " ++ + "Either z2d's PNG exporter changed or someone is " ++ + "trying to smuggle binary data in the test files!\n" ++ + "test={s}, reference={s}", + .{ test_path, ref_path }, + ); + return true; + } + + // Drop the diff image as a PNG in the cwd. + const diff_path = try std.fmt.allocPrint( + alloc, + "./sprite_face_diff-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(diff_path); + try z2d.png_exporter.writeToPNGFile(diff, diff_path, .{}); + log.err( + "One or more glyphs differ from reference file in range U+{X}...U+{X}! " ++ + "test={s}, reference={s}, diff={s}", + .{ i, i + 0xFF, test_path, ref_path, diff_path }, + ); + + return true; +} + +/// Draws all ranges in to a set of 16x16 glyph atlases, checks for regressions +/// against reference files, logs errors and exposes a diff for any difference +/// between the reference and test atlas. +/// +/// Returns true if there was a diff. +fn testDrawRanges( + width: u32, + ascent: u32, + descent: u32, + thickness: u32, +) !bool { + const testing = std.testing; + const alloc = testing.allocator; + + const metrics: font.Metrics = .calc(.{ + .cell_width = @floatFromInt(width), + .ascent = @floatFromInt(ascent), + .descent = -@as(f64, @floatFromInt(descent)), + .line_gap = 0.0, + .underline_thickness = @floatFromInt(thickness), + .strikethrough_thickness = @floatFromInt(thickness), + }); + + const height = ascent + descent; + + const padding_x = width / 4; + const padding_y = height / 4; + + // Canvas to draw glyphs on, we'll re-use this for all glyphs. + var canvas = try font.sprite.Canvas.init( + alloc, + width, + height, + padding_x, + padding_y, + ); + defer canvas.deinit(); + + // We render glyphs in batches of 256, which we copy (including padding) to + // a 16 by 16 surface to be compared with the reference file for that range. + const stride_x = width + 2 * padding_x; + const stride_y = height + 2 * padding_y; + var atlas = try z2d.Surface.init( + .image_surface_alpha8, + alloc, + @intCast(stride_x * 16), + @intCast(stride_y * 16), + ); + defer atlas.deinit(alloc); + + var i: u32 = std.mem.alignBackward(u32, ranges[0].min, 0x100); + + // Try to make the sprite_face_test folder if it doesn't already exist. + var dir = testing.tmpDir(.{}); + defer dir.cleanup(); + const tmp_dir = try dir.dir.realpathAlloc(alloc, "."); + defer alloc.free(tmp_dir); + + // We set this to true if we have any fails so we can + // return an error after we're done comparing all glyphs. + var fail: bool = false; + + inline for (ranges) |range| { + for (range.min..range.max + 1) |cp| { + // If we've moved to a new batch of 256, check the + // current one and clear the surface for the next one. + if (cp - i >= 0x100) { + // Export to our tmp dir. + const path = try std.fmt.allocPrint( + alloc, + "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ tmp_dir, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(path); + try z2d.png_exporter.writeToPNGFile(atlas, path, .{}); + + if (try testDiffAtlas( + alloc, + &atlas, + path, + i, + width, + height, + thickness, + )) fail = true; + + i = std.mem.alignBackward(u32, @intCast(cp), 0x100); + @memset(std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf), 0); + } + + try getDrawFn(@intCast(cp)).?( + @intCast(cp), + &canvas, + width, + height, + metrics, + ); + canvas.clearClippingRegions(); + atlas.composite( + &canvas.sfc, + .src, + @intCast(stride_x * ((cp - i) % 16)), + @intCast(stride_y * ((cp - i) / 16)), + .{}, + ); + @memset(std.mem.sliceAsBytes(canvas.sfc.image_surface_alpha8.buf), 0); + canvas.clip_top = 0; + canvas.clip_left = 0; + canvas.clip_right = 0; + canvas.clip_bottom = 0; + } + } + + const path = try std.fmt.allocPrint( + alloc, + "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ tmp_dir, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(path); + try z2d.png_exporter.writeToPNGFile(atlas, path, .{}); + if (try testDiffAtlas( + alloc, + &atlas, + path, + i, + width, + height, + thickness, + )) fail = true; + + return fail; +} + +test "sprite face render all sprites" { + // Renders all sprites to an atlas and compares + // it to a ground truth for regression testing. + + var diff: bool = false; + + // testDrawRanges(width, ascent, descent, thickness): + // + // We compare 4 different sets of metrics; + // - even cell size / even thickness + // - even cell size / odd thickness + // - odd cell size / even thickness + // - odd cell size / odd thickness + // (Also a decreasing range of sizes.) + if (try testDrawRanges(18, 30, 6, 4)) diff = true; + if (try testDrawRanges(12, 20, 4, 3)) diff = true; + if (try testDrawRanges(11, 19, 2, 2)) diff = true; + if (try testDrawRanges(9, 15, 2, 1)) diff = true; + + try std.testing.expect(!diff); // There should be no diffs from reference. +} + +// test "sprite face print all sprites" { +// std.debug.print("\n\n", .{}); +// inline for (ranges) |range| { +// for (range.min..range.max + 1) |cp| { +// std.debug.print("{u}", .{ @as(u21, @intCast(cp)) }); +// } +// } +// std.debug.print("\n\n", .{}); +// } test { - @import("std").testing.refAllDecls(@This()); + std.testing.refAllDecls(@This()); } diff --git a/src/font/sprite/Powerline.zig b/src/font/sprite/Powerline.zig deleted file mode 100644 index eaa7554b1..000000000 --- a/src/font/sprite/Powerline.zig +++ /dev/null @@ -1,564 +0,0 @@ -//! This file contains functions for drawing certain characters from Powerline -//! Extra (https://github.com/ryanoasis/powerline-extra-symbols). These -//! characters are similarly to box-drawing characters (see Box.zig), so the -//! logic will be mainly the same, just with a much reduced character set. -//! -//! Note that this is not the complete list of Powerline glyphs that may be -//! needed, so this may grow to add other glyphs from the set. -const Powerline = @This(); - -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const font = @import("../main.zig"); -const Quad = @import("canvas.zig").Quad; - -const log = std.log.scoped(.powerline_font); - -/// The cell width and height because the boxes are fit perfectly -/// into a cell so that they all properly connect with zero spacing. -width: u32, -height: u32, - -/// Base thickness value for glyphs that are not completely solid (backslashes, -/// thin half-circles, etc). If you want to do any DPI scaling, it is expected -/// to be done earlier. -/// -/// TODO: this and Thickness are currently unused but will be when the -/// aforementioned glyphs are added. -thickness: u32, - -/// The thickness of a line. -const Thickness = enum { - super_light, - light, - heavy, - - /// Calculate the real height of a line based on its thickness - /// and a base thickness value. The base thickness value is expected - /// to be in pixels. - fn height(self: Thickness, base: u32) u32 { - return switch (self) { - .super_light => @max(base / 2, 1), - .light => base, - .heavy => base * 2, - }; - } -}; - -inline fn sq(x: anytype) @TypeOf(x) { - return x * x; -} - -pub fn renderGlyph( - self: Powerline, - alloc: Allocator, - atlas: *font.Atlas, - cp: u32, -) !font.Glyph { - // Create the canvas we'll use to draw - var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height); - defer canvas.deinit(); - - // Perform the actual drawing - try self.draw(alloc, &canvas, cp); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - // Our coordinates start at the BOTTOM for our renderers so we have to - // specify an offset of the full height because we rendered a full size - // cell. - const offset_y = @as(i32, @intCast(self.height)); - - return font.Glyph{ - .width = self.width, - .height = self.height, - .offset_x = 0, - .offset_y = offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(self.width), - }; -} - -fn draw(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - switch (cp) { - // Hard dividers and triangles - 0xE0B0, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - => try self.draw_wedge_triangle(canvas, cp), - - // Soft Dividers - 0xE0B1, - 0xE0B3, - => try self.draw_chevron(canvas, cp), - - // Half-circles - 0xE0B4, - 0xE0B6, - => try self.draw_half_circle(alloc, canvas, cp), - - // Mirrored top-down trapezoids - 0xE0D2, - 0xE0D4, - => try self.draw_trapezoid_top_bottom(canvas, cp), - - else => return error.InvalidCodepoint, - } -} - -fn draw_chevron(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const width = self.width; - const height = self.height; - - var p1_x: u32 = 0; - var p1_y: u32 = 0; - var p2_x: u32 = 0; - var p2_y: u32 = 0; - var p3_x: u32 = 0; - var p3_y: u32 = 0; - - switch (cp) { - 0xE0B1 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height / 2; - p3_x = 0; - p3_y = height; - }, - 0xE0B3 => { - p1_x = width; - p1_y = 0; - p2_x = 0; - p2_y = height / 2; - p3_x = width; - p3_y = height; - }, - - else => unreachable, - } - - try canvas.triangle_outline(.{ - .p0 = .{ .x = @floatFromInt(p1_x), .y = @floatFromInt(p1_y) }, - .p1 = .{ .x = @floatFromInt(p2_x), .y = @floatFromInt(p2_y) }, - .p2 = .{ .x = @floatFromInt(p3_x), .y = @floatFromInt(p3_y) }, - }, @floatFromInt(Thickness.light.height(self.thickness)), .on); -} - -fn draw_wedge_triangle(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const width = self.width; - const height = self.height; - - var p1_x: u32 = 0; - var p2_x: u32 = 0; - var p3_x: u32 = 0; - var p1_y: u32 = 0; - var p2_y: u32 = 0; - var p3_y: u32 = 0; - - switch (cp) { - 0xE0B0 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height / 2; - p3_x = 0; - p3_y = height; - }, - - 0xE0B2 => { - p1_x = width; - p1_y = 0; - p2_x = 0; - p2_y = height / 2; - p3_x = width; - p3_y = height; - }, - - 0xE0B8 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height; - p3_x = 0; - p3_y = height; - }, - - 0xE0BA => { - p1_x = width; - p1_y = 0; - p2_x = width; - p2_y = height; - p3_x = 0; - p3_y = height; - }, - - 0xE0BC => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = 0; - p3_x = 0; - p3_y = height; - }, - - 0xE0BE => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = 0; - p3_x = width; - p3_y = height; - }, - - else => unreachable, - } - - try canvas.triangle(.{ - .p0 = .{ .x = @floatFromInt(p1_x), .y = @floatFromInt(p1_y) }, - .p1 = .{ .x = @floatFromInt(p2_x), .y = @floatFromInt(p2_y) }, - .p2 = .{ .x = @floatFromInt(p3_x), .y = @floatFromInt(p3_y) }, - }, .on); -} - -fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - const supersample = 4; - - // We make a canvas big enough for the whole circle, with the supersample - // applied. - const width = self.width * 2 * supersample; - const height = self.height * supersample; - - // We set a minimum super-sampled canvas to assert on. The minimum cell - // size is 1x3px, and this looked safe in empirical testing. - std.debug.assert(width >= 8); // 1 * 2 * 4 - std.debug.assert(height >= 12); // 3 * 4 - - const center_x = width / 2 - 1; - const center_y = height / 2 - 1; - - // Our radii. We're technically drawing an ellipse here to ensure that this - // works for fonts with different aspect ratios than a typical 2:1 H*W, e.g. - // Iosevka (which is around 2.6:1). - const radius_x = width / 2 - 1; // This gives us a small margin for smoothing - const radius_y = height / 2; - - // Pre-allocate a matrix to plot the points on. - const cap = height * width; - var points = try alloc.alloc(u8, cap); - defer alloc.free(points); - @memset(points, 0); - - { - // This is a midpoint ellipse algorithm, similar to a midpoint circle - // algorithm in that we only draw the octants we need and then reflect - // the result across the other axes. Since an ellipse has two radii, we - // need to calculate two octants instead of one. There are variations - // on the algorithm and you can find many examples online. This one - // does use some floating point math in calculating the decision - // parameter, but I've found it clear in its implementation and it does - // not require adjustment for integer error. - // - // This algorithm has undergone some iterations, so the following - // references might be helpful for understanding: - // - // * "Drawing a circle, point by point, without floating point - // support" (Dennis Yurichev, - // https://yurichev.com/news/20220322_circle/), which describes the - // midpoint circle algorithm and implementation we initially adapted - // here. - // - // * "Ellipse-Generating Algorithms" (RTU Latvia, - // https://daugavpils.rtu.lv/wp-content/uploads/sites/34/2020/11/LEC_3.pdf), - // which was used to further adapt the algorithm for ellipses. - // - // * "An Effective Approach to Minimize Error in Midpoint Ellipse - // Drawing Algorithm" (Dr. M. Javed Idrisi, Aayesha Ashraf, - // https://arxiv.org/abs/2103.04033), which includes a synopsis of - // the history of ellipse drawing algorithms, and further references. - - // Declare some casted constants for use in various calculations below - const rx: i32 = @intCast(radius_x); - const ry: i32 = @intCast(radius_y); - const rxf: f64 = @floatFromInt(radius_x); - const ryf: f64 = @floatFromInt(radius_y); - const cx: i32 = @intCast(center_x); - const cy: i32 = @intCast(center_y); - - // Our plotting x and y - var x: i32 = 0; - var y: i32 = @intCast(radius_y); - - // Decision parameter, initialized for region 1 - var dparam: f64 = sq(ryf) - sq(rxf) * ryf + sq(rxf) * 0.25; - - // Region 1 - while (2 * sq(ry) * x < 2 * sq(rx) * y) { - // Right side - const x1 = @max(0, cx + x); - const y1 = @max(0, cy + y); - const x2 = @max(0, cx + x); - const y2 = @max(0, cy - y); - - // Left side - const x3 = @max(0, cx - x); - const y3 = @max(0, cy + y); - const x4 = @max(0, cx - x); - const y4 = @max(0, cy - y); - - // Points - const p1 = y1 * width + x1; - const p2 = y2 * width + x2; - const p3 = y3 * width + x3; - const p4 = y4 * width + x4; - - // Set the points in the matrix, ignore any out of bounds - if (p1 < cap) points[p1] = 0xFF; - if (p2 < cap) points[p2] = 0xFF; - if (p3 < cap) points[p3] = 0xFF; - if (p4 < cap) points[p4] = 0xFF; - - // Calculate next pixels based on midpoint bounds - x += 1; - if (dparam < 0) { - const xf: f64 = @floatFromInt(x); - dparam += 2 * sq(ryf) * xf + sq(ryf); - } else { - y -= 1; - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam += 2 * sq(ryf) * xf - 2 * sq(rxf) * yf + sq(ryf); - } - } - - // Region 2 - { - // Reset our decision parameter for region 2 - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam = sq(ryf) * sq(xf + 0.5) + sq(rxf) * sq(yf - 1) - sq(rxf) * sq(ryf); - } - while (y >= 0) { - // Right side - const x1 = @max(0, cx + x); - const y1 = @max(0, cy + y); - const x2 = @max(0, cx + x); - const y2 = @max(0, cy - y); - - // Left side - const x3 = @max(0, cx - x); - const y3 = @max(0, cy + y); - const x4 = @max(0, cx - x); - const y4 = @max(0, cy - y); - - // Points - const p1 = y1 * width + x1; - const p2 = y2 * width + x2; - const p3 = y3 * width + x3; - const p4 = y4 * width + x4; - - // Set the points in the matrix, ignore any out of bounds - if (p1 < cap) points[p1] = 0xFF; - if (p2 < cap) points[p2] = 0xFF; - if (p3 < cap) points[p3] = 0xFF; - if (p4 < cap) points[p4] = 0xFF; - - // Calculate next pixels based on midpoint bounds - y -= 1; - if (dparam > 0) { - const yf: f64 = @floatFromInt(y); - dparam -= 2 * sq(rxf) * yf + sq(rxf); - } else { - x += 1; - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam += 2 * sq(ryf) * xf - 2 * sq(rxf) * yf + sq(rxf); - } - } - } - - // Fill - { - const u_height: u32 = @intCast(height); - const u_width: u32 = @intCast(width); - - for (0..u_height) |yf| { - for (0..u_width) |left| { - // Count forward from the left to the first filled pixel - if (points[yf * u_width + left] != 0) { - // Count back to our left point from the right to the first - // filled pixel on the other side. - var right: usize = u_width - 1; - while (right > left) : (right -= 1) { - if (points[yf * u_width + right] != 0) { - break; - } - } - - // Start filling 1 index after the left and go until we hit - // the right; this will be a no-op if the line length is < - // 3 as both left and right will have already been filled. - const start = yf * u_width + left; - const end = yf * u_width + right; - if (end - start >= 3) { - for (start + 1..end) |idx| { - points[idx] = 0xFF; - } - } - } - } - } - } - - // Now that we have our points, we need to "split" our matrix on the x - // axis for the downsample. - { - // The side of the circle we're drawing - const offset_j: u32 = if (cp == 0xE0B4) center_x + 1 else 0; - - for (0..self.height) |r| { - for (0..self.width) |c| { - var total: u32 = 0; - for (0..supersample) |i| { - for (0..supersample) |j| { - const idx = (r * supersample + i) * width + (c * supersample + j + offset_j); - total += points[idx]; - } - } - - const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF))); - canvas.rect( - .{ - .x = @intCast(c), - .y = @intCast(r), - .width = 1, - .height = 1, - }, - @as(font.sprite.Color, @enumFromInt(average)), - ); - } - } - } -} - -fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const t_top: Quad(f64) = if (cp == 0xE0D4) - .{ - .p0 = .{ - .x = 0, - .y = 0, - }, - .p1 = .{ - .x = @floatFromInt(self.width - self.width / 3), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = 0, - }, - } - else - .{ - .p0 = .{ - .x = 0, - .y = 0, - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p2 = .{ - .x = @floatFromInt(self.width / 3), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = 0, - }, - }; - - const t_bottom: Quad(f64) = if (cp == 0xE0D4) - .{ - .p0 = .{ - .x = @floatFromInt(self.width - self.width / 3), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - } - else - .{ - .p0 = .{ - .x = 0, - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height), - }, - .p3 = .{ - .x = @floatFromInt(self.width / 3), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - }; - - try canvas.quad(t_top, .on); - try canvas.quad(t_bottom, .on); -} - -test "all" { - const testing = std.testing; - const alloc = testing.allocator; - - const cps = [_]u32{ - 0xE0B0, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - 0xE0B4, - 0xE0B6, - 0xE0D2, - 0xE0D4, - 0xE0B1, - 0xE0B3, - }; - for (cps) |cp| { - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - const face: Powerline = .{ .width = 18, .height = 36, .thickness = 2 }; - const glyph = try face.renderGlyph( - alloc, - &atlas_grayscale, - cp, - ); - try testing.expectEqual(@as(u32, face.width), glyph.width); - try testing.expectEqual(@as(u32, face.height), glyph.height); - } -} diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index a5ca7b290..b981449bc 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -81,19 +81,39 @@ pub const Canvas = struct { /// The underlying z2d surface. sfc: z2d.Surface, + padding_x: u32, + padding_y: u32, + + clip_top: u32 = 0, + clip_left: u32 = 0, + clip_right: u32 = 0, + clip_bottom: u32 = 0, + alloc: Allocator, - pub fn init(alloc: Allocator, width: u32, height: u32) !Canvas { + pub fn init( + alloc: Allocator, + width: u32, + height: u32, + padding_x: u32, + padding_y: u32, + ) !Canvas { // Create the surface we'll be using. + // We add padding to both sides (hence `2 *`) const sfc = try z2d.Surface.initPixel( .{ .alpha8 = .{ .a = 0 } }, alloc, - @intCast(width), - @intCast(height), + @intCast(width + 2 * padding_x), + @intCast(height + 2 * padding_y), ); errdefer sfc.deinit(alloc); - return .{ .sfc = sfc, .alloc = alloc }; + return .{ + .sfc = sfc, + .padding_x = padding_x, + .padding_y = padding_y, + .alloc = alloc, + }; } pub fn deinit(self: *Canvas) void { @@ -109,30 +129,33 @@ pub const Canvas = struct { ) (Allocator.Error || font.Atlas.Error)!font.Atlas.Region { assert(atlas.format == .grayscale); - const width = @as(u32, @intCast(self.sfc.getWidth())); - const height = @as(u32, @intCast(self.sfc.getHeight())); + self.trim(); + + const sfc_width: u32 = @intCast(self.sfc.getWidth()); + const sfc_height: u32 = @intCast(self.sfc.getHeight()); + + // Subtract our clip margins from the + // width and height to get region size. + const region_width = sfc_width -| self.clip_left -| self.clip_right; + const region_height = sfc_height -| self.clip_top -| self.clip_bottom; // Allocate our texture atlas region const region = region: { - // We need to add a 1px padding to the font so that we don't - // get fuzzy issues when blending textures. - const padding = 1; - - // Get the full padded region + // Reserve a region with a 1px margin on the bottom and right edges + // so that we can avoid interpolation between adjacent glyphs during + // texture sampling. var region = try atlas.reserve( alloc, - width + (padding * 2), // * 2 because left+right - height + (padding * 2), // * 2 because top+bottom + region_width + 1, + region_height + 1, ); - // Modify the region so that we remove the padding so that - // we write to the non-zero location. The data in an Altlas - // is always initialized to zero (Atlas.clear) so we don't - // need to worry about zero-ing that. - region.x += padding; - region.y += padding; - region.width -= padding * 2; - region.height -= padding * 2; + // Modify the region to remove the margin so that we write to the + // non-zero location. The data in an Altlas is always initialized + // to zero (Atlas.clear) so we don't need to worry about zero-ing + // that. + region.width -= 1; + region.height -= 1; break :region region; }; @@ -140,38 +163,138 @@ pub const Canvas = struct { const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf); // Write the glyph information into the atlas - assert(region.width == width); - assert(region.height == height); - atlas.set(region, buffer); + assert(region.width == region_width); + assert(region.height == region_height); + atlas.setFromLarger( + region, + buffer, + sfc_width, + self.clip_left, + self.clip_top, + ); } return region; } + // Adjust clip boundaries to trim off any fully transparent rows or columns. + // This circumvents abstractions from z2d so that it can be performant. + fn trim(self: *Canvas) void { + const width: u32 = @intCast(self.sfc.getWidth()); + const height: u32 = @intCast(self.sfc.getHeight()); + + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + + top: while (self.clip_top < height - self.clip_bottom) { + const y = self.clip_top; + const x0 = self.clip_left; + const x1 = width - self.clip_right; + for (buf[y * width ..][x0..x1]) |v| { + if (v != 0) break :top; + } + self.clip_top += 1; + } + + bottom: while (self.clip_bottom < height - self.clip_top) { + const y = height - self.clip_bottom -| 1; + const x0 = self.clip_left; + const x1 = width - self.clip_right; + for (buf[y * width ..][x0..x1]) |v| { + if (v != 0) break :bottom; + } + self.clip_bottom += 1; + } + + left: while (self.clip_left < width - self.clip_right) { + const x = self.clip_left; + const y0 = self.clip_top; + const y1 = height - self.clip_bottom; + for (y0..y1) |y| { + if (buf[y * width + x] != 0) break :left; + } + self.clip_left += 1; + } + + right: while (self.clip_right < width - self.clip_left) { + const x = width - self.clip_right -| 1; + const y0 = self.clip_top; + const y1 = height - self.clip_bottom; + for (y0..y1) |y| { + if (buf[y * width + x] != 0) break :right; + } + self.clip_right += 1; + } + } + + /// Only really useful for test purposes, since the clipping region is + /// automatically excluded when writing to an atlas with `writeAtlas`. + pub fn clearClippingRegions(self: *Canvas) void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + + for (0..height) |y| { + for (0..self.clip_left) |x| { + buf[y * width + x] = 0; + } + } + + for (0..height) |y| { + for (width - self.clip_right..width) |x| { + buf[y * width + x] = 0; + } + } + + for (0..self.clip_top) |y| { + for (0..width) |x| { + buf[y * width + x] = 0; + } + } + + for (height - self.clip_bottom..height) |y| { + for (0..width) |x| { + buf[y * width + x] = 0; + } + } + } + + /// Return a transformation representing the translation for our padding. + pub fn transformation(self: Canvas) z2d.Transformation { + return .{ + .ax = 1, + .by = 0, + .cx = 0, + .dy = 1, + .tx = @as(f64, @floatFromInt(self.padding_x)), + .ty = @as(f64, @floatFromInt(self.padding_y)), + }; + } + /// Acquires a z2d drawing context, caller MUST deinit context. pub fn getContext(self: *Canvas) z2d.Context { - return .init(self.alloc, &self.sfc); + var ctx = z2d.Context.init(self.alloc, &self.sfc); + // Offset by our padding to keep + // coordinates relative to the cell. + ctx.setTransformation(self.transformation()); + return ctx; } /// Draw and fill a single pixel - pub fn pixel(self: *Canvas, x: u32, y: u32, color: Color) void { + pub fn pixel(self: *Canvas, x: i32, y: i32, color: Color) void { self.sfc.putPixel( - @intCast(x), - @intCast(y), + x + @as(i32, @intCast(self.padding_x)), + y + @as(i32, @intCast(self.padding_y)), .{ .alpha8 = .{ .a = @intFromEnum(color) } }, ); } /// Draw and fill a rectangle. This is the main primitive for drawing /// lines as well (which are just generally skinny rectangles...) - pub fn rect(self: *Canvas, v: Rect(u32), color: Color) void { - const x0 = v.x; - const x1 = v.x + v.width; - const y0 = v.y; - const y1 = v.y + v.height; - - for (y0..y1) |y| { - for (x0..x1) |x| { + pub fn rect(self: *Canvas, v: Rect(i32), color: Color) void { + var y = v.y; + while (y < v.y + v.height) : (y += 1) { + var x = v.x; + while (x < v.x + v.width) : (x += 1) { self.pixel( @intCast(x), @intCast(y), @@ -181,96 +304,226 @@ pub const Canvas = struct { } } + /// Convenience wrapper for `Canvas.rect` + pub fn box( + self: *Canvas, + x0: i32, + y0: i32, + x1: i32, + y1: i32, + color: Color, + ) void { + self.rect((Box(i32){ + .p0 = .{ .x = x0, .y = y0 }, + .p1 = .{ .x = x1, .y = y1 }, + }).rect(), color); + } + /// Draw and fill a quad. pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void { - var path: z2d.StaticPath(6) = .{}; - path.init(); // nodes.len = 0 - + var path = self.staticPath(6); // nodes.len = 0 path.moveTo(q.p0.x, q.p0.y); // +1, nodes.len = 1 path.lineTo(q.p1.x, q.p1.y); // +1, nodes.len = 2 path.lineTo(q.p2.x, q.p2.y); // +1, nodes.len = 3 path.lineTo(q.p3.x, q.p3.y); // +1, nodes.len = 4 path.close(); // +2, nodes.len = 6 - - try z2d.painter.fill( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); + try self.fillPath(path.wrapped_path, .{}, color); } /// Draw and fill a triangle. pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void { - var path: z2d.StaticPath(5) = .{}; - path.init(); // nodes.len = 0 - + var path = self.staticPath(5); // nodes.len = 0 path.moveTo(t.p0.x, t.p0.y); // +1, nodes.len = 1 path.lineTo(t.p1.x, t.p1.y); // +1, nodes.len = 2 path.lineTo(t.p2.x, t.p2.y); // +1, nodes.len = 3 path.close(); // +2, nodes.len = 5 + try self.fillPath(path.wrapped_path, .{}, color); + } + /// Stroke a line. + pub fn line( + self: *Canvas, + l: Line(f64), + thickness: f64, + color: Color, + ) !void { + var path = self.staticPath(2); // nodes.len = 0 + path.moveTo(l.p0.x, l.p0.y); // +1, nodes.len = 1 + path.lineTo(l.p1.x, l.p1.y); // +1, nodes.len = 2 + try self.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = thickness, + }, + color, + ); + } + + /// Create a static path of the provided len and initialize it. + /// Use this function instead of making the path manually since + /// it ensures that the transform is applied. + pub inline fn staticPath( + self: *Canvas, + comptime len: usize, + ) z2d.StaticPath(len) { + var path: z2d.StaticPath(len) = .{}; + path.init(); + path.wrapped_path.transformation = self.transformation(); + return path; + } + + /// Stroke a z2d path. + pub fn strokePath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.StrokeOpts, + color: Color, + ) z2d.painter.StrokeError!void { + try z2d.painter.stroke( + self.alloc, + &self.sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, + } }, + path.nodes.items, + opts, + ); + } + + /// Do an inner stroke on a z2d path, right now this involves a pretty + /// heavy workaround that uses two extra surfaces; in the future, z2d + /// should add inner and outer strokes natively. + pub fn innerStrokePath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.StrokeOpts, + color: Color, + ) (z2d.painter.StrokeError || z2d.painter.FillError)!void { + // On one surface we fill the shape, this will be a mask we + // multiply with the double-width stroke so that only the + // part inside is used. + var fill_sfc: z2d.Surface = try .init( + .image_surface_alpha8, + self.alloc, + self.sfc.getWidth(), + self.sfc.getHeight(), + ); + defer fill_sfc.deinit(self.alloc); + + // On the other we'll do the double width stroke. + var stroke_sfc: z2d.Surface = try .init( + .image_surface_alpha8, + self.alloc, + self.sfc.getWidth(), + self.sfc.getHeight(), + ); + defer stroke_sfc.deinit(self.alloc); + + // Make a closed version of the path for our fill, so + // that we can support open paths for inner stroke. + var closed_path = path; + closed_path.nodes = try path.nodes.clone(self.alloc); + defer closed_path.deinit(self.alloc); + try closed_path.close(self.alloc); + + // Fill the shape in white to the fill surface, we use + // white because this is a mask that we'll multiply with + // the stroke, we want everything inside to be the stroke + // color. + try z2d.painter.fill( + self.alloc, + &fill_sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = 255 } }, + } }, + closed_path.nodes.items, + .{}, + ); + + // Stroke the shape with double the desired width. + var mut_opts = opts; + mut_opts.line_width *= 2; + try z2d.painter.stroke( + self.alloc, + &stroke_sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, + } }, + path.nodes.items, + mut_opts, + ); + + // We multiply the stroke sfc on to the fill surface. + // The z2d composite operation doesn't seem to work for + // this with alpha8 surfaces, so we have to do it manually. + for ( + std.mem.sliceAsBytes(fill_sfc.image_surface_alpha8.buf), + std.mem.sliceAsBytes(stroke_sfc.image_surface_alpha8.buf), + ) |*d, s| { + d.* = @intFromFloat(@round( + 255.0 * + (@as(f64, @floatFromInt(s)) / 255.0) * + (@as(f64, @floatFromInt(d.*)) / 255.0), + )); + } + + // Then we composite the result on to the main surface. + self.sfc.composite(&fill_sfc, .src_over, 0, 0, .{}); + } + + /// Fill a z2d path. + pub fn fillPath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.FillOpts, + color: Color, + ) z2d.painter.FillError!void { try z2d.painter.fill( self.alloc, &self.sfc, &.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, } }, - path.wrapped_path.nodes.items, - .{}, - ); - } - - pub fn triangle_outline(self: *Canvas, t: Triangle(f64), thickness: f64, color: Color) !void { - var path: z2d.StaticPath(3) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(t.p0.x, t.p0.y); // +1, nodes.len = 1 - path.lineTo(t.p1.x, t.p1.y); // +1, nodes.len = 2 - path.lineTo(t.p2.x, t.p2.y); // +1, nodes.len = 3 - - try z2d.painter.stroke( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{ - .line_cap_mode = .round, - .line_width = thickness, - }, - ); - } - - /// Stroke a line. - pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void { - var path: z2d.StaticPath(2) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(l.p0.x, l.p0.y); // +1, nodes.len = 1 - path.lineTo(l.p1.x, l.p1.y); // +1, nodes.len = 2 - - try z2d.painter.stroke( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{ - .line_cap_mode = .round, - .line_width = thickness, - }, + path.nodes.items, + opts, ); } + /// Invert all pixels on the canvas. pub fn invert(self: *Canvas) void { for (std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf)) |*v| { v.* = 255 - v.*; } } + + /// Mirror the canvas horizontally. + pub fn flipHorizontal(self: *Canvas) Allocator.Error!void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const clone = try self.alloc.dupe(u8, buf); + defer self.alloc.free(clone); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + for (0..height) |y| { + for (0..width) |x| { + buf[y * width + x] = clone[y * width + width - x - 1]; + } + } + std.mem.swap(u32, &self.clip_left, &self.clip_right); + } + + /// Mirror the canvas vertically. + pub fn flipVertical(self: *Canvas) Allocator.Error!void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const clone = try self.alloc.dupe(u8, buf); + defer self.alloc.free(clone); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + for (0..height) |y| { + for (0..width) |x| { + buf[y * width + x] = clone[(height - y - 1) * width + x]; + } + } + std.mem.swap(u32, &self.clip_top, &self.clip_bottom); + } }; diff --git a/src/font/sprite/cursor.zig b/src/font/sprite/cursor.zig deleted file mode 100644 index d63db624a..000000000 --- a/src/font/sprite/cursor.zig +++ /dev/null @@ -1,65 +0,0 @@ -//! This file renders cursor sprites. -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const font = @import("../main.zig"); -const Sprite = font.sprite.Sprite; - -/// Draw a cursor. -pub fn renderGlyph( - alloc: Allocator, - atlas: *font.Atlas, - sprite: Sprite, - width: u32, - height: u32, - thickness: u32, -) !font.Glyph { - // Make a canvas of the desired size - var canvas = try font.sprite.Canvas.init(alloc, width, height); - defer canvas.deinit(); - - // Draw the appropriate sprite - switch (sprite) { - Sprite.cursor_rect => canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = height, - }, .on), - Sprite.cursor_hollow_rect => { - // left - canvas.rect(.{ .x = 0, .y = 0, .width = thickness, .height = height }, .on); - // right - canvas.rect(.{ .x = width -| thickness, .y = 0, .width = thickness, .height = height }, .on); - // top - canvas.rect(.{ .x = 0, .y = 0, .width = width, .height = thickness }, .on); - // bottom - canvas.rect(.{ .x = 0, .y = height -| thickness, .width = width, .height = thickness }, .on); - }, - Sprite.cursor_bar => canvas.rect(.{ - .x = 0, - .y = 0, - .width = thickness, - .height = height, - }, .on), - else => unreachable, - } - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - return font.Glyph{ - // HACK: Set the width for the bar cursor to just the thickness, - // this is just for the benefit of the custom shader cursor - // uniform code. -- In the future code will be introduced to - // auto-crop the canvas so that this isn't needed. - .width = if (sprite == .cursor_bar) thickness else width, - .height = height, - .offset_x = 0, - .offset_y = @intCast(height), - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(width), - }; -} diff --git a/src/font/sprite/draw/README.md b/src/font/sprite/draw/README.md new file mode 100644 index 000000000..c6219b83f --- /dev/null +++ b/src/font/sprite/draw/README.md @@ -0,0 +1,50 @@ +# This is a *special* directory. +The files in this directory are imported by `../Face.zig` and scanned for pub +functions with names matching a specific format, which are then used to handle +drawing specified codepoints. + +## IMPORTANT +When you add a new file here, you need to add the corresponding import in +`../Face.zig` for its draw functions to be picked up. I tried dynamically +listing these files to do this automatically but it was more pain than it +was worth. + +## `draw*` functions +Any function named `draw` or `draw_` will be used to +draw the codepoint or range of codepoints specified in the name. These are +hex-encoded values with upper case letters. + +`draw*` functions are provided with these arguments: +```zig +/// The codepoint being drawn. For single-codepoint draw functions this can +/// just be discarded, but it's needed for range draw functions to determine +/// which value in the range needs to be drawn. +cp: u32, +/// The canvas on which to draw the codepoint. +//// +/// This canvas has been prepared with an extra quarter of the width/height on +/// each edge, and its transform has been set so that [0, 0] is still the upper +/// left of the cell and [width, height] is still the bottom right; in order to +/// draw above or to the left, use negative values, and to draw below or to the +/// right use values greater than the width or the height. +/// +/// Because the canvas has been prepared this way, it's possible to draw glyphs +/// that exit the cell bounds by some amount- an example of when this is useful +/// is in drawing box-drawing diagonals, with enough overlap so that they can +/// seamlessly connect across corners of cells. +canvas: *font.sprite.Canvas, +/// The width of the cell to draw for. +width: u32, +/// The height of the cell to draw for. +height: u32, +/// The font grid metrics. +metrics: font.Metrics, +``` + +`draw*` functions may only return `DrawFnError!void` (defined in `../Face.zig`). + +## `special.zig` +The functions in `special.zig` are not for drawing unicode codepoints, +rather their names match the enum tag names in the `Sprite` enum from +`src/font/sprite.zig`. They are called with the same arguments as the +other `draw*` functions. diff --git a/src/font/sprite/draw/block.zig b/src/font/sprite/draw/block.zig new file mode 100644 index 000000000..27c6ae516 --- /dev/null +++ b/src/font/sprite/draw/block.zig @@ -0,0 +1,184 @@ +//! Block Elements | U+2580...U+259F +//! https://en.wikipedia.org/wiki/Block_Elements +//! +//! ▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏ +//! ▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Shade = common.Shade; +const Quads = common.Quads; +const Alignment = common.Alignment; +const rect = common.rect; + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +// Utility names for common fractions +const one_eighth: f64 = 0.125; +const one_quarter: f64 = 0.25; +const one_third: f64 = (1.0 / 3.0); +const three_eighths: f64 = 0.375; +const half: f64 = 0.5; +const five_eighths: f64 = 0.625; +const two_thirds: f64 = (2.0 / 3.0); +const three_quarters: f64 = 0.75; +const seven_eighths: f64 = 0.875; + +pub fn draw2580_259F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '▀' UPPER HALF BLOCK + 0x2580 => block(metrics, canvas, .upper, 1, half), + // '▁' LOWER ONE EIGHTH BLOCK + 0x2581 => block(metrics, canvas, .lower, 1, one_eighth), + // '▂' LOWER ONE QUARTER BLOCK + 0x2582 => block(metrics, canvas, .lower, 1, one_quarter), + // '▃' LOWER THREE EIGHTHS BLOCK + 0x2583 => block(metrics, canvas, .lower, 1, three_eighths), + // '▄' LOWER HALF BLOCK + 0x2584 => block(metrics, canvas, .lower, 1, half), + // '▅' LOWER FIVE EIGHTHS BLOCK + 0x2585 => block(metrics, canvas, .lower, 1, five_eighths), + // '▆' LOWER THREE QUARTERS BLOCK + 0x2586 => block(metrics, canvas, .lower, 1, three_quarters), + // '▇' LOWER SEVEN EIGHTHS BLOCK + 0x2587 => block(metrics, canvas, .lower, 1, seven_eighths), + // '█' FULL BLOCK + 0x2588 => fullBlockShade(metrics, canvas, .on), + // '▉' LEFT SEVEN EIGHTHS BLOCK + 0x2589 => block(metrics, canvas, .left, seven_eighths, 1), + // '▊' LEFT THREE QUARTERS BLOCK + 0x258a => block(metrics, canvas, .left, three_quarters, 1), + // '▋' LEFT FIVE EIGHTHS BLOCK + 0x258b => block(metrics, canvas, .left, five_eighths, 1), + // '▌' LEFT HALF BLOCK + 0x258c => block(metrics, canvas, .left, half, 1), + // '▍' LEFT THREE EIGHTHS BLOCK + 0x258d => block(metrics, canvas, .left, three_eighths, 1), + // '▎' LEFT ONE QUARTER BLOCK + 0x258e => block(metrics, canvas, .left, one_quarter, 1), + // '▏' LEFT ONE EIGHTH BLOCK + 0x258f => block(metrics, canvas, .left, one_eighth, 1), + + // '▐' RIGHT HALF BLOCK + 0x2590 => block(metrics, canvas, .right, half, 1), + // '░' + 0x2591 => fullBlockShade(metrics, canvas, .light), + // '▒' + 0x2592 => fullBlockShade(metrics, canvas, .medium), + // '▓' + 0x2593 => fullBlockShade(metrics, canvas, .dark), + // '▔' UPPER ONE EIGHTH BLOCK + 0x2594 => block(metrics, canvas, .upper, 1, one_eighth), + // '▕' RIGHT ONE EIGHTH BLOCK + 0x2595 => block(metrics, canvas, .right, one_eighth, 1), + // '▖' + 0x2596 => quadrant(metrics, canvas, .{ .bl = true }), + // '▗' + 0x2597 => quadrant(metrics, canvas, .{ .br = true }), + // '▘' + 0x2598 => quadrant(metrics, canvas, .{ .tl = true }), + // '▙' + 0x2599 => quadrant(metrics, canvas, .{ .tl = true, .bl = true, .br = true }), + // '▚' + 0x259a => quadrant(metrics, canvas, .{ .tl = true, .br = true }), + // '▛' + 0x259b => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }), + // '▜' + 0x259c => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .br = true }), + // '▝' + 0x259d => quadrant(metrics, canvas, .{ .tr = true }), + // '▞' + 0x259e => quadrant(metrics, canvas, .{ .tr = true, .bl = true }), + // '▟' + 0x259f => quadrant(metrics, canvas, .{ .tr = true, .bl = true, .br = true }), + + else => unreachable, + } +} + +pub fn block( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime alignment: Alignment, + comptime width: f64, + comptime height: f64, +) void { + blockShade(metrics, canvas, alignment, width, height, .on); +} + +pub fn blockShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime alignment: Alignment, + comptime width: f64, + comptime height: f64, + comptime shade: Shade, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const w: u32 = @intFromFloat(@round(float_width * width)); + const h: u32 = @intFromFloat(@round(float_height * height)); + + const x = switch (alignment.horizontal) { + .left => 0, + .right => metrics.cell_width - w, + .center => (metrics.cell_width - w) / 2, + }; + const y = switch (alignment.vertical) { + .top => 0, + .bottom => metrics.cell_height - h, + .middle => (metrics.cell_height - h) / 2, + }; + + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(y), + .width = @intCast(w), + .height = @intCast(h), + }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))); +} + +pub fn fullBlockShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + shade: Shade, +) void { + canvas.box( + 0, + 0, + @intCast(metrics.cell_width), + @intCast(metrics.cell_height), + @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))), + ); +} + +fn quadrant( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime quads: Quads, +) void { + const center_x = metrics.cell_width / 2 + metrics.cell_width % 2; + const center_y = metrics.cell_height / 2 + metrics.cell_height % 2; + + if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y); + if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y); + if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height); + if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height); +} diff --git a/src/font/sprite/draw/box.zig b/src/font/sprite/draw/box.zig new file mode 100644 index 000000000..91d78d2b2 --- /dev/null +++ b/src/font/sprite/draw/box.zig @@ -0,0 +1,947 @@ +//! Box Drawing | U+2500...U+257F +//! https://en.wikipedia.org/wiki/Box_Drawing +//! +//! ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏ +//! ┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟ +//! ┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯ +//! ┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿ +//! ╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏ +//! ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟ +//! ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯ +//! ╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; +const Quads = common.Quads; +const Corner = common.Corner; +const Edge = common.Edge; +const Alignment = common.Alignment; +const rect = common.rect; +const hline = common.hline; +const vline = common.vline; +const hlineMiddle = common.hlineMiddle; +const vlineMiddle = common.vlineMiddle; + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +/// Specification of a traditional intersection-style line/box-drawing char, +/// which can have a different style of line from each edge to the center. +pub const Lines = packed struct(u8) { + up: Style = .none, + right: Style = .none, + down: Style = .none, + left: Style = .none, + + const Style = enum(u2) { + none, + light, + heavy, + double, + }; +}; + +pub fn draw2500_257F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '─' + 0x2500 => linesChar(metrics, canvas, .{ .left = .light, .right = .light }), + // '━' + 0x2501 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .heavy }), + // '│' + 0x2502 => linesChar(metrics, canvas, .{ .up = .light, .down = .light }), + // '┃' + 0x2503 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy }), + // '┄' + 0x2504 => dashHorizontal( + metrics, + canvas, + 3, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┅' + 0x2505 => dashHorizontal( + metrics, + canvas, + 3, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┆' + 0x2506 => dashVertical( + metrics, + canvas, + 3, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┇' + 0x2507 => dashVertical( + metrics, + canvas, + 3, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┈' + 0x2508 => dashHorizontal( + metrics, + canvas, + 4, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┉' + 0x2509 => dashHorizontal( + metrics, + canvas, + 4, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┊' + 0x250a => dashVertical( + metrics, + canvas, + 4, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┋' + 0x250b => dashVertical( + metrics, + canvas, + 4, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┌' + 0x250c => linesChar(metrics, canvas, .{ .down = .light, .right = .light }), + // '┍' + 0x250d => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy }), + // '┎' + 0x250e => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light }), + // '┏' + 0x250f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .heavy }), + + // '┐' + 0x2510 => linesChar(metrics, canvas, .{ .down = .light, .left = .light }), + // '┑' + 0x2511 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy }), + // '┒' + 0x2512 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light }), + // '┓' + 0x2513 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy }), + // '└' + 0x2514 => linesChar(metrics, canvas, .{ .up = .light, .right = .light }), + // '┕' + 0x2515 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy }), + // '┖' + 0x2516 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light }), + // '┗' + 0x2517 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .heavy }), + // '┘' + 0x2518 => linesChar(metrics, canvas, .{ .up = .light, .left = .light }), + // '┙' + 0x2519 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy }), + // '┚' + 0x251a => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light }), + // '┛' + 0x251b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy }), + // '├' + 0x251c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .light }), + // '┝' + 0x251d => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .heavy }), + // '┞' + 0x251e => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light, .down = .light }), + // '┟' + 0x251f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light, .up = .light }), + + // '┠' + 0x2520 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .light }), + // '┡' + 0x2521 => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy, .up = .heavy }), + // '┢' + 0x2522 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy, .down = .heavy }), + // '┣' + 0x2523 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }), + // '┤' + 0x2524 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light }), + // '┥' + 0x2525 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy }), + // '┦' + 0x2526 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .down = .light }), + // '┧' + 0x2527 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .up = .light }), + // '┨' + 0x2528 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light }), + // '┩' + 0x2529 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .up = .heavy }), + // '┪' + 0x252a => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .down = .heavy }), + // '┫' + 0x252b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }), + // '┬' + 0x252c => linesChar(metrics, canvas, .{ .down = .light, .left = .light, .right = .light }), + // '┭' + 0x252d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .down = .light }), + // '┮' + 0x252e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .down = .light }), + // '┯' + 0x252f => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .right = .heavy }), + + // '┰' + 0x2530 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .right = .light }), + // '┱' + 0x2531 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .down = .heavy }), + // '┲' + 0x2532 => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .down = .heavy }), + // '┳' + 0x2533 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }), + // '┴' + 0x2534 => linesChar(metrics, canvas, .{ .up = .light, .left = .light, .right = .light }), + // '┵' + 0x2535 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light }), + // '┶' + 0x2536 => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light }), + // '┷' + 0x2537 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .right = .heavy }), + // '┸' + 0x2538 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .right = .light }), + // '┹' + 0x2539 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy }), + // '┺' + 0x253a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy }), + // '┻' + 0x253b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }), + // '┼' + 0x253c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }), + // '┽' + 0x253d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }), + // '┾' + 0x253e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }), + // '┿' + 0x253f => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }), + + // '╀' + 0x2540 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }), + // '╁' + 0x2541 => linesChar(metrics, canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }), + // '╂' + 0x2542 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), + // '╃' + 0x2543 => linesChar(metrics, canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }), + // '╄' + 0x2544 => linesChar(metrics, canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }), + // '╅' + 0x2545 => linesChar(metrics, canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }), + // '╆' + 0x2546 => linesChar(metrics, canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }), + // '╇' + 0x2547 => linesChar(metrics, canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }), + // '╈' + 0x2548 => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }), + // '╉' + 0x2549 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }), + // '╊' + 0x254a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }), + // '╋' + 0x254b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }), + // '╌' + 0x254c => dashHorizontal( + metrics, + canvas, + 2, + Thickness.light.height(metrics.box_thickness), + Thickness.light.height(metrics.box_thickness), + ), + // '╍' + 0x254d => dashHorizontal( + metrics, + canvas, + 2, + Thickness.heavy.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + // '╎' + 0x254e => dashVertical( + metrics, + canvas, + 2, + Thickness.light.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + // '╏' + 0x254f => dashVertical( + metrics, + canvas, + 2, + Thickness.heavy.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + + // '═' + 0x2550 => linesChar(metrics, canvas, .{ .left = .double, .right = .double }), + // '║' + 0x2551 => linesChar(metrics, canvas, .{ .up = .double, .down = .double }), + // '╒' + 0x2552 => linesChar(metrics, canvas, .{ .down = .light, .right = .double }), + // '╓' + 0x2553 => linesChar(metrics, canvas, .{ .down = .double, .right = .light }), + // '╔' + 0x2554 => linesChar(metrics, canvas, .{ .down = .double, .right = .double }), + // '╕' + 0x2555 => linesChar(metrics, canvas, .{ .down = .light, .left = .double }), + // '╖' + 0x2556 => linesChar(metrics, canvas, .{ .down = .double, .left = .light }), + // '╗' + 0x2557 => linesChar(metrics, canvas, .{ .down = .double, .left = .double }), + // '╘' + 0x2558 => linesChar(metrics, canvas, .{ .up = .light, .right = .double }), + // '╙' + 0x2559 => linesChar(metrics, canvas, .{ .up = .double, .right = .light }), + // '╚' + 0x255a => linesChar(metrics, canvas, .{ .up = .double, .right = .double }), + // '╛' + 0x255b => linesChar(metrics, canvas, .{ .up = .light, .left = .double }), + // '╜' + 0x255c => linesChar(metrics, canvas, .{ .up = .double, .left = .light }), + // '╝' + 0x255d => linesChar(metrics, canvas, .{ .up = .double, .left = .double }), + // '╞' + 0x255e => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .double }), + // '╟' + 0x255f => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .light }), + + // '╠' + 0x2560 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .double }), + // '╡' + 0x2561 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double }), + // '╢' + 0x2562 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light }), + // '╣' + 0x2563 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double }), + // '╤' + 0x2564 => linesChar(metrics, canvas, .{ .down = .light, .left = .double, .right = .double }), + // '╥' + 0x2565 => linesChar(metrics, canvas, .{ .down = .double, .left = .light, .right = .light }), + // '╦' + 0x2566 => linesChar(metrics, canvas, .{ .down = .double, .left = .double, .right = .double }), + // '╧' + 0x2567 => linesChar(metrics, canvas, .{ .up = .light, .left = .double, .right = .double }), + // '╨' + 0x2568 => linesChar(metrics, canvas, .{ .up = .double, .left = .light, .right = .light }), + // '╩' + 0x2569 => linesChar(metrics, canvas, .{ .up = .double, .left = .double, .right = .double }), + // '╪' + 0x256a => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }), + // '╫' + 0x256b => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }), + // '╬' + 0x256c => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), + // '╭' + 0x256d => try arc(metrics, canvas, .br, .light), + // '╮' + 0x256e => try arc(metrics, canvas, .bl, .light), + // '╯' + 0x256f => try arc(metrics, canvas, .tl, .light), + + // '╰' + 0x2570 => try arc(metrics, canvas, .tr, .light), + // '╱' + 0x2571 => lightDiagonalUpperRightToLowerLeft(metrics, canvas), + // '╲' + 0x2572 => lightDiagonalUpperLeftToLowerRight(metrics, canvas), + // '╳' + 0x2573 => lightDiagonalCross(metrics, canvas), + // '╴' + 0x2574 => linesChar(metrics, canvas, .{ .left = .light }), + // '╵' + 0x2575 => linesChar(metrics, canvas, .{ .up = .light }), + // '╶' + 0x2576 => linesChar(metrics, canvas, .{ .right = .light }), + // '╷' + 0x2577 => linesChar(metrics, canvas, .{ .down = .light }), + // '╸' + 0x2578 => linesChar(metrics, canvas, .{ .left = .heavy }), + // '╹' + 0x2579 => linesChar(metrics, canvas, .{ .up = .heavy }), + // '╺' + 0x257a => linesChar(metrics, canvas, .{ .right = .heavy }), + // '╻' + 0x257b => linesChar(metrics, canvas, .{ .down = .heavy }), + // '╼' + 0x257c => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy }), + // '╽' + 0x257d => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy }), + // '╾' + 0x257e => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light }), + // '╿' + 0x257f => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light }), + + else => unreachable, + } +} + +pub fn linesChar( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + lines: Lines, +) void { + const light_px = Thickness.light.height(metrics.box_thickness); + const heavy_px = Thickness.heavy.height(metrics.box_thickness); + + // Top of light horizontal strokes + const h_light_top = (metrics.cell_height -| light_px) / 2; + // Bottom of light horizontal strokes + const h_light_bottom = h_light_top +| light_px; + + // Top of heavy horizontal strokes + const h_heavy_top = (metrics.cell_height -| heavy_px) / 2; + // Bottom of heavy horizontal strokes + const h_heavy_bottom = h_heavy_top +| heavy_px; + + // Top of the top doubled horizontal stroke (bottom is `h_light_top`) + const h_double_top = h_light_top -| light_px; + // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`) + const h_double_bottom = h_light_bottom +| light_px; + + // Left of light vertical strokes + const v_light_left = (metrics.cell_width -| light_px) / 2; + // Right of light vertical strokes + const v_light_right = v_light_left +| light_px; + + // Left of heavy vertical strokes + const v_heavy_left = (metrics.cell_width -| heavy_px) / 2; + // Right of heavy vertical strokes + const v_heavy_right = v_heavy_left +| heavy_px; + + // Left of the left doubled vertical stroke (right is `v_light_left`) + const v_double_left = v_light_left -| light_px; + // Right of the right doubled vertical stroke (left is `v_light_right`) + const v_double_right = v_light_right +| light_px; + + // The bottom of the up line + const up_bottom = if (lines.left == .heavy or lines.right == .heavy) + h_heavy_bottom + else if (lines.left != lines.right or lines.down == lines.up) + if (lines.left == .double or lines.right == .double) + h_double_bottom + else + h_light_bottom + else if (lines.left == .none and lines.right == .none) + h_light_bottom + else + h_light_top; + + // The top of the down line + const down_top = if (lines.left == .heavy or lines.right == .heavy) + h_heavy_top + else if (lines.left != lines.right or lines.up == lines.down) + if (lines.left == .double or lines.right == .double) + h_double_top + else + h_light_top + else if (lines.left == .none and lines.right == .none) + h_light_top + else + h_light_bottom; + + // The right of the left line + const left_right = if (lines.up == .heavy or lines.down == .heavy) + v_heavy_right + else if (lines.up != lines.down or lines.left == lines.right) + if (lines.up == .double or lines.down == .double) + v_double_right + else + v_light_right + else if (lines.up == .none and lines.down == .none) + v_light_right + else + v_light_left; + + // The left of the right line + const right_left = if (lines.up == .heavy or lines.down == .heavy) + v_heavy_left + else if (lines.up != lines.down or lines.right == lines.left) + if (lines.up == .double or lines.down == .double) + v_double_left + else + v_light_left + else if (lines.up == .none and lines.down == .none) + v_light_left + else + v_light_right; + + switch (lines.up) { + .none => {}, + .light => canvas.box( + @intCast(v_light_left), + 0, + @intCast(v_light_right), + @intCast(up_bottom), + .on, + ), + .heavy => canvas.box( + @intCast(v_heavy_left), + 0, + @intCast(v_heavy_right), + @intCast(up_bottom), + .on, + ), + .double => { + const left_bottom = if (lines.left == .double) h_light_top else up_bottom; + const right_bottom = if (lines.right == .double) h_light_top else up_bottom; + + canvas.box( + @intCast(v_double_left), + 0, + @intCast(v_light_left), + @intCast(left_bottom), + .on, + ); + canvas.box( + @intCast(v_light_right), + 0, + @intCast(v_double_right), + @intCast(right_bottom), + .on, + ); + }, + } + + switch (lines.right) { + .none => {}, + .light => canvas.box( + @intCast(right_left), + @intCast(h_light_top), + @intCast(metrics.cell_width), + @intCast(h_light_bottom), + .on, + ), + .heavy => canvas.box( + @intCast(right_left), + @intCast(h_heavy_top), + @intCast(metrics.cell_width), + @intCast(h_heavy_bottom), + .on, + ), + .double => { + const top_left = if (lines.up == .double) v_light_right else right_left; + const bottom_left = if (lines.down == .double) v_light_right else right_left; + + canvas.box( + @intCast(top_left), + @intCast(h_double_top), + @intCast(metrics.cell_width), + @intCast(h_light_top), + .on, + ); + canvas.box( + @intCast(bottom_left), + @intCast(h_light_bottom), + @intCast(metrics.cell_width), + @intCast(h_double_bottom), + .on, + ); + }, + } + + switch (lines.down) { + .none => {}, + .light => canvas.box( + @intCast(v_light_left), + @intCast(down_top), + @intCast(v_light_right), + @intCast(metrics.cell_height), + .on, + ), + .heavy => canvas.box( + @intCast(v_heavy_left), + @intCast(down_top), + @intCast(v_heavy_right), + @intCast(metrics.cell_height), + .on, + ), + .double => { + const left_top = if (lines.left == .double) h_light_bottom else down_top; + const right_top = if (lines.right == .double) h_light_bottom else down_top; + + canvas.box( + @intCast(v_double_left), + @intCast(left_top), + @intCast(v_light_left), + @intCast(metrics.cell_height), + .on, + ); + canvas.box( + @intCast(v_light_right), + @intCast(right_top), + @intCast(v_double_right), + @intCast(metrics.cell_height), + .on, + ); + }, + } + + switch (lines.left) { + .none => {}, + .light => canvas.box( + 0, + @intCast(h_light_top), + @intCast(left_right), + @intCast(h_light_bottom), + .on, + ), + .heavy => canvas.box( + 0, + @intCast(h_heavy_top), + @intCast(left_right), + @intCast(h_heavy_bottom), + .on, + ), + .double => { + const top_right = if (lines.up == .double) v_light_left else left_right; + const bottom_right = if (lines.down == .double) v_light_left else left_right; + + canvas.box( + 0, + @intCast(h_double_top), + @intCast(top_right), + @intCast(h_light_top), + .on, + ); + canvas.box( + 0, + @intCast(h_light_bottom), + @intCast(bottom_right), + @intCast(h_double_bottom), + .on, + ); + }, + } +} + +pub fn lightDiagonalUpperRightToLowerLeft( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // We overshoot the corners by a tiny bit, but we need to + // maintain the correct slope, so we calculate that here. + const slope_x: f64 = @min(1.0, float_width / float_height); + const slope_y: f64 = @min(1.0, float_height / float_width); + + canvas.line(.{ + .p0 = .{ + .x = float_width + 0.5 * slope_x, + .y = -0.5 * slope_y, + }, + .p1 = .{ + .x = -0.5 * slope_x, + .y = float_height + 0.5 * slope_y, + }, + }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {}; +} + +pub fn lightDiagonalUpperLeftToLowerRight( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // We overshoot the corners by a tiny bit, but we need to + // maintain the correct slope, so we calculate that here. + const slope_x: f64 = @min(1.0, float_width / float_height); + const slope_y: f64 = @min(1.0, float_height / float_width); + + canvas.line(.{ + .p0 = .{ + .x = -0.5 * slope_x, + .y = -0.5 * slope_y, + }, + .p1 = .{ + .x = float_width + 0.5 * slope_x, + .y = float_height + 0.5 * slope_y, + }, + }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {}; +} + +pub fn lightDiagonalCross( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + lightDiagonalUpperRightToLowerLeft(metrics, canvas); + lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +fn quadrant( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime quads: Quads, +) void { + const center_x = metrics.cell_width / 2 + metrics.cell_width % 2; + const center_y = metrics.cell_height / 2 + metrics.cell_height % 2; + + if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y); + if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y); + if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height); + if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height); +} + +pub fn arc( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, + comptime thickness: Thickness, +) !void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const center_x: f64 = @as(f64, @floatFromInt((metrics.cell_width -| thick_px) / 2)) + float_thick / 2; + const center_y: f64 = @as(f64, @floatFromInt((metrics.cell_height -| thick_px) / 2)) + float_thick / 2; + + const r = @min(float_width, float_height) / 2; + + // Fraction away from the center to place the middle control points, + const s: f64 = 0.25; + + var path = canvas.staticPath(4); + + switch (corner) { + .tl => { + path.moveTo(center_x, 0); + path.lineTo(center_x, center_y - r); + path.curveTo( + center_x, + center_y - s * r, + center_x - s * r, + center_y, + center_x - r, + center_y, + ); + path.lineTo(0, center_y); + }, + .tr => { + path.moveTo(center_x, 0); + path.lineTo(center_x, center_y - r); + path.curveTo( + center_x, + center_y - s * r, + center_x + s * r, + center_y, + center_x + r, + center_y, + ); + path.lineTo(float_width, center_y); + }, + .bl => { + path.moveTo(center_x, float_height); + path.lineTo(center_x, center_y + r); + path.curveTo( + center_x, + center_y + s * r, + center_x - s * r, + center_y, + center_x - r, + center_y, + ); + path.lineTo(0, center_y); + }, + .br => { + path.moveTo(center_x, float_height); + path.lineTo(center_x, center_y + r); + path.curveTo( + center_x, + center_y + s * r, + center_x + s * r, + center_y, + center_x + r, + center_y, + ); + path.lineTo(float_width, center_y); + }, + } + + try canvas.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = float_thick, + }, + .on, + ); +} + +fn dashHorizontal( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + count: u8, + thick_px: u32, + desired_gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // +------------+ + // | | + // | | + // | | + // | | + // | -- -- -- | + // | | + // | | + // | | + // | | + // +------------+ + // Our dashed line should be made such that when tiled horizontally + // it creates one consistent line with no uneven gap or segment sizes. + // In order to make sure this is the case, we should have half-sized + // gaps on the left and right so that it is centered properly. + + // For N dashes, there are N - 1 gaps between them, but we also have + // half-sized gaps on either side, adding up to N total gaps. + const gap_count = count; + + // We need at least 1 pixel for each gap and each dash, if we don't + // have that then we can't draw our dashed line correctly so we just + // draw a solid line and return. + if (metrics.cell_width < count + gap_count) { + hlineMiddle(metrics, canvas, .light); + return; + } + + // We never want the gaps to take up more than 50% of the space, + // because if they do the dashes are too small and look wrong. + const gap_width: i32 = @intCast(@min(desired_gap, metrics.cell_width / (2 * count))); + const total_gap_width: i32 = gap_count * gap_width; + const total_dash_width: i32 = @as(i32, @intCast(metrics.cell_width)) - total_gap_width; + const dash_width: i32 = @divFloor(total_dash_width, count); + const remaining: i32 = @mod(total_dash_width, count); + + assert(dash_width * count + gap_width * gap_count + remaining == metrics.cell_width); + + // Our dashes should be centered vertically. + const y: i32 = @intCast((metrics.cell_height -| thick_px) / 2); + + // We start at half a gap from the left edge, in order to center + // our dashes properly. + var x: i32 = @divFloor(gap_width, 2); + + // We'll distribute the extra space in to dash widths, 1px at a + // time. We prefer this to making gaps larger since that is much + // more visually obvious. + var extra: i32 = remaining; + + for (0..count) |_| { + var x1 = x + dash_width; + // We distribute left-over size in to dash widths, + // since it's less obvious there than in the gaps. + if (extra > 0) { + extra -= 1; + x1 += 1; + } + hline(canvas, x, x1, y, thick_px); + // Advance by the width of the dash we drew and the width + // of a gap to get the the start of the next dash. + x = x1 + gap_width; + } +} + +fn dashVertical( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime count: u8, + thick_px: u32, + desired_gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // +-----------+ + // | | | + // | | | + // | | + // | | | + // | | | + // | | + // | | | + // | | | + // | | + // +-----------+ + // Our dashed line should be made such that when tiled vertically it + // it creates one consistent line with no uneven gap or segment sizes. + // In order to make sure this is the case, we should have an extra gap + // gap at the bottom. + // + // A single full-sized extra gap is preferred to two half-sized ones for + // vertical to allow better joining to solid characters without creating + // visible half-sized gaps. Unlike horizontal, centering is a lot less + // important, visually. + + // Because of the extra gap at the bottom, there are as many gaps as + // there are dashes. + const gap_count = count; + + // We need at least 1 pixel for each gap and each dash, if we don't + // have that then we can't draw our dashed line correctly so we just + // draw a solid line and return. + if (metrics.cell_height < count + gap_count) { + vlineMiddle(metrics, canvas, .light); + return; + } + + // We never want the gaps to take up more than 50% of the space, + // because if they do the dashes are too small and look wrong. + const gap_height: i32 = @intCast(@min(desired_gap, metrics.cell_height / (2 * count))); + const total_gap_height: i32 = gap_count * gap_height; + const total_dash_height: i32 = @as(i32, @intCast(metrics.cell_height)) - total_gap_height; + const dash_height: i32 = @divFloor(total_dash_height, count); + const remaining: i32 = @mod(total_dash_height, count); + + assert(dash_height * count + gap_height * gap_count + remaining == metrics.cell_height); + + // Our dashes should be centered horizontally. + const x: i32 = @intCast((metrics.cell_width -| thick_px) / 2); + + // We start at the top of the cell. + var y: i32 = 0; + + // We'll distribute the extra space in to dash heights, 1px at a + // time. We prefer this to making gaps larger since that is much + // more visually obvious. + var extra: i32 = remaining; + + inline for (0..count) |_| { + var y1 = y + dash_height; + // We distribute left-over size in to dash widths, + // since it's less obvious there than in the gaps. + if (extra > 0) { + extra -= 1; + y1 += 1; + } + vline(canvas, y, y1, x, thick_px); + // Advance by the height of the dash we drew and the height + // of a gap to get the the start of the next dash. + y = y1 + gap_height; + } +} diff --git a/src/font/sprite/draw/braille.zig b/src/font/sprite/draw/braille.zig new file mode 100644 index 000000000..c756ff369 --- /dev/null +++ b/src/font/sprite/draw/braille.zig @@ -0,0 +1,148 @@ +//! Braille Patterns | U+2800...U+28FF +//! https://en.wikipedia.org/wiki/Braille_Patterns +//! +//! (6 dot patterns) +//! ⠀ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏ +//! ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟ +//! ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯ +//! ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿ +//! +//! (8 dot patterns) +//! ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏ +//! ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟ +//! ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯ +//! ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿ +//! ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏ +//! ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟ +//! ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯ +//! ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿ +//! ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏ +//! ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟ +//! ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯ +//! ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const font = @import("../../main.zig"); + +/// A braille pattern. +/// +/// Mnemonic: +/// [t]op - . . +/// [u]pper - . . +/// [l]ower - . . +/// [b]ottom - . . +/// | | +/// [l]eft, [r]ight +/// +/// Struct layout matches bit patterns of unicode codepoints. +const Pattern = packed struct(u8) { + tl: bool, + ul: bool, + ll: bool, + tr: bool, + ur: bool, + lr: bool, + bl: bool, + br: bool, + + fn from(cp: u32) Pattern { + return @bitCast(@as(u8, @truncate(cp))); + } +}; + +pub fn draw2800_28FF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + var w: i32 = @intCast(@min(width / 4, height / 8)); + var x_spacing: i32 = @intCast(width / 4); + var y_spacing: i32 = @intCast(height / 8); + var x_margin: i32 = @divFloor(x_spacing, 2); + var y_margin: i32 = @divFloor(y_spacing, 2); + + var x_px_left: i32 = + @as(i32, @intCast(width)) - 2 * x_margin - x_spacing - 2 * w; + + var y_px_left: i32 = + @as(i32, @intCast(height)) - 2 * y_margin - 3 * y_spacing - 4 * w; + + // First, try hard to ensure the DOT width is non-zero + if (x_px_left >= 2 and y_px_left >= 4 and w == 0) { + w += 1; + x_px_left -= 2; + y_px_left -= 4; + } + + // Second, prefer a non-zero margin + if (x_px_left >= 2 and x_margin == 0) { + x_margin = 1; + x_px_left -= 2; + } + if (y_px_left >= 2 and y_margin == 0) { + y_margin = 1; + y_px_left -= 2; + } + + // Third, increase spacing + if (x_px_left >= 1) { + x_spacing += 1; + x_px_left -= 1; + } + if (y_px_left >= 3) { + y_spacing += 1; + y_px_left -= 3; + } + + // Fourth, margins (“spacing”, but on the sides) + if (x_px_left >= 2) { + x_margin += 1; + x_px_left -= 2; + } + if (y_px_left >= 2) { + y_margin += 1; + y_px_left -= 2; + } + + // Last - increase dot width + if (x_px_left >= 2 and y_px_left >= 4) { + w += 1; + x_px_left -= 2; + y_px_left -= 4; + } + + assert(x_px_left <= 1 or y_px_left <= 1); + assert(2 * x_margin + 2 * w + x_spacing <= width); + assert(2 * y_margin + 4 * w + 3 * y_spacing <= height); + + const x = [2]i32{ x_margin, x_margin + w + x_spacing }; + const y = y: { + var y: [4]i32 = undefined; + y[0] = y_margin; + y[1] = y[0] + w + y_spacing; + y[2] = y[1] + w + y_spacing; + y[3] = y[2] + w + y_spacing; + break :y y; + }; + + assert(cp >= 0x2800); + assert(cp <= 0x28ff); + const p: Pattern = .from(cp); + + if (p.tl) canvas.box(x[0], y[0], x[0] + w, y[0] + w, .on); + if (p.ul) canvas.box(x[0], y[1], x[0] + w, y[1] + w, .on); + if (p.ll) canvas.box(x[0], y[2], x[0] + w, y[2] + w, .on); + if (p.bl) canvas.box(x[0], y[3], x[0] + w, y[3] + w, .on); + if (p.tr) canvas.box(x[1], y[0], x[1] + w, y[0] + w, .on); + if (p.ur) canvas.box(x[1], y[1], x[1] + w, y[1] + w, .on); + if (p.lr) canvas.box(x[1], y[2], x[1] + w, y[2] + w, .on); + if (p.br) canvas.box(x[1], y[3], x[1] + w, y[3] + w, .on); +} diff --git a/src/font/sprite/draw/branch.zig b/src/font/sprite/draw/branch.zig new file mode 100644 index 000000000..ac7220390 --- /dev/null +++ b/src/font/sprite/draw/branch.zig @@ -0,0 +1,505 @@ +//! Branch Drawing Characters | U+F5D0...U+F60D +//! +//! Branch drawing character set, used for drawing git-like +//! graphs in the terminal. Originally implemented in Kitty. +//! Ref: +//! - https://github.com/kovidgoyal/kitty/pull/7681 +//! - https://github.com/kovidgoyal/kitty/pull/7805 +//! NOTE: Kitty is GPL licensed, and its code was not referenced +//! for these characters, only the loose specification of +//! the character set in the pull request descriptions. +//! +//!                 +//!                 +//!                 +//!               +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; +const Edge = common.Edge; +const hlineMiddle = common.hlineMiddle; +const vlineMiddle = common.vlineMiddle; + +const arc = @import("box.zig").arc; + +const font = @import("../../main.zig"); + +/// Specification of a branch drawing node, which consists of a +/// circle which is either empty or filled, and lines connecting +/// optionally between the circle and each of the 4 edges. +const BranchNode = packed struct(u5) { + up: bool = false, + right: bool = false, + down: bool = false, + left: bool = false, + filled: bool = false, +}; + +pub fn drawF5D0_F60D( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '' + 0x0f5d0 => hlineMiddle(metrics, canvas, .light), + // '' + 0x0f5d1 => vlineMiddle(metrics, canvas, .light), + // '' + 0x0f5d2 => fadingLine(metrics, canvas, .right, .light), + // '' + 0x0f5d3 => fadingLine(metrics, canvas, .left, .light), + // '' + 0x0f5d4 => fadingLine(metrics, canvas, .bottom, .light), + // '' + 0x0f5d5 => fadingLine(metrics, canvas, .top, .light), + // '' + 0x0f5d6 => try arc(metrics, canvas, .br, .light), + // '' + 0x0f5d7 => try arc(metrics, canvas, .bl, .light), + // '' + 0x0f5d8 => try arc(metrics, canvas, .tr, .light), + // '' + 0x0f5d9 => try arc(metrics, canvas, .tl, .light), + // '' + 0x0f5da => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + }, + // '' + 0x0f5db => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5dc => { + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5dd => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5de => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5df => { + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .bl, .light); + }, + + // '' + 0x0f5e0 => { + try arc(metrics, canvas, .bl, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e1 => { + try arc(metrics, canvas, .br, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e2 => { + try arc(metrics, canvas, .br, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5e3 => { + try arc(metrics, canvas, .tl, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e4 => { + try arc(metrics, canvas, .tr, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e5 => { + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5e6 => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .tr, .light); + }, + // '' + 0x0f5e7 => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5e8 => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5e9 => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5ea => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5eb => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5ec => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5ed => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5ee => branchNode(metrics, canvas, .{ .filled = true }, .light), + // '' + 0x0f5ef => branchNode(metrics, canvas, .{}, .light), + + // '' + 0x0f5f0 => branchNode(metrics, canvas, .{ + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f1 => branchNode(metrics, canvas, .{ + .right = true, + }, .light), + // '' + 0x0f5f2 => branchNode(metrics, canvas, .{ + .left = true, + .filled = true, + }, .light), + // '' + 0x0f5f3 => branchNode(metrics, canvas, .{ + .left = true, + }, .light), + // '' + 0x0f5f4 => branchNode(metrics, canvas, .{ + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f5 => branchNode(metrics, canvas, .{ + .left = true, + .right = true, + }, .light), + // '' + 0x0f5f6 => branchNode(metrics, canvas, .{ + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5f7 => branchNode(metrics, canvas, .{ + .down = true, + }, .light), + // '' + 0x0f5f8 => branchNode(metrics, canvas, .{ + .up = true, + .filled = true, + }, .light), + // '' + 0x0f5f9 => branchNode(metrics, canvas, .{ + .up = true, + }, .light), + // '' + 0x0f5fa => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fb => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + }, .light), + // '' + 0x0f5fc => branchNode(metrics, canvas, .{ + .right = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fd => branchNode(metrics, canvas, .{ + .right = true, + .down = true, + }, .light), + // '' + 0x0f5fe => branchNode(metrics, canvas, .{ + .left = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5ff => branchNode(metrics, canvas, .{ + .left = true, + .down = true, + }, .light), + + // '' + 0x0f600 => branchNode(metrics, canvas, .{ + .up = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f601 => branchNode(metrics, canvas, .{ + .up = true, + .right = true, + }, .light), + // '' + 0x0f602 => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f603 => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + }, .light), + // '' + 0x0f604 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f605 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .right = true, + }, .light), + // '' + 0x0f606 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f607 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + }, .light), + // '' + 0x0f608 => branchNode(metrics, canvas, .{ + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f609 => branchNode(metrics, canvas, .{ + .down = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60a => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f60b => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60c => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f60d => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + }, .light), + + else => unreachable, + } +} + +fn branchNode( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + node: BranchNode, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + + // Top of horizontal strokes + const h_top = (metrics.cell_height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (metrics.cell_width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // We calculate the center of the circle this way + // to ensure it aligns with box drawing characters + // since the lines are sometimes off center to + // make sure they aren't split between pixels. + const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; + const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; + // The radius needs to be the smallest distance from the center to an edge. + const r: f64 = @min( + @min(cx, cy), + @min(float_width - cx, float_height - cy), + ); + + var ctx = canvas.getContext(); + defer ctx.deinit(); + ctx.setSource(.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + } }); + ctx.setLineWidth(float_thick); + + // These @intFromFloat casts shouldn't ever fail since r can never + // be greater than cx or cy, so when subtracting it from them the + // result can never be negative. + if (node.up) canvas.box( + @intCast(v_left), + 0, + @intCast(v_right), + @intFromFloat(@ceil(cy - r + float_thick / 2)), + .on, + ); + if (node.right) canvas.box( + @intFromFloat(@floor(cx + r - float_thick / 2)), + @intCast(h_top), + @intCast(metrics.cell_width), + @intCast(h_bottom), + .on, + ); + if (node.down) canvas.box( + @intCast(v_left), + @intFromFloat(@floor(cy + r - float_thick / 2)), + @intCast(v_right), + @intCast(metrics.cell_height), + .on, + ); + if (node.left) canvas.box( + 0, + @intCast(h_top), + @intFromFloat(@ceil(cx - r + float_thick / 2)), + @intCast(h_bottom), + .on, + ); + + if (node.filled) { + ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.fill() catch return; + } else { + ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.stroke() catch return; + } +} + +fn fadingLine( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime to: Edge, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // Top of horizontal strokes + const h_top = (metrics.cell_height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (metrics.cell_width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // If we're fading to the top or left, we start with 0.0 + // and increment up as we progress, otherwise we start + // at 255.0 and increment down (negative). + var color: f64 = switch (to) { + .top, .left => 0.0, + .bottom, .right => 255.0, + }; + const inc: f64 = 255.0 / switch (to) { + .top => float_height, + .bottom => -float_height, + .left => float_width, + .right => -float_width, + }; + + switch (to) { + .top, .bottom => { + for (0..metrics.cell_height) |y| { + for (v_left..v_right) |x| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + .left, .right => { + for (0..metrics.cell_width) |x| { + for (h_top..h_bottom) |y| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + } +} diff --git a/src/font/sprite/draw/common.zig b/src/font/sprite/draw/common.zig new file mode 100644 index 000000000..2f608180e --- /dev/null +++ b/src/font/sprite/draw/common.zig @@ -0,0 +1,244 @@ +//! This file contains a set of useful helper functions +//! and types for drawing our sprite font glyphs. These +//! are generally applicable to multiple sets of glyphs +//! rather than being single-use. + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +const log = std.log.scoped(.sprite_font); + +// Utility names for common fractions +pub const one_eighth: f64 = 0.125; +pub const one_quarter: f64 = 0.25; +pub const one_third: f64 = (1.0 / 3.0); +pub const three_eighths: f64 = 0.375; +pub const half: f64 = 0.5; +pub const five_eighths: f64 = 0.625; +pub const two_thirds: f64 = (2.0 / 3.0); +pub const three_quarters: f64 = 0.75; +pub const seven_eighths: f64 = 0.875; + +/// The thickness of a line. +pub const Thickness = enum { + super_light, + light, + heavy, + + /// Calculate the real height of a line based on its + /// thickness and a base thickness value. The base + /// thickness value is expected to be in pixels. + pub fn height(self: Thickness, base: u32) u32 { + return switch (self) { + .super_light => @max(base / 2, 1), + .light => base, + .heavy => base * 2, + }; + } +}; + +/// Shades. +pub const Shade = enum(u8) { + off = 0x00, + light = 0x40, + medium = 0x80, + dark = 0xc0, + on = 0xff, + + _, +}; + +/// Applicable to any set of glyphs with features +/// that may be present or not in each quadrant. +pub const Quads = packed struct(u4) { + tl: bool = false, + tr: bool = false, + bl: bool = false, + br: bool = false, +}; + +/// A corner of a cell. +pub const Corner = enum(u2) { + tl, + tr, + bl, + br, +}; + +/// An edge of a cell. +pub const Edge = enum(u2) { + top, + left, + bottom, + right, +}; + +/// Alignment of a figure within a cell. +pub const Alignment = struct { + horizontal: enum { + left, + right, + center, + } = .center, + + vertical: enum { + top, + bottom, + middle, + } = .middle, + + pub const upper: Alignment = .{ .vertical = .top }; + pub const lower: Alignment = .{ .vertical = .bottom }; + pub const left: Alignment = .{ .horizontal = .left }; + pub const right: Alignment = .{ .horizontal = .right }; + + pub const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left }; + pub const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right }; + pub const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left }; + pub const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right }; + + pub const center: Alignment = .{}; + + pub const upper_center = upper; + pub const lower_center = lower; + pub const middle_left = left; + pub const middle_right = right; + pub const middle_center: Alignment = center; + + pub const top = upper; + pub const bottom = lower; + pub const center_top = top; + pub const center_bottom = bottom; + + pub const top_left = upper_left; + pub const top_right = upper_right; + pub const bottom_left = lower_left; + pub const bottom_right = lower_right; +}; + +/// Fill a rect, clamped to within the cell boundaries. +/// +/// TODO: Eliminate usages of this, prefer `canvas.box`. +pub fn rect( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + x1: u32, + y1: u32, + x2: u32, + y2: u32, +) void { + canvas.box( + @intCast(@min(@max(x1, 0), metrics.cell_width)), + @intCast(@min(@max(y1, 0), metrics.cell_height)), + @intCast(@min(@max(x2, 0), metrics.cell_width)), + @intCast(@min(@max(y2, 0), metrics.cell_height)), + .on, + ); +} + +/// Centered vertical line of the provided thickness. +pub fn vlineMiddle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + vline( + canvas, + 0, + @intCast(metrics.cell_height), + @intCast((metrics.cell_width -| thick_px) / 2), + thick_px, + ); +} + +/// Centered horizontal line of the provided thickness. +pub fn hlineMiddle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + hline( + canvas, + 0, + @intCast(metrics.cell_width), + @intCast((metrics.cell_height -| thick_px) / 2), + thick_px, + ); +} + +/// Vertical line with the left edge at `x`, between `y1` and `y2`. +pub fn vline( + canvas: *font.sprite.Canvas, + y1: i32, + y2: i32, + x: i32, + thickness_px: u32, +) void { + canvas.box(x, y1, x + @as(i32, @intCast(thickness_px)), y2, .on); +} + +/// Horizontal line with the top edge at `y`, between `x1` and `x2`. +pub fn hline( + canvas: *font.sprite.Canvas, + x1: i32, + x2: i32, + y: i32, + thickness_px: u32, +) void { + canvas.box(x1, y, x2, y + @as(i32, @intCast(thickness_px)), .on); +} + +/// xHalfs[0] should be used as the right edge of a left-aligned half. +/// xHalfs[1] should be used as the left edge of a right-aligned half. +pub fn xHalfs(metrics: font.Metrics) [2]u32 { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const half_width: u32 = @intFromFloat(@round(0.5 * float_width)); + return .{ half_width, metrics.cell_width - half_width }; +} + +/// Use these values as such: +/// yThirds[0] bottom edge of the first third. +/// yThirds[1] top edge of the second third. +/// yThirds[2] bottom edge of the second third. +/// yThirds[3] top edge of the final third. +pub fn yThirds(metrics: font.Metrics) [4]u32 { + const float_height: f64 = @floatFromInt(metrics.cell_height); + const one_third_height: u32 = @intFromFloat(@round(one_third * float_height)); + const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height)); + return .{ + one_third_height, + metrics.cell_height - two_thirds_height, + two_thirds_height, + metrics.cell_height - one_third_height, + }; +} + +/// Use these values as such: +/// yQuads[0] bottom edge of first quarter. +/// yQuads[1] top edge of second quarter. +/// yQuads[2] bottom edge of second quarter. +/// yQuads[3] top edge of third quarter. +/// yQuads[4] bottom edge of third quarter +/// yQuads[5] top edge of fourth quarter. +pub fn yQuads(metrics: font.Metrics) [6]u32 { + const float_height: f64 = @floatFromInt(metrics.cell_height); + const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height)); + const half_height: u32 = @intFromFloat(@round(0.50 * float_height)); + const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height)); + return .{ + quarter_height, + metrics.cell_height - three_quarters_height, + half_height, + metrics.cell_height - half_height, + three_quarters_height, + metrics.cell_height - quarter_height, + }; +} diff --git a/src/font/sprite/draw/geometric_shapes.zig b/src/font/sprite/draw/geometric_shapes.zig new file mode 100644 index 000000000..d95a4fd2f --- /dev/null +++ b/src/font/sprite/draw/geometric_shapes.zig @@ -0,0 +1,200 @@ +//! Geometric Shapes | U+25A0...U+25FF +//! https://en.wikipedia.org/wiki/Geometric_Shapes_(Unicode_block) +//! +//! ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ +//! ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ +//! ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● +//! ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ +//! ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯ +//! ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◽︎◾︎◿ +//! +//! Only a subset of this block is viable for sprite drawing; filling +//! out this file to have full coverage of this block is not the goal. +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Corner = common.Corner; +const Shade = common.Shade; + +const font = @import("../../main.zig"); + +/// ◢ ◣ ◤ ◥ +pub fn draw25E2_25E5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + switch (cp) { + // ◢ + 0x25e2 => try cornerTriangleShade(metrics, canvas, .br, .on), + // ◣ + 0x25e3 => try cornerTriangleShade(metrics, canvas, .bl, .on), + // ◤ + 0x25e4 => try cornerTriangleShade(metrics, canvas, .tl, .on), + // ◥ + 0x25e5 => try cornerTriangleShade(metrics, canvas, .tr, .on), + + else => unreachable, + } +} + +/// ◸ ◹ ◺ +pub fn draw25F8_25FA( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + switch (cp) { + // ◸ + 0x25f8 => try cornerTriangleOutline(metrics, canvas, .tl), + // ◹ + 0x25f9 => try cornerTriangleOutline(metrics, canvas, .tr), + // ◺ + 0x25fa => try cornerTriangleOutline(metrics, canvas, .bl), + + else => unreachable, + } +} + +/// ◿ +pub fn draw25FF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + try cornerTriangleOutline(metrics, canvas, .br); +} + +pub fn cornerTriangleShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, + comptime shade: Shade, +) !void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0, const y0, const x1, const y1, const x2, const y2 = + switch (corner) { + .tl => .{ + 0, + 0, + 0, + float_height, + float_width, + 0, + }, + .tr => .{ + 0, + 0, + float_width, + float_height, + float_width, + 0, + }, + .bl => .{ + 0, + 0, + 0, + float_height, + float_width, + float_height, + }, + .br => .{ + 0, + float_height, + float_width, + float_height, + float_width, + 0, + }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(x0, y0); // +1, nodes.len = 1 + path.lineTo(x1, y1); // +1, nodes.len = 2 + path.lineTo(x2, y2); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.fillPath( + path.wrapped_path, + .{}, + @enumFromInt(@intFromEnum(shade)), + ); +} + +pub fn cornerTriangleOutline( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, +) !void { + const float_thick: f64 = @floatFromInt(Thickness.light.height(metrics.box_thickness)); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0, const y0, const x1, const y1, const x2, const y2 = + switch (corner) { + .tl => .{ + 0, + 0, + 0, + float_height, + float_width, + 0, + }, + .tr => .{ + 0, + 0, + float_width, + float_height, + float_width, + 0, + }, + .bl => .{ + 0, + 0, + 0, + float_height, + float_width, + float_height, + }, + .br => .{ + 0, + float_height, + float_width, + float_height, + float_width, + 0, + }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(x0, y0); // +1, nodes.len = 1 + path.lineTo(x1, y1); // +1, nodes.len = 2 + path.lineTo(x2, y2); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.innerStrokePath(path.wrapped_path, .{ + .line_cap_mode = .butt, + .line_width = float_thick, + }, .on); +} diff --git a/src/font/sprite/octants.txt b/src/font/sprite/draw/octants.txt similarity index 100% rename from src/font/sprite/octants.txt rename to src/font/sprite/draw/octants.txt diff --git a/src/font/sprite/draw/powerline.zig b/src/font/sprite/draw/powerline.zig new file mode 100644 index 000000000..24fce454b --- /dev/null +++ b/src/font/sprite/draw/powerline.zig @@ -0,0 +1,396 @@ +//! Powerline + Powerline Extra Symbols | U+E0B0...U+E0D4 +//! https://github.com/ryanoasis/powerline-extra-symbols +//! +//!                 +//!               +//!     +//! +//! We implement the more geometric glyphs here, but not the stylized ones. +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; + +const box = @import("box.zig"); + +const font = @import("../../main.zig"); +const Quad = font.sprite.Canvas.Quad; + +///  +pub fn drawE0B0( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height / 2 }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B2( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = float_width, .y = 0 }, + .p1 = .{ .x = 0, .y = float_height / 2 }, + .p2 = .{ .x = float_width, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B8( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B9( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +///  +pub fn drawE0BA( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = float_width, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BB( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperRightToLowerLeft(metrics, canvas); +} + +///  +pub fn drawE0BC( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = 0 }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BD( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperRightToLowerLeft(metrics, canvas); +} + +///  +pub fn drawE0BE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = 0 }, + .p2 = .{ .x = float_width, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +///  +pub fn drawE0B1( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + var path = canvas.staticPath(3); + path.moveTo(0, 0); + path.lineTo(float_width, float_height / 2); + path.lineTo(0, float_height); + + try canvas.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = @floatFromInt( + Thickness.light.height(metrics.box_thickness), + ), + }, + .on, + ); +} + +///  +pub fn drawE0B3( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B1(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0B4( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + + const radius: f64 = @min(float_width, float_height / 2); + + var path = canvas.staticPath(6); + path.moveTo(0, 0); + path.curveTo( + radius * c, + 0, + radius, + radius - radius * c, + radius, + radius, + ); + path.lineTo(radius, float_height - radius); + path.curveTo( + radius, + float_height - radius + radius * c, + radius * c, + float_height, + 0, + float_height, + ); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +///  +pub fn drawE0B5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + + const radius: f64 = @min(float_width, float_height / 2); + + var path = canvas.staticPath(4); + path.moveTo(0, 0); + path.curveTo( + radius * c, + 0, + radius, + radius - radius * c, + radius, + radius, + ); + path.lineTo(radius, float_height - radius); + path.curveTo( + radius, + float_height - radius + radius * c, + radius * c, + float_height, + 0, + float_height, + ); + + try canvas.innerStrokePath(path.wrapped_path, .{ + .line_width = @floatFromInt(metrics.box_thickness), + .line_cap_mode = .butt, + }, .on); +} + +///  +pub fn drawE0B6( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B4(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0B7( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B5(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0D2( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + const float_thick: f64 = @floatFromInt(metrics.box_thickness); + + // Top piece + { + var path = canvas.staticPath(6); + path.moveTo(0, 0); + path.lineTo(float_width, 0); + path.lineTo(float_width / 2, float_height / 2 - float_thick / 2); + path.lineTo(0, float_height / 2 - float_thick / 2); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); + } + + // Bottom piece + { + var path = canvas.staticPath(6); + path.moveTo(0, float_height); + path.lineTo(float_width, float_height); + path.lineTo(float_width / 2, float_height / 2 + float_thick / 2); + path.lineTo(0, float_height / 2 + float_thick / 2); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); + } +} + +///  +pub fn drawE0D4( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0D2(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} diff --git a/src/font/sprite/draw/special.zig b/src/font/sprite/draw/special.zig new file mode 100644 index 000000000..3d75360e3 --- /dev/null +++ b/src/font/sprite/draw/special.zig @@ -0,0 +1,328 @@ +//! This file contains glyph drawing functions for all of the +//! non-Unicode sprite glyphs, such as cursors and underlines. +//! +//! The naming convention in this file differs from the usual +//! because the draw functions for special sprites are found by +//! having names that exactly match the enum fields in Sprite. + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const font = @import("../../main.zig"); +const Sprite = font.sprite.Sprite; + +pub fn underline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); +} + +pub fn underline_double( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // We place one underline above the underline position, and one below + // by one thickness, creating a "negative" underline where the single + // underline would be placed. + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position -| metrics.underline_thickness), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position +| metrics.underline_thickness), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); +} + +pub fn underline_dotted( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // TODO: Rework this now that we can go out of bounds, just + // make sure that adjacent versions of this glyph align. + const dot_width = @max(metrics.underline_thickness, 3); + const dot_count = @max((width / dot_width) / 2, 1); + const gap_width = std.math.divCeil( + u32, + width -| (dot_count * dot_width), + dot_count, + ) catch return error.MathError; + var i: u32 = 0; + while (i < dot_count) : (i += 1) { + // Ensure we never go out of bounds for the rect + const x = @min(i * (dot_width + gap_width), width - 1); + const rect_width = @min(width - x, dot_width); + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(metrics.underline_position), + .width = @intCast(rect_width), + .height = @intCast(metrics.underline_thickness), + }, .on); + } +} + +pub fn underline_dashed( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + const dash_width = width / 3 + 1; + const dash_count = (width / dash_width) + 1; + var i: u32 = 0; + while (i < dash_count) : (i += 2) { + // Ensure we never go out of bounds for the rect + const x = @min(i * dash_width, width - 1); + const rect_width = @min(width - x, dash_width); + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(metrics.underline_position), + .width = @intCast(rect_width), + .height = @intCast(metrics.underline_thickness), + }, .on); + } +} + +pub fn underline_curly( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // TODO: Rework this using z2d, this is pretty cool code and all but + // it doesn't need to be highly optimized and z2d path drawing + // code would be clearer and nicer to have. + + const float_width: f64 = @floatFromInt(width); + // Because of we way we draw the undercurl, we end up making it around 1px + // thicker than it should be, to fix this we just reduce the thickness by 1. + // + // We use a minimum thickness of 0.414 because this empirically produces + // the nicest undercurls at 1px underline thickness; thinner tends to look + // too thin compared to straight underlines and has artefacting. + const float_thick: f64 = @max( + 0.414, + @as(f64, @floatFromInt(metrics.underline_thickness -| 1)), + ); + + // Calculate the wave period for a single character + // `2 * pi...` = 1 peak per character + // `4 * pi...` = 2 peaks per character + const wave_period = 2 * std.math.pi / float_width; + + // The full amplitude of the wave can be from the bottom to the + // underline position. We also calculate our mid y point of the wave + const half_amplitude = 1.0 / wave_period; + const y_mid: f64 = half_amplitude + float_thick * 0.5 + 1; + + // Offset to move the undercurl up slightly. + const y_off: u32 = @intFromFloat(half_amplitude * 0.5); + + // This is used in calculating the offset curve estimate below. + const offset_factor = @min(1.0, float_thick * 0.5 * wave_period) * @min( + 1.0, + half_amplitude * wave_period, + ); + + // follow Xiaolin Wu's antialias algorithm to draw the curve + var x: u32 = 0; + while (x < width) : (x += 1) { + // We sample the wave function at the *middle* of each + // pixel column, to ensure that it renders symmetrically. + const t: f64 = (@as(f64, @floatFromInt(x)) + 0.5) * wave_period; + // Use the slope at this location to add thickness to + // the line on this column, counteracting the thinning + // caused by the slope. + // + // This is not the exact offset curve for a sine wave, + // but it's a decent enough approximation. + // + // How did I derive this? I stared at Desmos and fiddled + // with numbers for an hour until it was good enough. + const t_u: f64 = t + std.math.pi; + const slope_factor_u: f64 = + (@sin(t_u) * @sin(t_u) * offset_factor) / + ((1.0 + @cos(t_u / 2) * @cos(t_u / 2) * 2) * wave_period); + const slope_factor_l: f64 = + (@sin(t) * @sin(t) * offset_factor) / + ((1.0 + @cos(t / 2) * @cos(t / 2) * 2) * wave_period); + + const cosx: f64 = @cos(t); + // This will be the center of our stroke. + const y: f64 = y_mid + half_amplitude * cosx; + + // The upper pixel and lower pixel are + // calculated relative to the center. + const y_u: f64 = y - float_thick * 0.5 - slope_factor_u; + const y_l: f64 = y + float_thick * 0.5 + slope_factor_l; + const y_upper: u32 = @intFromFloat(@floor(y_u)); + const y_lower: u32 = @intFromFloat(@ceil(y_l)); + const alpha_u: u8 = @intFromFloat( + @round(255 * (1.0 - @abs(y_u - @floor(y_u)))), + ); + const alpha_l: u8 = @intFromFloat( + @round(255 * (1.0 - @abs(y_l - @ceil(y_l)))), + ); + + // upper and lower bounds + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_upper -| y_off), + @enumFromInt(alpha_u), + ); + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_lower -| y_off), + @enumFromInt(alpha_l), + ); + + // fill between upper and lower bound + var y_fill: u32 = y_upper + 1; + while (y_fill < y_lower) : (y_fill += 1) { + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_fill -| y_off), + .on, + ); + } + } +} + +pub fn strikethrough( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.strikethrough_position), + .width = @intCast(width), + .height = @intCast(metrics.strikethrough_thickness), + }, .on); +} + +pub fn overline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.overline_position), + .width = @intCast(width), + .height = @intCast(metrics.overline_thickness), + }, .on); +} + +pub fn cursor_rect( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + + canvas.rect(.{ + .x = 0, + .y = 0, + .width = @intCast(width), + .height = @intCast(height), + }, .on); +} + +pub fn cursor_hollow_rect( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + // We fill the entire rect and then hollow out the inside, this isn't very + // efficient but it doesn't need to be and it's the easiest way to write it. + canvas.rect(.{ + .x = 0, + .y = 0, + .width = @intCast(width), + .height = @intCast(height), + }, .on); + canvas.rect(.{ + .x = @intCast(metrics.cursor_thickness), + .y = @intCast(metrics.cursor_thickness), + .width = @intCast(width -| metrics.cursor_thickness * 2), + .height = @intCast(height -| metrics.cursor_thickness * 2), + }, .off); +} + +pub fn cursor_bar( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + + // We place the bar cursor half of its thickness over the left edge of the + // cell, so that it sits centered between characters, not biased to a side. + // + // We round up (add 1 before dividing by 2) because, empirically, having a + // 1px cursor shifted left a pixel looks better than having it not shifted. + canvas.rect(.{ + .x = -@as(i32, @intCast((metrics.cursor_thickness + 1) / 2)), + .y = 0, + .width = @intCast(metrics.cursor_thickness), + .height = @intCast(height), + }, .on); +} diff --git a/src/font/sprite/draw/symbols_for_legacy_computing.zig b/src/font/sprite/draw/symbols_for_legacy_computing.zig new file mode 100644 index 000000000..a17ddb494 --- /dev/null +++ b/src/font/sprite/draw/symbols_for_legacy_computing.zig @@ -0,0 +1,1431 @@ +//! Symbols for Legacy Computing | U+1FB00...U+1FBFF +//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing +//! +//! 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 +//! 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 +//! 🬠 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 +//! 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 🬼 🬽 🬾 🬿 +//! 🭀 🭁 🭂 🭃 🭄 🭅 🭆 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 +//! 🭐 🭑 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 🭝 🭞 🭟 +//! 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 +//! 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 🭼 🭽 🭾 🭿 +//! 🮀 🮁 🮂 🮃 🮄 🮅 🮆 🮇 🮈 🮉 🮊 🮋 🮌 🮍 🮎 🮏 +//! 🮐 🮑 🮒 🮔 🮕 🮖 🮗 🮘 🮙 🮚 🮛 🮜 🮝 🮞 🮟 +//! 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 🮯 +//! 🮰 🮱 🮲 🮳 🮴 🮵 🮶 🮷 🮸 🮹 🮺 🮻 🮼 🮽 🮾 🮿 +//! 🯀 🯁 🯂 🯃 🯄 🯅 🯆 🯇 🯈 🯉 🯊 🯋 🯌 🯍 🯎 🯏 +//! 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 +//! 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 +//! 🯰 🯱 🯲 🯳 🯴 🯵 🯶 🯷 🯸 🯹 +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Alignment = common.Alignment; +const Corner = common.Corner; +const Quads = common.Quads; +const Edge = common.Edge; +const Shade = common.Shade; +const xHalfs = common.xHalfs; +const yThirds = common.yThirds; +const rect = common.rect; + +const box = @import("box.zig"); +const block = @import("block.zig"); +const geo = @import("geometric_shapes.zig"); + +const font = @import("../../main.zig"); + +// Utility names for common fractions +const one_eighth: f64 = 0.125; +const one_quarter: f64 = 0.25; +const one_third: f64 = (1.0 / 3.0); +const three_eighths: f64 = 0.375; +const half: f64 = 0.5; +const five_eighths: f64 = 0.625; +const two_thirds: f64 = (2.0 / 3.0); +const three_quarters: f64 = 0.75; +const seven_eighths: f64 = 0.875; + +const SmoothMosaic = packed struct(u10) { + tl: bool, + ul: bool, + ll: bool, + bl: bool, + bc: bool, + br: bool, + lr: bool, + ur: bool, + tr: bool, + tc: bool, + + fn from(comptime pattern: *const [15:0]u8) SmoothMosaic { + return .{ + .tl = pattern[0] == '#', + + .ul = pattern[4] == '#' and + (pattern[0] != '#' or pattern[8] != '#'), + + .ll = pattern[8] == '#' and + (pattern[4] != '#' or pattern[12] != '#'), + + .bl = pattern[12] == '#', + + .bc = pattern[13] == '#' and + (pattern[12] != '#' or pattern[14] != '#'), + + .br = pattern[14] == '#', + + .lr = pattern[10] == '#' and + (pattern[14] != '#' or pattern[6] != '#'), + + .ur = pattern[6] == '#' and + (pattern[10] != '#' or pattern[2] != '#'), + + .tr = pattern[2] == '#', + + .tc = pattern[1] == '#' and + (pattern[2] != '#' or pattern[0] != '#'), + }; + } +}; + +/// Sextants +pub fn draw1FB00_1FB3B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const Sextants = packed struct(u6) { + tl: bool, + tr: bool, + ml: bool, + mr: bool, + bl: bool, + br: bool, + }; + + assert(cp >= 0x1fb00 and cp <= 0x1fb3b); + const idx = cp - 0x1fb00; + const sex: Sextants = @bitCast(@as(u6, @intCast( + idx + (idx / 0x14) + 1, + ))); + + const x_halfs = xHalfs(metrics); + const y_thirds = yThirds(metrics); + + if (sex.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_thirds[0]); + if (sex.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_thirds[0]); + if (sex.ml) rect(metrics, canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]); + if (sex.mr) rect(metrics, canvas, x_halfs[1], y_thirds[1], metrics.cell_width, y_thirds[2]); + if (sex.bl) rect(metrics, canvas, 0, y_thirds[3], x_halfs[0], metrics.cell_height); + if (sex.br) rect(metrics, canvas, x_halfs[1], y_thirds[3], metrics.cell_width, metrics.cell_height); +} + +/// Smooth Mosaics +pub fn draw1FB3C_1FB67( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + // Hand written lookup table for these shapes since I couldn't + // determine any sort of mathematical pattern in the codepoints. + const mosaic: SmoothMosaic = switch (cp) { + // '🬼' + 0x1fb3c => SmoothMosaic.from( + \\... + \\... + \\#.. + \\##. + ), + // '🬽' + 0x1fb3d => SmoothMosaic.from( + \\... + \\... + \\#\. + \\### + ), + // '🬾' + 0x1fb3e => SmoothMosaic.from( + \\... + \\#.. + \\#\. + \\##. + ), + // '🬿' + 0x1fb3f => SmoothMosaic.from( + \\... + \\#.. + \\##. + \\### + ), + // '🭀' + 0x1fb40 => SmoothMosaic.from( + \\#.. + \\#.. + \\##. + \\##. + ), + + // '🭁' + 0x1fb41 => SmoothMosaic.from( + \\/## + \\### + \\### + \\### + ), + // '🭂' + 0x1fb42 => SmoothMosaic.from( + \\./# + \\### + \\### + \\### + ), + // '🭃' + 0x1fb43 => SmoothMosaic.from( + \\.## + \\.## + \\### + \\### + ), + // '🭄' + 0x1fb44 => SmoothMosaic.from( + \\..# + \\.## + \\### + \\### + ), + // '🭅' + 0x1fb45 => SmoothMosaic.from( + \\.## + \\.## + \\.## + \\### + ), + // '🭆' + 0x1fb46 => SmoothMosaic.from( + \\... + \\./# + \\### + \\### + ), + + // '🭇' + 0x1fb47 => SmoothMosaic.from( + \\... + \\... + \\..# + \\.## + ), + // '🭈' + 0x1fb48 => SmoothMosaic.from( + \\... + \\... + \\./# + \\### + ), + // '🭉' + 0x1fb49 => SmoothMosaic.from( + \\... + \\..# + \\./# + \\.## + ), + // '🭊' + 0x1fb4a => SmoothMosaic.from( + \\... + \\..# + \\.## + \\### + ), + // '🭋' + 0x1fb4b => SmoothMosaic.from( + \\..# + \\..# + \\.## + \\.## + ), + + // '🭌' + 0x1fb4c => SmoothMosaic.from( + \\##\ + \\### + \\### + \\### + ), + // '🭍' + 0x1fb4d => SmoothMosaic.from( + \\#\. + \\### + \\### + \\### + ), + // '🭎' + 0x1fb4e => SmoothMosaic.from( + \\##. + \\##. + \\### + \\### + ), + // '🭏' + 0x1fb4f => SmoothMosaic.from( + \\#.. + \\##. + \\### + \\### + ), + // '🭐' + 0x1fb50 => SmoothMosaic.from( + \\##. + \\##. + \\##. + \\### + ), + // '🭑' + 0x1fb51 => SmoothMosaic.from( + \\... + \\#\. + \\### + \\### + ), + + // '🭒' + 0x1fb52 => SmoothMosaic.from( + \\### + \\### + \\### + \\\## + ), + // '🭓' + 0x1fb53 => SmoothMosaic.from( + \\### + \\### + \\### + \\.\# + ), + // '🭔' + 0x1fb54 => SmoothMosaic.from( + \\### + \\### + \\.## + \\.## + ), + // '🭕' + 0x1fb55 => SmoothMosaic.from( + \\### + \\### + \\.## + \\..# + ), + // '🭖' + 0x1fb56 => SmoothMosaic.from( + \\### + \\.## + \\.## + \\.## + ), + + // '🭗' + 0x1fb57 => SmoothMosaic.from( + \\##. + \\#.. + \\... + \\... + ), + // '🭘' + 0x1fb58 => SmoothMosaic.from( + \\### + \\#/. + \\... + \\... + ), + // '🭙' + 0x1fb59 => SmoothMosaic.from( + \\##. + \\#/. + \\#.. + \\... + ), + // '🭚' + 0x1fb5a => SmoothMosaic.from( + \\### + \\##. + \\#.. + \\... + ), + // '🭛' + 0x1fb5b => SmoothMosaic.from( + \\##. + \\##. + \\#.. + \\#.. + ), + + // '🭜' + 0x1fb5c => SmoothMosaic.from( + \\### + \\### + \\#/. + \\... + ), + // '🭝' + 0x1fb5d => SmoothMosaic.from( + \\### + \\### + \\### + \\##/ + ), + // '🭞' + 0x1fb5e => SmoothMosaic.from( + \\### + \\### + \\### + \\#/. + ), + // '🭟' + 0x1fb5f => SmoothMosaic.from( + \\### + \\### + \\##. + \\##. + ), + // '🭠' + 0x1fb60 => SmoothMosaic.from( + \\### + \\### + \\##. + \\#.. + ), + // '🭡' + 0x1fb61 => SmoothMosaic.from( + \\### + \\##. + \\##. + \\##. + ), + + // '🭢' + 0x1fb62 => SmoothMosaic.from( + \\.## + \\..# + \\... + \\... + ), + // '🭣' + 0x1fb63 => SmoothMosaic.from( + \\### + \\.\# + \\... + \\... + ), + // '🭤' + 0x1fb64 => SmoothMosaic.from( + \\.## + \\.\# + \\..# + \\... + ), + // '🭥' + 0x1fb65 => SmoothMosaic.from( + \\### + \\.## + \\..# + \\... + ), + // '🭦' + 0x1fb66 => SmoothMosaic.from( + \\.## + \\.## + \\..# + \\..# + ), + // '🭧' + 0x1fb67 => SmoothMosaic.from( + \\### + \\### + \\.\# + \\... + ), + else => unreachable, + }; + + const y_thirds = yThirds(metrics); + const top: f64 = 0.0; + // We average the edge positions for the y_thirds boundaries here + // rather than having to deal with varying alignments depending on + // the surrounding pieces. The most this will be off by is half of + // a pixel, so hopefully it's not noticeable. + const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1]))); + const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3]))); + const bottom: f64 = @floatFromInt(metrics.cell_height); + const left: f64 = 0.0; + const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2); + const right: f64 = @floatFromInt(metrics.cell_width); + + var path = canvas.staticPath(12); // nodes.len = 0 + if (mosaic.tl) path.lineTo(left, top); // +1, nodes.len = 1 + if (mosaic.ul) path.lineTo(left, upper); // +1, nodes.len = 2 + if (mosaic.ll) path.lineTo(left, lower); // +1, nodes.len = 3 + if (mosaic.bl) path.lineTo(left, bottom); // +1, nodes.len = 4 + if (mosaic.bc) path.lineTo(center, bottom); // +1, nodes.len = 5 + if (mosaic.br) path.lineTo(right, bottom); // +1, nodes.len = 6 + if (mosaic.lr) path.lineTo(right, lower); // +1, nodes.len = 7 + if (mosaic.ur) path.lineTo(right, upper); // +1, nodes.len = 8 + if (mosaic.tr) path.lineTo(right, top); // +1, nodes.len = 9 + if (mosaic.tc) path.lineTo(center, top); // +1, nodes.len = 10 + path.close(); // +2, nodes.len = 12 + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +pub fn draw1FB68_1FB6F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🭨' + 0x1fb68 => { + try edgeTriangle(metrics, canvas, .left); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭩' + 0x1fb69 => { + try edgeTriangle(metrics, canvas, .top); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭪' + 0x1fb6a => { + try edgeTriangle(metrics, canvas, .right); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭫' + 0x1fb6b => { + try edgeTriangle(metrics, canvas, .bottom); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭬' + 0x1fb6c => try edgeTriangle(metrics, canvas, .left), + // '🭭' + 0x1fb6d => try edgeTriangle(metrics, canvas, .top), + // '🭮' + 0x1fb6e => try edgeTriangle(metrics, canvas, .right), + // '🭯' + 0x1fb6f => try edgeTriangle(metrics, canvas, .bottom), + + else => unreachable, + } +} + +/// Vertical one eighth blocks +pub fn draw1FB70_1FB75( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const n = cp + 1 - 0x1fb70; + + const x: u32 = @intFromFloat( + @round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(metrics.cell_width)) / 8), + ); + const w: u32 = @intFromFloat( + @round(@as(f64, @floatFromInt(metrics.cell_width)) / 8), + ); + rect(metrics, canvas, x, 0, x + w, metrics.cell_height); +} + +/// Horizontal one eighth blocks +pub fn draw1FB76_1FB7B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const n = cp + 1 - 0x1fb76; + + const h = @as( + u32, + @intFromFloat(@round(@as(f64, @floatFromInt(metrics.cell_height)) / 8)), + ); + const y = @min( + metrics.cell_height -| h, + @as( + u32, + @intFromFloat( + @round(@as(f64, @floatFromInt(n)) * + @as(f64, @floatFromInt(metrics.cell_height)) / 8), + ), + ), + ); + rect(metrics, canvas, 0, y, metrics.cell_width, y + h); +} + +pub fn draw1FB7C_1FB97( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + switch (cp) { + + // '🭼' LEFT AND LOWER ONE EIGHTH BLOCK + 0x1fb7c => { + block.block(metrics, canvas, .left, one_eighth, 1); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🭽' LEFT AND UPPER ONE EIGHTH BLOCK + 0x1fb7d => { + block.block(metrics, canvas, .left, one_eighth, 1); + block.block(metrics, canvas, .upper, 1, one_eighth); + }, + // '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK + 0x1fb7e => { + block.block(metrics, canvas, .right, one_eighth, 1); + block.block(metrics, canvas, .upper, 1, one_eighth); + }, + // '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK + 0x1fb7f => { + block.block(metrics, canvas, .right, one_eighth, 1); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🮀' UPPER AND LOWER ONE EIGHTH BLOCK + 0x1fb80 => { + block.block(metrics, canvas, .upper, 1, one_eighth); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🮁' Horizontal One Eighth Block 1358 + 0x1fb81 => { + // We just call the draw function for each of the relevant codepoints. + // The first codepoint is actually a lie, it's before the range, but + // we need it to get the first (0th) block position. This might be a + // bit brittle, oh well, if it breaks we can fix it. + try draw1FB76_1FB7B(0x1fb74 + 1, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 3, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 5, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 8, canvas, width, height, metrics); + }, + + // '🮂' UPPER ONE QUARTER BLOCK + 0x1fb82 => block.block(metrics, canvas, .upper, 1, one_quarter), + // '🮃' UPPER THREE EIGHTHS BLOCK + 0x1fb83 => block.block(metrics, canvas, .upper, 1, three_eighths), + // '🮄' UPPER FIVE EIGHTHS BLOCK + 0x1fb84 => block.block(metrics, canvas, .upper, 1, five_eighths), + // '🮅' UPPER THREE QUARTERS BLOCK + 0x1fb85 => block.block(metrics, canvas, .upper, 1, three_quarters), + // '🮆' UPPER SEVEN EIGHTHS BLOCK + 0x1fb86 => block.block(metrics, canvas, .upper, 1, seven_eighths), + + // '🮇' RIGHT ONE QUARTER BLOCK + 0x1fb87 => block.block(metrics, canvas, .right, one_quarter, 1), + // '🮈' RIGHT THREE EIGHTHS BLOCK + 0x1fb88 => block.block(metrics, canvas, .right, three_eighths, 1), + // '🮉' RIGHT FIVE EIGHTHS BLOCK + 0x1fb89 => block.block(metrics, canvas, .right, five_eighths, 1), + // '🮊' RIGHT THREE QUARTERS BLOCK + 0x1fb8a => block.block(metrics, canvas, .right, three_quarters, 1), + // '🮋' RIGHT SEVEN EIGHTHS BLOCK/ + 0x1fb8b => block.block(metrics, canvas, .right, seven_eighths, 1), + + // '🮌' + 0x1fb8c => block.blockShade(metrics, canvas, .left, half, 1, .medium), + // '🮍' + 0x1fb8d => block.blockShade(metrics, canvas, .right, half, 1, .medium), + // '🮎' + 0x1fb8e => block.blockShade(metrics, canvas, .upper, 1, half, .medium), + // '🮏' + 0x1fb8f => block.blockShade(metrics, canvas, .lower, 1, half, .medium), + + // '🮐' + 0x1fb90 => block.fullBlockShade(metrics, canvas, .medium), + // '🮑' + 0x1fb91 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .upper, 1, half); + }, + // '🮒' + 0x1fb92 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .lower, 1, half); + }, + 0x1fb93 => { + // NOTE: This codepoint is currently un-allocated, it's a hole + // in the unicode block, so it's safe to just render it + // as an empty glyph, probably. + }, + // '🮔' + 0x1fb94 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .right, half, 1); + }, + // '🮕' + 0x1fb95 => checkerboardFill(metrics, canvas, 0), + // '🮖' + 0x1fb96 => checkerboardFill(metrics, canvas, 1), + // '🮗' + 0x1fb97 => { + canvas.box( + 0, + @intCast(height / 4), + @intCast(width), + @intCast(2 * height / 4), + .on, + ); + canvas.box( + 0, + @intCast(3 * height / 4), + @intCast(width), + @intCast(height), + .on, + ); + }, + + else => unreachable, + } +} + +/// Upper Left to Lower Right Fill +/// 🮘 +pub fn draw1FB98( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // TODO: This doesn't align properly for most cell sizes, fix that. + + const thick_px = Thickness.light.height(metrics.box_thickness); + const line_count = metrics.cell_width / (2 * thick_px); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); + + for (0..line_count * 2 + 1) |_i| { + const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); + const top_x = @as(f64, @floatFromInt(i)) * stride; + const bottom_x = float_width + top_x; + canvas.line(.{ + .p0 = .{ .x = top_x, .y = 0 }, + .p1 = .{ .x = bottom_x, .y = float_height }, + }, float_thick, .on) catch {}; + } +} + +/// Upper Right to Lower Left Fill +/// 🮙 +pub fn draw1FB99( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // TODO: This doesn't align properly for most cell sizes, fix that. + + const thick_px = Thickness.light.height(metrics.box_thickness); + const line_count = metrics.cell_width / (2 * thick_px); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); + + for (0..line_count * 2 + 1) |_i| { + const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); + const bottom_x = @as(f64, @floatFromInt(i)) * stride; + const top_x = float_width + bottom_x; + canvas.line(.{ + .p0 = .{ .x = top_x, .y = 0 }, + .p1 = .{ .x = bottom_x, .y = float_height }, + }, float_thick, .on) catch {}; + } +} + +pub fn draw1FB9A_1FB9F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🮚' + 0x1fb9a => { + try edgeTriangle(metrics, canvas, .top); + try edgeTriangle(metrics, canvas, .bottom); + }, + // '🮛' + 0x1fb9b => { + try edgeTriangle(metrics, canvas, .left); + try edgeTriangle(metrics, canvas, .right); + }, + // '🮜' + 0x1fb9c => try geo.cornerTriangleShade(metrics, canvas, .tl, .medium), + // '🮝' + 0x1fb9d => try geo.cornerTriangleShade(metrics, canvas, .tr, .medium), + // '🮞' + 0x1fb9e => try geo.cornerTriangleShade(metrics, canvas, .br, .medium), + // '🮟' + 0x1fb9f => try geo.cornerTriangleShade(metrics, canvas, .bl, .medium), + + else => unreachable, + } +} + +pub fn draw1FBA0_1FBAE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🮠' + 0x1fba0 => cornerDiagonalLines(metrics, canvas, .{ .tl = true }), + // '🮡' + 0x1fba1 => cornerDiagonalLines(metrics, canvas, .{ .tr = true }), + // '🮢' + 0x1fba2 => cornerDiagonalLines(metrics, canvas, .{ .bl = true }), + // '🮣' + 0x1fba3 => cornerDiagonalLines(metrics, canvas, .{ .br = true }), + // '🮤' + 0x1fba4 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true }), + // '🮥' + 0x1fba5 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .br = true }), + // '🮦' + 0x1fba6 => cornerDiagonalLines(metrics, canvas, .{ .bl = true, .br = true }), + // '🮧' + 0x1fba7 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true }), + // '🮨' + 0x1fba8 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .br = true }), + // '🮩' + 0x1fba9 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true }), + // '🮪' + 0x1fbaa => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true, .br = true }), + // '🮫' + 0x1fbab => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true, .br = true }), + // '🮬' + 0x1fbac => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .br = true }), + // '🮭' + 0x1fbad => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }), + // '🮮' + 0x1fbae => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }), + + else => unreachable, + } +} + +/// 🮯 +pub fn draw1FBAF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + box.linesChar(metrics, canvas, .{ + .up = .heavy, + .down = .heavy, + .left = .light, + .right = .light, + }); +} + +/// 🮽 +pub fn draw1FBBD( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + box.lightDiagonalCross(metrics, canvas); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🮾 +pub fn draw1FBBE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + cornerDiagonalLines(metrics, canvas, .{ .br = true }); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🮿 +pub fn draw1FBBF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + cornerDiagonalLines(metrics, canvas, .{ + .tl = true, + .tr = true, + .bl = true, + .br = true, + }); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🯎 +pub fn draw1FBCE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + block.block(metrics, canvas, .left, two_thirds, 1); +} + +// 🯏 +pub fn draw1FBCF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + block.block(metrics, canvas, .left, one_third, 1); +} + +/// Cell diagonals. +pub fn draw1FBD0_1FBDF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🯐' + 0x1fbd0 => cellDiagonal( + metrics, + canvas, + .middle_right, + .lower_left, + ), + // '🯑' + 0x1fbd1 => cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_left, + ), + // '🯒' + 0x1fbd2 => cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_right, + ), + // '🯓' + 0x1fbd3 => cellDiagonal( + metrics, + canvas, + .middle_left, + .lower_right, + ), + // '🯔' + 0x1fbd4 => cellDiagonal( + metrics, + canvas, + .upper_left, + .lower_center, + ), + // '🯕' + 0x1fbd5 => cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_right, + ), + // '🯖' + 0x1fbd6 => cellDiagonal( + metrics, + canvas, + .upper_right, + .lower_center, + ), + // '🯗' + 0x1fbd7 => cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_left, + ), + // '🯘' + 0x1fbd8 => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .upper_right, + ); + }, + // '🯙' + 0x1fbd9 => { + cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_right, + ); + }, + // '🯚' + 0x1fbda => { + cellDiagonal( + metrics, + canvas, + .lower_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_right, + ); + }, + // '🯛' + 0x1fbdb => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_left, + ); + }, + // '🯜' + 0x1fbdc => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .lower_center, + ); + cellDiagonal( + metrics, + canvas, + .lower_center, + .upper_right, + ); + }, + // '🯝' + 0x1fbdd => { + cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_left, + ); + cellDiagonal( + metrics, + canvas, + .middle_left, + .lower_right, + ); + }, + // '🯞' + 0x1fbde => { + cellDiagonal( + metrics, + canvas, + .lower_left, + .upper_center, + ); + cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_right, + ); + }, + // '🯟' + 0x1fbdf => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_right, + ); + cellDiagonal( + metrics, + canvas, + .middle_right, + .lower_left, + ); + }, + + else => unreachable, + } +} + +pub fn draw1FBE0_1FBEF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🯠' + 0x1fbe0 => circle(metrics, canvas, .top, false), + // '🯡' + 0x1fbe1 => circle(metrics, canvas, .right, false), + // '🯢' + 0x1fbe2 => circle(metrics, canvas, .bottom, false), + // '🯣' + 0x1fbe3 => circle(metrics, canvas, .left, false), + // '🯤' + 0x1fbe4 => block.block(metrics, canvas, .upper_center, 0.5, 0.5), + // '🯥' + 0x1fbe5 => block.block(metrics, canvas, .lower_center, 0.5, 0.5), + // '🯦' + 0x1fbe6 => block.block(metrics, canvas, .middle_left, 0.5, 0.5), + // '🯧' + 0x1fbe7 => block.block(metrics, canvas, .middle_right, 0.5, 0.5), + // '🯨' + 0x1fbe8 => circle(metrics, canvas, .top, true), + // '🯩' + 0x1fbe9 => circle(metrics, canvas, .right, true), + // '🯪' + 0x1fbea => circle(metrics, canvas, .bottom, true), + // '🯫' + 0x1fbeb => circle(metrics, canvas, .left, true), + // '🯬' + 0x1fbec => circle(metrics, canvas, .top_right, true), + // '🯭' + 0x1fbed => circle(metrics, canvas, .bottom_left, true), + // '🯮' + 0x1fbee => circle(metrics, canvas, .bottom_right, true), + // '🯯' + 0x1fbef => circle(metrics, canvas, .top_left, true), + + else => unreachable, + } +} + +fn edgeTriangle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime edge: Edge, +) !void { + const upper: f64 = 0.0; + const middle: f64 = @round(@as(f64, @floatFromInt(metrics.cell_height)) / 2); + const lower: f64 = @floatFromInt(metrics.cell_height); + const left: f64 = 0.0; + const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2); + const right: f64 = @floatFromInt(metrics.cell_width); + + const x0, const y0, const x1, const y1 = switch (edge) { + .top => .{ right, upper, left, upper }, + .left => .{ left, upper, left, lower }, + .bottom => .{ left, lower, right, lower }, + .right => .{ right, lower, right, upper }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(center, middle); // +1, nodes.len = 1 + path.lineTo(x0, y0); // +1, nodes.len = 2 + path.lineTo(x1, y1); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +fn cornerDiagonalLines( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corners: Quads, +) void { + const thick_px = Thickness.light.height(metrics.box_thickness); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const center_x: f64 = @floatFromInt(metrics.cell_width / 2 + metrics.cell_width % 2); + const center_y: f64 = @floatFromInt(metrics.cell_height / 2 + metrics.cell_height % 2); + + if (corners.tl) canvas.line(.{ + .p0 = .{ .x = center_x, .y = 0 }, + .p1 = .{ .x = 0, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.tr) canvas.line(.{ + .p0 = .{ .x = center_x, .y = 0 }, + .p1 = .{ .x = float_width, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.bl) canvas.line(.{ + .p0 = .{ .x = center_x, .y = float_height }, + .p1 = .{ .x = 0, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.br) canvas.line(.{ + .p0 = .{ .x = center_x, .y = float_height }, + .p1 = .{ .x = float_width, .y = center_y }, + }, float_thick, .on) catch {}; +} + +fn cellDiagonal( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime from: Alignment, + comptime to: Alignment, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0: f64 = switch (from.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y0: f64 = switch (from.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + const x1: f64 = switch (to.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y1: f64 = switch (to.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + + canvas.line( + .{ + .p0 = .{ .x = x0, .y = y0 }, + .p1 = .{ .x = x1, .y = y1 }, + }, + @floatFromInt(Thickness.light.height(metrics.box_thickness)), + .on, + ) catch {}; +} + +fn checkerboardFill( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + parity: u1, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const x_size: usize = 4; + const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width))); + for (0..x_size) |x| { + const x0 = (metrics.cell_width * x) / x_size; + const x1 = (metrics.cell_width * (x + 1)) / x_size; + for (0..y_size) |y| { + const y0 = (metrics.cell_height * y) / y_size; + const y1 = (metrics.cell_height * (y + 1)) / y_size; + if ((x + y) % 2 == parity) { + canvas.rect(.{ + .x = @intCast(x0), + .y = @intCast(y0), + .width = @intCast(x1 -| x0), + .height = @intCast(y1 -| y0), + }, .on); + } + } + } +} + +fn circle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime position: Alignment, + comptime filled: bool, +) void { + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x: f64 = switch (position.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y: f64 = switch (position.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + const r: f64 = 0.5 * @min(float_width, float_height); + + var ctx = canvas.getContext(); + defer ctx.deinit(); + ctx.setSource(.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + } }); + ctx.setLineWidth( + @floatFromInt(Thickness.light.height(metrics.box_thickness)), + ); + + if (filled) { + ctx.arc(x, y, r, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.fill() catch return; + } else { + ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.stroke() catch return; + } +} diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig new file mode 100644 index 000000000..0a57a0439 --- /dev/null +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -0,0 +1,193 @@ +//! Symbols for Legacy Computing Supplement | U+1CC00...U+1CEBF +//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing_Supplement +//! +//! 𜰀 𜰁 𜰂 𜰃 𜰄 𜰅 𜰆 𜰇 𜰈 𜰉 𜰊 𜰋 𜰌 𜰍 𜰎 𜰏 +//! 𜰐 𜰑 𜰒 𜰓 𜰔 𜰕 𜰖 𜰗 𜰘 𜰙 𜰚 𜰛 𜰜 𜰝 𜰞 𜰟 +//! 𜰠 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 +//! 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿 +//! 𜱀 𜱁 𜱂 𜱃 𜱄 𜱅 𜱆 𜱇 𜱈 𜱉 𜱊 𜱋 𜱌 𜱍 𜱎 𜱏 +//! 𜱐 𜱑 𜱒 𜱓 𜱔 𜱕 𜱖 𜱗 𜱘 𜱙 𜱚 𜱛 𜱜 𜱝 𜱞 𜱟 +//! 𜱠 𜱡 𜱢 𜱣 𜱤 𜱥 𜱦 𜱧 𜱨 𜱩 𜱪 𜱫 𜱬 𜱭 𜱮 𜱯 +//! 𜱰 𜱱 𜱲 𜱳 𜱴 𜱵 𜱶 𜱷 𜱸 𜱹 𜱺 𜱻 𜱼 𜱽 𜱾 𜱿 +//! 𜲀 𜲁 𜲂 𜲃 𜲄 𜲅 𜲆 𜲇 𜲈 𜲉 𜲊 𜲋 𜲌 𜲍 𜲎 𜲏 +//! 𜲐 𜲑 𜲒 𜲓 𜲔 𜲕 𜲖 𜲗 𜲘 𜲙 𜲚 𜲛 𜲜 𜲝 𜲞 𜲟 +//! 𜲠 𜲡 𜲢 𜲣 𜲤 𜲥 𜲦 𜲧 𜲨 𜲩 𜲪 𜲫 𜲬 𜲭 𜲮 𜲯 +//! 𜲰 𜲱 𜲲 𜲳 𜲴 𜲵 𜲶 𜲷 𜲸 𜲹 𜲺 𜲻 𜲼 𜲽 𜲾 𜲿 +//! 𜳀 𜳁 𜳂 𜳃 𜳄 𜳅 𜳆 𜳇 𜳈 𜳉 𜳊 𜳋 𜳌 𜳍 𜳎 𜳏 +//! 𜳐 𜳑 𜳒 𜳓 𜳔 𜳕 𜳖 𜳗 𜳘 𜳙 𜳚 𜳛 𜳜 𜳝 𜳞 𜳟 +//! 𜳠 𜳡 𜳢 𜳣 𜳤 𜳥 𜳦 𜳧 𜳨 𜳩 𜳪 𜳫 𜳬 𜳭 𜳮 𜳯 +//! 𜳰 𜳱 𜳲 𜳳 𜳴 𜳵 𜳶 𜳷 𜳸 𜳹 +//! 𜴀 𜴁 𜴂 𜴃 𜴄 𜴅 𜴆 𜴇 𜴈 𜴉 𜴊 𜴋 𜴌 𜴍 𜴎 𜴏 +//! 𜴐 𜴑 𜴒 𜴓 𜴔 𜴕 𜴖 𜴗 𜴘 𜴙 𜴚 𜴛 𜴜 𜴝 𜴞 𜴟 +//! 𜴠 𜴡 𜴢 𜴣 𜴤 𜴥 𜴦 𜴧 𜴨 𜴩 𜴪 𜴫 𜴬 𜴭 𜴮 𜴯 +//! 𜴰 𜴱 𜴲 𜴳 𜴴 𜴵 𜴶 𜴷 𜴸 𜴹 𜴺 𜴻 𜴼 𜴽 𜴾 𜴿 +//! 𜵀 𜵁 𜵂 𜵃 𜵄 𜵅 𜵆 𜵇 𜵈 𜵉 𜵊 𜵋 𜵌 𜵍 𜵎 𜵏 +//! 𜵐 𜵑 𜵒 𜵓 𜵔 𜵕 𜵖 𜵗 𜵘 𜵙 𜵚 𜵛 𜵜 𜵝 𜵞 𜵟 +//! 𜵠 𜵡 𜵢 𜵣 𜵤 𜵥 𜵦 𜵧 𜵨 𜵩 𜵪 𜵫 𜵬 𜵭 𜵮 𜵯 +//! 𜵰 𜵱 𜵲 𜵳 𜵴 𜵵 𜵶 𜵷 𜵸 𜵹 𜵺 𜵻 𜵼 𜵽 𜵾 𜵿 +//! 𜶀 𜶁 𜶂 𜶃 𜶄 𜶅 𜶆 𜶇 𜶈 𜶉 𜶊 𜶋 𜶌 𜶍 𜶎 𜶏 +//! 𜶐 𜶑 𜶒 𜶓 𜶔 𜶕 𜶖 𜶗 𜶘 𜶙 𜶚 𜶛 𜶜 𜶝 𜶞 𜶟 +//! 𜶠 𜶡 𜶢 𜶣 𜶤 𜶥 𜶦 𜶧 𜶨 𜶩 𜶪 𜶫 𜶬 𜶭 𜶮 𜶯 +//! 𜶰 𜶱 𜶲 𜶳 𜶴 𜶵 𜶶 𜶷 𜶸 𜶹 𜶺 𜶻 𜶼 𜶽 𜶾 𜶿 +//! 𜷀 𜷁 𜷂 𜷃 𜷄 𜷅 𜷆 𜷇 𜷈 𜷉 𜷊 𜷋 𜷌 𜷍 𜷎 𜷏 +//! 𜷐 𜷑 𜷒 𜷓 𜷔 𜷕 𜷖 𜷗 𜷘 𜷙 𜷚 𜷛 𜷜 𜷝 𜷞 𜷟 +//! 𜷠 𜷡 𜷢 𜷣 𜷤 𜷥 𜷦 𜷧 𜷨 𜷩 𜷪 𜷫 𜷬 𜷭 𜷮 𜷯 +//! 𜷰 𜷱 𜷲 𜷳 𜷴 𜷵 𜷶 𜷷 𜷸 𜷹 𜷺 𜷻 𜷼 𜷽 𜷾 𜷿 +//! 𜸀 𜸁 𜸂 𜸃 𜸄 𜸅 𜸆 𜸇 𜸈 𜸉 𜸊 𜸋 𜸌 𜸍 𜸎 𜸏 +//! 𜸐 𜸑 𜸒 𜸓 𜸔 𜸕 𜸖 𜸗 𜸘 𜸙 𜸚 𜸛 𜸜 𜸝 𜸞 𜸟 +//! 𜸠 𜸡 𜸢 𜸣 𜸤 𜸥 𜸦 𜸧 𜸨 𜸩 𜸪 𜸫 𜸬 𜸭 𜸮 𜸯 +//! 𜸰 𜸱 𜸲 𜸳 𜸴 𜸵 𜸶 𜸷 𜸸 𜸹 𜸺 𜸻 𜸼 𜸽 𜸾 𜸿 +//! 𜹀 𜹁 𜹂 𜹃 𜹄 𜹅 𜹆 𜹇 𜹈 𜹉 𜹊 𜹋 𜹌 𜹍 𜹎 𜹏 +//! 𜹐 𜹑 𜹒 𜹓 𜹔 𜹕 𜹖 𜹗 𜹘 𜹙 𜹚 𜹛 𜹜 𜹝 𜹞 𜹟 +//! 𜹠 𜹡 𜹢 𜹣 𜹤 𜹥 𜹦 𜹧 𜹨 𜹩 𜹪 𜹫 𜹬 𜹭 𜹮 𜹯 +//! 𜹰 𜹱 𜹲 𜹳 𜹴 𜹵 𜹶 𜹷 𜹸 𜹹 𜹺 𜹻 𜹼 𜹽 𜹾 𜹿 +//! 𜺀 𜺁 𜺂 𜺃 𜺄 𜺅 𜺆 𜺇 𜺈 𜺉 𜺊 𜺋 𜺌 𜺍 𜺎 𜺏 +//! 𜺐 𜺑 𜺒 𜺓 𜺔 𜺕 𜺖 𜺗 𜺘 𜺙 𜺚 𜺛 𜺜 𜺝 𜺞 𜺟 +//! 𜺠 𜺡 𜺢 𜺣 𜺤 𜺥 𜺦 𜺧 𜺨 𜺩 𜺪 𜺫 𜺬 𜺭 𜺮 𜺯 +//! 𜺰 𜺱 𜺲 𜺳 +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Corner = common.Corner; +const Shade = common.Shade; +const xHalfs = common.xHalfs; +const yQuads = common.yQuads; +const rect = common.rect; + +const font = @import("../../main.zig"); + +const octant_min = 0x1cd00; +const octant_max = 0x1cde5; + +/// Octants +pub fn draw1CD00_1CDE5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + // Octant representation. We use the funny numeric string keys + // so its easier to parse the actual name used in the Symbols for + // Legacy Computing spec. + const Octant = packed struct(u8) { + @"1": bool = false, + @"2": bool = false, + @"3": bool = false, + @"4": bool = false, + @"5": bool = false, + @"6": bool = false, + @"7": bool = false, + @"8": bool = false, + }; + + // Parse the octant data. This is all done at comptime so + // that this is static data that is embedded in the binary. + const octants_len = octant_max - octant_min + 1; + const octants: [octants_len]Octant = comptime octants: { + @setEvalBranchQuota(10_000); + + var result: [octants_len]Octant = @splat(.{}); + var i: usize = 0; + + const data = @embedFile("octants.txt"); + var it = std.mem.splitScalar(u8, data, '\n'); + while (it.next()) |line| { + // Skip comments + if (line.len == 0 or line[0] == '#') continue; + + const current = &result[i]; + i += 1; + + // Octants are in the format "BLOCK OCTANT-1235". The numbers + // at the end are keys into our packed struct. Since we're + // at comptime we can metaprogram it all. + const idx = std.mem.indexOfScalar(u8, line, '-').?; + for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true; + } + + assert(i == octants_len); + break :octants result; + }; + + const x_halfs = xHalfs(metrics); + const y_quads = yQuads(metrics); + const oct = octants[cp - octant_min]; + if (oct.@"1") rect(metrics, canvas, 0, 0, x_halfs[0], y_quads[0]); + if (oct.@"2") rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_quads[0]); + if (oct.@"3") rect(metrics, canvas, 0, y_quads[1], x_halfs[0], y_quads[2]); + if (oct.@"4") rect(metrics, canvas, x_halfs[1], y_quads[1], metrics.cell_width, y_quads[2]); + if (oct.@"5") rect(metrics, canvas, 0, y_quads[3], x_halfs[0], y_quads[4]); + if (oct.@"6") rect(metrics, canvas, x_halfs[1], y_quads[3], metrics.cell_width, y_quads[4]); + if (oct.@"7") rect(metrics, canvas, 0, y_quads[5], x_halfs[0], metrics.cell_height); + if (oct.@"8") rect(metrics, canvas, x_halfs[1], y_quads[5], metrics.cell_width, metrics.cell_height); +} + +// Separated Block Quadrants +// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 +pub fn draw1CC21_1CC2F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + // Struct laid out to match the codepoint order so we can cast from it. + const Quads = packed struct(u4) { + tl: bool, + tr: bool, + bl: bool, + br: bool, + }; + + const quad: Quads = @bitCast(@as(u4, @truncate(cp - 0x1CC20))); + + const gap: i32 = @intCast(@max(1, width / 12)); + + const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2)); + const mid_gap_y: i32 = gap * 2 + @as(i32, @intCast(height % 2)); + + const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2); + const h: i32 = @divExact(@as(i32, @intCast(height)) - gap * 2 - mid_gap_y, 2); + + if (quad.tl) canvas.box( + gap, + gap, + gap + w, + gap + h, + .on, + ); + if (quad.tr) canvas.box( + gap + w + mid_gap_x, + gap, + gap + w + mid_gap_x + w, + gap + h, + .on, + ); + if (quad.bl) canvas.box( + gap, + gap + h + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h, + .on, + ); + if (quad.br) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h, + .on, + ); +} diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm deleted file mode 100644 index 6082475af7d3e2264cf04061e9f63d7c6e6fdc9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1048593 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 z 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 z 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 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..8e54879661a7e7ff268d9ee5819a4dd253c98633 GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|Vn-PZ!6KiaBo%7&0<2 zFdSO&)jyY8Ujw873H*>`U|{(F|IOu#%MJ%fv|RjN|EgB+TC%u!q(_3t& Z++hvq|84tv-WeN^#h$KyF6*2UngE#jpY;F$ literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd28ba0d428b099b2f709b64e4458d7770942d6 GIT binary patch literal 1022 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfr0s`r;B4q#hf<>3>g_1 z7!ED?>YvN4uK`kk1a?R=Ffjc8|K_kEF9QQZ!-9Wnbsus{Ga;#9U|=xdf~aZaVPIf5 zpiuu>IcC8&7F0D4Six$pIC3=yFdPYZU;nZ!@P*%s5H?Qz2gla8D6Zf>P`Jk~rs_%c zz8FWL{z>|Tt42I9ALDk^)`GFX8C73=hMh_fJpZh=q2kt3Mj~@s@ z_Y|hb59pw)!Spz$nzIl1(E|B^FuFOlJ?If}0GhgRhfRSX*c9obixWh8ngSby85lC6 WKOK2{rEn1_U3j|sxvX9m z?p%L3fu;Yrp^Oq&t7B^rSL?y^T#O&qb2Bjf|NrK&Auj_1L&Ji9YyX~QkY<9J1px(& z5H*aPU^V~OOHXP-Q^LcFrsTIE7syLU;Day&nwr~!8X8Cn7#J81xP#TaJ>19x;wjX> g)|OeYjfH_Bf%A2D-GueIETtf2p00i_>zopr0BHeV%K!iX literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..d9b2aef99d9f2940399c7b29f5191bfef318ed62 GIT binary patch literal 1275 zcmXZceN>WH90%|V4?ezuFQr1x%9XwBgoImVGr%+*J!#pC7PHi6$68A)vDq;3Bxq%= zki|~9vgS-(GgCQp6$f7cIbvoS>432$C=%7b-#xp18=K@6 z>;(WIA)DiN0stHU;2a(R0MLfCy#s*IfyBim=U*K)s&8sS{Wd;wx{_|_;@x)et_rXG z+*~lfuzMfBjV63;&KPR4{Ke#-^5s{OLK;~CQB;o1swt^RVBZ-n>%ARIj$U~wlp{mc zzJyZ4z4I-8RE}d%eopBP1Awk-$dC+~(psMvN%p16P_>>BwR_x4yx_+XyCsw(vrein z*AA#x(cWDSTz;fL-{s~VRQMeo05Cp@1Hk1PHOBaKV7&L03R;EVMs3t-_0)XwF1O=m z|LF%!bw=~Y+=NBJ6iR-(hL`=uOPrL-%*YvKK`;!{K4le^bfyQ<=?+g6k%l4tN@L3> zjp@tx80=$CQ!0vDpIM(ULMn=`NUC-x$+i?U<0%w5b^U%Y6FkkwL68w1*6Sh{%zqr` zan-Oq77JN+%uG@)WO5CsPcT>s)5#Hdx&yJ2!7SFGK6)yYxbcz8mDRHUw0~j%b-K?S z@BZLW5RG`^gX?~LvwD;ELU=45UZh`AKB|> zac{X186M8Jxfhv2ktnBl86sU%U;Ss{(+ivlRGcKY!pSUE?hZLE>M{Tw#tLz zxt!0c!w_k^Zlv6vYj~dBT`Lla$lk_8@n!Ggud)a*O!H}PpY)v5#rTxWb>MFr;=zFl z>Q?1P^?B(nx%%ldPs1GVa?7#nZcuLsx8=Yvd<066h8>EnEpN;l+wvjWI%9ve%Q@qqYfF#cR%^88B*44trrc9GZhzpm+cKzRk2pUPCcr0;emF>oy#MXgne-k)3?Wh8*0gV>y-bT1xF{+ni)$nL@3L8F2f77{k3GbZ0014o%rrDiZ2d+klgVVffiw#ChXDYDa-3Xu@_J_FF*$Zb M;$#-hdl0f3HG3jhEB literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..0a616d72cc695dac524733880abe56fa572797a1 GIT binary patch literal 1870 zcmZWq3s93+7XC>>2r&r(!UCaogAEp?lti!*v+Vw15lEE~At>^a5(cwjpj4~~tcLuN zmxv%K_yFBsc2{GN0PH-Ff5;oLX?j1^aznmV3zzcG14k1x+f58W7QQ4$BTv%V^(Ksp`9 zD2GSBX!h=s#{K-9d6zvO26fp(7>hCIJ9B zR{d|MA3xIM)xCJ&@c9pg9^X{hA9VNpSiz0r#9S+=nljAl$+4Bw2`kk-U!iORh%D61 zT|v5bgz>w9?XvH)hJOzLfXOudn2zY}1q&%>8p9anu{ki0s-p=-rdo?E{P zL=X3yl>p*@>A5j6u6{PF#}57mpJ+~UMnX*&VqOfb)+AnX5j@2E=+We(NPMd}@15L= zgKzhumx^(T6lAWwnSR;F8}~O5?$dDS|GX9nogz!+3!yP&oRO2zkaoF^ehSPE)D+d& zoPRYj{Z6--j&NJ;_LCgA_dU z=ZIo4f)C$wa;KrOXT=EV5fUubDZ+uzF zxl-xQ+Sp!SHD-(ZN3I1F=v((frn~2_u~yn2Tz}0=epTa|u2Ix|=kTOCp8}1{xE8<} z*)7&eQ+vhqu&!oz<*oU8FW{#aE5m=`ZJyrN>w>zvR6r-S`Aoz;SdSwsM2@)<5 z+52{Mx2I*jIOihF2qW*MH_m=*Zg!l;MHr?RHBiNG;=%R*v%32>U03Wpm^%_O>K#E5qqh4X zYBw#kcC6yVGq{yEnX;aN%>zl31floWR?W_4D)$guHH{O8Uti6Xe)(nIMnlA6NEdCg zQ| literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..ecbb650a1c0b08f8038ff65ecd1a70d91f5b8e37 GIT binary patch literal 3404 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 B*17-y literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6cc75fa6d12fd7fe88e1caa1bfc6382b39a733 GIT binary patch literal 1101 zcmV-T1hV^yP)h{!1 z$T8kDiEZeN)WaeSJGm5oeKD^(;msG!+G>wCZE$8+Twgz<& zLTDu+vFVwR@O>vdAao@lgiy9neFcONS~>s#0F-n5&tz7=3$G6iXAnXtTgV?08HUC1 z%iK`Z4+tStF7(hvS@&H3a@_(#-NW8O8-xZG5c)GA007vCHvj+t|NrcjF%AMT3A@wj%$fykn0uvCrOb7q~ppvzDc~_Ku<6dBD9fT076bfSg9}Jn^=V5;j2qA<} zfsix^sWVgP_@H4xNIi@b0ssJz;e5-kO{NSCIkQgAb}ZAW#PKnc_wl-LI~{^s+OH0a?Un=4o>R*2qAPr zXy(-yLsp@No{j@T|1VT-5e(r!LC68296|s90H-h$$uX~#&q$X;NWI*=P(KW8ZNKUa z5$eMTA(UHa^o?dwB@jYrGxI{>FIO}OR6r=15C8zg`2YX_|Nrck!45(p3A!{u zAykZAE)oPl2%%ySLZ}#o)P(@x&3pg=0RR8(!vP5Z004lX|F7E-C@25`008_2;B=jV T=N* literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..58afe3de7ab7579b86fd68a5e0dfff03db55dd65 GIT binary patch literal 5450 zcmXw7cQ_mD*N#nyy_ME(#E4B1RF$aFMynJRrM3#C)JzF#w07*$qOIDB8bOFzd&l0z zOJk3k-_`g1efK}-dhY8y*Yn4@&VBClMBO*m0|9w~00004*S~`R0027x0ND;L0000q z!W&Nk01g)T9qosHSzAPHV3-zz)`RU3XZl%oYQrR#v8e5BA-3!vJ{BU*rQ^Pq1LGz} zufE_EpwOMe>(nrMD;GZ_=b8P`mixSK{$e`Ram&+tfkfuZWS%Gj50SW(NjM3J*TAjm z=#j`NC~QKUkqdsxv*p*~4&NE+IIGJckx1l1Qj6x?ddG6AuyUO!6bgl+0`DY>z|)@s z0D!Bjtu*(9A}wa!Wu(XrGP(q2I(;p2Rw1%~Lp+p~RJ0`8>#f}5fE~Dh{P1^N<{VuR zLZsLG9~oK=_T5ASceav-N%x=3>+Nv;Os)#;dc5r{G^`FN9g=X5$GO zpS&3j9aKi|L>9GY@BYfpSCF2=;1v20OyzqDv*|mUgKL_e-W{(kTLVPY8%z3uYyjAX zY$C2IWb%4#Z@)n4vG2;2Ur@rZ!b&!W=G8Zqq(D3pLy2}&z5CiZZM0DaMsaCy(jbT| zf%#u@53_2CJ&Ki>h&||HhKAx4#D?=S+OBgx=*Ws+-wVG_)P%4BPwgfY*tTps*MipQd}xp%}ocrayy1xVIN zRYP&ZU*(rHhFr;Q@mO>W`wsbK6s8lV5Qg(saq+gHQRo91g~>_+ox!v;=O3PCrjKZZ zZtM^HdKaG5e}X?V-y!E#$`?h-P^HD=6{>I+rF7XuSU?@al=B1PScfS1dXX z6#0Txq`N2A#yYX<;#p03I+OB_A+^i}T)1s6;+AnMM&81G?`TicUAxx^ok2{Hh;vNBM1^g|P z$DXekDtsSG7*|s;6~p`Ra&h`E!YpR(!&!aR2gmhDOX3e?S=>1^)P%L$?aOyt*B0l8 z3kwzJng+nPsuzOGF*!O>UsL?(^AHFjY6ctC)`5?TWBj#@;?71Jk@|N|z@u&T$pc5q zMn>15IBx8Y{CYbNM*u)gWaMCpjL@)1r>U)rPWGY)i}f;nfm z_uZQoKzt#x6Nv|JP2!TS7~WS|-neF;Uc;<^8TPArpDKrVl3j;&yXbo@R=?Rpii*Y% zL_4Wj&G)04&t6P7BbPmDl9JOj^jNhu8k|_@2&bQ#x+ZZ8+riUEf3N1oHEuz)6h8y0aEF?hs3;XtMri`&LC#>5j5Z@FJe zUcQ2nP$RjOluze0|LREA#p~%C%pKL*8liXBsmG}gsr#t;1*dW$_F;aM?t?Fw-Q(S~ zuJ4PIR?!5{52wp>&4JW0XK&E*UM`fw1{Zbe!3(GU-^5mA9x`ZD_7 z#JB0gW>1Eeq3T0Zue-Bi3(INuu)j_JIP>BUHk{|b#jdFE$QNilh~Rom6~$-Aqte+^ zlnXpJzAk1qzm@^_LdItfUk;ZZTxbQ1vt%2Z+NS)&#Bj@^ak=_vH1O9Rr>H6`FYHUO zVY02oO%y}33NrRv?M@N-c}#}2wfU>{i6~=d0E0e&VQi?D28=@RlZMMix1O4FT-#(c z_)=BPh+{76h{_*NePPYOUh<>Fe=BYyIQ{3{>O@MKo4+Ql4Z2GSs-Znjre=!UQxelOpc1FoNGsmmkD)U zU~?~zb*69jvMFFDlp+t(oSjkp1U+_@&N8g~YmiM(}CEEk3j@fIK33=pP z^p{nB%+4^XOQ8k&JsU7IxCrmKsf3mt5kKLmh2)T5rQ-8qY~D2NS&O2wuLRJ$tUxR9 zf&h9q8bzS*F-5}8K!C@8S`vzVXUL(Px%luvLx&WDe;*!8Eo4cx={E#omRdMa3^;X* z?;NYdrc7npLfabOUQ#Vhx_ag38H3%1#eQm4-lc`4?1wTeG^kIsDhz; zQDr4i+&Q>pZ5J>)N9XrZQU)!$;nJ!*N1|o?o2r+Jlf~ zPB8(`P4JN?HtZ+zAMx}2X}8}jJN$D;ZII+Gxo@!$mmMh{K@NddH4Q2jfBX--%{1?D z*r^*Yd`GcN_ncR1*)0)%e7(tPws$?+c!C`FHpS-C+l$Xh%Vvq$h32LiWSZ*Mzpw#* zr@5)Z%`YmsjM5%yO&NG8aLDvW;qTPyY#Oyo2cM$mc2~?s*iLj8$0<7Rk=qr|Tk4V0?gH7Nm zpM8qO=LorO*hLYQ!FiQwJ>b14<4tqq!KQS}uHrMZ^t)UBkVJ1fqV-d9qM^XenaJ_7 zx=iW)O=)e*V|1}rsf#N73__+eVTR9cX7k<4w?L=~Tq>VFJR_^DUwtm0$|uG%HLSYb zz+{DePIEGd7k=dZO+hDNr~Sn~1Kxh1nl>wACLMkJ$At28BBEuUrh|MM{hiHmddU&t4E6SoL+^z!VW~90 z{sA|r-eT71c{GGY46mT^{#}J4RMK;hoR-HsDQMz~Ov$<>56q>7C~^g5$vhik<>K)k z!FBC>)Q4U$EprZa8$SpAhj&c6S>ab?|BAEqqWiKs!z%SI^Ll^cy5;qoN<#){TabP zqg22AEX~b!Nnl)F9yoKaR3+gjCeRUg=0I>~Jmp~)Bas3Wtv|JC*hiZd8g_wlP83V8 zsBX}+9j{#4INY1D{;ZPlGrSjC+s1a?4cyXp*dF7S4&kBztFA=IY;cgOmY*0}+2Fdc zbzZXZi8>A^bk+;H%5S>b_+!?JBOn|)VDL(W=HQ{mMubaxt=(?c7^>jeP0{e)oew7d z*&-Y|S%x!(Dh@tYR+9RQ@qCwU{g_P)LR)sUomppldnh|&`l50-(ed@Z?b2L~j#-ax z`zLnbAB&7ySzcwOrLAv`;SJJ^lT^xqGdMb{hmr;#p%?D(xx^h2J!k7zfwo7k^^Ny? zpDN1~EU*%tyNM>I{;I7Bl}cwRLAT=Bm!lCt>2!t>nmMI*wwKmN_{e%lKKJ8zgr|E% z&Q8C|oSL=S(88T)Pbx6SghQI~SB?F~5&K6V<9u!|18|)Z;eg8Q^I4e3<9qFY6LUk=AbdtH81gDoKf#HZv&EMFWL?9{1;-PszI7V}>|+RM4i zcTp{90Gj9hG!f0vJQz@ZY%D_w%Gqd+C_UtELcHWV^AoiviuaF>*%2FuRS$MKE?8&l zgndYN6-Mn;s))xdyaKq#zWH_<5jrtJVILRYzwduN{UfJoP_N|==T%0ksDlb@u{6)+ z@&k6?gSa-RX8?+0@~Z`F27QccJ&8DEk~rU%w+p*CYvS}%9yr)yB`lgo|0cMgM;F(3 ztnzG67~<57Tp}KslVKj!e?eC42NVaGQ(y)bv8|bYw7$<6T>*2>hokQoRRc3KT6xjO z!%~N`ZbAM)SHOAAiW)D-rjX}XIW>Np(3EFNQ2XasIS|bjH#pS2GdOt7j12YJCyS}V z$`7}BYovZz`M*(N-SVyo%3MHk>m*r=>`IFcEGU|fNscE!(xOX_*8%3Duhmc{?+Z9k z5d{JqwsaxC1;+!14Ji2r0@P)OK8^96|J~>TfOF9$Wo+|_GVYjyQU~=MjifDQU}k7Z zAqy3(mD7S!KBlIPSojHOsivp?o4)XSkkmLG-Qwqvf%UAE@1S1pI(5q4jLj26%?(5* zR8ls*dYXqcQQ-PK5C37v6Z^IVmS1cvXrfD9N7aR%yX!dQIScFdm{s-g8zk_jh(bG` zXHN82r3E3cG0n;sEC<8MQgdzTJhQGzn(7msN9`XRv(EE(z9z&xC4H6DwzkX)t|1?R zRsT+!7}RSEvgDjER?tu5d#74gZRHzHGA{n{rA%x2?b7D9&(VUb+uM8Dl2nY! zUX|Z^)Wn9St=790g3V@R6ueSc5PF@Drr|c68n_;T7VEE{)m6}6&6>(xUh05{Ml@PKn_XO9qwxS=C&Eiveo+i51 z5b}y~V|(qNDebWE_NEu+7O1E61qzj{HQqjljZgcT+OMx zoH%g^x!3P1s4XnsGMbrFI-}s`XtOQE8l3MaW;~@OS2J-4f%Lly!U^$%FWi2gjBU%Ao1V$LVUb2^>k;+H!s`nQ-pU`(8`szsgC%tCR(N?sNB%Jq7(Mw%L8|%TVU< z**9wC2KrzL+j}TF3Bey$Uix$r+WdE@wzNfS!<<+q#nu{m40H_8Z>ThpWFv|Cuze4Q z%0czZ>hVI=XYC)qdR%hD?04k?%d9R0q!;%azv=a*=9m;c9(^~mvA-r16D7+)T=r;` zyq4;6UQn_DSfCv_S=LpJ`1TljDY*mpdO-2Z#TF(1-nIM54sC(lMU3vGTrq;gQM$9E zPJ6IXeXK*p5WxNT!td{s>>Z!snCq6Upf_i`|G;&KB|Lfj)0@KUBp$f8Fkpbd82 zktsPGNAl0)X!0uT$Hza1#ooXoSvIl3QMM3TaWNH>&#nddho7z3(VCQvh$eQr-tnFf zUv^0rvRk%RMr#%ng@%tF9BBLDh>#=f@uqIOvK>pOZJg#04uJ}DApv%#il>v;?082Z z(UIj^j$M(=LwR#5)^=aP5w*pDw03{7F#Xsk56W&X6;R#^^SAo(mWfxPL(j(lx^XmK z_d!|d&KxA#8B16E*RK{JQ~?P!P3s%OSL;f81_CzE9+o<$PGesFsfz9c@7Tn(Lmu#O}PBJyx86Pn<_DdFK zC^sWfqy0Ab8f zMsgZcii^^;xi#$Zs{3I?=)P0W$?UWK1Km1g7;N&zZm;jqfT^p|?n^b>9vb>!6RTO( zV`XOq9Vm6wXm7Y8G;?%pHNc}QA0U5p3JCM;1U8idrB#)(y&)kYcvlfX^q&~L;nwr7G}}_ literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..784622dd0d6d0db26ac98c6342373459b20fad90 GIT binary patch literal 5724 zcmZ`-cTf|~vkwV`79ey8Ei|PI3Q7@30@5LoD!oYW2r5NNNGKvDROtjnL=dUc1f=)g ziy}&w{-p^hywUIbZ?(B1no;nQ`j0yk%&}eF?>H`2IZ~y>^ zpa1{>0E(lPEC7IoUsDxn=%2kki;U@zWKuF|*6py9#TpuZ#O~0`>=n zLudBZqw01hxh883o?;gCz92mm9+Q9gpTLXmu@5gT-ImdoL33(E#DB<322rTce+UHt zKx`l6**pyO{Q7(Hy;rEWIi)^Xw`DIeT=pY3Lqe` ze+B76R0aS5S6ACJZM5MS2-(=xmFt(5&Zf^)Y!YjO{W>Bw)hfi}Is_MXxctK6R;IhT z8IkY*szwRupPN{cm>#K~X}7P_VCYX&WpYEw>^V&^g?h9 zf*6J%!2*F~q>W<8YPr-8>kurds|=7*fPhG#Aq4=!KCRLGJ)qf68BP+i4UTQ4h;65c zZ586e(z8X-%LDm;ZX#RUt+mY(Z!;)u{_d6G`I+)m=E=^8O);%pxzmVJ@TtsR)|yMrzfQ{T(w2vXEInKBT7Hs z@G+Iqc?$hex@X`$KlJ$A#wDXcb+@xbPEIg92Sg}Bg9dTtC>>LKi57gCu4{%E0d_0 zV9OZ$7217cOnIVIuG3><#+Jz*PqULZ{Qc3O4Xydbp=7^Qd*tVyKEe5Jn)KxNzXRCF zGRVkzB*t#TzGc^+oZ2$kr_*$?Uoz}yEy>BMnD@K*LytRf zh0C~DrGXvbz-!V37Uz#ju^IUY&_gpZJ7i52p5?ARPk*NL4Q<@wUFYc_kQQjmq)&Py zZO3id`)4Bp$0Rfx!}B6jT2vdi*wkXs7G`I2d0vt; zt$&E8SLfgVG}zaEkU+JNp!K_;hd=PJKedXq5rj27k=~Hw@K;KuJAT!!cNydPQ-fGh z{?WGSPJ&rY50Hd^W{y#Z)leywPMCb8!P6z_APr$_eP^vXzy)OrJdQy=y2}Lm=k2qj za$kw3b|FkY+n?rsrJt@vP&!abi-PUZWp)^))PysK;Z^i6Rk+u4tW9&k>`G|!<5z4c zidekCy1|*V)w;!;mRURLRt;pta)h?5inh>|J+z54K3j_(p+86!S zmHF_(qCl2GIc4Rq?er#r2iaQ{&P(vDCX=7#?|%@p$DkI%lzm5WQwzOF7pT5LGu2uc z8mAI2s>nd8i!|XUe~r}8J{Cx4OuPw85)CHe8!Ka2#5wJgdqN~d?M1Hn4#t;LIe2|l&f;vqMel9oJH)mC#)Pt0ILp4nTP`sJYylBP z`hgc*85S30^cio9J!6~jM#77i@j4RmucB#CuHDgY&vO2`8rt3Djh%%TIzp65g=Gk{ z+uY^tf9X+!0%Pi=q204v|I&(TZZef~+uWne`-avfE=$bEuWxCe)93QRB%fq?NdykjnTZ zTVCpUZ@*L%<3LeC!NGvJ$eW$u;pdV6ut>Ue$MnZKRse+q7{8WPBB;cI_$hmwyG1G) zkM_w(>R)%#)<3+*kh>ITSZLaCYjIswn%M>QR7XR9F1JVckQom@AH;|sM(n01;vY)a zB~o<>if^@(OS8mOSLQfKD}Y^O;AdYU?LBvDVPU%u{uq%wlrDwh;on&_$KSR8@hWW( zPY2z<`6}mY>Sr-X*Mz1PWlmvD3V|U_Pd~=DYJev{=r!52>>)8|__X4(_15{lUN~6p zk~#vFKLeG}^*KAwcZzRzs80%iGKI=9%uBs zBR)tVBg%_zG~F>^Wuwe5f4ryx6K=)KUD@u#3-+QTXjR|!@i%Y8xJ+0Gsg(D}KloM; zeON~pJr$Z9nk##G!?L(mn2q7cbt9d*i2A;tdDsK8wu8M3Y7Gt65)Y~K7Hr3XgW}17&DZPcJTO-n$*I_)l0=h8p+(K8w6GV zc(Zy=wMKt%k0TE)C@JFv5a8?usyaWQ3!UKPL?8ECQJ9I6n+9&2F#%D_^iWg{D-;EA z#y?Op%?YY+`6s3}BD~;3*WO4Z8wsfjBr3m|lsTODHf=`&-NZWg`yFVFaZ;^II@*@t z80JUlMkuW!poK41xLJ#WexKIPyt#%5qXn8V5}`yWv9;0>j7cY9a+Dyg5l8SM@~KJ0pQ&w5 zUV-9v!8Z|MN}zdB1TTUYu}VwcNb)yTe>ELJ^=?l);2*l8E|r*yDj z+JQ}Ul<#V2_fA)oQ$QBLcFS+?%dwbKF|d63&6tG#hs+ zzBgHwCmu4_J&qX_hdhPSgPC1D*YYg(k61WH0pl-}IBLO2T{!(Med3yD=8QzKVr;i? z4IT8mzxFy*tdl31>_b-jU92ibHzM<+li9~5FvS~;Gy>3pFXE4pF9FJEkBYKNhYSM@ zgkW%7WSdTu#baY;SC)8})o^lnI_d%tFpT_Z38vjDHjr|mF~(+I?{NPJKf|#pMzoUv zxu=nDa3`=bDj9T0-;*{5LN4k6-~yi}9lsxU=RHtH^rL$Nk%v+Poxhih+yA&<5YKuE zYFF9=y+;l8i3jMLs>oXxY^xhjJjikcw1BPE|W z^4_r*a_DhK0^dkj?>`c4yP%p%R$r^NA^5#alFC)fq&RTz%!>w3CRWXCF_Od`OX9zP zS=>E_>zWAnAXfKJL9{ndwtsZp9r|&zKb0K*rR8LZYQ9r9o6w2yrOZ3pKGs=RMML}^dUOslF^~E2ftl7g0rV&6M_{Jgn`~`(PfYN96F_X z7w~nxS}>=%nQ9&Suv_XD)E;{5mEg0WC3B7CZKFk|cWZ`5N)0ix;4}081wFtOa8-yG z`oxPUkg3Q97^x2P$0NtUvn=DX>Zv*5yxC16eCzaXn5K~T5z@oOnQv^^u9wF2nRZGE zSt-q+U`~cZ29mMKMqMjnb~GsC1dU}TH*q;1=JkP$yX^QQWxhBAzo5S7rS;a+FWkE^ zXZLOHHaWdXUA|IMs*@QurCB$6+-S8cx8B=(`L=4yRDBAT>@jS|_b_6Z6gvORh0l)g zp~K8^ry`9vi(FR~0_^+}N#I=Rxv0xa=x#W3lJa|-oBl%dQ7Ni-V>NC4vox-F0h-hy zO$el(ku5+DdQkM)!Zd;@CS?ndqsz*<9WsQ#*iqR6WG@NB$TygVVzevg7<_}7G_&J2 z;ao&zIU3A+qjHq97CZUU7N#yu5lBO|HcG?v>=3;BHvaRgiLG+eAeIXjhWwsGl{>xaKC4oq(O_ibdf4RydF5$t?XoR4f$ zLJigWgs!f=Z5@Tia=c}&3*Ji>dEH6APRX(GYwboYNBINRBk>0z^QhUyEt5~V0%nCL zp&}U9w2+WS`f?6u35}{nkOYhN5pvJzNbil?&OF_*RvR%?~42 zdGGQ>TW6H;P!t6(9*X+K?q%fAHSvlL2mFG-dJ++LFhuhIF4qR)RwN;+Dj85tt*8oK zS%HWpY55nlZHViw5l?KR^)Q!tZH*NAaR^?^zabFHph|#;lhO-B!;z$vd>T+> zIK}@9QU2MTf;WW!^{>!N;b*%eBg6GPJ5Dw#Wu~#}^CkY_`oHe-jC`IfduaUY*3xmM zo71nV5w{biE#!17{a}0W@AIpT-NV~KBm60Ly3hfI!QGsLh3)j_H-XH&qc=MC7Ru;m z&N72pdBd}^dpXeb2^WW_w70&zhZ@eDa9yeYc8EU*^;A+s`WXZ)Iy_nHLAzOpg`%HO zskB-yJbe&a-)~BmGq9g&%QwUkkL+%)McNh_umS&>Z!r;CDhUT-MQvaM3!zlmZv`{zZhZmDJ9Z%>;e?-Ia`J&<+MXbPq!D!=w23&)kmW<^mqdl?gXc$`eeA@5}Pa)TBe#cbM{tq=E_&ysuiFtm`gUfHE=H1tQbFNqW7ZxF;wyCk$bS zC|%SZJN1;0^-*?Yf!ic+h@e9M&Bm5P0UdA%R~x@QK7Qr?U6gvS5IdIs(e@aBy|uO- z$L1zEA!wsV!H#+5PVQuh`AgQxq)Vw0eg*^@waf&9eGsw|>g92ZOydItqLHV?SJ} zLo^W)GP;_qf}a=Yb^b&L@(5&C3s!sSn*S?oOFN1}MvKr-Jv`a_Smco1VgP)oa`l-X zRmM=bE5>UeDzTC=vSi0ok?&x|&BQAwS`l{Oi&PV#((HBMv4VYf$crr~?05!#&D$B7 z77&18`BtGTC&5Z1-gHaa%A!-St4gRhc&)3;>Nfa4#xvQg#o#N$`^k8nsU?OAbWf_* z`9m-GcpJK0`NBOUsA9LuW9!++p{-i+oJBlTnunCV%m3;xarErsFk8O)`5de8KJ`)N zkXQWlZ-1$yclEzgoc69e2TG*`DwjXJ{YlQ^sTudy`GfP{&-JI}Jj|(PtuNd|mP;Oo zMknVYQM%y*pzGEBrC^31-Fwr?-0k84;>aJ4lnY5A_6NqC-wQ&-ktLrapEvTH{d}3* zUa_Pfegskw5i3SiU6rPU*uP{bjC$tdgXJjd`Mjhb?w3^zssYlFBD1RJKJgXXzVPv< zJO}?R7-!q4+7Xa@f18yh$r`>{2&&U}7I=N`*^H*^mtC#u=aX!sjS14SYs-~IVbffP zO9gE4AINHU3Mh=@44e>K#&I{i^K%p|T2U8HEj6QR&Vo0Yp(U#0XLf=Nir6$)1Co4w z@thlJt|S%%v}B#GbM7r>17Pn;WhSWTN%fg)C6Ds?y83F)IVdo={_3Xh_2+*o(^jBVLs+&YfkKtuRX&lR9~Pfztd$|n5308<-vMgRZ+ literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..d4e488525135da1459ef46642d2ccb21bb9d1018 GIT binary patch literal 9997 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 F`(L$nMXLY+ literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png new file mode 100644 index 0000000000000000000000000000000000000000..9be9501ba18ef61d9715e67027bf5b9ef23e861e GIT binary patch literal 4298 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 bjno~6&hMTW%=PxaPYN{^O-#9xW!V1#Cja!) literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png b/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..100d9fa18d6f3b5d73c93c83ca1e32288da0a875 GIT binary patch literal 2223 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 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png b/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..304cc711f6c5ca26da0ce6c5066931775cbb542e GIT binary patch literal 1547 zcmY+Ee>~H99LGQNqiKW=M^|U+A(G5$U6GOb@hHl+$`LK;9@pyTT%jMwu+77dx}+b@ z&rPU@v)03pXf#HfJg$}>O`)?257tp=Y|9Muy~lSvy1PG~|GwToUhmiY3YoZX4eA>d z0ANkf{(!>(ARGWN@HGJd028&G4S?~>pa4SXubfHQ15}%@Wnj-DM5?)#;V{0cO@3!A zl!ecH+IBu+0}wGHV&0V|YG)RcuM?>a!6YhPT5wRyVxC05?&n06%Q%#B*@od%uLZn7 z#rF~TEdv(5@}%0p3l;~_7arI zN+{$h^V(SY4St>HZP{*yG}n~(S)N9N#lg86u4LwmZ#CI002zw z&xH}&s?#ENIzQR!Igz#g4K)=j(?J+hf& z_zLe+x-YsoV6R8W+``#BIjZj#r92WYSNr&;*Ub+&_Y*#UyfDj)@*HK_Q=cP^w$1iU zLDls544NJ$)Hm1`lMK$HoVOM0E)+oHHUcZ4rZA>I>kI@xGkGl zm7JbX=Dsl-vmfDQFHtXHwF<(nJ7-X=`ZHR@w5wCc zjrT=tcXS8t?n^HmW_;6;R)^^N%KoLwZix-A3kK0zGiws150XAiLQNAQPN}HjhH)?6 zat-xD002COeq2|F#|`vMc4h}FIvb>zg2@9r^X$>5Nw%+{QF-r|(FWPV5TC^V?n&!}_RlXZO1?XgNVRTJ zWh2$22ek+UJLUK>jXI5{k*Co#pfRL>X2ff}3Tlh_@}0IRYmR$g_#)-+uJ2}QfPhN( zH8T=HP!hTpX|@01xA#Q{O#E?D`(8r;+{3hXb*--!RS#N^pi;(iOq>1+YITT?sxQj( zL0=$J${r?+7*v!PPSP4QjKd#Pz0IR6d>regvfVhi!*E;C$B*}NANl?SV+EM*^N1w3 VL35k)9u4r*3koC#RQpAz{R0dG@)ZC8 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png b/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..012a5b952848f82e54e56fbb0568b14e15cbd914 GIT binary patch literal 2490 zcmYk73pCUHAICql&1KA#lA;hImrN?5vX!2wr{=CklBbqyxkM|MhLCuwV^prK>wl#w zVQ%F?(f^?_<<@9!ixU4_D?Hl&Y-9ib@0{29owKv^{d|7!&-?TKrZ^tjhsCVH007p( z-p&~S!2m$|$O8b7j0V*K6yhB0h_2M!(GGJA4X=de@`dk0!j(Xi{BeXiD;8l2h0^sFQH4A0U7@`S(SlLk)Bd5}n$Kvb6 z1ZOP=yl-cNl2t!3tS&)tN$zh1P^YJ^zL{S&5-5B$=HkI*k&fPB?}|v|&57%}Y?4-_ zIm)BB^SzbEc7AEEqPXv57XoJxi_K|Nk1j{Irr;apZ>eHSKecUbyW!I5=&1}L9Hdy0ZX%`zFTb~TzPHlA5T)z8%Yy4>J|1~GcaHo$ zLui|cr36%6xNES3erkvk$vdr6M z<@5U-ix|nqidPu}*Ngx{8sx+u!Yjfg;+4>jLSey2FI0+t7u8CRKUtELW3qZg((-uF zr@{_~MVUvTt+y9RH)2w-RN zesjIz*|VU_l=lnO?~^z3c3m1BY;dh^$Bg*T2HHEeT&o%#aahNu)g_xU#*hC%wSAI| zs7FUR5f386t@%@1yQ?GOoBlDkvm8r;Oe}aZRdw2)cI*_7~U-z4Q>dwv1JaCK{s9YV^nJwT%68dw-Xmys|Yu&)7W&iN4 zE$ZRTWG_z!+bSZCH`Im>$U(w*?|#zxSPOsrnbHE|$u@LAlglcmDd{MKy(4-|GP`b~ zC}2b;5`tp@_$)!jH%mHd%A>L|cUZEdop-RHqk?YzRrL^o zN_zG?zqof)@YV?@iTMf!83#4|Vxdk`>*hXGDsFm0^h!2bk}c%DRGvhDBW}Oy>w$TG0^+`^vaT{@?Sv60IZbXM~dc5>>k-Q24M8HTgi3x_N3&%8!I zkOI*C&yG0Hxf_Um&&nC~O*GxZg{x4KMr_tf+U*fXa#HJ$i(hSeS!xd^rZhvI^pkFC z6LA2@u_u05>*C_5ZL@FdJoyonu;QcKaY_}VpTnM01kKRnJDi3Id0q_i5^JH)CGxj4 z&s|L0=~GvwSA*!9SO+xQuk?D!m+;InU@pFWE)JwUX@$ zCE62C=%vY)9}l~`@^MyAerBMu_XjtZb!>Efaxih?xXl7Oc5%%+%`uno>X%yQdwFEX z>*{xBsW^7_K+7}EXSo-aC&SZB)X(L!%E@{h~1y#LYj6dmGJBR~&Rsv{$5zD4gf6r${TD%kf1V9<;)pKk0>`_s(4to#T J74G(l|1bGGWx)Ud literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png b/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png new file mode 100644 index 0000000000000000000000000000000000000000..563f23d093b48bd455c244ca49e3b3448efbab10 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0y~yV7S1*z&L?}fq{YH*Mp4(3=GUMo-U3d6?5L)aO67V zz{7gs-v3LYEaFXzC6=lF+_PQNglDq2eUu#+4iaGbIrb~-^0wd?_GCCt|QQ{ zul&QzYiFl28D6h?P;KA3``t-y76t}^?bExtUBdUP@06=JZoGE3Hmj1)oj5%`0R{#K zFugeB-)-ypnRn-?o-C=?J8*6G(^K<)HC2CNVq`G5&dk8@|3An%%?<)A2mb%J(x31^ zWruC9XvGHui?fdsZy#1H-5V3D5OjCX#pCB)^v$_kcI}E=2QvMDFIXmQ=f2PH=XA#9 ziFE8$i;ezN{)_43lK5TEk4$e6{U^i0@L=t$s-3$Wmi(%lbe(;kh49`x8Um?cA)A*q zlb2JoXSyyMp3gRMU3_1Wp&|d+*P_Q_N>`7%$k(e^?O5CD zasBG_Qrq-3#G0u@eyg(C9mL0)xdV%V|b_0v~Og;P%MnpmS&WwHD2%eZ%M zzn}fIWck0t#lM&)LKGUo6i$5g)J?t~Y|^E*uTF>AF4q@$x$kGJN*$-f!{dz%4D)K? zLC0*!(em*3{ax%DLSbxkeg5(P+Tan9`ddK#U3h$l$<)uwmaOc!y-xcv^Fytz^6*`S_!j9gPMnC(B#gOKee)<6vNT5L&o1=8i)_>Ay)|*~@K`@5W>+v{?RE z{U<-?{$z6jwYTb-k7Dxnvwn17U|?9kO5bJHDND->AxCnrPyN_fF!kKMcQwa$SvpO+ zUCVRe*6x2-sz0$cZTzd^JLmMW}P}oq-AqFTej`-n0APHS6z8oHI^Fon$`X eWHF)RM68|Cr4Ns$rfU_0@|<pUXO@geCw+GruYT literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png b/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..aa1569bfaba5e3e5faea55947ed3bdf3456e6736 GIT binary patch literal 1104 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=Aydo-U3d6?5JkFl1z4 zU^ukktA8%Hz6MAE64)Wdz`*eT|C_^xybKHs4GaFQ)qTh*&4i?afq}t*6QZV(hk=3N zfI|IiZJ7nzSWwj@a6!~Ca)QnIzg~J$6Phu1SkaXH7UTl?00|U`L!1*4yYY(x&rwF9 zFelYDE!qo(v=}+H89Ap0aC)p@QF$_3FV;gNwfeOA;aZFIW&AT+4xH57{{w2ygY}1B zcqiWab3gC+)1(JR|1MRZ$Z7rR{H|qV$K}4dDM3%3O>9knCTDf7kLy{m%i0n*mt(zK z@?>3gSl>^&v-Hdq5?bA%Wd34+cO_73##gqtv{2>_459HkEX>dPkcYgy;iNLOL*GV8+)^!weZh; z$(4LMw5!Hm)>`c6&;V&jd2>YrVkK6I-u%gNIXiDGyEm#(?l`hVpO^P|mnt-=5M0^0wzXG~VH>D;QDb8FFQ_hmQLq?FIx`LeYz!%H8@A3dvD7Nmq*&~gLK~~T3YRmGK#6J{L{L9o+MJbgo6kDkL4jr z^!8cKW(NV*2SM9Jyf?H=nIxohG)U*Dn3IqSOPOtJ!{>ecHg9EWI^OXw=4M!tentQL zx4Zh`htvaK_iQ`VIQ`v+AhUw~ia&pRT$1o0F#6frj0d?98?N%$@NzGk)OdYz*Vcpw zd(Bm4Z^dbS_?u~`J2T$2ez$U+_B+nwdk@cyf4Qg9B7dpq_G#yC>mGgE{@Kqa=-d`q l`uCCpyYMX{FCt|$b;fYz@5)a&<;_6KJzf1=);T3K0RVa7(p3Ne literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png b/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..b77aee11eedc8bb85ce379c2fc4844ee22653e64 GIT binary patch literal 1251 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 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png b/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..d09df5e457b30258dda2c21cd7dcd49516ef62a5 GIT binary patch literal 1114 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=Axao-U3d6?5JkFl1z4 zU^ukktA8%Hz6MAE64)Wdz`*eT|C_^xybKHs4GaFQ)qTh*&4i?afq}t*6QZV(hk=3N zfI|IiZJ7nzSWwj@a6!~Ca)QnIzg~J$6Phu1SkaXH7UTl?00|U`L!ARR=C+^)nwt-V zLe$JO^!w}}&|1u(;$^{LD#q^ca>9xg8WWE)3h?qywU)BtYf$y#Yj8>JWKIYyW_S@) z(BP=DZBFHG>(`2YNztE|#P6S1mTojZ^6fg?Z|75tX8NdI_HfywIsBJJ(Z zynCaO#LoHW>(Mv=_9>N3I52#M~lh+2R`gG{c%auyomZY3i z4RMm>+@7??rDJBG)`REiY3I+2ZnRAG)>|xd>ckGwWYvT-Jwc52c1G2eD?8>2rGCAy zd9q_!fSd8Fu-1)<2`7AdZ8pFB{rKz4f6pF0Dae2KozGt{@7?@3`+ct^`T5^WU$;&` zOaJ*#BdyPxJ-(NImH*dTHih3R^y?c2Fjeikw)itL;- zQ;=yt^P7oTp3I6LPIrfevaR`$S#j_%SO5QYToTs|))Z7mPkPF**;Z%zwkEq-mk+xV{XvWk_^=hYny2gWSWA#${7Vtzc_uAIZvv9UsBQFj_Si@mQD#3(GBID zf=q{hE9}wj6Og#~<6-pyj>d7QyE%jT{91F{JH4nq~B}9eu&=B zjOG$+SiJIvMjGF-Wu@zN*1maDRsOw7EZgAc({Gt`wxwQRD(DW$5iw>@J}tFlP0p?d zaW{B&wciQz-Mj3#y8plGRX2{d$i>fH)v-KFEqvp_GYdoBD2D%Ee@LL(sz)Fx`GDOa z2QAqRMN_j{vv<7RG^gU40jv7wkWZ85WqenhTQEIq_eUR_-6EWmrw1Q3*S+ewSa_cE z+J3R~mM(`p%+=0JHaHqjEjmOLkKjy!k<<4V$NcG?bWuAodKh#zw z*M!TKzSYdze_`I`@4@nBhJUa8PCi|H{acdo^vTkJe@nj`uc`dN)xYK0)u4*Zm49@X zwK}bJjg2k}^zPi!?)^^HV9vDehg)_j8qArdoh8AbFqwJY1{-?;Yy0=04Cv|V=d#Wz Gp$Pz$r`l}* literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png b/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..f88688c1409204a3158c8c30902d1e1ead9afce1 GIT binary patch literal 1423 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 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png b/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..3a60a59d5b294d62b39cf4f00c0a75aa1c9d8981 GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E80JY5_^D(1Yoc96H( zfTuOlEWyO>14qvhjh+dCMop`?WzLFo;E7ykHBtkdoKbyIeKxa5!Ux$Boc){cGNfBfU}(9@AcH#e44Fd9Vj|G6dhK2?I*WVV@(11&W!2w^W z8be;NntyB8K4g?;LREB!1!4|dP3}WhbTu(dXlisHa^h0c$iu+Ea6qB{wQ|gYZ7isE wNid?hS$a|vnschbO5P&8^;?h&6ptX#!z#vB!P|9P?mvj<>FVdQ&MBb@0NY@q&;S4c literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png b/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..8b3134e52adbba897a0f480d92636471d713cd7b GIT binary patch literal 636 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|S$@r;B4q#hf?SALh0g z@ErY^bBLqo07uV(3`xz!6)fSR0#|~rPTTQW^2`6)8q>}%A&u)r7#QC9g$Aq2O)L)# zxRNR&GNmG5LSqw8V>xeV=<5j@f>Yx?FMpf1+eYnTzR}l|o0Fb?G?qL3Ys#uOXZvocFCI?-%66Pb-PvShCx5o9&6+JCEgVpLn}$gY~~SZdXS(E+ep0 zL{b(gfcyvn9`Css82 literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png b/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..64b1a80b8d1d89ee9a062a167cde4161afa90df2 GIT binary patch literal 1218 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfq|vd)5S5QV$PeH4|AIh zL|Sfk2-+|yr;4~$DjRsYRfJv@ySdDzK(|i3`2KpA>7T9Vs!qCbSfNG0i9@kPz-hvx zclRbY9hlR*;L;DF8Jh(|q~e*1&6>O(dncT83&^bDJadynt5?6#QrdCpM^%G#;Kc|(uiLvL|{TmujqgWGbew`|wQna*b!uqgv>QDEJ za(rf5Ei$_;anpT~O22A>N#RSJDr@%E<%m}LRVzHJx-duk^}Fb`CyMVeZ@kiM6TWP| zdGNX%9q)A_a@n@&k3Ytq|2L=GMqoYrT6yo|hehW#AKiO)j(w$qmFBwpyqA>w{s&z5_^`_XpAU^@l|hX4QHTr}imP+&OX@PA$52jPW0<({HnoY*A4gzZ1L zVCDUU3s>?DGG3WWn0%GyVFE<~6g23|K(rrbxEkMw?C5INqnpF~ zkQ-f%H&jg{4+8_k0fqY4+%gNdv7kCAjG)gCgrF zL4eJ{YC+3>mgz5qG#V#q990$gx%A1AgSU=&%-k6=e z)wF!}_JuJ~`Ks}WxqH`E{yVi`{ try drawSingle(alloc, width, line_thickness), - .underline_double => try drawDouble(alloc, width, line_thickness), - .underline_dotted => try drawDotted(alloc, width, line_thickness), - .underline_dashed => try drawDashed(alloc, width, line_thickness), - .underline_curly => try drawCurly(alloc, width, line_thickness), - .overline => try drawSingle(alloc, width, line_thickness), - .strikethrough => try drawSingle(alloc, width, line_thickness), - else => unreachable, - }; - defer canvas.deinit(); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - return font.Glyph{ - .width = width, - .height = @intCast(region.height), - .offset_x = 0, - // Glyph.offset_y is the distance between the top of the glyph and the - // bottom of the cell. We want the top of the glyph to be at line_pos - // from the TOP of the cell, and then offset by the offset_y from the - // draw function. - .offset_y = @as(i32, @intCast(height -| line_pos)) - offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(width), - }; -} - -/// A tuple with the canvas that the desired sprite was drawn on and -/// a recommended offset (+Y = down) to shift its Y position by, to -/// correct for underline styles with additional thickness. -const CanvasAndOffset = struct { font.sprite.Canvas, i32 }; - -/// Draw a single underline. -fn drawSingle(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = thickness, - }, .on); - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a double underline. -fn drawDouble(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - // Our gap between lines will be at least 2px. - // (i.e. if our thickness is 1, we still have a gap of 2) - const gap = @max(2, thickness); - - const height: u32 = thickness * 2 * gap; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = thickness, - }, .on); - - canvas.rect(.{ - .x = 0, - .y = thickness * 2, - .width = width, - .height = thickness, - }, .on); - - const offset_y: i32 = -@as(i32, @intCast(thickness)); - - return .{ canvas, offset_y }; -} - -/// Draw a dotted underline. -fn drawDotted(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - const dot_width = @max(thickness, 3); - const dot_count = @max((width / dot_width) / 2, 1); - const gap_width = try std.math.divCeil(u32, width -| (dot_count * dot_width), dot_count); - var i: u32 = 0; - while (i < dot_count) : (i += 1) { - // Ensure we never go out of bounds for the rect - const x = @min(i * (dot_width + gap_width), width - 1); - const rect_width = @min(width - x, dot_width); - canvas.rect(.{ - .x = @intCast(x), - .y = 0, - .width = rect_width, - .height = thickness, - }, .on); - } - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a dashed underline. -fn drawDashed(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - const dash_width = width / 3 + 1; - const dash_count = (width / dash_width) + 1; - var i: u32 = 0; - while (i < dash_count) : (i += 2) { - // Ensure we never go out of bounds for the rect - const x = @min(i * dash_width, width - 1); - const rect_width = @min(width - x, dash_width); - canvas.rect(.{ - .x = @intCast(x), - .y = 0, - .width = rect_width, - .height = thickness, - }, .on); - } - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a curly underline. Thanks to Wez Furlong for providing -/// the basic math structure for this since I was lazy with the -/// geometry. -fn drawCurly(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const float_width: f64 = @floatFromInt(width); - // Because of we way we draw the undercurl, we end up making it around 1px - // thicker than it should be, to fix this we just reduce the thickness by 1. - // - // We use a minimum thickness of 0.414 because this empirically produces - // the nicest undercurls at 1px underline thickness; thinner tends to look - // too thin compared to straight underlines and has artefacting. - const float_thick: f64 = @max(0.414, @as(f64, @floatFromInt(thickness -| 1))); - - // Calculate the wave period for a single character - // `2 * pi...` = 1 peak per character - // `4 * pi...` = 2 peaks per character - const wave_period = 2 * std.math.pi / float_width; - - // The full amplitude of the wave can be from the bottom to the - // underline position. We also calculate our mid y point of the wave - const half_amplitude = 1.0 / wave_period; - const y_mid: f64 = half_amplitude + float_thick * 0.5 + 1; - - // This is used in calculating the offset curve estimate below. - const offset_factor = @min(1.0, float_thick * 0.5 * wave_period) * @min(1.0, half_amplitude * wave_period); - - const height: u32 = @intFromFloat(@ceil(half_amplitude + float_thick + 1) * 2); - - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - // follow Xiaolin Wu's antialias algorithm to draw the curve - var x: u32 = 0; - while (x < width) : (x += 1) { - // We sample the wave function at the *middle* of each - // pixel column, to ensure that it renders symmetrically. - const t: f64 = (@as(f64, @floatFromInt(x)) + 0.5) * wave_period; - // Use the slope at this location to add thickness to - // the line on this column, counteracting the thinning - // caused by the slope. - // - // This is not the exact offset curve for a sine wave, - // but it's a decent enough approximation. - // - // How did I derive this? I stared at Desmos and fiddled - // with numbers for an hour until it was good enough. - const t_u: f64 = t + std.math.pi; - const slope_factor_u: f64 = (@sin(t_u) * @sin(t_u) * offset_factor) / ((1.0 + @cos(t_u / 2) * @cos(t_u / 2) * 2) * wave_period); - const slope_factor_l: f64 = (@sin(t) * @sin(t) * offset_factor) / ((1.0 + @cos(t / 2) * @cos(t / 2) * 2) * wave_period); - - const cosx: f64 = @cos(t); - // This will be the center of our stroke. - const y: f64 = y_mid + half_amplitude * cosx; - - // The upper pixel and lower pixel are - // calculated relative to the center. - const y_u: f64 = y - float_thick * 0.5 - slope_factor_u; - const y_l: f64 = y + float_thick * 0.5 + slope_factor_l; - const y_upper: u32 = @intFromFloat(@floor(y_u)); - const y_lower: u32 = @intFromFloat(@ceil(y_l)); - const alpha_u: u8 = @intFromFloat(@round(255 * (1.0 - @abs(y_u - @floor(y_u))))); - const alpha_l: u8 = @intFromFloat(@round(255 * (1.0 - @abs(y_l - @ceil(y_l))))); - - // upper and lower bounds - canvas.pixel(x, @min(y_upper, height - 1), @enumFromInt(alpha_u)); - canvas.pixel(x, @min(y_lower, height - 1), @enumFromInt(alpha_l)); - - // fill between upper and lower bound - var y_fill: u32 = y_upper + 1; - while (y_fill < y_lower) : (y_fill += 1) { - canvas.pixel(x, @min(y_fill, height - 1), .on); - } - } - - const offset_y: i32 = @intFromFloat(-@round(half_amplitude)); - - return .{ canvas, offset_y }; -} - -test "single" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline, - 36, - 18, - 9, - 2, - ); -} - -test "strikethrough" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .strikethrough, - 36, - 18, - 9, - 2, - ); -} - -test "single large thickness" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - // unrealistic thickness but used to cause a crash - // https://github.com/mitchellh/ghostty/pull/1548 - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline, - 36, - 18, - 9, - 200, - ); -} - -test "curly" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline_curly, - 36, - 18, - 9, - 2, - ); -} diff --git a/typos.toml b/typos.toml index fafc38858..a8b296755 100644 --- a/typos.toml +++ b/typos.toml @@ -32,6 +32,8 @@ extend-ignore-re = [ # Ignore typos in test expectations "testing\\.expect[^;]*;", "kHOM\\d*", + # Ignore "typos" in sprite font draw fn names + "draw[0-9A-F]+(_[0-9A-F]+)?\\(", ] [default.extend-words] From c96af1b3b15ceb03c39178b1dda2c86422247db3 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 29 Jun 2025 15:59:33 -0600 Subject: [PATCH 014/119] font/sprite: add separated sextants from sflc supplement --- ...ymbols_for_legacy_computing_supplement.zig | 80 ++++++++++++++++++ .../testdata/U+1CE00...U+1CEFF-11x21+2.png | Bin 0 -> 559 bytes .../testdata/U+1CE00...U+1CEFF-12x24+3.png | Bin 0 -> 744 bytes .../testdata/U+1CE00...U+1CEFF-18x36+4.png | Bin 0 -> 1388 bytes .../testdata/U+1CE00...U+1CEFF-9x17+1.png | Bin 0 -> 400 bytes 5 files changed, 80 insertions(+) create mode 100644 src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png create mode 100644 src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png create mode 100644 src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png create mode 100644 src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index 0a57a0439..9f7e8815d 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -191,3 +191,83 @@ pub fn draw1CC21_1CC2F( .on, ); } + +/// Separated Block Sextants +pub fn draw1CE51_1CE8F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + // Struct laid out to match the codepoint order so we can cast from it. + const Sextants = packed struct(u6) { + tl: bool, + tr: bool, + ml: bool, + mr: bool, + bl: bool, + br: bool, + }; + + const sex: Sextants = @bitCast(@as(u6, @truncate(cp - 0x1CE50))); + + const gap: i32 = @intCast(@max(1, width / 12)); + + const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2)); + const y_extra: i32 = @as(i32, @intCast(height % 3)); + const mid_gap_y: i32 = gap * 2 + @divFloor(y_extra, 2); + + const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2); + const h: i32 = @divFloor( + @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2, + 3, + ); + // Distribute any leftover height in to the middle row of blocks. + const h_m: i32 = @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2 - h * 2; + + if (sex.tl) canvas.box( + gap, + gap, + gap + w, + gap + h, + .on, + ); + if (sex.tr) canvas.box( + gap + w + mid_gap_x, + gap, + gap + w + mid_gap_x + w, + gap + h, + .on, + ); + if (sex.ml) canvas.box( + gap, + gap + h + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h_m, + .on, + ); + if (sex.mr) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h_m, + .on, + ); + if (sex.bl) canvas.box( + gap, + gap + h + mid_gap_y + h_m + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h_m + mid_gap_y + h, + .on, + ); + if (sex.br) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y + h_m + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h_m + mid_gap_y + h, + .on, + ); +} diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png new file mode 100644 index 0000000000000000000000000000000000000000..d47d83b743de43e11332c1f080a2a0c9e8f8b167 GIT binary patch literal 559 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E9lJY5_^D(1X7V93b8 zz;I~6SN~jYeGQNTB(Ot@fq~)w|2KyXc^McO8W#LptNV~snh8k-0|SErCs@r*N3I3~ z9+m_D_gl&BnX<@h?8y@ zaxp0KxLo{wzv{MYK!z&!Aey{Odw*-8T4hDX2%mr$TNr zZ%gt+esG9f+Q`Ykz`=a*@AfkGT%R4x+}83Yu6pk$?x@@8ydmM>X|N~ZfWrV9){LCs zu>QaPw;&fN3X#AYR!DTfmE0E8K#Q{t;$UOmBBIPzmk}Oi+}i(5(>hbPo&p)_>FVdQ I&MBb@090ti>i_@% literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png new file mode 100644 index 0000000000000000000000000000000000000000..6366a0ff6030ac12f05c3b87c8e1a3b9469c5b90 GIT binary patch literal 744 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|V1~PZ!6KiaBo%7&0<2 zFdSO&)jyY8Ujw873H*>`U|{(F|IJ}TUIqq+h6VrD>OSO@W8*g(ML;=kQ~jGSvcME>aRFQ0SRKF%SF4Jevut-dZk5|{|u z12emn#f1Zn4;IJHf3y;2%udIxD&{MuJz={l1d2`j3P^Y%A~*LTD|+OvCn5pB)Zj}1 z^PuK1a)J}m|Mksf8fd9)16avhgc9jVO|YcK&3KOeJww`p3pYXO#M9N!Wt~$(69BUs B6fFP% literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png new file mode 100644 index 0000000000000000000000000000000000000000..72b744510b2285d16fc8f85960ac1350758c79f8 GIT binary patch literal 1388 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfq~W7)5S5QV$PcbhKvjh z42Kqc_0Q$j*8nL%0y`ub7#RNle{c9J62>3#yt2tPnMfoDg%?zw2m1GbVr;Malp5zXiEKK0pE+#GuZB8uNesZ9xq* zHy?;Zck^Q|ITHc1D_*#BC=WZrklq<0s{){2?u&#!d< zXU?}r=XsRhx&U4E@DBsr|*8bmJKK)Ab>;6l3RoxUC7x8nc1Wa>aa+MeHSfJC$ z(q*SH!QsHpE6a~>`gyXhzR7jLqtpYOVvSwX8Py|LwBp$2BnYmkkSu6=YAyhFyx~zzCK~QT2<7XWxLZ6EY080T=DPyJKtqo^YxeRJt@H=XcFZi z;J8`9_g_$jC3cQX0Sl+2xH`LxqtgY^lic9+ zbjy&d#X!XM;@|gGyE0W42sstmF8{)P{`m2bjSH03rE8ixS!HcNSpWng9v}PLdRqHG zJHMX&iOT7C;u@9|7|o3Sl|T0jW9{Q#x^r@>-HrtyAOHUkiiQRU5$A*dc5h?mSGt)p zbLX=S@^V@e9PEWW7QAR?;+!Qmo1tB{8Gf=>z=t|B$;72p10Gj#Xj@g@oo+CFv-2%_p(v#52ha46(Ph%7h9|X}ogz4!&=uYW< n$c*ONH%uUl-XdHJQ*uCiPNQFMo8J9EP*U-9^>bP0l+XkKc_-Ju literal 0 HcmV?d00001 diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca37ee32883f64d6d02a278c8c2e60c3c6f854e2 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0y~yV7S1*z&L?}fq{YH*Mp4(3=E9no-U3d6?5JkFl1z4 zU^ukktA8%Hz6MAE5~z@5U|{(F|IOu#Yz&G5tQY?Oe--&HSLK*Z$`#8srcW;|twAWZ z(N5a_>Hqb&4`*I%59WC0^?j>FNz=xAF?o_TrcU*9)p(L~2%S0k8Oo4efpDeUAw+ukI~619IB1B1Rv zcu>{OZ%-FFT*!>+RSmGH&=Gj^YhP{9xmYm{hJ>{{CtW(dRbQO7$?tKNPUE42$&Crl zpXQs%bA<_<&}L@{sf4)Tupuu414F}te`|XmGD|ZdJOKhVOkh7F)c8JR$FAlrLe1KT cj36}|g!tR4_m-WQ0`e+@r>mdKI;Vst05b=Wr~m)} literal 0 HcmV?d00001 From 4f9d7c565a13a38d21ac1b0f1fe16c79be3fc0d0 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 29 Jun 2025 16:11:55 -0600 Subject: [PATCH 015/119] font/sprite: add explicit underline cursor Resolves #7651 - uses cursor thickness rather than underline thickness. --- src/font/sprite.zig | 1 + src/font/sprite/draw/special.zig | 18 ++++++++++++++++++ src/renderer/generic.zig | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/font/sprite.zig b/src/font/sprite.zig index 4be06a918..cf86fa6dd 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -32,6 +32,7 @@ pub const Sprite = enum(u32) { cursor_rect, cursor_hollow_rect, cursor_bar, + cursor_underline, test { const testing = std.testing; diff --git a/src/font/sprite/draw/special.zig b/src/font/sprite/draw/special.zig index 3d75360e3..e41cac487 100644 --- a/src/font/sprite/draw/special.zig +++ b/src/font/sprite/draw/special.zig @@ -326,3 +326,21 @@ pub fn cursor_bar( .height = @intCast(height), }, .on); } + +pub fn cursor_underline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position), + .width = @intCast(width), + .height = @intCast(metrics.cursor_thickness), + }, .on); +} diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index fba577231..0e97808af 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -3098,7 +3098,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .block => .cursor_rect, .block_hollow => .cursor_hollow_rect, .bar => .cursor_bar, - .underline => .underline, + .underline => .cursor_underline, .lock => unreachable, }; From e691404a57b242c598a7617acb8ced27a06af6b9 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 29 Jun 2025 16:58:34 -0600 Subject: [PATCH 016/119] prettier format --- src/font/sprite/draw/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/font/sprite/draw/README.md b/src/font/sprite/draw/README.md index c6219b83f..d16035996 100644 --- a/src/font/sprite/draw/README.md +++ b/src/font/sprite/draw/README.md @@ -1,20 +1,24 @@ -# This is a *special* directory. +# This is a _special_ directory. + The files in this directory are imported by `../Face.zig` and scanned for pub functions with names matching a specific format, which are then used to handle drawing specified codepoints. ## IMPORTANT + When you add a new file here, you need to add the corresponding import in `../Face.zig` for its draw functions to be picked up. I tried dynamically listing these files to do this automatically but it was more pain than it was worth. ## `draw*` functions + Any function named `draw` or `draw_` will be used to draw the codepoint or range of codepoints specified in the name. These are hex-encoded values with upper case letters. `draw*` functions are provided with these arguments: + ```zig /// The codepoint being drawn. For single-codepoint draw functions this can /// just be discarded, but it's needed for range draw functions to determine @@ -44,6 +48,7 @@ metrics: font.Metrics, `draw*` functions may only return `DrawFnError!void` (defined in `../Face.zig`). ## `special.zig` + The functions in `special.zig` are not for drawing unicode codepoints, rather their names match the enum tag names in the `Sprite` enum from `src/font/sprite.zig`. They are called with the same arguments as the From 2084d5f256c2260c3ad6dc7d750c46b339585f0c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 29 Jun 2025 21:32:22 -0600 Subject: [PATCH 017/119] font/sprite+renderer: never constrain sprite glyphs This was creating problems with the branch drawing glyphs at some sizes. In the future the whole "foreground modes" thing needs to be reworked, so this is just a stopgap until that gets turned in to something nicer. --- src/font/Glyph.zig | 3 +++ src/font/sprite/Face.zig | 3 ++- src/renderer/generic.zig | 25 ++++++++++++++++--------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/font/Glyph.zig b/src/font/Glyph.zig index 5449e2440..fa29e44fa 100644 --- a/src/font/Glyph.zig +++ b/src/font/Glyph.zig @@ -20,3 +20,6 @@ atlas_y: u32, /// horizontal position to increase drawing position for strings advance_x: f32, + +/// Whether we drew this glyph ourselves with the sprite font. +sprite: bool = false, diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 25968e865..8c39daef4 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -216,7 +216,7 @@ pub fn renderGlyph( // Write the drawing to the atlas const region = try canvas.writeAtlas(alloc, atlas); - return font.Glyph{ + return .{ .width = region.width, .height = region.height, .offset_x = @as(i32, @intCast(canvas.clip_left)) - @as(i32, @intCast(padding_x)), @@ -224,6 +224,7 @@ pub fn renderGlyph( .atlas_x = region.x, .atlas_y = region.y, .advance_x = @floatFromInt(width), + .sprite = true, }; } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 0e97808af..810e17686 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -3039,15 +3039,22 @@ pub fn Renderer(comptime GraphicsAPI: type) type { return; } - const mode: shaderpkg.CellText.Mode = switch (fgMode( - render.presentation, - cell_pin, - )) { - .normal => .fg, - .color => .fg_color, - .constrained => .fg_constrained, - .powerline => .fg_powerline, - }; + // We always use fg mode for sprite glyphs, since we know we never + // need to constrain them, and we don't have any color sprites. + // + // Otherwise we defer to `fgMode`. + const mode: shaderpkg.CellText.Mode = + if (render.glyph.sprite) + .fg + else switch (fgMode( + render.presentation, + cell_pin, + )) { + .normal => .fg, + .color => .fg_color, + .constrained => .fg_constrained, + .powerline => .fg_powerline, + }; try self.cells.add(self.alloc, .text, .{ .mode = mode, From 61b7dffcaa1d4668937f2adce16ad2844ddfe7b0 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 11:01:07 -0600 Subject: [PATCH 018/119] deps: update z2d We need to use this version of z2d so that we can get reproducible PNG exports in CI for testing, since previously the PNG export was affected by the CPU arch / features because it depended on vector width. --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- src/build/SharedDeps.zig | 11 +++++------ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 51e2e4538..68d65fbe9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,8 +20,8 @@ }, .z2d = .{ // vancluever/z2d - .url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - .hash = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", + .url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + .hash = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg", .lazy = true, }, .zig_objc = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 1d95ed93a..3099ca823 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -124,10 +124,10 @@ "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW": { + "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg": { "name": "z2d", - "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - "hash": "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U=" + "url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + "hash": "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4=" }, "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": { "name": "zf", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index fffc639b4..133284201 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -282,11 +282,11 @@ in }; } { - name = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW"; + name = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg"; path = fetchZigArtifact { name = "z2d"; - url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz"; - hash = "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U="; + url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz"; + hash = "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index d032711e5..bb0a27105 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -31,4 +31,4 @@ https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025c https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz -https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz +https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index ec97a9c9f..f173e4856 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -405,12 +405,11 @@ pub fn add( })) |dep| { step.root_module.addImport("xev", dep.module("xev")); } - if (b.lazyDependency("z2d", .{})) |dep| { - step.root_module.addImport("z2d", b.addModule("z2d", .{ - .root_source_file = dep.path("src/z2d.zig"), - .target = target, - .optimize = optimize, - })); + if (b.lazyDependency("z2d", .{ + .target = target, + .optimize = optimize, + })) |dep| { + step.root_module.addImport("z2d", dep.module("z2d")); } if (b.lazyDependency("ziglyph", .{ .target = target, From 8b6e1fe5b143a81cd4158cddf58d4fd135ad9231 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 11:14:47 -0600 Subject: [PATCH 019/119] font/sprite: update reference PNGs to match new z2d export --- .../testdata/U+1CC00...U+1CCFF-11x21+2.png | Bin 403 -> 402 bytes .../testdata/U+1CC00...U+1CCFF-12x24+3.png | Bin 534 -> 534 bytes .../testdata/U+1CC00...U+1CCFF-18x36+4.png | Bin 1022 -> 1025 bytes .../testdata/U+1CC00...U+1CCFF-9x17+1.png | Bin 316 -> 316 bytes .../testdata/U+1CD00...U+1CDFF-11x21+2.png | Bin 1275 -> 1280 bytes .../testdata/U+1CD00...U+1CDFF-12x24+3.png | Bin 1870 -> 1870 bytes .../testdata/U+1CD00...U+1CDFF-18x36+4.png | Bin 3404 -> 3411 bytes .../testdata/U+1CD00...U+1CDFF-9x17+1.png | Bin 1101 -> 1103 bytes .../testdata/U+1CE00...U+1CEFF-11x21+2.png | Bin 559 -> 562 bytes .../testdata/U+1CE00...U+1CEFF-12x24+3.png | Bin 744 -> 741 bytes .../testdata/U+1CE00...U+1CEFF-18x36+4.png | Bin 1388 -> 1388 bytes .../testdata/U+1CE00...U+1CEFF-9x17+1.png | Bin 400 -> 399 bytes .../testdata/U+1FB00...U+1FBFF-11x21+2.png | Bin 5450 -> 5448 bytes .../testdata/U+1FB00...U+1FBFF-12x24+3.png | Bin 5724 -> 5724 bytes .../testdata/U+1FB00...U+1FBFF-18x36+4.png | Bin 9997 -> 9973 bytes .../testdata/U+1FB00...U+1FBFF-9x17+1.png | Bin 4298 -> 4295 bytes .../testdata/U+2500...U+25FF-11x21+2.png | Bin 2223 -> 2220 bytes .../testdata/U+2500...U+25FF-12x24+3.png | Bin 2638 -> 2635 bytes .../testdata/U+2500...U+25FF-18x36+4.png | Bin 4541 -> 4570 bytes .../testdata/U+2500...U+25FF-9x17+1.png | Bin 1848 -> 1844 bytes .../testdata/U+2800...U+28FF-11x21+2.png | Bin 1022 -> 1022 bytes .../testdata/U+2800...U+28FF-12x24+3.png | Bin 1547 -> 1541 bytes .../testdata/U+2800...U+28FF-18x36+4.png | Bin 2490 -> 2501 bytes .../testdata/U+2800...U+28FF-9x17+1.png | Bin 917 -> 917 bytes .../testdata/U+E000...U+E0FF-11x21+2.png | Bin 1104 -> 1102 bytes .../testdata/U+E000...U+E0FF-12x24+3.png | Bin 1251 -> 1252 bytes .../testdata/U+E000...U+E0FF-18x36+4.png | Bin 2228 -> 2220 bytes .../testdata/U+E000...U+E0FF-9x17+1.png | Bin 895 -> 894 bytes .../testdata/U+F500...U+F5FF-11x21+2.png | Bin 1114 -> 1114 bytes .../testdata/U+F500...U+F5FF-12x24+3.png | Bin 1423 -> 1421 bytes .../testdata/U+F500...U+F5FF-18x36+4.png | Bin 2470 -> 2473 bytes .../testdata/U+F500...U+F5FF-9x17+1.png | Bin 871 -> 872 bytes .../testdata/U+F600...U+F6FF-11x21+2.png | Bin 493 -> 495 bytes .../testdata/U+F600...U+F6FF-12x24+3.png | Bin 636 -> 637 bytes .../testdata/U+F600...U+F6FF-18x36+4.png | Bin 1218 -> 1210 bytes .../testdata/U+F600...U+F6FF-9x17+1.png | Bin 394 -> 393 bytes 36 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png index 6623bd0ff4d42cbfb869a8aaa315e820d8c2d582..581b0bbf0547c2526878bb36c8ecbb8277689956 100644 GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E8so-U3d6?5JkFl1z4 zU^ukktA8$+y#`1D64)WZz`*eT|C`Gjxf&dJm@oYQ|H^8ItX7NP!S4JZH3yXwB zB({RAgn))KtY8}sGjcL8Ff=Uqzy7x%7s%mApg<7f3WS>5f*NQ}J>UveW5^3O=il13 z4;iJIkW67o-U3d6?5JkFl1z4 zU^ukktA8%Hz6MAE64)Wdz`*eT|C`H>Tnz>S%m@DO-zB$4RP=C@z#PVdqT5y4tL{Z- z+t%2`>;2cfyOw8d!G>tbsD}yLjkX**n3wt@F!9Icw@EIKpR~MY diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png index 8e54879661a7e7ff268d9ee5819a4dd253c98633..852fc999be5a707fc47bbad639495a07ed7b56b8 100644 GIT binary patch delta 268 zcmbQnGL2<|c|E7U1_LViA<4kN@c;jt%Ykf%10-55{$2m;+rs|9V8LH2i#5J>8@fGW z2WvP`e9kic?*0Gvwm#J7sZ2I64%zdhW@!@$6B kK%xG%^qd9TSRncuL<9PN+m_uC7X39GC-(U4ONKP(eQjUJW&A{;g|C_^x zybKHs4GaFQt>tBuW~Xk6yh$Z|_*fj|D3O6dJd2s9XrqnBWl2B9x&e;-T_K6g6aSO delta 395 zcmZqV_{Tm$Mg6F!i(^Q|oHqvy85tND4lVfVpUbVU!GH>ONKV#cQVurYVqp0H|IOh> z9tH-60}A!8m17ocV?kB=fEBFfiX&HZ0K<`h_w_H!0$=#82w~&Ye{gJli{c9I1BH9+ zVyd20KW?91`ETM$qltIft(hM<&Z*!1ecqau=k`mh{nI#9E`(@IaEN9R%Fq(=SfIzopr0PwMOQ2+n{ diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png index 630d26dbfaf79d560f364845536935b414b94378..ecdb2ce10121405cfa22222a848c0ce9c99f775b 100644 GIT binary patch delta 174 zcmdnPw1;Vee&7KG0p@`J|Cj5QOb(jBd+8j6ZO&*VLQ&nXz*y_lB^(q1_p-z|KA)o6Fmf_X>`KscX8>{E9&Y4eU|=|)Q2$z6X2CWV28IOA*WGm!*5|U6 Pf^>Ph`njxgN@xNAX469e diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png index d9b2aef99d9f2940399c7b29f5191bfef318ed62..8d7de36aceef89fbd945cf8a56477f1936953b56 100644 GIT binary patch delta 1144 zcmV-;1c&?k34jWaB!9$d<#gyH@DpC&dy33l=Q+h2YQ{JL4?` z3l_XK-s0zi0RRBFO7GWWefCse^1msU7A#nBGtZ9AyF%lR#ea48-ym49;Pb&dOK%6C zxafCQSh(p^9~UfGaPD9L008p)0{{U3|LoU64#FT1K+*f3x-Od#K*n|q)4rd$m@3HY zjvGGnYwB_KkhLWb4GauC-(fg5dxgd;t)8zpcAo(Q0|OTsufM@!FAEqL_z7T1k|ase z+w0vFS1*$*Yk%{0{%tKVFmQ2jZyt`#Kh4Kbt0!0`eOUnv3|tl*G63HO{$Ierz_r17 zHS|r!#5NHEr?rU?Sdt`3NgZdyZ%5CXwV@x6&k{aWi~tP0Gx!4l0RR8&*1>XxAPhj! z|Nm;cbjk=M3O;bo-9-n?X*UlzM(7!WW2(!gn_EUtM1Ph91_lOB8g7j5|5rNA1`ioW zwg?FrcvOp!I^ae200ssIj)5gfk~FpI&#$W&=dEDa3Y0JjgPDsD-QPcA43 z?GYFl_=Dg#KK!fij{HmSF#@l#7BKK2@aM->_5cP3z6q8jNz$y2v+CR0vmMWFxil~^ za1@-XxPK)zKDnSEv`66o{Q&>~|NrdP-3r1m41iJJ|E1^UAUJ6of12!kcR^Zm6r{kw z)xqt@)VEb}1m0sUVBlqNu(uy~*#j6Dco8f~lB92SoINfcV)4_fw_1&}z!jDT1|9}a z6^>1fnG0V2NP{CVFfj0az$F9j9&q%fvENm@|p(3P%TssAr{zPWhVtGxO4S_c6b z7=QR|a8t#lTeiF%v$dYbeE|al#{{oiYss5O#5|7@qY(+-2e;>oL9FQ^B?Pd128af1}sUEqz#Q8 zT~FU-9nZF08W;$&he-z`(%K*rQ5t{?D`5c*-8Y zz`%=ONs=V(YV_!O`Y!8uw&l{mz`)_*`*Hq6xX09(>3Rz=Fz|=KnWfX6@h;M00960?AAFFgD?z0(fi*zRZ=jdE<4z%_XTRTA|%38Cz8k5tlM2=&Z-3Sz5e1x^=~hjwLQf@}l$> zS>tTxvYBC`{SGKnSM>> z4KTw)PJ1Hf8?d+#f-lo7S9q zOXqfmeSjBBjLA!Cqa(YPE5U(IlHX(|DGIZOH9A}iLwcUC0utlvsmLBcmM4@o|p#3h8a>ka`a65ynrh`LB{u+jChlxji|Y(O0*Jc_Nui zCS%G4>u>PK5gq6`#eMHEBIKex-ekUZyZ`_}a7ZSiGHciyi)zi?f0j>v@uefTyct0P zvHj=qO7E8S5hI6&U?K?eZ1jq9e5uzRpDMT~OMhTwK{wIwW`7gJN07{752omJYvb>Q z&+@x;8t*Cyf+~vR61sU!iWB7c(tQ)UvEKNQt~DF*vEW~r!AzSq_W26~D5VE;C zMo?{D{7D)Kf@oI#{hOwTyilu>!yf!eW16ra651zyyEP}dH(R-L>p6Apb#|5E!GUO? zw=NTcpzC0?hcIpUm^s7JfY!(;*ox>}7hOa<=7_LhRw zU-iB)jJ?Zu)ud`noN+>0B>sOJe68e$Htfyy6qGjd8#-&Z{jEQeS~&Pc@?aQi=o$MC zXG#455WH%bb-=`{$<2S0RO=a|hM$njw{`m4S;7|I=}kw2NQVv9TbRy+5LBUXSs^Xr z^a4SGL6~e1{uSRCsh;M1Jk)V8Wh?7B;l_!9pq~{n>PvhCNg<7UeGjefPPD2EIWu`s zitUsX39j@Y9V7NpDx*kT#>`v#gOyu(O0Wa~VBp97wzl>2pYz3Hu{diwi2@uRzJwr| VDE%q5K38J6U`Bix-W~=Q{|6!q5+ncs diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png index 0a616d72cc695dac524733880abe56fa572797a1..ab6bec96d48174c49b4822c5bfe87c766207399a 100644 GIT binary patch literal 1870 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 diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png index ecbb650a1c0b08f8038ff65ecd1a70d91f5b8e37..43035aefbcdfade74b5a321ad0330850f531af8f 100644 GIT binary patch literal 3411 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 Yfq)`SS=TGv*?K2{kY46AXH>{4k(*OVf literal 3404 zcmb7G3piBk8eY~c#!SP^m{BN9=`bh^N-kxMrd)Cv(Pi6RjYa;+ipa6hzl?DI+aL{_I001%+YZlWvIHg~}I9;hnkaTPpK7F`JOD#(C z=@PI+Q_YPeEYcCV14rV@i1$@}b#b-RS9s@*uf8Z1e%R4Qf#j@cNRkh@yj5RAJ{gX^ zS7*&r=U-kPurVKZ*ZXmb^M0|Z?H)`#w(l!??gCcLW6dGTlUV$-Q!pqWc*6yOLM0GL ztJkFf#EpVrs(kOF(Bo}m2T3FD^Qijf_u$2UF90h)5dlCb>~H1bD)mS#!OV4gJPB0I zhA$c&D^$XCi>JEp#|3C>Y{@wvukHM}R{mOd7ryD>!J~VPi!2IK zwq0_uzsY|dFrKY;VOlMv#I|VaK@Ua719g#o)|c?~J6X2s9FBh2 zeTsM07H(PiR@dHBe39~+1^-Cu5= z9r{w^9?rSc4*lcZID*GI{WLr0T^q!Ktt$Nmg2E)rh{ACTma&h^1ECR zNY4A<4H(237Hy^>Q0S`K(x?5_kHpE)!o*jwNs`>jbQdB`QBHa^EEn98LEfPoFq+*! ztiFM7n&wQCLZsdHk+0d zUprz$x+?L=NQd@w;mBa4Z*cb>qa8cT?)26orQ%%qLqN7*kif~IR=o2L*`8>fG7p_P z9Zp*M;Yo<;^_&!i`Uz>$Ok|(5NwGS_l(ygtrA-nCe2U5CtG$c8yaFqf5^$%tS{pS0f^sm~?P ztL3*Srshd`a@6LZy>!RGWS7h%$a2}zwO*g!FXF|HK2PHGul4rD-zHuv(yUn%)ssb= zBqb&P{19g-{h(0uA{>dvCjY0RDoM7;3Xzyi0TBYOq_v$Q1lU?x2?Faf?9CqHnTwv^ zo}&sp+%y)OeO%)f#;hg&tc6b+z2p)*S;NMU{;VhF=|X%#$mP*vKRecm8SLH`Hsom& zJacA8kwWiyEJ838O)bMXWH%!YS$|Y2v==tl?)vo64HcFYi-QtyaZxZyQZSX#46rg+ zZz2wDf9x6hRkdB}v=bpgSL(z93@;XW!-GG%p~0H3Gc)bkgu8{%59{2s9qtQNnA$xY z6Ess2@KQCYI6(Y{1^F^82BDHaeZN#fb`PgKZY=%R`6nx6(jG8<{&s2$+Dv2H&uVsl zwe*XI8)9JaNg52E!b{3|k3(|a&UF5L&@P6AXvM&tEYyDqdli|UB2Bf2@EYLJc%hCJ z6&DOH;k(HVLoAfHv;!9n$6A+DNXUH3g0Md+i+Q3-kH8X?3a^;9MKJ86*n2;!)Y(+6 z82yYDUuoy0?)k@&XBuvm1XtBhyCW4r5WbyE53@vvmA_X~AC-r za8wO;%Adl^br+_2pN^2}rP9=$&wm|&RM22jo9-(SR z#FfZ`%Fz=*v$XvX?{ru>!NgNN<&wOhe)zEeRV$dJ<_G z-NRL8jVwwl_`1v*_uL`$d{s3pbpkKrf%YrZz>b#?LV_|zk*;-8(q?sv$H=_TLcAPC zK3+oGir{Q3l}lr=8H#zFDwyZ-R7Wn{hUZcKo!vKjhd9O-F1}KafZ+>;eFLSpeHwPirQ2%n@^qZVv<&xhw(~t>r&LZRyxmUTRY&s3*~LHcPv%^$+mh7*tw(n``GacwJk3=I+4}`GrCi!@zZk;dEn#yQI?6`sQ7Z?- z`vw4SOiXjg)%`KIoTEA#CZa{7xQjdRp}Xc?#nsi%wzx;ye{kdvt4PWW{kYlkhwkj7 zTpQ-*8Rh3q(vZ!B67Aj!-6OE@w$h*q%0*mK^DcXSp^+n%=_B9h?8f#_ZBlHN8g4yauJ(@KW zWz>eK@7mUSpuR9N>xp6z|E}>{*`$?TBf53#8jh?wbjz6MmIs6K3J7F!0tU~m(T;;b zpA{f3Fri&4DEo`);azzw)E~lIe!qa=cH+gQlsr(4?f&D6L1m2Qay%bbHQRYTES0VL zC3oeQV(jVpW(3M3F{$JYoiS?u1M6aZf!J2?XM@FF{ElR(ktT- zm2CtMbd!`mzp-7Z29{DPsjC~F!I|YHG;-*qVM`jJjpJ$w zb~I0Oic>Ib;mx@*t6mp4jwiaRj+$ z>6J_B|Iaqjg%y`!E-pfh%{@-^GK<|WKHpTkJ$&XDa_#{fsE|6u1+bXrBW>8xKj4)^aD?Lz^Bft~+DR zTJ7DWGu9`eMC1oYRAUTP(2puIL7f2{S-#_(4xGYzo562qU4o8H_GRFt;QTchbZfc< zJGTL#H+uop4+g-AF-3qkL5Ohs{lA)LM2&=~wlHJ>0I1Lt-ei>XDjU_E@>;_qnRCd^ zj%2Z8r36mIv#9V^8<|f3&Y|35)?pzp!N}EvRW4+Fvb<@I?^Difh B*17-y diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png index 1a6cc75fa6d12fd7fe88e1caa1bfc6382b39a733..fc111e2d77136c5735c7665615301ff09dd5f436 100644 GIT binary patch delta 1021 zcmV@s7?rm41p^Ds}dsgBB3cEJ(g96K#0%` zApih?Qpyhik?SIV3u0b%!kaIcx%ae_GYBD+Eo9Ph7|!;Ch;U(Cd$JWFgc60srU;dR z5JF2cn3aT^K?tEJ0001#;=e7;>f~`6Z}X2A8>mGQLMT}%Xc`9*Th8qxYz^ulgwRSt zV$(Ar;rmW_KnQs9flwi?Z&y{^hy_gt~{lg*FHcDj@V{LI42Jmp1?a0RR8&l|c>yArJ)p|EKZ9 zVIsuIXqE!15)Zv=Ctd7;8-A@belvVP?x4phL?8EOp(LT443%COIu8mdv{dMTP&x&2 zP>3GR6AEg72L!+bg)S2k5fNEQ>%2x+RD3hMz-S$X6j~{iB>X=aD!tFc{vH%kNTCHn zXcVGzrqJ}khCv~EI8I1JLC3F5Cs4Kt8<gb>;+KG|aNX=Y6S*28gmw#6%gzuvXPZ6;C-r`W5IP|= z^XiLHA*)bBPsahF?+cY%1Vi{w5OP2$hY$b&z$wf`a?C5`Gt%V{QZF|z)DHt&+pju9 zg!(W-2;~+UeWO`a34{>Z%)C(e%M}d*6%a}$1OV`1{*zh*B7a`Mw2>V9N_j@Q975{l z@g35_|vv0RR8( r!!Zp2004lX{;dt+3mXCe00016;B=jV40`aj00000NkvXXu0mjfBHO&G delta 1019 zcmVR|t;rnrKl%gnmINcxfh#fe@k2 zyiN!J008%M{H8m8aL&AE>T-l;FW0iCNP$qC5DXbwdl4cuLP#i6E-Tb!a9`cnGPz!N z5hB!C2mk;8&*k_Fa@}~;o~g?bn!Q{~Xt%K*iW7n%L!ipJDj`BI5*iZHW0`dbgb0lg z0ssIw;}4PTB7c1`uR7t)7tGvy+Q}J&5Xu%Z={O8$`$0swFs?n>iV#AHLSj>d%0LLA zr5Vgh!p$IrP!s?F07~)SmS%PGIE}aY$BPZrq6i_BEEF`2gNQBX_7S!Qbr3>mB_Xlt znUL^(Cp;i@B_M=QworWqgb-Rf0001#bNtU_R=*3c4}T435JD(h$R83JhQ;v9+)&gH z2q9E1^w33F_gw#S-2y`0!`?z0ga#E5`ZFN_0N98(00030|Lm194gxU@1o{7`LzTk9 z5tc#cwKF48>>VrZt&RW2eW2Wt9*2I@O; z!w4aiTWIu+W>F;<EGdLg6o0Gze5cD47rd0L1x|TmvG1@=P1av9FY8q{|_sUM??W zhmE7puR247aTp}2AbTVxu$G$o^ z6S;bvLkOWxAv87=+Y?0N~Ah00030|Lwy8 p2><{9fS~`c+Yu-z00000`~~22oq_1@evkkF002ovPDHLkV1mW(wXy&J diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png index d47d83b743de43e11332c1f080a2a0c9e8f8b167..ed0e8381614585f5bbb2eac395badc97154b2740 100644 GIT binary patch literal 562 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E7vJzX3_D(1X7V93b8 zz;I~6SN~itdkv5RB(Ot*fq~)w|2Kylc^DWN4k*;W)|OeYjRi>s0|P?>7g)_rN3IqF z9@Y!L=UZ)_;j5|Dvf#%@TY;V#llbo*Wd`f`kQ!S0@#FuZAO0EnPcAT-S1GrP?YPLj z`t7_er%rO-*3F%&%*0(2=jSKzW90#mjy-{ob{)xEc~RPO%Yoi|ClI?nj8 z&|>Lv+eDDL?l%*hEbUB3Mj-O*Yw7q4b z^YcQrW16-c!geCdzwA&41;T=p|2}Qm&#!vUZJJulj9wkz9R}PsFXN}~%dOaZDJaJg zZ1JD|5By+9URub@z`((L@b7lFM+di@oO}4A=jGR2#&-~P$aImTWAUI@TU;`T@ zxEVPa7#JEB{9pfDkP8%xNFav=qQsCFtmNO?wGSDknUEAPFfc?jg4Dc47$ZHY38sXZ XQTxAXnkq|03P`1=tDnm{r-UW|F|^Ys literal 559 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E9lJY5_^D(1X7V93b8 zz;I~6SN~jYeGQNTB(Ot@fq~)w|2KyXc^McO8W#LptNV~snh8k-0|SErCs@r*N3I3~ z9+m_D_gl&BnX<@h?8y@ zaxp0KxLo{wzv{MYK!z&!Aey{Odw*-8T4hDX2%mr$TNr zZ%gt+esG9f+Q`Ykz`=a*@AfkGT%R4x+}83Yu6pk$?x@@8ydmM>X|N~ZfWrV9){LCs zu>QaPw;&fN3X#AYR!DTfmE0E8K#Q{t;$UOmBBIPzmk}Oi+}i(5(>hbPo&p)_>FVdQ I&MBb@090ti>i_@% diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png index 6366a0ff6030ac12f05c3b87c8e1a3b9469c5b90..bfc7882152eff8b889fff1fbf733c0dd89746c07 100644 GIT binary patch literal 741 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|V0`U|{(F|IJ}TUIqq+h6VrDo_)YC&4i?afq|ib1x-yYFC)4d z14gi#%NN;>IEXM``2YXaZvNjP$t{K^uVki`hPrI21DpB5z~hMrEN#72^g#4=c4gVlsoUyw(x+$hh)8G{1SBjt-~iTq;2J;Jl3R{k zOojq37k}Ts#jH^_gY${-J!bR08eL71uKSI%>djX^eZqCMsHio|a;MYQIWY^P{MH5N zviE)XKE1J#jZI1-BVj>flcK9)i(0clTvi4Kh8+h^1)n+f z`NUde6V$_3Oq;A7a=KCZo89I;{=ff4%!}P-c;SEpGczxbiGjeMjjx$^ex0PfrR*mp zP;VaOJZvD~a`E48KSs_q9jyO^-pf8W;OVZN#GE?m&Vr~((jneO%~_nFAl@={`HZR0 zCq`EnHD}rGblf`c&Vnfaynwap<}0o-ufOs8z^6TLf2aS{zj>^YjnAZDK_fG-3`gCq zubTH}Jz=|wWSVDQfG#M){XRir1QD~o582UUZ9NgO?~kr#?^_0RbHt%)7(sq%Snz+n m^rR-#M0CIhtmG|LC39HLvA<^sljH`U|{(F|IJ}TUIqq+h6VrD>OSO@W8*g(ML;=kQ~jGSvcME>aRFQ0SRKF%SF4Jevut-dZk5|{|u z12emn#f1Zn4;IJHf3y;2%udIxD&{MuJz={l1d2`j3P^Y%A~*LTD|+OvCn5pB)Zj}1 z^PuK1a)J}m|Mksf8fd9)16avhgc9jVO|YcK&3KOeJww`p3pYXO#M9N!Wt~$(69BUs B6fFP% diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png index 72b744510b2285d16fc8f85960ac1350758c79f8..2dae7ab4a259d960f5fb5ba98df06d39145915c1 100644 GIT binary patch literal 1388 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfq~W7)5S5QV$PcbhKvjh z42Kqc_0Q$>*8nL%0y`ub7#RNle{;Bzhk=3NfI|IiZJ7nzSddgOFfcsehNv;*Wnf@v zSnzLcEia=q6RH{mMu-|lPOzH)>!l|(p(&Zef~MrQpaz+!}56pFYa%Fg#_z^9tm+24y?@?Q%cw@A$B3 zPP*kzr>%2h7DoB43(!>$Uoma6cF5_IqN|=h;ksH>)S3m7mU-ZM?f$!alUE-2>Yw@k zqy)2MhRFp7wppnP%)M+<9j*P-_A=`f{VPkKex>>O{7Y|ylqWb`WM>u9@K$JC#LuM?FwKFyLl>H2v>?PO-+W>8$DzELw45a}oqsR74guxh{B=dXRHkwgT9R@8lryam$d4 z$x(#m;_v&n&bWw`{H#gu4o^tpw)K7J@|c-{fx+kVQRlY)`Fr;NcqrO3uiAE}4UW`b%%7@HTNo>8f-r zh~-eq*g8R>sZ6nB!BwXw4km*W*JM?8R+j(&&mq(#H%F#`h0~GWkI$khCGr9!S>6K0 zF@wv+-}iUzR`PIAZTV4~e^ax^`9i1)-wWrHZ2by_4*Gtu5Wlvgp#I^P>Hqo7_3U3% zVsja~!x_zt>dK$1t>!u|zjWuxi_kc5)&-i%Bl-< W8vT0T@TA@bxysYk&t;ucLK6VA$lsX& literal 1388 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfq~W7)5S5QV$PcbhKvjh z42Kqc_0Q$j*8nL%0y`ub7#RNle{c9J62>3#yt2tPnMfoDg%?zw2m1GbVr;Malp5zXiEKK0pE+#GuZB8uNesZ9xq* zHy?;Zck^Q|ITHc1D_*#BC=WZrklq<0s{){2?u&#!d< zXU?}r=XsRhx&U4E@DBsr|*8bmJKK)Ab>;6l3RoxUC7x8nc1Wa>aa+MeHSfJC$ z(q*SH!QsHpE6a~>`gyXhzR7jLqtpYOVvSwX8Py|LwBp$2BnYmkkSu6=YAyhFyx~zzCK~QT2<7XWxLZ6EY080T=DPyJKtqo^YxeRJt@H=XcFZi z;J8`9_g_$jC3cQX0Sl+2xH`LxqtgY^lic9+ zbjy&d#X!XM;@|gGyE0W42sstmF8{)P{`m2bjSH03rE8ixS!HcNSpWng9v}PLdRqHG zJHMX&iOT7C;u@9|7|o3Sl|T0jW9{Q#x^r@>-HrtyAOHUkiiQRU5$A*dc5h?mSGt)p zbLX=S@^V@e9PEWW7QAR?;+!Qmo1tB{8Gf=>z=t|B$;72p10Gj#Xj@g@oo+CFv-2%_p(v#52ha46(Ph%7h9|X}ogz4!&=uYW< n$c*ONH%uUl-XdHJQ*uCiPNQFMo8J9EP*U-9^>bP0l+XkKc_-Ju diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png index ca37ee32883f64d6d02a278c8c2e60c3c6f854e2..cf8d5afc7e5294fbbe63568eb4fbcfdba549ca52 100644 GIT binary patch delta 309 zcmbQh+|N8gr9RBl#WAE}&YJ^H(hLj?|NpT3G4M7G1%m@Dezoi}$w6bt6BfqUq&Vg?N_q~c$mrl=)_q%vP;c=Eu z;~_^^<@gDA+MhWey8O&$k>Q+lGk_-$C|NpdE z1XF8sm)k#uo&0Cpn?zZn_Ag^#&^HMWs@nPO=^}>br>mdKI;Vst0N*uup8x;= diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png index 58afe3de7ab7579b86fd68a5e0dfff03db55dd65..ecf6eae26b9a2cfcd2d027d2c15f9373eb6c0d9e 100644 GIT binary patch delta 5419 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 F{{VArY|{V$ delta 5421 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 p3;+Nif_PC~y+n2u2mk47>fV^ delta 5516 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 Xn7M|6v_=2?WP)zpyshyLV;lNEGRz~% diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png index d4e488525135da1459ef46642d2ccb21bb9d1018..e1a9e7841def9008a0c8cc31a8f5b5936e76ea1c 100644 GIT binary patch literal 9973 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 MsOTtHDOv{q2N{SX!2kdN literal 9997 zcmb8V2T)W^(<^1ha^FE$sl0?C1*i`L;u>t(tSHr+cQSXS&aHPtS=(>8g`KZ$JS60GXzSiU9xs z!~p=55jWh#WN_lmuK)nR_4VfW4w482CBeu= zy%b3GxsjGX#gT+)XkDV+3NI=b;RWg@q5TU0{|E_y{qUb^K4+fxC)B?OeXSOvM7>IMKvqRw$;Je?ka<~iDY-#iK;#(&JuGd4B@0COhm z_t&*Zah^Dempk&tDJ>6%^#Y|y_z>tDJs?n;0U96~PXOU70RUYXNr_QfK+u?|pB#Ry z^7`p$H_M6f6CQLY4PuT2w*x`>gZSiuwD|z29w3tUH@bI`?2p=}Ix0F*lu;wpR{S-z zcWb%s))X6*qYMqOidq%?-{2yYTFskNSuRgVyb+XJn`_+jD??n0n zB!}GR5wH17H80)=SCu!m?{Vz~(jj_zSFYl`^EVXOkoxknfF08VzJJV7`3L-&%J6r+ z%}0`7iNXZu(rdX<{el?k>NLSQtx1D7v|6)-Aokdjp+!PMN29ZN>HrqS;$1nJ8iaLP z_IEc80@*f%NdMrL)C*9aH7;AeH_We?tOCJ68xyaDY#E8Vha-ims1@qGANmhy$ufz5 zr+Dto_=`dB8{t5JJXAG6FevopQ}G`n%dwZy9Rh#>hNY^_%L>1KRGY~+h<7F6W+eGj z&JFmuE|b)oTP>b6RUl7VEl}EPR)eiHb}8DULLMkAP^y$lP51VD$S|y~(2din2!Ye* zOF;ECYcv`XenF`yx)e1fgMY;{Uy{MXUSvI$ixRpDzf|b12WRD zJ0h;w@&TGWjjK0DuOcBrb7sYMq(RivWMBbdvcQeAH1(n}$lN$7{60s;!kf>jZCD() z#e0xrObfGGy(g;YpO0MkNYxRuJ-xl0!}r2mlF}+9pVU&*+dJoEE&D&|0>s@7r{8{i zY)(lDwKxpZNcLYZbsMtCGw~*^v<;I8A54AKVk>do*yqG|D>)Ui2sV}{8RepuaC^k_ z-G4~oRX2A+9P(=~ms7?gc_k;O7>)A#?pf{kMWTFs5`KKuKHAL*GSPFSRX8BjE^7h!?-lzj4Rby5Dtp&;B+7x;M(jx4a+7?@hu@ZtQp0{Wf8Z5cR zpTIZ1Hc*{;wpUzL;3~9KM zls0ZPJ4aygpCP`qu~r0utq;Y^Pg!0IDD&pNQj|pQ@o|-o=x}C59C5EAlUJX;T70C& znKQ#4}$`;b%3l;9K+{i+ZDr$GdSggoubY$;W# z*rq1R^DSB0hYXJ7?}fZ+v7I}K_s23l0!|I#*0&y*TTxYw7Q?e zX{fmpl>&dxNPsk*l1LSe>?qkJQAb*FD!+xWUyT_36wtsc>ldN`Won<6P`cL~%m9sx z=(o~I*4US`Z)1`z^m)v>6`Kgol3#Scp=kkan2wC%z`X8lyRaMD#&#G3i)gfXS4Q$z zWpK7JADkg6cvc1rdin^#pu{jKmyqaJVJ%d7DB0UdM=HHQ7S*rn4{vZ%%XUZVc!Vq^ zO7^UJ@Tjx}6-{?`*6gK<5#L#p(6+2R)Mr@TP;O9THXP0hF~&{LL)!x@J$Tg}Z*WjI z4-y<7HaR3DO6w1ohfJWSsp-&LybmaYN1bCdR>c`^5iIe>lsj6qZM3_`7QoK+df+E7 zw#DFu0~WQkX?gHh`2=UvV0bK>yjHjcecmx67VjWz0`1qKG#ci({58QD3vKWIG*G(|(?&>%i27w3sRac3!zyfR ztiB;+!h~y#Bq7!MF7{tWsc?q3WM`I+$tYe4U8{Ut=#k=^>%ZGlp1ym7C@dmrk8M}c zAPHZ5pkZF|otq@TE>CDuo7#i5{=St|qRAhYErglIET? zWj`f2qXzY$Vy>&C8A?-s{477ETf1Aqq|7`NTREsoVtU*p8yb0*GTMWRxONj|D3MUR zuR~cz1Z~z$1;|)io2^KsSx58_o@#a=9fW`eAe%i|qIPpX9^(ze->zvGDEZ;_ zN~R}_oLwW}QNVZFyCO;s%enI!er<}uqHWWVDssSb+dG6$TnR&=xcB?_SyIKsX6BPR z_<4Xt|Eh3ZzTKK?`QXi}?{ixgVauXd;gN2V`^82Y9e+9T&4si27m|o4g#(|Oax60 zUhtAm6kqwNN+EZP5ixR$LK3fyS8iT&m7P2C?#XE?9R3Y$@+^J3L}8I#Hk7bJ6qKEk zu!Is?h$8HuRI^=au)Ja3oN`f|sWMZnaJ%Az+HO@+wKL{soWmQS6mmUWTaZy83-2R> zr-L<#Go<9b7;Ks~aDPKmOA^p9(vp&3xDC22nsbu;;@mWQul60VmL#z4j*Wxf zEi;9+nI@Ok!a4EKLMCgwnv@m4cAiz=iLlqa=}!yN0WX(HZwh1bcO_OGZ`csHhUGsI z;tLA%>U?ip{`!!JLVX>0{m%u*np2A{gwEk!nu4o>xEGDVn~;S$|N8g5;pSq-3pZ%+ z32FvMJ6#^@Qmw9Ztj4Bq@mX1QZ_N}3UPsYP% z6^!C~mEq`V``ZdDb!@J(32(a7;-@ISBA*1a7YF+Bfrm@?bIr9oa2Q7gizo%7!%Y1$c z7cqcK6C+ejZhMhMkY-|)&ZsVHfXd!^XXy7jq>QA1>8QaCjx^nkrmf#m?N6m&P*;Zv zEGNi0@NPU&5#_4H+UPB8h?r+x3-swekRkT#8^J2Dxs-=dIt zL^@(?KbIi>%PgLmh2KfqV~}4?r>!`Q-IU<%wq&Iv8>4sDbNVimp_tY?)jlt9)Ap%` ze$6531j?_WOuu=sj9IM}_25l41LJ8}_*eh&SoCH*kh{f5h@^!4PZ1qt`*+ir_L`Q3 zMT~#qpUd!^LTY)E@ON{+g=E_JVX1Wmvo-2esLzF*5?JM#wzRDpI6&S+7ai>7=Q9b+ z((XUrdM)%Q5?$Fo4~5O_k}3XUp3Gl79`@Dc>iQx{;Y>{GwB2~B59r5Exf?SeeSz{u zmL?jR5@xtJJqYS*B$3}~Sw_WHPV{&ZbF3jgyVabeh3a(eheY;VM(O$ObI}R1=RBxMYoyju^ zIZKDUZQ>E^5s6Zk#B>c`iPr)h#Uox}`-a}(l?z^{9TJ;N*K&|QYc(p;7d@7Z!jF`a z7RG(O!D6|s9fcjIdUu;4u$}+z{Ur1_nV=PC>X_DMxXmwLSYfHomqUW8M)7Yyv!U!F zoGGcoJ~+dSB34W#bH;+Fv#1>;kgL_#E$-o2YmHMC3$fd_b|luA>XRme*$y&PKdBbw z+ZAW66!pSr{*(X{Md|W=GtU}tCQW|!Gn=?jMaACH(y5*3QdObz$l|{At#czjMlaBZ zr>Z!P?CN7dvkfEa)+anld?S=orVKZ-!msLB=R{O|yIy-lhEeh2pB=in8q$673q?M< zG?QP;lZX%}NzV71L(8ou&3CUy(QWtNHW%DINkOQJxm_DKeBFMS`cqSx&ydo20W?OG zS0SlnCX3z$INYC^1bCFdq9WUeRp@$@FTr+qDz);c@8DalaxyjJ|y$ zn*0s35{A$ZY)(5sAko0wL*ISnP-}dJp>Jr<6rg1Gt3!OS?d7p9LK8-@WCg!M?3b}Q zAxVQ@g6fuph{sHSx~B3%oooWab*`=iOp1+*3EXrv0^^lA;}9!&Vj>*_g0V<7KBv97 z<9-Hbq&ZCr>%wF@7oOj*Tfgca(6>%7&LuKvX`lgDMZ&M=<*84z%4o4z``|TNw?h>s zrxvti?O=xg8j)5eb0!G&iMe`gA!CXtTNUYvTnSp5x#x`h#{K?gzA=GI*Z$47dJK4W z`C-iY+EGQGfRIK@L}ss^Jtk+DhSgM)0dIQ|p>9iZ2e`+Q0~4N)`vtS31LOKsz=EB{Cy!hhwYuHlykgT595RXc@0`#{MExZB(zJE zQhDTjrg_8emYSVw)Cp-bb)VY|2R4G@RB4g4@umF8+GEtw%oqO#VJR(jU;_oB3yj|Z zNXP?B6<0&zLfv}_5t;x{xdbtu2Ow$bu15f8LJM#s9@4l2(jdR#r9L^5rd0qGjGrRM zH3IqMdObQ5jM0H>02c}b3k%{A81-gZdX5k=l`|8;Tmz{1=cz1Om=G6uZ2WJAYrKO5 zjex0)T(!JGbLYgsfHPn{@a_)9^E1f02Pp3u!WY+4()#t<;}KOk58gGN#!0G&M1pyt z^=aPcjo}GMpeAk!Vy25*3d8)(qntb^{gZ>q79;Nxg@N1h%bv`0Sy%ZJLr#c&3KudK zg$U-=E2^(PH0K2w2zp8i56bP_*)51E6H1a-4Q2vscee93f{YhB--EYltP!AZ38JSj zjAmUdvYyO7ohQ{mtWaaoQoNGny^A|PVq1C`u;|a+I4X3`zQi`M27)q*^YLSM+TR^D z5tXoA0xp~fHyqfZ_n7i?@4E$34aAHVg7bI6iM*9Dn5#E8Iw zI?3p2@B#SG9LD9K0Cs?K$N5ZPrO+)C2&HkIe!JkOyfo0>vsrHBW1ZfiU)NY z(@%0w7sq0KfCQxz<#1sUpejlYT=d^XK|jCq*C=vF5*9>ZDqoOVD1HIel0{iVO@3r_ zu2AfvlT%ITBqA=y- zv2}Qwyuo;&HB@kIU6s&LK1m__Gj(gN7g&os(7L^?8wM0$#T|Rsw^67qfj3Xx-C%*- z-zj88?**NC0d8eIEO=7Z4xppJ5{wAa=gK^f#eW>tju1aYn82v)ymj^|4gw-TAaSlh z0+7V;Lk@`w37SGJ1xj+|h)Ih9$VbkDrwEF;4)vz5%p7Bal(^&I%s4E1)aN$`CRa|C z(ws$O3p}wZ_#lj!L;*M>Ovm2wVvYFzI%aP|Ek?Uh10kU_0?;R<$jpzysuX!StYhk$ za=m!hL}G2J;t2bmVZnMT8p19)#wDzbPrfici6i{TK}(KR?l8Ynix|r(MLVnmv&<8x zHA5xf^o85tJuy$8jd!;he~f#mXly}g>$DUA9$)I@x?gWRq;r3EHtJIP8u@HS2K{~k*o@bo_Hjs^ zg%CG^TIjz=2SCt7kc0ii^-{s_uDpo1**_a6(ZigG3ug2c=+8&+yu-amlsJ68Ce z0j2@pY`Ld=B;d*p^`$73gUN`kP0maJ_%$tSP$}d!3<2m-6(I!RBv-kC! zaa&3&<7?D?@f`ckXq%#b;kzP)amGbSi4*01^f)~*gyufIv;~Y-Ia0kB-uW4H=!Lc^ z(gL+qVw7$*jq(kXDI9GB#~B~{Ofl4|q^tZi21nWimDs*z4*azbC{#&LQ@cU9H;#R; zJ6}eAA&=nG=WeWw!UVq)5V1e4;8dif)VQbVRp$UV)*BQ|uyq{?rEp4TykI*Pv56I^ zxOxLygti9o%ft(vJEKuWTK)Sv;HkJ^tI8yAaOe$qT6xN0YAI$jMYA=FdL`;g0>P!v zO={r=&Nkj3uiokqxpGGDsrK647QZriK=9fPzVFg{^PR#8IQ0K_zJ3u}@o%tgG2qsP zf(;Nd@fZ7Y)nrh`O={enT~i*FZ$J|tsK^`83$EA<>`ODk2*W#UL{E(0dJNQe(ImND zu6Gseyz@v}j9*o*OsB(ZV}?2B=?c_8lKVfks$ux($W@T`X}lk5QtBTHzX}|xU1u<Hr29w_PLpPCzUIYsRF(w=tJSJ}S7 zu53B4oO<+H+_`--DnFk-v|#I`L3+F{jB%eXlFOK4_?OCI;Uf7!I>*f5D)RZx}p{`r@Nm6+c+SF|jsvhg8_QdHiV57$ zw|b(NfT?H?;o&Yxr$TGvbe4E3TskWa0eO-u>St|bA9)WkUMn>g6s<1Bw>7E3*((F= zZ_%bTk$;Ue9Z;jb-9v(EZHNUuQPT`2&JtP)e0!;hn=`Yoh zaWMF@M_D{*T~ewY*tZV-(E3+D(@1DH92Q~gfm1)~`w34nPS@ri(_w#h!RGUT3=(BJ zK=d~R0RRAL^GT;U+47C<6vv8|Q4gf_ipY{|Boq}P5;fIxSwr<$s3t}i=*vPrDN9v@ zrC+7Qx5`F?-+V4|jy#in`=02a&1{2}-2D2k;$O#|t=|t8BaWz~DEF`#+DUptDKEMxK1zysb0&(2>0r>C32oyPRjf zK2S|L+DMP%PQ6cqigO0fyqWZ1%h#YP=AVDp<9i2LxF{v0{i}hQ)?Md-<(uX|KG3rd zX}lk#zzDe?|9m7-dtT6ZV{%Db?dN&%KYNMU5o$+C1$d1?xSy}=ofyj!SlF{N@Y`8C zEw;DHsb)rFdImWlpx0&!g}H{KaNr-AZhCE{bn`J?B7asPKf;qzZnese((E9>*~E?u

- *
- * Copyright © 2022 Kenny Levinsen
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct wp_fractional_scale_manager_v1; -struct wp_fractional_scale_v1; - -#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE -#define WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE -/** - * @page page_iface_wp_fractional_scale_manager_v1 wp_fractional_scale_manager_v1 - * @section page_iface_wp_fractional_scale_manager_v1_desc Description - * - * A global interface for requesting surfaces to use fractional scales. - * @section page_iface_wp_fractional_scale_manager_v1_api API - * See @ref iface_wp_fractional_scale_manager_v1. - */ -/** - * @defgroup iface_wp_fractional_scale_manager_v1 The wp_fractional_scale_manager_v1 interface - * - * A global interface for requesting surfaces to use fractional scales. - */ -extern const struct wl_interface wp_fractional_scale_manager_v1_interface; -#endif -#ifndef WP_FRACTIONAL_SCALE_V1_INTERFACE -#define WP_FRACTIONAL_SCALE_V1_INTERFACE -/** - * @page page_iface_wp_fractional_scale_v1 wp_fractional_scale_v1 - * @section page_iface_wp_fractional_scale_v1_desc Description - * - * An additional interface to a wl_surface object which allows the compositor - * to inform the client of the preferred scale. - * @section page_iface_wp_fractional_scale_v1_api API - * See @ref iface_wp_fractional_scale_v1. - */ -/** - * @defgroup iface_wp_fractional_scale_v1 The wp_fractional_scale_v1 interface - * - * An additional interface to a wl_surface object which allows the compositor - * to inform the client of the preferred scale. - */ -extern const struct wl_interface wp_fractional_scale_v1_interface; -#endif - -#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM -#define WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM -enum wp_fractional_scale_manager_v1_error { - /** - * the surface already has a fractional_scale object associated - */ - WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS = 0, -}; -#endif /* WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM */ - -#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY 0 -#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE 1 - - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - */ -#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - */ -#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE_SINCE_VERSION 1 - -/** @ingroup iface_wp_fractional_scale_manager_v1 */ -static inline void -wp_fractional_scale_manager_v1_set_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1, user_data); -} - -/** @ingroup iface_wp_fractional_scale_manager_v1 */ -static inline void * -wp_fractional_scale_manager_v1_get_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1); -} - -static inline uint32_t -wp_fractional_scale_manager_v1_get_version(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1); -} - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - * - * Informs the server that the client will not be using this protocol - * object anymore. This does not affect any other objects, - * wp_fractional_scale_v1 objects included. - */ -static inline void -wp_fractional_scale_manager_v1_destroy(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1, - WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - * - * Create an add-on object for the the wl_surface to let the compositor - * request fractional scales. If the given wl_surface already has a - * wp_fractional_scale_v1 object associated, the fractional_scale_exists - * protocol error is raised. - */ -static inline struct wp_fractional_scale_v1 * -wp_fractional_scale_manager_v1_get_fractional_scale(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1, - WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE, &wp_fractional_scale_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), 0, NULL, surface); - - return (struct wp_fractional_scale_v1 *) id; -} - -/** - * @ingroup iface_wp_fractional_scale_v1 - * @struct wp_fractional_scale_v1_listener - */ -struct wp_fractional_scale_v1_listener { - /** - * notify of new preferred scale - * - * Notification of a new preferred scale for this surface that - * the compositor suggests that the client should use. - * - * The sent scale is the numerator of a fraction with a denominator - * of 120. - * @param scale the new preferred scale - */ - void (*preferred_scale)(void *data, - struct wp_fractional_scale_v1 *wp_fractional_scale_v1, - uint32_t scale); -}; - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -static inline int -wp_fractional_scale_v1_add_listener(struct wp_fractional_scale_v1 *wp_fractional_scale_v1, - const struct wp_fractional_scale_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wp_fractional_scale_v1, - (void (**)(void)) listener, data); -} - -#define WP_FRACTIONAL_SCALE_V1_DESTROY 0 - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -#define WP_FRACTIONAL_SCALE_V1_PREFERRED_SCALE_SINCE_VERSION 1 - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -#define WP_FRACTIONAL_SCALE_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_wp_fractional_scale_v1 */ -static inline void -wp_fractional_scale_v1_set_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_v1, user_data); -} - -/** @ingroup iface_wp_fractional_scale_v1 */ -static inline void * -wp_fractional_scale_v1_get_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_v1); -} - -static inline uint32_t -wp_fractional_scale_v1_get_version(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1); -} - -/** - * @ingroup iface_wp_fractional_scale_v1 - * - * Destroy the fractional scale object. When this object is destroyed, - * preferred_scale events will no longer be sent. - */ -static inline void -wp_fractional_scale_v1_destroy(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_v1, - WP_FRACTIONAL_SCALE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h deleted file mode 100644 index 0399e11ae..000000000 --- a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface zwp_idle_inhibitor_v1_interface; - -static const struct wl_interface *idle_inhibit_unstable_v1_types[] = { - &zwp_idle_inhibitor_v1_interface, - &wl_surface_interface, -}; - -static const struct wl_message zwp_idle_inhibit_manager_v1_requests[] = { - { "destroy", "", idle_inhibit_unstable_v1_types + 0 }, - { "create_inhibitor", "no", idle_inhibit_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_idle_inhibit_manager_v1_interface = { - "zwp_idle_inhibit_manager_v1", 1, - 2, zwp_idle_inhibit_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_idle_inhibitor_v1_requests[] = { - { "destroy", "", idle_inhibit_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_idle_inhibitor_v1_interface = { - "zwp_idle_inhibitor_v1", 1, - 1, zwp_idle_inhibitor_v1_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h deleted file mode 100644 index ef97ceb86..000000000 --- a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h +++ /dev/null @@ -1,232 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_idle_inhibit_unstable_v1 The idle_inhibit_unstable_v1 protocol - * @section page_ifaces_idle_inhibit_unstable_v1 Interfaces - * - @subpage page_iface_zwp_idle_inhibit_manager_v1 - control behavior when display idles - * - @subpage page_iface_zwp_idle_inhibitor_v1 - context object for inhibiting idle behavior - * @section page_copyright_idle_inhibit_unstable_v1 Copyright - *
- *
- * Copyright © 2015 Samsung Electronics Co., Ltd
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct zwp_idle_inhibit_manager_v1; -struct zwp_idle_inhibitor_v1; - -#ifndef ZWP_IDLE_INHIBIT_MANAGER_V1_INTERFACE -#define ZWP_IDLE_INHIBIT_MANAGER_V1_INTERFACE -/** - * @page page_iface_zwp_idle_inhibit_manager_v1 zwp_idle_inhibit_manager_v1 - * @section page_iface_zwp_idle_inhibit_manager_v1_desc Description - * - * This interface permits inhibiting the idle behavior such as screen - * blanking, locking, and screensaving. The client binds the idle manager - * globally, then creates idle-inhibitor objects for each surface. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - * @section page_iface_zwp_idle_inhibit_manager_v1_api API - * See @ref iface_zwp_idle_inhibit_manager_v1. - */ -/** - * @defgroup iface_zwp_idle_inhibit_manager_v1 The zwp_idle_inhibit_manager_v1 interface - * - * This interface permits inhibiting the idle behavior such as screen - * blanking, locking, and screensaving. The client binds the idle manager - * globally, then creates idle-inhibitor objects for each surface. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - */ -extern const struct wl_interface zwp_idle_inhibit_manager_v1_interface; -#endif -#ifndef ZWP_IDLE_INHIBITOR_V1_INTERFACE -#define ZWP_IDLE_INHIBITOR_V1_INTERFACE -/** - * @page page_iface_zwp_idle_inhibitor_v1 zwp_idle_inhibitor_v1 - * @section page_iface_zwp_idle_inhibitor_v1_desc Description - * - * An idle inhibitor prevents the output that the associated surface is - * visible on from being set to a state where it is not visually usable due - * to lack of user interaction (e.g. blanked, dimmed, locked, set to power - * save, etc.) Any screensaver processes are also blocked from displaying. - * - * If the surface is destroyed, unmapped, becomes occluded, loses - * visibility, or otherwise becomes not visually relevant for the user, the - * idle inhibitor will not be honored by the compositor; if the surface - * subsequently regains visibility the inhibitor takes effect once again. - * Likewise, the inhibitor isn't honored if the system was already idled at - * the time the inhibitor was established, although if the system later - * de-idles and re-idles the inhibitor will take effect. - * @section page_iface_zwp_idle_inhibitor_v1_api API - * See @ref iface_zwp_idle_inhibitor_v1. - */ -/** - * @defgroup iface_zwp_idle_inhibitor_v1 The zwp_idle_inhibitor_v1 interface - * - * An idle inhibitor prevents the output that the associated surface is - * visible on from being set to a state where it is not visually usable due - * to lack of user interaction (e.g. blanked, dimmed, locked, set to power - * save, etc.) Any screensaver processes are also blocked from displaying. - * - * If the surface is destroyed, unmapped, becomes occluded, loses - * visibility, or otherwise becomes not visually relevant for the user, the - * idle inhibitor will not be honored by the compositor; if the surface - * subsequently regains visibility the inhibitor takes effect once again. - * Likewise, the inhibitor isn't honored if the system was already idled at - * the time the inhibitor was established, although if the system later - * de-idles and re-idles the inhibitor will take effect. - */ -extern const struct wl_interface zwp_idle_inhibitor_v1_interface; -#endif - -#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY 0 -#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR 1 - - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - */ -#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - */ -#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR_SINCE_VERSION 1 - -/** @ingroup iface_zwp_idle_inhibit_manager_v1 */ -static inline void -zwp_idle_inhibit_manager_v1_set_user_data(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_idle_inhibit_manager_v1, user_data); -} - -/** @ingroup iface_zwp_idle_inhibit_manager_v1 */ -static inline void * -zwp_idle_inhibit_manager_v1_get_user_data(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_idle_inhibit_manager_v1); -} - -static inline uint32_t -zwp_idle_inhibit_manager_v1_get_version(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1); -} - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - * - * Destroy the inhibit manager. - */ -static inline void -zwp_idle_inhibit_manager_v1_destroy(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibit_manager_v1, - ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - * - * Create a new inhibitor object associated with the given surface. - */ -static inline struct zwp_idle_inhibitor_v1 * -zwp_idle_inhibit_manager_v1_create_inhibitor(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibit_manager_v1, - ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR, &zwp_idle_inhibitor_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1), 0, NULL, surface); - - return (struct zwp_idle_inhibitor_v1 *) id; -} - -#define ZWP_IDLE_INHIBITOR_V1_DESTROY 0 - - -/** - * @ingroup iface_zwp_idle_inhibitor_v1 - */ -#define ZWP_IDLE_INHIBITOR_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_zwp_idle_inhibitor_v1 */ -static inline void -zwp_idle_inhibitor_v1_set_user_data(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_idle_inhibitor_v1, user_data); -} - -/** @ingroup iface_zwp_idle_inhibitor_v1 */ -static inline void * -zwp_idle_inhibitor_v1_get_user_data(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_idle_inhibitor_v1); -} - -static inline uint32_t -zwp_idle_inhibitor_v1_get_version(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibitor_v1); -} - -/** - * @ingroup iface_zwp_idle_inhibitor_v1 - * - * Remove the inhibitor effect from the associated wl_surface. - */ -static inline void -zwp_idle_inhibitor_v1_destroy(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibitor_v1, - ZWP_IDLE_INHIBITOR_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibitor_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h deleted file mode 100644 index 4184538d5..000000000 --- a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,109 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2014 Jonas Ådahl - * Copyright © 2015 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface wl_region_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface zwp_confined_pointer_v1_interface; -extern const struct wl_interface zwp_locked_pointer_v1_interface; - -static const struct wl_interface *pointer_constraints_unstable_v1_types[] = { - NULL, - NULL, - &zwp_locked_pointer_v1_interface, - &wl_surface_interface, - &wl_pointer_interface, - &wl_region_interface, - NULL, - &zwp_confined_pointer_v1_interface, - &wl_surface_interface, - &wl_pointer_interface, - &wl_region_interface, - NULL, - &wl_region_interface, - &wl_region_interface, -}; - -static const struct wl_message zwp_pointer_constraints_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "lock_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 2 }, - { "confine_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 7 }, -}; - -WL_PRIVATE const struct wl_interface zwp_pointer_constraints_v1_interface = { - "zwp_pointer_constraints_v1", 1, - 3, zwp_pointer_constraints_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_locked_pointer_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "set_cursor_position_hint", "ff", pointer_constraints_unstable_v1_types + 0 }, - { "set_region", "?o", pointer_constraints_unstable_v1_types + 12 }, -}; - -static const struct wl_message zwp_locked_pointer_v1_events[] = { - { "locked", "", pointer_constraints_unstable_v1_types + 0 }, - { "unlocked", "", pointer_constraints_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_locked_pointer_v1_interface = { - "zwp_locked_pointer_v1", 1, - 3, zwp_locked_pointer_v1_requests, - 2, zwp_locked_pointer_v1_events, -}; - -static const struct wl_message zwp_confined_pointer_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "set_region", "?o", pointer_constraints_unstable_v1_types + 13 }, -}; - -static const struct wl_message zwp_confined_pointer_v1_events[] = { - { "confined", "", pointer_constraints_unstable_v1_types + 0 }, - { "unconfined", "", pointer_constraints_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_confined_pointer_v1_interface = { - "zwp_confined_pointer_v1", 1, - 2, zwp_confined_pointer_v1_requests, - 2, zwp_confined_pointer_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h deleted file mode 100644 index 09c05ea8c..000000000 --- a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h +++ /dev/null @@ -1,667 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_pointer_constraints_unstable_v1 The pointer_constraints_unstable_v1 protocol - * protocol for constraining pointer motions - * - * @section page_desc_pointer_constraints_unstable_v1 Description - * - * This protocol specifies a set of interfaces used for adding constraints to - * the motion of a pointer. Possible constraints include confining pointer - * motions to a given region, or locking it to its current position. - * - * In order to constrain the pointer, a client must first bind the global - * interface "wp_pointer_constraints" which, if a compositor supports pointer - * constraints, is exposed by the registry. Using the bound global object, the - * client uses the request that corresponds to the type of constraint it wants - * to make. See wp_pointer_constraints for more details. - * - * Warning! The protocol described in this file is experimental and backward - * incompatible changes may be made. Backward compatible changes may be added - * together with the corresponding interface version bump. Backward - * incompatible changes are done by bumping the version number in the protocol - * and interface names and resetting the interface version. Once the protocol - * is to be declared stable, the 'z' prefix and the version number in the - * protocol and interface names are removed and the interface version number is - * reset. - * - * @section page_ifaces_pointer_constraints_unstable_v1 Interfaces - * - @subpage page_iface_zwp_pointer_constraints_v1 - constrain the movement of a pointer - * - @subpage page_iface_zwp_locked_pointer_v1 - receive relative pointer motion events - * - @subpage page_iface_zwp_confined_pointer_v1 - confined pointer object - * @section page_copyright_pointer_constraints_unstable_v1 Copyright - *
- *
- * Copyright © 2014      Jonas Ådahl
- * Copyright © 2015      Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_pointer; -struct wl_region; -struct wl_surface; -struct zwp_confined_pointer_v1; -struct zwp_locked_pointer_v1; -struct zwp_pointer_constraints_v1; - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_INTERFACE -#define ZWP_POINTER_CONSTRAINTS_V1_INTERFACE -/** - * @page page_iface_zwp_pointer_constraints_v1 zwp_pointer_constraints_v1 - * @section page_iface_zwp_pointer_constraints_v1_desc Description - * - * The global interface exposing pointer constraining functionality. It - * exposes two requests: lock_pointer for locking the pointer to its - * position, and confine_pointer for locking the pointer to a region. - * - * The lock_pointer and confine_pointer requests create the objects - * wp_locked_pointer and wp_confined_pointer respectively, and the client can - * use these objects to interact with the lock. - * - * For any surface, only one lock or confinement may be active across all - * wl_pointer objects of the same seat. If a lock or confinement is requested - * when another lock or confinement is active or requested on the same surface - * and with any of the wl_pointer objects of the same seat, an - * 'already_constrained' error will be raised. - * @section page_iface_zwp_pointer_constraints_v1_api API - * See @ref iface_zwp_pointer_constraints_v1. - */ -/** - * @defgroup iface_zwp_pointer_constraints_v1 The zwp_pointer_constraints_v1 interface - * - * The global interface exposing pointer constraining functionality. It - * exposes two requests: lock_pointer for locking the pointer to its - * position, and confine_pointer for locking the pointer to a region. - * - * The lock_pointer and confine_pointer requests create the objects - * wp_locked_pointer and wp_confined_pointer respectively, and the client can - * use these objects to interact with the lock. - * - * For any surface, only one lock or confinement may be active across all - * wl_pointer objects of the same seat. If a lock or confinement is requested - * when another lock or confinement is active or requested on the same surface - * and with any of the wl_pointer objects of the same seat, an - * 'already_constrained' error will be raised. - */ -extern const struct wl_interface zwp_pointer_constraints_v1_interface; -#endif -#ifndef ZWP_LOCKED_POINTER_V1_INTERFACE -#define ZWP_LOCKED_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_locked_pointer_v1 zwp_locked_pointer_v1 - * @section page_iface_zwp_locked_pointer_v1_desc Description - * - * The wp_locked_pointer interface represents a locked pointer state. - * - * While the lock of this object is active, the wl_pointer objects of the - * associated seat will not emit any wl_pointer.motion events. - * - * This object will send the event 'locked' when the lock is activated. - * Whenever the lock is activated, it is guaranteed that the locked surface - * will already have received pointer focus and that the pointer will be - * within the region passed to the request creating this object. - * - * To unlock the pointer, send the destroy request. This will also destroy - * the wp_locked_pointer object. - * - * If the compositor decides to unlock the pointer the unlocked event is - * sent. See wp_locked_pointer.unlock for details. - * - * When unlocking, the compositor may warp the cursor position to the set - * cursor position hint. If it does, it will not result in any relative - * motion events emitted via wp_relative_pointer. - * - * If the surface the lock was requested on is destroyed and the lock is not - * yet activated, the wp_locked_pointer object is now defunct and must be - * destroyed. - * @section page_iface_zwp_locked_pointer_v1_api API - * See @ref iface_zwp_locked_pointer_v1. - */ -/** - * @defgroup iface_zwp_locked_pointer_v1 The zwp_locked_pointer_v1 interface - * - * The wp_locked_pointer interface represents a locked pointer state. - * - * While the lock of this object is active, the wl_pointer objects of the - * associated seat will not emit any wl_pointer.motion events. - * - * This object will send the event 'locked' when the lock is activated. - * Whenever the lock is activated, it is guaranteed that the locked surface - * will already have received pointer focus and that the pointer will be - * within the region passed to the request creating this object. - * - * To unlock the pointer, send the destroy request. This will also destroy - * the wp_locked_pointer object. - * - * If the compositor decides to unlock the pointer the unlocked event is - * sent. See wp_locked_pointer.unlock for details. - * - * When unlocking, the compositor may warp the cursor position to the set - * cursor position hint. If it does, it will not result in any relative - * motion events emitted via wp_relative_pointer. - * - * If the surface the lock was requested on is destroyed and the lock is not - * yet activated, the wp_locked_pointer object is now defunct and must be - * destroyed. - */ -extern const struct wl_interface zwp_locked_pointer_v1_interface; -#endif -#ifndef ZWP_CONFINED_POINTER_V1_INTERFACE -#define ZWP_CONFINED_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_confined_pointer_v1 zwp_confined_pointer_v1 - * @section page_iface_zwp_confined_pointer_v1_desc Description - * - * The wp_confined_pointer interface represents a confined pointer state. - * - * This object will send the event 'confined' when the confinement is - * activated. Whenever the confinement is activated, it is guaranteed that - * the surface the pointer is confined to will already have received pointer - * focus and that the pointer will be within the region passed to the request - * creating this object. It is up to the compositor to decide whether this - * requires some user interaction and if the pointer will warp to within the - * passed region if outside. - * - * To unconfine the pointer, send the destroy request. This will also destroy - * the wp_confined_pointer object. - * - * If the compositor decides to unconfine the pointer the unconfined event is - * sent. The wp_confined_pointer object is at this point defunct and should - * be destroyed. - * @section page_iface_zwp_confined_pointer_v1_api API - * See @ref iface_zwp_confined_pointer_v1. - */ -/** - * @defgroup iface_zwp_confined_pointer_v1 The zwp_confined_pointer_v1 interface - * - * The wp_confined_pointer interface represents a confined pointer state. - * - * This object will send the event 'confined' when the confinement is - * activated. Whenever the confinement is activated, it is guaranteed that - * the surface the pointer is confined to will already have received pointer - * focus and that the pointer will be within the region passed to the request - * creating this object. It is up to the compositor to decide whether this - * requires some user interaction and if the pointer will warp to within the - * passed region if outside. - * - * To unconfine the pointer, send the destroy request. This will also destroy - * the wp_confined_pointer object. - * - * If the compositor decides to unconfine the pointer the unconfined event is - * sent. The wp_confined_pointer object is at this point defunct and should - * be destroyed. - */ -extern const struct wl_interface zwp_confined_pointer_v1_interface; -#endif - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM -#define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * wp_pointer_constraints error values - * - * These errors can be emitted in response to wp_pointer_constraints - * requests. - */ -enum zwp_pointer_constraints_v1_error { - /** - * pointer constraint already requested on that surface - */ - ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED = 1, -}; -#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM */ - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM -#define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * constraint lifetime - * - * These values represent different lifetime semantics. They are passed - * as arguments to the factory requests to specify how the constraint - * lifetimes should be managed. - */ -enum zwp_pointer_constraints_v1_lifetime { - /** - * the pointer constraint is defunct once deactivated - * - * A oneshot pointer constraint will never reactivate once it has - * been deactivated. See the corresponding deactivation event - * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) - * for details. - */ - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT = 1, - /** - * the pointer constraint may reactivate - * - * A persistent pointer constraint may again reactivate once it - * has been deactivated. See the corresponding deactivation event - * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) - * for details. - */ - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT = 2, -}; -#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM */ - -#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY 0 -#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER 1 -#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER 2 - - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER_SINCE_VERSION 1 - -/** @ingroup iface_zwp_pointer_constraints_v1 */ -static inline void -zwp_pointer_constraints_v1_set_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_pointer_constraints_v1, user_data); -} - -/** @ingroup iface_zwp_pointer_constraints_v1 */ -static inline void * -zwp_pointer_constraints_v1_get_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_pointer_constraints_v1); -} - -static inline uint32_t -zwp_pointer_constraints_v1_get_version(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1); -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * Used by the client to notify the server that it will no longer use this - * pointer constraints object. - */ -static inline void -zwp_pointer_constraints_v1_destroy(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * The lock_pointer request lets the client request to disable movements of - * the virtual pointer (i.e. the cursor), effectively locking the pointer - * to a position. This request may not take effect immediately; in the - * future, when the compositor deems implementation-specific constraints - * are satisfied, the pointer lock will be activated and the compositor - * sends a locked event. - * - * The protocol provides no guarantee that the constraints are ever - * satisfied, and does not require the compositor to send an error if the - * constraints cannot ever be satisfied. It is thus possible to request a - * lock that will never activate. - * - * There may not be another pointer constraint of any kind requested or - * active on the surface for any of the wl_pointer objects of the seat of - * the passed pointer when requesting a lock. If there is, an error will be - * raised. See general pointer lock documentation for more details. - * - * The intersection of the region passed with this request and the input - * region of the surface is used to determine where the pointer must be - * in order for the lock to activate. It is up to the compositor whether to - * warp the pointer or require some kind of user interaction for the lock - * to activate. If the region is null the surface input region is used. - * - * A surface may receive pointer focus without the lock being activated. - * - * The request creates a new object wp_locked_pointer which is used to - * interact with the lock as well as receive updates about its state. See - * the the description of wp_locked_pointer for further information. - * - * Note that while a pointer is locked, the wl_pointer objects of the - * corresponding seat will not emit any wl_pointer.motion events, but - * relative motion events will still be emitted via wp_relative_pointer - * objects of the same seat. wl_pointer.axis and wl_pointer.button events - * are unaffected. - */ -static inline struct zwp_locked_pointer_v1 * -zwp_pointer_constraints_v1_lock_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER, &zwp_locked_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), 0, NULL, surface, pointer, region, lifetime); - - return (struct zwp_locked_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * The confine_pointer request lets the client request to confine the - * pointer cursor to a given region. This request may not take effect - * immediately; in the future, when the compositor deems implementation- - * specific constraints are satisfied, the pointer confinement will be - * activated and the compositor sends a confined event. - * - * The intersection of the region passed with this request and the input - * region of the surface is used to determine where the pointer must be - * in order for the confinement to activate. It is up to the compositor - * whether to warp the pointer or require some kind of user interaction for - * the confinement to activate. If the region is null the surface input - * region is used. - * - * The request will create a new object wp_confined_pointer which is used - * to interact with the confinement as well as receive updates about its - * state. See the the description of wp_confined_pointer for further - * information. - */ -static inline struct zwp_confined_pointer_v1 * -zwp_pointer_constraints_v1_confine_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER, &zwp_confined_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), 0, NULL, surface, pointer, region, lifetime); - - return (struct zwp_confined_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * @struct zwp_locked_pointer_v1_listener - */ -struct zwp_locked_pointer_v1_listener { - /** - * lock activation event - * - * Notification that the pointer lock of the seat's pointer is - * activated. - */ - void (*locked)(void *data, - struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1); - /** - * lock deactivation event - * - * Notification that the pointer lock of the seat's pointer is no - * longer active. If this is a oneshot pointer lock (see - * wp_pointer_constraints.lifetime) this object is now defunct and - * should be destroyed. If this is a persistent pointer lock (see - * wp_pointer_constraints.lifetime) this pointer lock may again - * reactivate in the future. - */ - void (*unlocked)(void *data, - struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1); -}; - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -static inline int -zwp_locked_pointer_v1_add_listener(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, - const struct zwp_locked_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_locked_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_LOCKED_POINTER_V1_DESTROY 0 -#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT 1 -#define ZWP_LOCKED_POINTER_V1_SET_REGION 2 - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_LOCKED_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_UNLOCKED_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_SET_REGION_SINCE_VERSION 1 - -/** @ingroup iface_zwp_locked_pointer_v1 */ -static inline void -zwp_locked_pointer_v1_set_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_locked_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_locked_pointer_v1 */ -static inline void * -zwp_locked_pointer_v1_get_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_locked_pointer_v1); -} - -static inline uint32_t -zwp_locked_pointer_v1_get_version(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Destroy the locked pointer object. If applicable, the compositor will - * unlock the pointer. - */ -static inline void -zwp_locked_pointer_v1_destroy(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Set the cursor position hint relative to the top left corner of the - * surface. - * - * If the client is drawing its own cursor, it should update the position - * hint to the position of its own cursor. A compositor may use this - * information to warp the pointer upon unlock in order to avoid pointer - * jumps. - * - * The cursor position hint is double buffered. The new hint will only take - * effect when the associated surface gets it pending state applied. See - * wl_surface.commit for details. - */ -static inline void -zwp_locked_pointer_v1_set_cursor_position_hint(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, wl_fixed_t surface_x, wl_fixed_t surface_y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), 0, surface_x, surface_y); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Set a new region used to lock the pointer. - * - * The new lock region is double-buffered. The new lock region will - * only take effect when the associated surface gets its pending state - * applied. See wl_surface.commit for details. - * - * For details about the lock region, see wp_locked_pointer. - */ -static inline void -zwp_locked_pointer_v1_set_region(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_SET_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), 0, region); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * @struct zwp_confined_pointer_v1_listener - */ -struct zwp_confined_pointer_v1_listener { - /** - * pointer confined - * - * Notification that the pointer confinement of the seat's - * pointer is activated. - */ - void (*confined)(void *data, - struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1); - /** - * pointer unconfined - * - * Notification that the pointer confinement of the seat's - * pointer is no longer active. If this is a oneshot pointer - * confinement (see wp_pointer_constraints.lifetime) this object is - * now defunct and should be destroyed. If this is a persistent - * pointer confinement (see wp_pointer_constraints.lifetime) this - * pointer confinement may again reactivate in the future. - */ - void (*unconfined)(void *data, - struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1); -}; - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -static inline int -zwp_confined_pointer_v1_add_listener(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, - const struct zwp_confined_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_confined_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_CONFINED_POINTER_V1_DESTROY 0 -#define ZWP_CONFINED_POINTER_V1_SET_REGION 1 - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_CONFINED_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_UNCONFINED_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_SET_REGION_SINCE_VERSION 1 - -/** @ingroup iface_zwp_confined_pointer_v1 */ -static inline void -zwp_confined_pointer_v1_set_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_confined_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_confined_pointer_v1 */ -static inline void * -zwp_confined_pointer_v1_get_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_confined_pointer_v1); -} - -static inline uint32_t -zwp_confined_pointer_v1_get_version(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * - * Destroy the confined pointer object. If applicable, the compositor will - * unconfine the pointer. - */ -static inline void -zwp_confined_pointer_v1_destroy(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_confined_pointer_v1, - ZWP_CONFINED_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * - * Set a new region used to confine the pointer. - * - * The new confine region is double-buffered. The new confine region will - * only take effect when the associated surface gets its pending state - * applied. See wl_surface.commit for details. - * - * If the confinement is active when the new confinement region is applied - * and the pointer ends up outside of newly applied region, the pointer may - * warped to a position within the new confinement region. If warped, a - * wl_pointer.motion event will be emitted, but no - * wp_relative_pointer.relative_motion event. - * - * The compositor may also, instead of using the new region, unconfine the - * pointer. - * - * For details about the confine region, see wp_confined_pointer. - */ -static inline void -zwp_confined_pointer_v1_set_region(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_confined_pointer_v1, - ZWP_CONFINED_POINTER_V1_SET_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1), 0, region); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h deleted file mode 100644 index 605149b2a..000000000 --- a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2014 Jonas Ådahl - * Copyright © 2015 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface zwp_relative_pointer_v1_interface; - -static const struct wl_interface *relative_pointer_unstable_v1_types[] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - &zwp_relative_pointer_v1_interface, - &wl_pointer_interface, -}; - -static const struct wl_message zwp_relative_pointer_manager_v1_requests[] = { - { "destroy", "", relative_pointer_unstable_v1_types + 0 }, - { "get_relative_pointer", "no", relative_pointer_unstable_v1_types + 6 }, -}; - -WL_PRIVATE const struct wl_interface zwp_relative_pointer_manager_v1_interface = { - "zwp_relative_pointer_manager_v1", 1, - 2, zwp_relative_pointer_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_relative_pointer_v1_requests[] = { - { "destroy", "", relative_pointer_unstable_v1_types + 0 }, -}; - -static const struct wl_message zwp_relative_pointer_v1_events[] = { - { "relative_motion", "uuffff", relative_pointer_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_relative_pointer_v1_interface = { - "zwp_relative_pointer_v1", 1, - 1, zwp_relative_pointer_v1_requests, - 1, zwp_relative_pointer_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h deleted file mode 100644 index 5c79482f9..000000000 --- a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h +++ /dev/null @@ -1,297 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_relative_pointer_unstable_v1 The relative_pointer_unstable_v1 protocol - * protocol for relative pointer motion events - * - * @section page_desc_relative_pointer_unstable_v1 Description - * - * This protocol specifies a set of interfaces used for making clients able to - * receive relative pointer events not obstructed by barriers (such as the - * monitor edge or other pointer barriers). - * - * To start receiving relative pointer events, a client must first bind the - * global interface "wp_relative_pointer_manager" which, if a compositor - * supports relative pointer motion events, is exposed by the registry. After - * having created the relative pointer manager proxy object, the client uses - * it to create the actual relative pointer object using the - * "get_relative_pointer" request given a wl_pointer. The relative pointer - * motion events will then, when applicable, be transmitted via the proxy of - * the newly created relative pointer object. See the documentation of the - * relative pointer interface for more details. - * - * Warning! The protocol described in this file is experimental and backward - * incompatible changes may be made. Backward compatible changes may be added - * together with the corresponding interface version bump. Backward - * incompatible changes are done by bumping the version number in the protocol - * and interface names and resetting the interface version. Once the protocol - * is to be declared stable, the 'z' prefix and the version number in the - * protocol and interface names are removed and the interface version number is - * reset. - * - * @section page_ifaces_relative_pointer_unstable_v1 Interfaces - * - @subpage page_iface_zwp_relative_pointer_manager_v1 - get relative pointer objects - * - @subpage page_iface_zwp_relative_pointer_v1 - relative pointer object - * @section page_copyright_relative_pointer_unstable_v1 Copyright - *
- *
- * Copyright © 2014      Jonas Ådahl
- * Copyright © 2015      Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_pointer; -struct zwp_relative_pointer_manager_v1; -struct zwp_relative_pointer_v1; - -#ifndef ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE -#define ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE -/** - * @page page_iface_zwp_relative_pointer_manager_v1 zwp_relative_pointer_manager_v1 - * @section page_iface_zwp_relative_pointer_manager_v1_desc Description - * - * A global interface used for getting the relative pointer object for a - * given pointer. - * @section page_iface_zwp_relative_pointer_manager_v1_api API - * See @ref iface_zwp_relative_pointer_manager_v1. - */ -/** - * @defgroup iface_zwp_relative_pointer_manager_v1 The zwp_relative_pointer_manager_v1 interface - * - * A global interface used for getting the relative pointer object for a - * given pointer. - */ -extern const struct wl_interface zwp_relative_pointer_manager_v1_interface; -#endif -#ifndef ZWP_RELATIVE_POINTER_V1_INTERFACE -#define ZWP_RELATIVE_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_relative_pointer_v1 zwp_relative_pointer_v1 - * @section page_iface_zwp_relative_pointer_v1_desc Description - * - * A wp_relative_pointer object is an extension to the wl_pointer interface - * used for emitting relative pointer events. It shares the same focus as - * wl_pointer objects of the same seat and will only emit events when it has - * focus. - * @section page_iface_zwp_relative_pointer_v1_api API - * See @ref iface_zwp_relative_pointer_v1. - */ -/** - * @defgroup iface_zwp_relative_pointer_v1 The zwp_relative_pointer_v1 interface - * - * A wp_relative_pointer object is an extension to the wl_pointer interface - * used for emitting relative pointer events. It shares the same focus as - * wl_pointer objects of the same seat and will only emit events when it has - * focus. - */ -extern const struct wl_interface zwp_relative_pointer_v1_interface; -#endif - -#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY 0 -#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER 1 - - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - */ -#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - */ -#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER_SINCE_VERSION 1 - -/** @ingroup iface_zwp_relative_pointer_manager_v1 */ -static inline void -zwp_relative_pointer_manager_v1_set_user_data(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_relative_pointer_manager_v1, user_data); -} - -/** @ingroup iface_zwp_relative_pointer_manager_v1 */ -static inline void * -zwp_relative_pointer_manager_v1_get_user_data(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_relative_pointer_manager_v1); -} - -static inline uint32_t -zwp_relative_pointer_manager_v1_get_version(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1); -} - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - * - * Used by the client to notify the server that it will no longer use this - * relative pointer manager object. - */ -static inline void -zwp_relative_pointer_manager_v1_destroy(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_manager_v1, - ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - * - * Create a relative pointer interface given a wl_pointer object. See the - * wp_relative_pointer interface for more details. - */ -static inline struct zwp_relative_pointer_v1 * -zwp_relative_pointer_manager_v1_get_relative_pointer(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1, struct wl_pointer *pointer) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_manager_v1, - ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER, &zwp_relative_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1), 0, NULL, pointer); - - return (struct zwp_relative_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_relative_pointer_v1 - * @struct zwp_relative_pointer_v1_listener - */ -struct zwp_relative_pointer_v1_listener { - /** - * relative pointer motion - * - * Relative x/y pointer motion from the pointer of the seat - * associated with this object. - * - * A relative motion is in the same dimension as regular wl_pointer - * motion events, except they do not represent an absolute - * position. For example, moving a pointer from (x, y) to (x', y') - * would have the equivalent relative motion (x' - x, y' - y). If a - * pointer motion caused the absolute pointer position to be - * clipped by for example the edge of the monitor, the relative - * motion is unaffected by the clipping and will represent the - * unclipped motion. - * - * This event also contains non-accelerated motion deltas. The - * non-accelerated delta is, when applicable, the regular pointer - * motion delta as it was before having applied motion acceleration - * and other transformations such as normalization. - * - * Note that the non-accelerated delta does not represent 'raw' - * events as they were read from some device. Pointer motion - * acceleration is device- and configuration-specific and - * non-accelerated deltas and accelerated deltas may have the same - * value on some devices. - * - * Relative motions are not coupled to wl_pointer.motion events, - * and can be sent in combination with such events, but also - * independently. There may also be scenarios where - * wl_pointer.motion is sent, but there is no relative motion. The - * order of an absolute and relative motion event originating from - * the same physical motion is not guaranteed. - * - * If the client needs button events or focus state, it can receive - * them from a wl_pointer object of the same seat that the - * wp_relative_pointer object is associated with. - * @param utime_hi high 32 bits of a 64 bit timestamp with microsecond granularity - * @param utime_lo low 32 bits of a 64 bit timestamp with microsecond granularity - * @param dx the x component of the motion vector - * @param dy the y component of the motion vector - * @param dx_unaccel the x component of the unaccelerated motion vector - * @param dy_unaccel the y component of the unaccelerated motion vector - */ - void (*relative_motion)(void *data, - struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, - uint32_t utime_hi, - uint32_t utime_lo, - wl_fixed_t dx, - wl_fixed_t dy, - wl_fixed_t dx_unaccel, - wl_fixed_t dy_unaccel); -}; - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -static inline int -zwp_relative_pointer_v1_add_listener(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, - const struct zwp_relative_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_relative_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_RELATIVE_POINTER_V1_DESTROY 0 - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -#define ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -#define ZWP_RELATIVE_POINTER_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_zwp_relative_pointer_v1 */ -static inline void -zwp_relative_pointer_v1_set_user_data(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_relative_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_relative_pointer_v1 */ -static inline void * -zwp_relative_pointer_v1_get_user_data(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_relative_pointer_v1); -} - -static inline uint32_t -zwp_relative_pointer_v1_get_version(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_v1); -} - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -static inline void -zwp_relative_pointer_v1_destroy(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_v1, - ZWP_RELATIVE_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h b/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h deleted file mode 100644 index d6858580e..000000000 --- a/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h +++ /dev/null @@ -1,75 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2013-2016 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wp_viewport_interface; - -static const struct wl_interface *viewporter_types[] = { - NULL, - NULL, - NULL, - NULL, - &wp_viewport_interface, - &wl_surface_interface, -}; - -static const struct wl_message wp_viewporter_requests[] = { - { "destroy", "", viewporter_types + 0 }, - { "get_viewport", "no", viewporter_types + 4 }, -}; - -WL_PRIVATE const struct wl_interface wp_viewporter_interface = { - "wp_viewporter", 1, - 2, wp_viewporter_requests, - 0, NULL, -}; - -static const struct wl_message wp_viewport_requests[] = { - { "destroy", "", viewporter_types + 0 }, - { "set_source", "ffff", viewporter_types + 0 }, - { "set_destination", "ii", viewporter_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wp_viewport_interface = { - "wp_viewport", 1, - 3, wp_viewport_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/viewporter-client-protocol.h b/pkg/glfw/wayland-headers/viewporter-client-protocol.h deleted file mode 100644 index afe60ef59..000000000 --- a/pkg/glfw/wayland-headers/viewporter-client-protocol.h +++ /dev/null @@ -1,398 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef VIEWPORTER_CLIENT_PROTOCOL_H -#define VIEWPORTER_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_viewporter The viewporter protocol - * @section page_ifaces_viewporter Interfaces - * - @subpage page_iface_wp_viewporter - surface cropping and scaling - * - @subpage page_iface_wp_viewport - crop and scale interface to a wl_surface - * @section page_copyright_viewporter Copyright - *
- *
- * Copyright © 2013-2016 Collabora, Ltd.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct wp_viewport; -struct wp_viewporter; - -#ifndef WP_VIEWPORTER_INTERFACE -#define WP_VIEWPORTER_INTERFACE -/** - * @page page_iface_wp_viewporter wp_viewporter - * @section page_iface_wp_viewporter_desc Description - * - * The global interface exposing surface cropping and scaling - * capabilities is used to instantiate an interface extension for a - * wl_surface object. This extended interface will then allow - * cropping and scaling the surface contents, effectively - * disconnecting the direct relationship between the buffer and the - * surface size. - * @section page_iface_wp_viewporter_api API - * See @ref iface_wp_viewporter. - */ -/** - * @defgroup iface_wp_viewporter The wp_viewporter interface - * - * The global interface exposing surface cropping and scaling - * capabilities is used to instantiate an interface extension for a - * wl_surface object. This extended interface will then allow - * cropping and scaling the surface contents, effectively - * disconnecting the direct relationship between the buffer and the - * surface size. - */ -extern const struct wl_interface wp_viewporter_interface; -#endif -#ifndef WP_VIEWPORT_INTERFACE -#define WP_VIEWPORT_INTERFACE -/** - * @page page_iface_wp_viewport wp_viewport - * @section page_iface_wp_viewport_desc Description - * - * An additional interface to a wl_surface object, which allows the - * client to specify the cropping and scaling of the surface - * contents. - * - * This interface works with two concepts: the source rectangle (src_x, - * src_y, src_width, src_height), and the destination size (dst_width, - * dst_height). The contents of the source rectangle are scaled to the - * destination size, and content outside the source rectangle is ignored. - * This state is double-buffered, and is applied on the next - * wl_surface.commit. - * - * The two parts of crop and scale state are independent: the source - * rectangle, and the destination size. Initially both are unset, that - * is, no scaling is applied. The whole of the current wl_buffer is - * used as the source, and the surface size is as defined in - * wl_surface.attach. - * - * If the destination size is set, it causes the surface size to become - * dst_width, dst_height. The source (rectangle) is scaled to exactly - * this size. This overrides whatever the attached wl_buffer size is, - * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface - * has no content and therefore no size. Otherwise, the size is always - * at least 1x1 in surface local coordinates. - * - * If the source rectangle is set, it defines what area of the wl_buffer is - * taken as the source. If the source rectangle is set and the destination - * size is not set, then src_width and src_height must be integers, and the - * surface size becomes the source rectangle size. This results in cropping - * without scaling. If src_width or src_height are not integers and - * destination size is not set, the bad_size protocol error is raised when - * the surface state is applied. - * - * The coordinate transformations from buffer pixel coordinates up to - * the surface-local coordinates happen in the following order: - * 1. buffer_transform (wl_surface.set_buffer_transform) - * 2. buffer_scale (wl_surface.set_buffer_scale) - * 3. crop and scale (wp_viewport.set*) - * This means, that the source rectangle coordinates of crop and scale - * are given in the coordinates after the buffer transform and scale, - * i.e. in the coordinates that would be the surface-local coordinates - * if the crop and scale was not applied. - * - * If src_x or src_y are negative, the bad_value protocol error is raised. - * Otherwise, if the source rectangle is partially or completely outside of - * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised - * when the surface state is applied. A NULL wl_buffer does not raise the - * out_of_buffer error. - * - * If the wl_surface associated with the wp_viewport is destroyed, - * all wp_viewport requests except 'destroy' raise the protocol error - * no_surface. - * - * If the wp_viewport object is destroyed, the crop and scale - * state is removed from the wl_surface. The change will be applied - * on the next wl_surface.commit. - * @section page_iface_wp_viewport_api API - * See @ref iface_wp_viewport. - */ -/** - * @defgroup iface_wp_viewport The wp_viewport interface - * - * An additional interface to a wl_surface object, which allows the - * client to specify the cropping and scaling of the surface - * contents. - * - * This interface works with two concepts: the source rectangle (src_x, - * src_y, src_width, src_height), and the destination size (dst_width, - * dst_height). The contents of the source rectangle are scaled to the - * destination size, and content outside the source rectangle is ignored. - * This state is double-buffered, and is applied on the next - * wl_surface.commit. - * - * The two parts of crop and scale state are independent: the source - * rectangle, and the destination size. Initially both are unset, that - * is, no scaling is applied. The whole of the current wl_buffer is - * used as the source, and the surface size is as defined in - * wl_surface.attach. - * - * If the destination size is set, it causes the surface size to become - * dst_width, dst_height. The source (rectangle) is scaled to exactly - * this size. This overrides whatever the attached wl_buffer size is, - * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface - * has no content and therefore no size. Otherwise, the size is always - * at least 1x1 in surface local coordinates. - * - * If the source rectangle is set, it defines what area of the wl_buffer is - * taken as the source. If the source rectangle is set and the destination - * size is not set, then src_width and src_height must be integers, and the - * surface size becomes the source rectangle size. This results in cropping - * without scaling. If src_width or src_height are not integers and - * destination size is not set, the bad_size protocol error is raised when - * the surface state is applied. - * - * The coordinate transformations from buffer pixel coordinates up to - * the surface-local coordinates happen in the following order: - * 1. buffer_transform (wl_surface.set_buffer_transform) - * 2. buffer_scale (wl_surface.set_buffer_scale) - * 3. crop and scale (wp_viewport.set*) - * This means, that the source rectangle coordinates of crop and scale - * are given in the coordinates after the buffer transform and scale, - * i.e. in the coordinates that would be the surface-local coordinates - * if the crop and scale was not applied. - * - * If src_x or src_y are negative, the bad_value protocol error is raised. - * Otherwise, if the source rectangle is partially or completely outside of - * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised - * when the surface state is applied. A NULL wl_buffer does not raise the - * out_of_buffer error. - * - * If the wl_surface associated with the wp_viewport is destroyed, - * all wp_viewport requests except 'destroy' raise the protocol error - * no_surface. - * - * If the wp_viewport object is destroyed, the crop and scale - * state is removed from the wl_surface. The change will be applied - * on the next wl_surface.commit. - */ -extern const struct wl_interface wp_viewport_interface; -#endif - -#ifndef WP_VIEWPORTER_ERROR_ENUM -#define WP_VIEWPORTER_ERROR_ENUM -enum wp_viewporter_error { - /** - * the surface already has a viewport object associated - */ - WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS = 0, -}; -#endif /* WP_VIEWPORTER_ERROR_ENUM */ - -#define WP_VIEWPORTER_DESTROY 0 -#define WP_VIEWPORTER_GET_VIEWPORT 1 - - -/** - * @ingroup iface_wp_viewporter - */ -#define WP_VIEWPORTER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewporter - */ -#define WP_VIEWPORTER_GET_VIEWPORT_SINCE_VERSION 1 - -/** @ingroup iface_wp_viewporter */ -static inline void -wp_viewporter_set_user_data(struct wp_viewporter *wp_viewporter, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_viewporter, user_data); -} - -/** @ingroup iface_wp_viewporter */ -static inline void * -wp_viewporter_get_user_data(struct wp_viewporter *wp_viewporter) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_viewporter); -} - -static inline uint32_t -wp_viewporter_get_version(struct wp_viewporter *wp_viewporter) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_viewporter); -} - -/** - * @ingroup iface_wp_viewporter - * - * Informs the server that the client will not be using this - * protocol object anymore. This does not affect any other objects, - * wp_viewport objects included. - */ -static inline void -wp_viewporter_destroy(struct wp_viewporter *wp_viewporter) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter, - WP_VIEWPORTER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_viewporter - * - * Instantiate an interface extension for the given wl_surface to - * crop and scale its content. If the given wl_surface already has - * a wp_viewport object associated, the viewport_exists - * protocol error is raised. - */ -static inline struct wp_viewport * -wp_viewporter_get_viewport(struct wp_viewporter *wp_viewporter, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter, - WP_VIEWPORTER_GET_VIEWPORT, &wp_viewport_interface, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), 0, NULL, surface); - - return (struct wp_viewport *) id; -} - -#ifndef WP_VIEWPORT_ERROR_ENUM -#define WP_VIEWPORT_ERROR_ENUM -enum wp_viewport_error { - /** - * negative or zero values in width or height - */ - WP_VIEWPORT_ERROR_BAD_VALUE = 0, - /** - * destination size is not integer - */ - WP_VIEWPORT_ERROR_BAD_SIZE = 1, - /** - * source rectangle extends outside of the content area - */ - WP_VIEWPORT_ERROR_OUT_OF_BUFFER = 2, - /** - * the wl_surface was destroyed - */ - WP_VIEWPORT_ERROR_NO_SURFACE = 3, -}; -#endif /* WP_VIEWPORT_ERROR_ENUM */ - -#define WP_VIEWPORT_DESTROY 0 -#define WP_VIEWPORT_SET_SOURCE 1 -#define WP_VIEWPORT_SET_DESTINATION 2 - - -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_SET_SOURCE_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_SET_DESTINATION_SINCE_VERSION 1 - -/** @ingroup iface_wp_viewport */ -static inline void -wp_viewport_set_user_data(struct wp_viewport *wp_viewport, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_viewport, user_data); -} - -/** @ingroup iface_wp_viewport */ -static inline void * -wp_viewport_get_user_data(struct wp_viewport *wp_viewport) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_viewport); -} - -static inline uint32_t -wp_viewport_get_version(struct wp_viewport *wp_viewport) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_viewport); -} - -/** - * @ingroup iface_wp_viewport - * - * The associated wl_surface's crop and scale state is removed. - * The change is applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_destroy(struct wp_viewport *wp_viewport) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_viewport - * - * Set the source rectangle of the associated wl_surface. See - * wp_viewport for the description, and relation to the wl_buffer - * size. - * - * If all of x, y, width and height are -1.0, the source rectangle is - * unset instead. Any other set of values where width or height are zero - * or negative, or x or y are negative, raise the bad_value protocol - * error. - * - * The crop and scale state is double-buffered state, and will be - * applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_set_source(struct wp_viewport *wp_viewport, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_SET_SOURCE, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, x, y, width, height); -} - -/** - * @ingroup iface_wp_viewport - * - * Set the destination size of the associated wl_surface. See - * wp_viewport for the description, and relation to the wl_buffer - * size. - * - * If width is -1 and height is -1, the destination size is unset - * instead. Any other pair of values for width and height that - * contains zero or negative values raises the bad_value protocol - * error. - * - * The crop and scale state is double-buffered state, and will be - * applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_set_destination(struct wp_viewport *wp_viewport, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_SET_DESTINATION, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, width, height); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/wayland-client-protocol-code.h b/pkg/glfw/wayland-headers/wayland-client-protocol-code.h deleted file mode 100644 index 7ea8e7c66..000000000 --- a/pkg/glfw/wayland-headers/wayland-client-protocol-code.h +++ /dev/null @@ -1,525 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2010-2011 Intel Corporation - * Copyright © 2012-2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_buffer_interface; -extern const struct wl_interface wl_callback_interface; -extern const struct wl_interface wl_data_device_interface; -extern const struct wl_interface wl_data_offer_interface; -extern const struct wl_interface wl_data_source_interface; -extern const struct wl_interface wl_keyboard_interface; -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface wl_region_interface; -extern const struct wl_interface wl_registry_interface; -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_shell_surface_interface; -extern const struct wl_interface wl_shm_pool_interface; -extern const struct wl_interface wl_subsurface_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wl_touch_interface; - -static const struct wl_interface *wayland_types[] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_callback_interface, - &wl_registry_interface, - &wl_surface_interface, - &wl_region_interface, - &wl_buffer_interface, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_shm_pool_interface, - NULL, - NULL, - &wl_data_source_interface, - &wl_surface_interface, - &wl_surface_interface, - NULL, - &wl_data_source_interface, - NULL, - &wl_data_offer_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_data_offer_interface, - &wl_data_offer_interface, - &wl_data_source_interface, - &wl_data_device_interface, - &wl_seat_interface, - &wl_shell_surface_interface, - &wl_surface_interface, - &wl_seat_interface, - NULL, - &wl_seat_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_output_interface, - &wl_seat_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_output_interface, - &wl_buffer_interface, - NULL, - NULL, - &wl_callback_interface, - &wl_region_interface, - &wl_region_interface, - &wl_output_interface, - &wl_output_interface, - &wl_pointer_interface, - &wl_keyboard_interface, - &wl_touch_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_surface_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_subsurface_interface, - &wl_surface_interface, - &wl_surface_interface, - &wl_surface_interface, - &wl_surface_interface, -}; - -static const struct wl_message wl_display_requests[] = { - { "sync", "n", wayland_types + 8 }, - { "get_registry", "n", wayland_types + 9 }, -}; - -static const struct wl_message wl_display_events[] = { - { "error", "ous", wayland_types + 0 }, - { "delete_id", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_display_interface = { - "wl_display", 1, - 2, wl_display_requests, - 2, wl_display_events, -}; - -static const struct wl_message wl_registry_requests[] = { - { "bind", "usun", wayland_types + 0 }, -}; - -static const struct wl_message wl_registry_events[] = { - { "global", "usu", wayland_types + 0 }, - { "global_remove", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_registry_interface = { - "wl_registry", 1, - 1, wl_registry_requests, - 2, wl_registry_events, -}; - -static const struct wl_message wl_callback_events[] = { - { "done", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_callback_interface = { - "wl_callback", 1, - 0, NULL, - 1, wl_callback_events, -}; - -static const struct wl_message wl_compositor_requests[] = { - { "create_surface", "n", wayland_types + 10 }, - { "create_region", "n", wayland_types + 11 }, -}; - -WL_PRIVATE const struct wl_interface wl_compositor_interface = { - "wl_compositor", 6, - 2, wl_compositor_requests, - 0, NULL, -}; - -static const struct wl_message wl_shm_pool_requests[] = { - { "create_buffer", "niiiiu", wayland_types + 12 }, - { "destroy", "", wayland_types + 0 }, - { "resize", "i", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shm_pool_interface = { - "wl_shm_pool", 1, - 3, wl_shm_pool_requests, - 0, NULL, -}; - -static const struct wl_message wl_shm_requests[] = { - { "create_pool", "nhi", wayland_types + 18 }, -}; - -static const struct wl_message wl_shm_events[] = { - { "format", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shm_interface = { - "wl_shm", 1, - 1, wl_shm_requests, - 1, wl_shm_events, -}; - -static const struct wl_message wl_buffer_requests[] = { - { "destroy", "", wayland_types + 0 }, -}; - -static const struct wl_message wl_buffer_events[] = { - { "release", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_buffer_interface = { - "wl_buffer", 1, - 1, wl_buffer_requests, - 1, wl_buffer_events, -}; - -static const struct wl_message wl_data_offer_requests[] = { - { "accept", "u?s", wayland_types + 0 }, - { "receive", "sh", wayland_types + 0 }, - { "destroy", "", wayland_types + 0 }, - { "finish", "3", wayland_types + 0 }, - { "set_actions", "3uu", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_offer_events[] = { - { "offer", "s", wayland_types + 0 }, - { "source_actions", "3u", wayland_types + 0 }, - { "action", "3u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_offer_interface = { - "wl_data_offer", 3, - 5, wl_data_offer_requests, - 3, wl_data_offer_events, -}; - -static const struct wl_message wl_data_source_requests[] = { - { "offer", "s", wayland_types + 0 }, - { "destroy", "", wayland_types + 0 }, - { "set_actions", "3u", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_source_events[] = { - { "target", "?s", wayland_types + 0 }, - { "send", "sh", wayland_types + 0 }, - { "cancelled", "", wayland_types + 0 }, - { "dnd_drop_performed", "3", wayland_types + 0 }, - { "dnd_finished", "3", wayland_types + 0 }, - { "action", "3u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_source_interface = { - "wl_data_source", 3, - 3, wl_data_source_requests, - 6, wl_data_source_events, -}; - -static const struct wl_message wl_data_device_requests[] = { - { "start_drag", "?oo?ou", wayland_types + 21 }, - { "set_selection", "?ou", wayland_types + 25 }, - { "release", "2", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_device_events[] = { - { "data_offer", "n", wayland_types + 27 }, - { "enter", "uoff?o", wayland_types + 28 }, - { "leave", "", wayland_types + 0 }, - { "motion", "uff", wayland_types + 0 }, - { "drop", "", wayland_types + 0 }, - { "selection", "?o", wayland_types + 33 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_device_interface = { - "wl_data_device", 3, - 3, wl_data_device_requests, - 6, wl_data_device_events, -}; - -static const struct wl_message wl_data_device_manager_requests[] = { - { "create_data_source", "n", wayland_types + 34 }, - { "get_data_device", "no", wayland_types + 35 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_device_manager_interface = { - "wl_data_device_manager", 3, - 2, wl_data_device_manager_requests, - 0, NULL, -}; - -static const struct wl_message wl_shell_requests[] = { - { "get_shell_surface", "no", wayland_types + 37 }, -}; - -WL_PRIVATE const struct wl_interface wl_shell_interface = { - "wl_shell", 1, - 1, wl_shell_requests, - 0, NULL, -}; - -static const struct wl_message wl_shell_surface_requests[] = { - { "pong", "u", wayland_types + 0 }, - { "move", "ou", wayland_types + 39 }, - { "resize", "ouu", wayland_types + 41 }, - { "set_toplevel", "", wayland_types + 0 }, - { "set_transient", "oiiu", wayland_types + 44 }, - { "set_fullscreen", "uu?o", wayland_types + 48 }, - { "set_popup", "ouoiiu", wayland_types + 51 }, - { "set_maximized", "?o", wayland_types + 57 }, - { "set_title", "s", wayland_types + 0 }, - { "set_class", "s", wayland_types + 0 }, -}; - -static const struct wl_message wl_shell_surface_events[] = { - { "ping", "u", wayland_types + 0 }, - { "configure", "uii", wayland_types + 0 }, - { "popup_done", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shell_surface_interface = { - "wl_shell_surface", 1, - 10, wl_shell_surface_requests, - 3, wl_shell_surface_events, -}; - -static const struct wl_message wl_surface_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "attach", "?oii", wayland_types + 58 }, - { "damage", "iiii", wayland_types + 0 }, - { "frame", "n", wayland_types + 61 }, - { "set_opaque_region", "?o", wayland_types + 62 }, - { "set_input_region", "?o", wayland_types + 63 }, - { "commit", "", wayland_types + 0 }, - { "set_buffer_transform", "2i", wayland_types + 0 }, - { "set_buffer_scale", "3i", wayland_types + 0 }, - { "damage_buffer", "4iiii", wayland_types + 0 }, - { "offset", "5ii", wayland_types + 0 }, -}; - -static const struct wl_message wl_surface_events[] = { - { "enter", "o", wayland_types + 64 }, - { "leave", "o", wayland_types + 65 }, - { "preferred_buffer_scale", "6i", wayland_types + 0 }, - { "preferred_buffer_transform", "6u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_surface_interface = { - "wl_surface", 6, - 11, wl_surface_requests, - 4, wl_surface_events, -}; - -static const struct wl_message wl_seat_requests[] = { - { "get_pointer", "n", wayland_types + 66 }, - { "get_keyboard", "n", wayland_types + 67 }, - { "get_touch", "n", wayland_types + 68 }, - { "release", "5", wayland_types + 0 }, -}; - -static const struct wl_message wl_seat_events[] = { - { "capabilities", "u", wayland_types + 0 }, - { "name", "2s", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_seat_interface = { - "wl_seat", 9, - 4, wl_seat_requests, - 2, wl_seat_events, -}; - -static const struct wl_message wl_pointer_requests[] = { - { "set_cursor", "u?oii", wayland_types + 69 }, - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_pointer_events[] = { - { "enter", "uoff", wayland_types + 73 }, - { "leave", "uo", wayland_types + 77 }, - { "motion", "uff", wayland_types + 0 }, - { "button", "uuuu", wayland_types + 0 }, - { "axis", "uuf", wayland_types + 0 }, - { "frame", "5", wayland_types + 0 }, - { "axis_source", "5u", wayland_types + 0 }, - { "axis_stop", "5uu", wayland_types + 0 }, - { "axis_discrete", "5ui", wayland_types + 0 }, - { "axis_value120", "8ui", wayland_types + 0 }, - { "axis_relative_direction", "9uu", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_pointer_interface = { - "wl_pointer", 9, - 2, wl_pointer_requests, - 11, wl_pointer_events, -}; - -static const struct wl_message wl_keyboard_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_keyboard_events[] = { - { "keymap", "uhu", wayland_types + 0 }, - { "enter", "uoa", wayland_types + 79 }, - { "leave", "uo", wayland_types + 82 }, - { "key", "uuuu", wayland_types + 0 }, - { "modifiers", "uuuuu", wayland_types + 0 }, - { "repeat_info", "4ii", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_keyboard_interface = { - "wl_keyboard", 9, - 1, wl_keyboard_requests, - 6, wl_keyboard_events, -}; - -static const struct wl_message wl_touch_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_touch_events[] = { - { "down", "uuoiff", wayland_types + 84 }, - { "up", "uui", wayland_types + 0 }, - { "motion", "uiff", wayland_types + 0 }, - { "frame", "", wayland_types + 0 }, - { "cancel", "", wayland_types + 0 }, - { "shape", "6iff", wayland_types + 0 }, - { "orientation", "6if", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_touch_interface = { - "wl_touch", 9, - 1, wl_touch_requests, - 7, wl_touch_events, -}; - -static const struct wl_message wl_output_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_output_events[] = { - { "geometry", "iiiiissi", wayland_types + 0 }, - { "mode", "uiii", wayland_types + 0 }, - { "done", "2", wayland_types + 0 }, - { "scale", "2i", wayland_types + 0 }, - { "name", "4s", wayland_types + 0 }, - { "description", "4s", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_output_interface = { - "wl_output", 4, - 1, wl_output_requests, - 6, wl_output_events, -}; - -static const struct wl_message wl_region_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "add", "iiii", wayland_types + 0 }, - { "subtract", "iiii", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_region_interface = { - "wl_region", 1, - 3, wl_region_requests, - 0, NULL, -}; - -static const struct wl_message wl_subcompositor_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "get_subsurface", "noo", wayland_types + 90 }, -}; - -WL_PRIVATE const struct wl_interface wl_subcompositor_interface = { - "wl_subcompositor", 1, - 2, wl_subcompositor_requests, - 0, NULL, -}; - -static const struct wl_message wl_subsurface_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "set_position", "ii", wayland_types + 0 }, - { "place_above", "o", wayland_types + 93 }, - { "place_below", "o", wayland_types + 94 }, - { "set_sync", "", wayland_types + 0 }, - { "set_desync", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_subsurface_interface = { - "wl_subsurface", 1, - 6, wl_subsurface_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/wayland-client-protocol.h b/pkg/glfw/wayland-headers/wayland-client-protocol.h deleted file mode 100644 index 764c5958c..000000000 --- a/pkg/glfw/wayland-headers/wayland-client-protocol.h +++ /dev/null @@ -1,6236 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef WAYLAND_CLIENT_PROTOCOL_H -#define WAYLAND_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_wayland The wayland protocol - * @section page_ifaces_wayland Interfaces - * - @subpage page_iface_wl_display - core global object - * - @subpage page_iface_wl_registry - global registry object - * - @subpage page_iface_wl_callback - callback object - * - @subpage page_iface_wl_compositor - the compositor singleton - * - @subpage page_iface_wl_shm_pool - a shared memory pool - * - @subpage page_iface_wl_shm - shared memory support - * - @subpage page_iface_wl_buffer - content for a wl_surface - * - @subpage page_iface_wl_data_offer - offer to transfer data - * - @subpage page_iface_wl_data_source - offer to transfer data - * - @subpage page_iface_wl_data_device - data transfer device - * - @subpage page_iface_wl_data_device_manager - data transfer interface - * - @subpage page_iface_wl_shell - create desktop-style surfaces - * - @subpage page_iface_wl_shell_surface - desktop-style metadata interface - * - @subpage page_iface_wl_surface - an onscreen surface - * - @subpage page_iface_wl_seat - group of input devices - * - @subpage page_iface_wl_pointer - pointer input device - * - @subpage page_iface_wl_keyboard - keyboard input device - * - @subpage page_iface_wl_touch - touchscreen input device - * - @subpage page_iface_wl_output - compositor output region - * - @subpage page_iface_wl_region - region interface - * - @subpage page_iface_wl_subcompositor - sub-surface compositing - * - @subpage page_iface_wl_subsurface - sub-surface interface to a wl_surface - * @section page_copyright_wayland Copyright - *
- *
- * Copyright © 2008-2011 Kristian Høgsberg
- * Copyright © 2010-2011 Intel Corporation
- * Copyright © 2012-2013 Collabora, Ltd.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation files
- * (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the
- * next paragraph) shall be included in all copies or substantial
- * portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * 
- */ -struct wl_buffer; -struct wl_callback; -struct wl_compositor; -struct wl_data_device; -struct wl_data_device_manager; -struct wl_data_offer; -struct wl_data_source; -struct wl_display; -struct wl_keyboard; -struct wl_output; -struct wl_pointer; -struct wl_region; -struct wl_registry; -struct wl_seat; -struct wl_shell; -struct wl_shell_surface; -struct wl_shm; -struct wl_shm_pool; -struct wl_subcompositor; -struct wl_subsurface; -struct wl_surface; -struct wl_touch; - -#ifndef WL_DISPLAY_INTERFACE -#define WL_DISPLAY_INTERFACE -/** - * @page page_iface_wl_display wl_display - * @section page_iface_wl_display_desc Description - * - * The core global object. This is a special singleton object. It - * is used for internal Wayland protocol features. - * @section page_iface_wl_display_api API - * See @ref iface_wl_display. - */ -/** - * @defgroup iface_wl_display The wl_display interface - * - * The core global object. This is a special singleton object. It - * is used for internal Wayland protocol features. - */ -extern const struct wl_interface wl_display_interface; -#endif -#ifndef WL_REGISTRY_INTERFACE -#define WL_REGISTRY_INTERFACE -/** - * @page page_iface_wl_registry wl_registry - * @section page_iface_wl_registry_desc Description - * - * The singleton global registry object. The server has a number of - * global objects that are available to all clients. These objects - * typically represent an actual object in the server (for example, - * an input device) or they are singleton objects that provide - * extension functionality. - * - * When a client creates a registry object, the registry object - * will emit a global event for each global currently in the - * registry. Globals come and go as a result of device or - * monitor hotplugs, reconfiguration or other events, and the - * registry will send out global and global_remove events to - * keep the client up to date with the changes. To mark the end - * of the initial burst of events, the client can use the - * wl_display.sync request immediately after calling - * wl_display.get_registry. - * - * A client can bind to a global object by using the bind - * request. This creates a client-side handle that lets the object - * emit events to the client and lets the client invoke requests on - * the object. - * @section page_iface_wl_registry_api API - * See @ref iface_wl_registry. - */ -/** - * @defgroup iface_wl_registry The wl_registry interface - * - * The singleton global registry object. The server has a number of - * global objects that are available to all clients. These objects - * typically represent an actual object in the server (for example, - * an input device) or they are singleton objects that provide - * extension functionality. - * - * When a client creates a registry object, the registry object - * will emit a global event for each global currently in the - * registry. Globals come and go as a result of device or - * monitor hotplugs, reconfiguration or other events, and the - * registry will send out global and global_remove events to - * keep the client up to date with the changes. To mark the end - * of the initial burst of events, the client can use the - * wl_display.sync request immediately after calling - * wl_display.get_registry. - * - * A client can bind to a global object by using the bind - * request. This creates a client-side handle that lets the object - * emit events to the client and lets the client invoke requests on - * the object. - */ -extern const struct wl_interface wl_registry_interface; -#endif -#ifndef WL_CALLBACK_INTERFACE -#define WL_CALLBACK_INTERFACE -/** - * @page page_iface_wl_callback wl_callback - * @section page_iface_wl_callback_desc Description - * - * Clients can handle the 'done' event to get notified when - * the related request is done. - * - * Note, because wl_callback objects are created from multiple independent - * factory interfaces, the wl_callback interface is frozen at version 1. - * @section page_iface_wl_callback_api API - * See @ref iface_wl_callback. - */ -/** - * @defgroup iface_wl_callback The wl_callback interface - * - * Clients can handle the 'done' event to get notified when - * the related request is done. - * - * Note, because wl_callback objects are created from multiple independent - * factory interfaces, the wl_callback interface is frozen at version 1. - */ -extern const struct wl_interface wl_callback_interface; -#endif -#ifndef WL_COMPOSITOR_INTERFACE -#define WL_COMPOSITOR_INTERFACE -/** - * @page page_iface_wl_compositor wl_compositor - * @section page_iface_wl_compositor_desc Description - * - * A compositor. This object is a singleton global. The - * compositor is in charge of combining the contents of multiple - * surfaces into one displayable output. - * @section page_iface_wl_compositor_api API - * See @ref iface_wl_compositor. - */ -/** - * @defgroup iface_wl_compositor The wl_compositor interface - * - * A compositor. This object is a singleton global. The - * compositor is in charge of combining the contents of multiple - * surfaces into one displayable output. - */ -extern const struct wl_interface wl_compositor_interface; -#endif -#ifndef WL_SHM_POOL_INTERFACE -#define WL_SHM_POOL_INTERFACE -/** - * @page page_iface_wl_shm_pool wl_shm_pool - * @section page_iface_wl_shm_pool_desc Description - * - * The wl_shm_pool object encapsulates a piece of memory shared - * between the compositor and client. Through the wl_shm_pool - * object, the client can allocate shared memory wl_buffer objects. - * All objects created through the same pool share the same - * underlying mapped memory. Reusing the mapped memory avoids the - * setup/teardown overhead and is useful when interactively resizing - * a surface or for many small buffers. - * @section page_iface_wl_shm_pool_api API - * See @ref iface_wl_shm_pool. - */ -/** - * @defgroup iface_wl_shm_pool The wl_shm_pool interface - * - * The wl_shm_pool object encapsulates a piece of memory shared - * between the compositor and client. Through the wl_shm_pool - * object, the client can allocate shared memory wl_buffer objects. - * All objects created through the same pool share the same - * underlying mapped memory. Reusing the mapped memory avoids the - * setup/teardown overhead and is useful when interactively resizing - * a surface or for many small buffers. - */ -extern const struct wl_interface wl_shm_pool_interface; -#endif -#ifndef WL_SHM_INTERFACE -#define WL_SHM_INTERFACE -/** - * @page page_iface_wl_shm wl_shm - * @section page_iface_wl_shm_desc Description - * - * A singleton global object that provides support for shared - * memory. - * - * Clients can create wl_shm_pool objects using the create_pool - * request. - * - * On binding the wl_shm object one or more format events - * are emitted to inform clients about the valid pixel formats - * that can be used for buffers. - * @section page_iface_wl_shm_api API - * See @ref iface_wl_shm. - */ -/** - * @defgroup iface_wl_shm The wl_shm interface - * - * A singleton global object that provides support for shared - * memory. - * - * Clients can create wl_shm_pool objects using the create_pool - * request. - * - * On binding the wl_shm object one or more format events - * are emitted to inform clients about the valid pixel formats - * that can be used for buffers. - */ -extern const struct wl_interface wl_shm_interface; -#endif -#ifndef WL_BUFFER_INTERFACE -#define WL_BUFFER_INTERFACE -/** - * @page page_iface_wl_buffer wl_buffer - * @section page_iface_wl_buffer_desc Description - * - * A buffer provides the content for a wl_surface. Buffers are - * created through factory interfaces such as wl_shm, wp_linux_buffer_params - * (from the linux-dmabuf protocol extension) or similar. It has a width and - * a height and can be attached to a wl_surface, but the mechanism by which a - * client provides and updates the contents is defined by the buffer factory - * interface. - * - * If the buffer uses a format that has an alpha channel, the alpha channel - * is assumed to be premultiplied in the color channels unless otherwise - * specified. - * - * Note, because wl_buffer objects are created from multiple independent - * factory interfaces, the wl_buffer interface is frozen at version 1. - * @section page_iface_wl_buffer_api API - * See @ref iface_wl_buffer. - */ -/** - * @defgroup iface_wl_buffer The wl_buffer interface - * - * A buffer provides the content for a wl_surface. Buffers are - * created through factory interfaces such as wl_shm, wp_linux_buffer_params - * (from the linux-dmabuf protocol extension) or similar. It has a width and - * a height and can be attached to a wl_surface, but the mechanism by which a - * client provides and updates the contents is defined by the buffer factory - * interface. - * - * If the buffer uses a format that has an alpha channel, the alpha channel - * is assumed to be premultiplied in the color channels unless otherwise - * specified. - * - * Note, because wl_buffer objects are created from multiple independent - * factory interfaces, the wl_buffer interface is frozen at version 1. - */ -extern const struct wl_interface wl_buffer_interface; -#endif -#ifndef WL_DATA_OFFER_INTERFACE -#define WL_DATA_OFFER_INTERFACE -/** - * @page page_iface_wl_data_offer wl_data_offer - * @section page_iface_wl_data_offer_desc Description - * - * A wl_data_offer represents a piece of data offered for transfer - * by another client (the source client). It is used by the - * copy-and-paste and drag-and-drop mechanisms. The offer - * describes the different mime types that the data can be - * converted to and provides the mechanism for transferring the - * data directly from the source client. - * @section page_iface_wl_data_offer_api API - * See @ref iface_wl_data_offer. - */ -/** - * @defgroup iface_wl_data_offer The wl_data_offer interface - * - * A wl_data_offer represents a piece of data offered for transfer - * by another client (the source client). It is used by the - * copy-and-paste and drag-and-drop mechanisms. The offer - * describes the different mime types that the data can be - * converted to and provides the mechanism for transferring the - * data directly from the source client. - */ -extern const struct wl_interface wl_data_offer_interface; -#endif -#ifndef WL_DATA_SOURCE_INTERFACE -#define WL_DATA_SOURCE_INTERFACE -/** - * @page page_iface_wl_data_source wl_data_source - * @section page_iface_wl_data_source_desc Description - * - * The wl_data_source object is the source side of a wl_data_offer. - * It is created by the source client in a data transfer and - * provides a way to describe the offered data and a way to respond - * to requests to transfer the data. - * @section page_iface_wl_data_source_api API - * See @ref iface_wl_data_source. - */ -/** - * @defgroup iface_wl_data_source The wl_data_source interface - * - * The wl_data_source object is the source side of a wl_data_offer. - * It is created by the source client in a data transfer and - * provides a way to describe the offered data and a way to respond - * to requests to transfer the data. - */ -extern const struct wl_interface wl_data_source_interface; -#endif -#ifndef WL_DATA_DEVICE_INTERFACE -#define WL_DATA_DEVICE_INTERFACE -/** - * @page page_iface_wl_data_device wl_data_device - * @section page_iface_wl_data_device_desc Description - * - * There is one wl_data_device per seat which can be obtained - * from the global wl_data_device_manager singleton. - * - * A wl_data_device provides access to inter-client data transfer - * mechanisms such as copy-and-paste and drag-and-drop. - * @section page_iface_wl_data_device_api API - * See @ref iface_wl_data_device. - */ -/** - * @defgroup iface_wl_data_device The wl_data_device interface - * - * There is one wl_data_device per seat which can be obtained - * from the global wl_data_device_manager singleton. - * - * A wl_data_device provides access to inter-client data transfer - * mechanisms such as copy-and-paste and drag-and-drop. - */ -extern const struct wl_interface wl_data_device_interface; -#endif -#ifndef WL_DATA_DEVICE_MANAGER_INTERFACE -#define WL_DATA_DEVICE_MANAGER_INTERFACE -/** - * @page page_iface_wl_data_device_manager wl_data_device_manager - * @section page_iface_wl_data_device_manager_desc Description - * - * The wl_data_device_manager is a singleton global object that - * provides access to inter-client data transfer mechanisms such as - * copy-and-paste and drag-and-drop. These mechanisms are tied to - * a wl_seat and this interface lets a client get a wl_data_device - * corresponding to a wl_seat. - * - * Depending on the version bound, the objects created from the bound - * wl_data_device_manager object will have different requirements for - * functioning properly. See wl_data_source.set_actions, - * wl_data_offer.accept and wl_data_offer.finish for details. - * @section page_iface_wl_data_device_manager_api API - * See @ref iface_wl_data_device_manager. - */ -/** - * @defgroup iface_wl_data_device_manager The wl_data_device_manager interface - * - * The wl_data_device_manager is a singleton global object that - * provides access to inter-client data transfer mechanisms such as - * copy-and-paste and drag-and-drop. These mechanisms are tied to - * a wl_seat and this interface lets a client get a wl_data_device - * corresponding to a wl_seat. - * - * Depending on the version bound, the objects created from the bound - * wl_data_device_manager object will have different requirements for - * functioning properly. See wl_data_source.set_actions, - * wl_data_offer.accept and wl_data_offer.finish for details. - */ -extern const struct wl_interface wl_data_device_manager_interface; -#endif -#ifndef WL_SHELL_INTERFACE -#define WL_SHELL_INTERFACE -/** - * @page page_iface_wl_shell wl_shell - * @section page_iface_wl_shell_desc Description - * - * This interface is implemented by servers that provide - * desktop-style user interfaces. - * - * It allows clients to associate a wl_shell_surface with - * a basic surface. - * - * Note! This protocol is deprecated and not intended for production use. - * For desktop-style user interfaces, use xdg_shell. Compositors and clients - * should not implement this interface. - * @section page_iface_wl_shell_api API - * See @ref iface_wl_shell. - */ -/** - * @defgroup iface_wl_shell The wl_shell interface - * - * This interface is implemented by servers that provide - * desktop-style user interfaces. - * - * It allows clients to associate a wl_shell_surface with - * a basic surface. - * - * Note! This protocol is deprecated and not intended for production use. - * For desktop-style user interfaces, use xdg_shell. Compositors and clients - * should not implement this interface. - */ -extern const struct wl_interface wl_shell_interface; -#endif -#ifndef WL_SHELL_SURFACE_INTERFACE -#define WL_SHELL_SURFACE_INTERFACE -/** - * @page page_iface_wl_shell_surface wl_shell_surface - * @section page_iface_wl_shell_surface_desc Description - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides requests to treat surfaces like toplevel, fullscreen - * or popup windows, move, resize or maximize them, associate - * metadata like title and class, etc. - * - * On the server side the object is automatically destroyed when - * the related wl_surface is destroyed. On the client side, - * wl_shell_surface_destroy() must be called before destroying - * the wl_surface object. - * @section page_iface_wl_shell_surface_api API - * See @ref iface_wl_shell_surface. - */ -/** - * @defgroup iface_wl_shell_surface The wl_shell_surface interface - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides requests to treat surfaces like toplevel, fullscreen - * or popup windows, move, resize or maximize them, associate - * metadata like title and class, etc. - * - * On the server side the object is automatically destroyed when - * the related wl_surface is destroyed. On the client side, - * wl_shell_surface_destroy() must be called before destroying - * the wl_surface object. - */ -extern const struct wl_interface wl_shell_surface_interface; -#endif -#ifndef WL_SURFACE_INTERFACE -#define WL_SURFACE_INTERFACE -/** - * @page page_iface_wl_surface wl_surface - * @section page_iface_wl_surface_desc Description - * - * A surface is a rectangular area that may be displayed on zero - * or more outputs, and shown any number of times at the compositor's - * discretion. They can present wl_buffers, receive user input, and - * define a local coordinate system. - * - * The size of a surface (and relative positions on it) is described - * in surface-local coordinates, which may differ from the buffer - * coordinates of the pixel content, in case a buffer_transform - * or a buffer_scale is used. - * - * A surface without a "role" is fairly useless: a compositor does - * not know where, when or how to present it. The role is the - * purpose of a wl_surface. Examples of roles are a cursor for a - * pointer (as set by wl_pointer.set_cursor), a drag icon - * (wl_data_device.start_drag), a sub-surface - * (wl_subcompositor.get_subsurface), and a window as defined by a - * shell protocol (e.g. wl_shell.get_shell_surface). - * - * A surface can have only one role at a time. Initially a - * wl_surface does not have a role. Once a wl_surface is given a - * role, it is set permanently for the whole lifetime of the - * wl_surface object. Giving the current role again is allowed, - * unless explicitly forbidden by the relevant interface - * specification. - * - * Surface roles are given by requests in other interfaces such as - * wl_pointer.set_cursor. The request should explicitly mention - * that this request gives a role to a wl_surface. Often, this - * request also creates a new protocol object that represents the - * role and adds additional functionality to wl_surface. When a - * client wants to destroy a wl_surface, they must destroy this role - * object before the wl_surface, otherwise a defunct_role_object error is - * sent. - * - * Destroying the role object does not remove the role from the - * wl_surface, but it may stop the wl_surface from "playing the role". - * For instance, if a wl_subsurface object is destroyed, the wl_surface - * it was created for will be unmapped and forget its position and - * z-order. It is allowed to create a wl_subsurface for the same - * wl_surface again, but it is not allowed to use the wl_surface as - * a cursor (cursor is a different role than sub-surface, and role - * switching is not allowed). - * @section page_iface_wl_surface_api API - * See @ref iface_wl_surface. - */ -/** - * @defgroup iface_wl_surface The wl_surface interface - * - * A surface is a rectangular area that may be displayed on zero - * or more outputs, and shown any number of times at the compositor's - * discretion. They can present wl_buffers, receive user input, and - * define a local coordinate system. - * - * The size of a surface (and relative positions on it) is described - * in surface-local coordinates, which may differ from the buffer - * coordinates of the pixel content, in case a buffer_transform - * or a buffer_scale is used. - * - * A surface without a "role" is fairly useless: a compositor does - * not know where, when or how to present it. The role is the - * purpose of a wl_surface. Examples of roles are a cursor for a - * pointer (as set by wl_pointer.set_cursor), a drag icon - * (wl_data_device.start_drag), a sub-surface - * (wl_subcompositor.get_subsurface), and a window as defined by a - * shell protocol (e.g. wl_shell.get_shell_surface). - * - * A surface can have only one role at a time. Initially a - * wl_surface does not have a role. Once a wl_surface is given a - * role, it is set permanently for the whole lifetime of the - * wl_surface object. Giving the current role again is allowed, - * unless explicitly forbidden by the relevant interface - * specification. - * - * Surface roles are given by requests in other interfaces such as - * wl_pointer.set_cursor. The request should explicitly mention - * that this request gives a role to a wl_surface. Often, this - * request also creates a new protocol object that represents the - * role and adds additional functionality to wl_surface. When a - * client wants to destroy a wl_surface, they must destroy this role - * object before the wl_surface, otherwise a defunct_role_object error is - * sent. - * - * Destroying the role object does not remove the role from the - * wl_surface, but it may stop the wl_surface from "playing the role". - * For instance, if a wl_subsurface object is destroyed, the wl_surface - * it was created for will be unmapped and forget its position and - * z-order. It is allowed to create a wl_subsurface for the same - * wl_surface again, but it is not allowed to use the wl_surface as - * a cursor (cursor is a different role than sub-surface, and role - * switching is not allowed). - */ -extern const struct wl_interface wl_surface_interface; -#endif -#ifndef WL_SEAT_INTERFACE -#define WL_SEAT_INTERFACE -/** - * @page page_iface_wl_seat wl_seat - * @section page_iface_wl_seat_desc Description - * - * A seat is a group of keyboards, pointer and touch devices. This - * object is published as a global during start up, or when such a - * device is hot plugged. A seat typically has a pointer and - * maintains a keyboard focus and a pointer focus. - * @section page_iface_wl_seat_api API - * See @ref iface_wl_seat. - */ -/** - * @defgroup iface_wl_seat The wl_seat interface - * - * A seat is a group of keyboards, pointer and touch devices. This - * object is published as a global during start up, or when such a - * device is hot plugged. A seat typically has a pointer and - * maintains a keyboard focus and a pointer focus. - */ -extern const struct wl_interface wl_seat_interface; -#endif -#ifndef WL_POINTER_INTERFACE -#define WL_POINTER_INTERFACE -/** - * @page page_iface_wl_pointer wl_pointer - * @section page_iface_wl_pointer_desc Description - * - * The wl_pointer interface represents one or more input devices, - * such as mice, which control the pointer location and pointer_focus - * of a seat. - * - * The wl_pointer interface generates motion, enter and leave - * events for the surfaces that the pointer is located over, - * and button and axis events for button presses, button releases - * and scrolling. - * @section page_iface_wl_pointer_api API - * See @ref iface_wl_pointer. - */ -/** - * @defgroup iface_wl_pointer The wl_pointer interface - * - * The wl_pointer interface represents one or more input devices, - * such as mice, which control the pointer location and pointer_focus - * of a seat. - * - * The wl_pointer interface generates motion, enter and leave - * events for the surfaces that the pointer is located over, - * and button and axis events for button presses, button releases - * and scrolling. - */ -extern const struct wl_interface wl_pointer_interface; -#endif -#ifndef WL_KEYBOARD_INTERFACE -#define WL_KEYBOARD_INTERFACE -/** - * @page page_iface_wl_keyboard wl_keyboard - * @section page_iface_wl_keyboard_desc Description - * - * The wl_keyboard interface represents one or more keyboards - * associated with a seat. - * @section page_iface_wl_keyboard_api API - * See @ref iface_wl_keyboard. - */ -/** - * @defgroup iface_wl_keyboard The wl_keyboard interface - * - * The wl_keyboard interface represents one or more keyboards - * associated with a seat. - */ -extern const struct wl_interface wl_keyboard_interface; -#endif -#ifndef WL_TOUCH_INTERFACE -#define WL_TOUCH_INTERFACE -/** - * @page page_iface_wl_touch wl_touch - * @section page_iface_wl_touch_desc Description - * - * The wl_touch interface represents a touchscreen - * associated with a seat. - * - * Touch interactions can consist of one or more contacts. - * For each contact, a series of events is generated, starting - * with a down event, followed by zero or more motion events, - * and ending with an up event. Events relating to the same - * contact point can be identified by the ID of the sequence. - * @section page_iface_wl_touch_api API - * See @ref iface_wl_touch. - */ -/** - * @defgroup iface_wl_touch The wl_touch interface - * - * The wl_touch interface represents a touchscreen - * associated with a seat. - * - * Touch interactions can consist of one or more contacts. - * For each contact, a series of events is generated, starting - * with a down event, followed by zero or more motion events, - * and ending with an up event. Events relating to the same - * contact point can be identified by the ID of the sequence. - */ -extern const struct wl_interface wl_touch_interface; -#endif -#ifndef WL_OUTPUT_INTERFACE -#define WL_OUTPUT_INTERFACE -/** - * @page page_iface_wl_output wl_output - * @section page_iface_wl_output_desc Description - * - * An output describes part of the compositor geometry. The - * compositor works in the 'compositor coordinate system' and an - * output corresponds to a rectangular area in that space that is - * actually visible. This typically corresponds to a monitor that - * displays part of the compositor space. This object is published - * as global during start up, or when a monitor is hotplugged. - * @section page_iface_wl_output_api API - * See @ref iface_wl_output. - */ -/** - * @defgroup iface_wl_output The wl_output interface - * - * An output describes part of the compositor geometry. The - * compositor works in the 'compositor coordinate system' and an - * output corresponds to a rectangular area in that space that is - * actually visible. This typically corresponds to a monitor that - * displays part of the compositor space. This object is published - * as global during start up, or when a monitor is hotplugged. - */ -extern const struct wl_interface wl_output_interface; -#endif -#ifndef WL_REGION_INTERFACE -#define WL_REGION_INTERFACE -/** - * @page page_iface_wl_region wl_region - * @section page_iface_wl_region_desc Description - * - * A region object describes an area. - * - * Region objects are used to describe the opaque and input - * regions of a surface. - * @section page_iface_wl_region_api API - * See @ref iface_wl_region. - */ -/** - * @defgroup iface_wl_region The wl_region interface - * - * A region object describes an area. - * - * Region objects are used to describe the opaque and input - * regions of a surface. - */ -extern const struct wl_interface wl_region_interface; -#endif -#ifndef WL_SUBCOMPOSITOR_INTERFACE -#define WL_SUBCOMPOSITOR_INTERFACE -/** - * @page page_iface_wl_subcompositor wl_subcompositor - * @section page_iface_wl_subcompositor_desc Description - * - * The global interface exposing sub-surface compositing capabilities. - * A wl_surface, that has sub-surfaces associated, is called the - * parent surface. Sub-surfaces can be arbitrarily nested and create - * a tree of sub-surfaces. - * - * The root surface in a tree of sub-surfaces is the main - * surface. The main surface cannot be a sub-surface, because - * sub-surfaces must always have a parent. - * - * A main surface with its sub-surfaces forms a (compound) window. - * For window management purposes, this set of wl_surface objects is - * to be considered as a single window, and it should also behave as - * such. - * - * The aim of sub-surfaces is to offload some of the compositing work - * within a window from clients to the compositor. A prime example is - * a video player with decorations and video in separate wl_surface - * objects. This should allow the compositor to pass YUV video buffer - * processing to dedicated overlay hardware when possible. - * @section page_iface_wl_subcompositor_api API - * See @ref iface_wl_subcompositor. - */ -/** - * @defgroup iface_wl_subcompositor The wl_subcompositor interface - * - * The global interface exposing sub-surface compositing capabilities. - * A wl_surface, that has sub-surfaces associated, is called the - * parent surface. Sub-surfaces can be arbitrarily nested and create - * a tree of sub-surfaces. - * - * The root surface in a tree of sub-surfaces is the main - * surface. The main surface cannot be a sub-surface, because - * sub-surfaces must always have a parent. - * - * A main surface with its sub-surfaces forms a (compound) window. - * For window management purposes, this set of wl_surface objects is - * to be considered as a single window, and it should also behave as - * such. - * - * The aim of sub-surfaces is to offload some of the compositing work - * within a window from clients to the compositor. A prime example is - * a video player with decorations and video in separate wl_surface - * objects. This should allow the compositor to pass YUV video buffer - * processing to dedicated overlay hardware when possible. - */ -extern const struct wl_interface wl_subcompositor_interface; -#endif -#ifndef WL_SUBSURFACE_INTERFACE -#define WL_SUBSURFACE_INTERFACE -/** - * @page page_iface_wl_subsurface wl_subsurface - * @section page_iface_wl_subsurface_desc Description - * - * An additional interface to a wl_surface object, which has been - * made a sub-surface. A sub-surface has one parent surface. A - * sub-surface's size and position are not limited to that of the parent. - * Particularly, a sub-surface is not automatically clipped to its - * parent's area. - * - * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied - * and the parent surface is mapped. The order of which one happens - * first is irrelevant. A sub-surface is hidden if the parent becomes - * hidden, or if a NULL wl_buffer is applied. These rules apply - * recursively through the tree of surfaces. - * - * The behaviour of a wl_surface.commit request on a sub-surface - * depends on the sub-surface's mode. The possible modes are - * synchronized and desynchronized, see methods - * wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized - * mode caches the wl_surface state to be applied when the parent's - * state gets applied, and desynchronized mode applies the pending - * wl_surface state directly. A sub-surface is initially in the - * synchronized mode. - * - * Sub-surfaces also have another kind of state, which is managed by - * wl_subsurface requests, as opposed to wl_surface requests. This - * state includes the sub-surface position relative to the parent - * surface (wl_subsurface.set_position), and the stacking order of - * the parent and its sub-surfaces (wl_subsurface.place_above and - * .place_below). This state is applied when the parent surface's - * wl_surface state is applied, regardless of the sub-surface's mode. - * As the exception, set_sync and set_desync are effective immediately. - * - * The main surface can be thought to be always in desynchronized mode, - * since it does not have a parent in the sub-surfaces sense. - * - * Even if a sub-surface is in desynchronized mode, it will behave as - * in synchronized mode, if its parent surface behaves as in - * synchronized mode. This rule is applied recursively throughout the - * tree of surfaces. This means, that one can set a sub-surface into - * synchronized mode, and then assume that all its child and grand-child - * sub-surfaces are synchronized, too, without explicitly setting them. - * - * Destroying a sub-surface takes effect immediately. If you need to - * synchronize the removal of a sub-surface to the parent surface update, - * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, - * and then destroy the sub-surface. - * - * If the parent wl_surface object is destroyed, the sub-surface is - * unmapped. - * @section page_iface_wl_subsurface_api API - * See @ref iface_wl_subsurface. - */ -/** - * @defgroup iface_wl_subsurface The wl_subsurface interface - * - * An additional interface to a wl_surface object, which has been - * made a sub-surface. A sub-surface has one parent surface. A - * sub-surface's size and position are not limited to that of the parent. - * Particularly, a sub-surface is not automatically clipped to its - * parent's area. - * - * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied - * and the parent surface is mapped. The order of which one happens - * first is irrelevant. A sub-surface is hidden if the parent becomes - * hidden, or if a NULL wl_buffer is applied. These rules apply - * recursively through the tree of surfaces. - * - * The behaviour of a wl_surface.commit request on a sub-surface - * depends on the sub-surface's mode. The possible modes are - * synchronized and desynchronized, see methods - * wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized - * mode caches the wl_surface state to be applied when the parent's - * state gets applied, and desynchronized mode applies the pending - * wl_surface state directly. A sub-surface is initially in the - * synchronized mode. - * - * Sub-surfaces also have another kind of state, which is managed by - * wl_subsurface requests, as opposed to wl_surface requests. This - * state includes the sub-surface position relative to the parent - * surface (wl_subsurface.set_position), and the stacking order of - * the parent and its sub-surfaces (wl_subsurface.place_above and - * .place_below). This state is applied when the parent surface's - * wl_surface state is applied, regardless of the sub-surface's mode. - * As the exception, set_sync and set_desync are effective immediately. - * - * The main surface can be thought to be always in desynchronized mode, - * since it does not have a parent in the sub-surfaces sense. - * - * Even if a sub-surface is in desynchronized mode, it will behave as - * in synchronized mode, if its parent surface behaves as in - * synchronized mode. This rule is applied recursively throughout the - * tree of surfaces. This means, that one can set a sub-surface into - * synchronized mode, and then assume that all its child and grand-child - * sub-surfaces are synchronized, too, without explicitly setting them. - * - * Destroying a sub-surface takes effect immediately. If you need to - * synchronize the removal of a sub-surface to the parent surface update, - * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, - * and then destroy the sub-surface. - * - * If the parent wl_surface object is destroyed, the sub-surface is - * unmapped. - */ -extern const struct wl_interface wl_subsurface_interface; -#endif - -#ifndef WL_DISPLAY_ERROR_ENUM -#define WL_DISPLAY_ERROR_ENUM -/** - * @ingroup iface_wl_display - * global error values - * - * These errors are global and can be emitted in response to any - * server request. - */ -enum wl_display_error { - /** - * server couldn't find object - */ - WL_DISPLAY_ERROR_INVALID_OBJECT = 0, - /** - * method doesn't exist on the specified interface or malformed request - */ - WL_DISPLAY_ERROR_INVALID_METHOD = 1, - /** - * server is out of memory - */ - WL_DISPLAY_ERROR_NO_MEMORY = 2, - /** - * implementation error in compositor - */ - WL_DISPLAY_ERROR_IMPLEMENTATION = 3, -}; -#endif /* WL_DISPLAY_ERROR_ENUM */ - -/** - * @ingroup iface_wl_display - * @struct wl_display_listener - */ -struct wl_display_listener { - /** - * fatal error event - * - * The error event is sent out when a fatal (non-recoverable) - * error has occurred. The object_id argument is the object where - * the error occurred, most often in response to a request to that - * object. The code identifies the error and is defined by the - * object interface. As such, each interface defines its own set of - * error codes. The message is a brief description of the error, - * for (debugging) convenience. - * @param object_id object where the error occurred - * @param code error code - * @param message error description - */ - void (*error)(void *data, - struct wl_display *wl_display, - void *object_id, - uint32_t code, - const char *message); - /** - * acknowledge object ID deletion - * - * This event is used internally by the object ID management - * logic. When a client deletes an object that it had created, the - * server will send this event to acknowledge that it has seen the - * delete request. When the client receives this event, it will - * know that it can safely reuse the object ID. - * @param id deleted object ID - */ - void (*delete_id)(void *data, - struct wl_display *wl_display, - uint32_t id); -}; - -/** - * @ingroup iface_wl_display - */ -static inline int -wl_display_add_listener(struct wl_display *wl_display, - const struct wl_display_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_display, - (void (**)(void)) listener, data); -} - -#define WL_DISPLAY_SYNC 0 -#define WL_DISPLAY_GET_REGISTRY 1 - -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_ERROR_SINCE_VERSION 1 -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_DELETE_ID_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_SYNC_SINCE_VERSION 1 -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_GET_REGISTRY_SINCE_VERSION 1 - -/** @ingroup iface_wl_display */ -static inline void -wl_display_set_user_data(struct wl_display *wl_display, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_display, user_data); -} - -/** @ingroup iface_wl_display */ -static inline void * -wl_display_get_user_data(struct wl_display *wl_display) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_display); -} - -static inline uint32_t -wl_display_get_version(struct wl_display *wl_display) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_display); -} - -/** - * @ingroup iface_wl_display - * - * The sync request asks the server to emit the 'done' event - * on the returned wl_callback object. Since requests are - * handled in-order and events are delivered in-order, this can - * be used as a barrier to ensure all previous requests and the - * resulting events have been handled. - * - * The object returned by this request will be destroyed by the - * compositor after the callback is fired and as such the client must not - * attempt to use it after that point. - * - * The callback_data passed in the callback is the event serial. - */ -static inline struct wl_callback * -wl_display_sync(struct wl_display *wl_display) -{ - struct wl_proxy *callback; - - callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, - WL_DISPLAY_SYNC, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); - - return (struct wl_callback *) callback; -} - -/** - * @ingroup iface_wl_display - * - * This request creates a registry object that allows the client - * to list and bind the global objects available from the - * compositor. - * - * It should be noted that the server side resources consumed in - * response to a get_registry request can only be released when the - * client disconnects, not when the client side proxy is destroyed. - * Therefore, clients should invoke get_registry as infrequently as - * possible to avoid wasting memory. - */ -static inline struct wl_registry * -wl_display_get_registry(struct wl_display *wl_display) -{ - struct wl_proxy *registry; - - registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, - WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); - - return (struct wl_registry *) registry; -} - -/** - * @ingroup iface_wl_registry - * @struct wl_registry_listener - */ -struct wl_registry_listener { - /** - * announce global object - * - * Notify the client of global objects. - * - * The event notifies the client that a global object with the - * given name is now available, and it implements the given version - * of the given interface. - * @param name numeric name of the global object - * @param interface interface implemented by the object - * @param version interface version - */ - void (*global)(void *data, - struct wl_registry *wl_registry, - uint32_t name, - const char *interface, - uint32_t version); - /** - * announce removal of global object - * - * Notify the client of removed global objects. - * - * This event notifies the client that the global identified by - * name is no longer available. If the client bound to the global - * using the bind request, the client should now destroy that - * object. - * - * The object remains valid and requests to the object will be - * ignored until the client destroys it, to avoid races between the - * global going away and a client sending a request to it. - * @param name numeric name of the global object - */ - void (*global_remove)(void *data, - struct wl_registry *wl_registry, - uint32_t name); -}; - -/** - * @ingroup iface_wl_registry - */ -static inline int -wl_registry_add_listener(struct wl_registry *wl_registry, - const struct wl_registry_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_registry, - (void (**)(void)) listener, data); -} - -#define WL_REGISTRY_BIND 0 - -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_GLOBAL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_GLOBAL_REMOVE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_BIND_SINCE_VERSION 1 - -/** @ingroup iface_wl_registry */ -static inline void -wl_registry_set_user_data(struct wl_registry *wl_registry, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_registry, user_data); -} - -/** @ingroup iface_wl_registry */ -static inline void * -wl_registry_get_user_data(struct wl_registry *wl_registry) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_registry); -} - -static inline uint32_t -wl_registry_get_version(struct wl_registry *wl_registry) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_registry); -} - -/** @ingroup iface_wl_registry */ -static inline void -wl_registry_destroy(struct wl_registry *wl_registry) -{ - wl_proxy_destroy((struct wl_proxy *) wl_registry); -} - -/** - * @ingroup iface_wl_registry - * - * Binds a new, client-created object to the server using the - * specified name as the identifier. - */ -static inline void * -wl_registry_bind(struct wl_registry *wl_registry, uint32_t name, const struct wl_interface *interface, uint32_t version) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_registry, - WL_REGISTRY_BIND, interface, version, 0, name, interface->name, version, NULL); - - return (void *) id; -} - -/** - * @ingroup iface_wl_callback - * @struct wl_callback_listener - */ -struct wl_callback_listener { - /** - * done event - * - * Notify the client when the related request is done. - * @param callback_data request-specific data for the callback - */ - void (*done)(void *data, - struct wl_callback *wl_callback, - uint32_t callback_data); -}; - -/** - * @ingroup iface_wl_callback - */ -static inline int -wl_callback_add_listener(struct wl_callback *wl_callback, - const struct wl_callback_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_callback, - (void (**)(void)) listener, data); -} - -/** - * @ingroup iface_wl_callback - */ -#define WL_CALLBACK_DONE_SINCE_VERSION 1 - - -/** @ingroup iface_wl_callback */ -static inline void -wl_callback_set_user_data(struct wl_callback *wl_callback, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_callback, user_data); -} - -/** @ingroup iface_wl_callback */ -static inline void * -wl_callback_get_user_data(struct wl_callback *wl_callback) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_callback); -} - -static inline uint32_t -wl_callback_get_version(struct wl_callback *wl_callback) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_callback); -} - -/** @ingroup iface_wl_callback */ -static inline void -wl_callback_destroy(struct wl_callback *wl_callback) -{ - wl_proxy_destroy((struct wl_proxy *) wl_callback); -} - -#define WL_COMPOSITOR_CREATE_SURFACE 0 -#define WL_COMPOSITOR_CREATE_REGION 1 - - -/** - * @ingroup iface_wl_compositor - */ -#define WL_COMPOSITOR_CREATE_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_compositor - */ -#define WL_COMPOSITOR_CREATE_REGION_SINCE_VERSION 1 - -/** @ingroup iface_wl_compositor */ -static inline void -wl_compositor_set_user_data(struct wl_compositor *wl_compositor, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_compositor, user_data); -} - -/** @ingroup iface_wl_compositor */ -static inline void * -wl_compositor_get_user_data(struct wl_compositor *wl_compositor) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_compositor); -} - -static inline uint32_t -wl_compositor_get_version(struct wl_compositor *wl_compositor) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_compositor); -} - -/** @ingroup iface_wl_compositor */ -static inline void -wl_compositor_destroy(struct wl_compositor *wl_compositor) -{ - wl_proxy_destroy((struct wl_proxy *) wl_compositor); -} - -/** - * @ingroup iface_wl_compositor - * - * Ask the compositor to create a new surface. - */ -static inline struct wl_surface * -wl_compositor_create_surface(struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, - WL_COMPOSITOR_CREATE_SURFACE, &wl_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); - - return (struct wl_surface *) id; -} - -/** - * @ingroup iface_wl_compositor - * - * Ask the compositor to create a new region. - */ -static inline struct wl_region * -wl_compositor_create_region(struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, - WL_COMPOSITOR_CREATE_REGION, &wl_region_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); - - return (struct wl_region *) id; -} - -#define WL_SHM_POOL_CREATE_BUFFER 0 -#define WL_SHM_POOL_DESTROY 1 -#define WL_SHM_POOL_RESIZE 2 - - -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_CREATE_BUFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_RESIZE_SINCE_VERSION 1 - -/** @ingroup iface_wl_shm_pool */ -static inline void -wl_shm_pool_set_user_data(struct wl_shm_pool *wl_shm_pool, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shm_pool, user_data); -} - -/** @ingroup iface_wl_shm_pool */ -static inline void * -wl_shm_pool_get_user_data(struct wl_shm_pool *wl_shm_pool) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shm_pool); -} - -static inline uint32_t -wl_shm_pool_get_version(struct wl_shm_pool *wl_shm_pool) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shm_pool); -} - -/** - * @ingroup iface_wl_shm_pool - * - * Create a wl_buffer object from the pool. - * - * The buffer is created offset bytes into the pool and has - * width and height as specified. The stride argument specifies - * the number of bytes from the beginning of one row to the beginning - * of the next. The format is the pixel format of the buffer and - * must be one of those advertised through the wl_shm.format event. - * - * A buffer will keep a reference to the pool it was created from - * so it is valid to destroy the pool immediately after creating - * a buffer from it. - */ -static inline struct wl_buffer * -wl_shm_pool_create_buffer(struct wl_shm_pool *wl_shm_pool, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_CREATE_BUFFER, &wl_buffer_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, NULL, offset, width, height, stride, format); - - return (struct wl_buffer *) id; -} - -/** - * @ingroup iface_wl_shm_pool - * - * Destroy the shared memory pool. - * - * The mmapped memory will be released when all - * buffers that have been created from this pool - * are gone. - */ -static inline void -wl_shm_pool_destroy(struct wl_shm_pool *wl_shm_pool) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_shm_pool - * - * This request will cause the server to remap the backing memory - * for the pool from the file descriptor passed when the pool was - * created, but using the new size. This request can only be - * used to make the pool bigger. - * - * This request only changes the amount of bytes that are mmapped - * by the server and does not touch the file corresponding to the - * file descriptor passed at creation time. It is the client's - * responsibility to ensure that the file is at least as big as - * the new pool size. - */ -static inline void -wl_shm_pool_resize(struct wl_shm_pool *wl_shm_pool, int32_t size) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, size); -} - -#ifndef WL_SHM_ERROR_ENUM -#define WL_SHM_ERROR_ENUM -/** - * @ingroup iface_wl_shm - * wl_shm error values - * - * These errors can be emitted in response to wl_shm requests. - */ -enum wl_shm_error { - /** - * buffer format is not known - */ - WL_SHM_ERROR_INVALID_FORMAT = 0, - /** - * invalid size or stride during pool or buffer creation - */ - WL_SHM_ERROR_INVALID_STRIDE = 1, - /** - * mmapping the file descriptor failed - */ - WL_SHM_ERROR_INVALID_FD = 2, -}; -#endif /* WL_SHM_ERROR_ENUM */ - -#ifndef WL_SHM_FORMAT_ENUM -#define WL_SHM_FORMAT_ENUM -/** - * @ingroup iface_wl_shm - * pixel formats - * - * This describes the memory layout of an individual pixel. - * - * All renderers should support argb8888 and xrgb8888 but any other - * formats are optional and may not be supported by the particular - * renderer in use. - * - * The drm format codes match the macros defined in drm_fourcc.h, except - * argb8888 and xrgb8888. The formats actually supported by the compositor - * will be reported by the format event. - * - * For all wl_shm formats and unless specified in another protocol - * extension, pre-multiplied alpha is used for pixel values. - */ -enum wl_shm_format { - /** - * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian - */ - WL_SHM_FORMAT_ARGB8888 = 0, - /** - * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XRGB8888 = 1, - /** - * 8-bit color index format, [7:0] C - */ - WL_SHM_FORMAT_C8 = 0x20203843, - /** - * 8-bit RGB format, [7:0] R:G:B 3:3:2 - */ - WL_SHM_FORMAT_RGB332 = 0x38424752, - /** - * 8-bit BGR format, [7:0] B:G:R 2:3:3 - */ - WL_SHM_FORMAT_BGR233 = 0x38524742, - /** - * 16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian - */ - WL_SHM_FORMAT_XRGB4444 = 0x32315258, - /** - * 16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian - */ - WL_SHM_FORMAT_XBGR4444 = 0x32314258, - /** - * 16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian - */ - WL_SHM_FORMAT_RGBX4444 = 0x32315852, - /** - * 16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian - */ - WL_SHM_FORMAT_BGRX4444 = 0x32315842, - /** - * 16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian - */ - WL_SHM_FORMAT_ARGB4444 = 0x32315241, - /** - * 16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian - */ - WL_SHM_FORMAT_ABGR4444 = 0x32314241, - /** - * 16-bit RBGA format, [15:0] R:G:B:A 4:4:4:4 little endian - */ - WL_SHM_FORMAT_RGBA4444 = 0x32314152, - /** - * 16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian - */ - WL_SHM_FORMAT_BGRA4444 = 0x32314142, - /** - * 16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian - */ - WL_SHM_FORMAT_XRGB1555 = 0x35315258, - /** - * 16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian - */ - WL_SHM_FORMAT_XBGR1555 = 0x35314258, - /** - * 16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian - */ - WL_SHM_FORMAT_RGBX5551 = 0x35315852, - /** - * 16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian - */ - WL_SHM_FORMAT_BGRX5551 = 0x35315842, - /** - * 16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian - */ - WL_SHM_FORMAT_ARGB1555 = 0x35315241, - /** - * 16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian - */ - WL_SHM_FORMAT_ABGR1555 = 0x35314241, - /** - * 16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian - */ - WL_SHM_FORMAT_RGBA5551 = 0x35314152, - /** - * 16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian - */ - WL_SHM_FORMAT_BGRA5551 = 0x35314142, - /** - * 16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian - */ - WL_SHM_FORMAT_RGB565 = 0x36314752, - /** - * 16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian - */ - WL_SHM_FORMAT_BGR565 = 0x36314742, - /** - * 24-bit RGB format, [23:0] R:G:B little endian - */ - WL_SHM_FORMAT_RGB888 = 0x34324752, - /** - * 24-bit BGR format, [23:0] B:G:R little endian - */ - WL_SHM_FORMAT_BGR888 = 0x34324742, - /** - * 32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XBGR8888 = 0x34324258, - /** - * 32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian - */ - WL_SHM_FORMAT_RGBX8888 = 0x34325852, - /** - * 32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian - */ - WL_SHM_FORMAT_BGRX8888 = 0x34325842, - /** - * 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian - */ - WL_SHM_FORMAT_ABGR8888 = 0x34324241, - /** - * 32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian - */ - WL_SHM_FORMAT_RGBA8888 = 0x34324152, - /** - * 32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian - */ - WL_SHM_FORMAT_BGRA8888 = 0x34324142, - /** - * 32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XRGB2101010 = 0x30335258, - /** - * 32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XBGR2101010 = 0x30334258, - /** - * 32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian - */ - WL_SHM_FORMAT_RGBX1010102 = 0x30335852, - /** - * 32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian - */ - WL_SHM_FORMAT_BGRX1010102 = 0x30335842, - /** - * 32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian - */ - WL_SHM_FORMAT_ARGB2101010 = 0x30335241, - /** - * 32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian - */ - WL_SHM_FORMAT_ABGR2101010 = 0x30334241, - /** - * 32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian - */ - WL_SHM_FORMAT_RGBA1010102 = 0x30334152, - /** - * 32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian - */ - WL_SHM_FORMAT_BGRA1010102 = 0x30334142, - /** - * packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_YUYV = 0x56595559, - /** - * packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_YVYU = 0x55595659, - /** - * packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_UYVY = 0x59565955, - /** - * packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_VYUY = 0x59555956, - /** - * packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian - */ - WL_SHM_FORMAT_AYUV = 0x56555941, - /** - * 2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV12 = 0x3231564e, - /** - * 2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV21 = 0x3132564e, - /** - * 2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV16 = 0x3631564e, - /** - * 2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV61 = 0x3136564e, - /** - * 3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV410 = 0x39565559, - /** - * 3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU410 = 0x39555659, - /** - * 3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV411 = 0x31315559, - /** - * 3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU411 = 0x31315659, - /** - * 3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV420 = 0x32315559, - /** - * 3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU420 = 0x32315659, - /** - * 3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV422 = 0x36315559, - /** - * 3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU422 = 0x36315659, - /** - * 3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV444 = 0x34325559, - /** - * 3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU444 = 0x34325659, - /** - * [7:0] R - */ - WL_SHM_FORMAT_R8 = 0x20203852, - /** - * [15:0] R little endian - */ - WL_SHM_FORMAT_R16 = 0x20363152, - /** - * [15:0] R:G 8:8 little endian - */ - WL_SHM_FORMAT_RG88 = 0x38384752, - /** - * [15:0] G:R 8:8 little endian - */ - WL_SHM_FORMAT_GR88 = 0x38385247, - /** - * [31:0] R:G 16:16 little endian - */ - WL_SHM_FORMAT_RG1616 = 0x32334752, - /** - * [31:0] G:R 16:16 little endian - */ - WL_SHM_FORMAT_GR1616 = 0x32335247, - /** - * [63:0] x:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XRGB16161616F = 0x48345258, - /** - * [63:0] x:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XBGR16161616F = 0x48344258, - /** - * [63:0] A:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ARGB16161616F = 0x48345241, - /** - * [63:0] A:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ABGR16161616F = 0x48344241, - /** - * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XYUV8888 = 0x56555958, - /** - * [23:0] Cr:Cb:Y 8:8:8 little endian - */ - WL_SHM_FORMAT_VUY888 = 0x34325556, - /** - * Y followed by U then V, 10:10:10. Non-linear modifier only - */ - WL_SHM_FORMAT_VUY101010 = 0x30335556, - /** - * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y210 = 0x30313259, - /** - * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y212 = 0x32313259, - /** - * [63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y216 = 0x36313259, - /** - * [31:0] A:Cr:Y:Cb 2:10:10:10 little endian - */ - WL_SHM_FORMAT_Y410 = 0x30313459, - /** - * [63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian - */ - WL_SHM_FORMAT_Y412 = 0x32313459, - /** - * [63:0] A:Cr:Y:Cb 16:16:16:16 little endian - */ - WL_SHM_FORMAT_Y416 = 0x36313459, - /** - * [31:0] X:Cr:Y:Cb 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XVYU2101010 = 0x30335658, - /** - * [63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian - */ - WL_SHM_FORMAT_XVYU12_16161616 = 0x36335658, - /** - * [63:0] X:Cr:Y:Cb 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XVYU16161616 = 0x38345658, - /** - * [63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian - */ - WL_SHM_FORMAT_Y0L0 = 0x304c3059, - /** - * [63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian - */ - WL_SHM_FORMAT_X0L0 = 0x304c3058, - /** - * [63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian - */ - WL_SHM_FORMAT_Y0L2 = 0x324c3059, - /** - * [63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian - */ - WL_SHM_FORMAT_X0L2 = 0x324c3058, - WL_SHM_FORMAT_YUV420_8BIT = 0x38305559, - WL_SHM_FORMAT_YUV420_10BIT = 0x30315559, - WL_SHM_FORMAT_XRGB8888_A8 = 0x38415258, - WL_SHM_FORMAT_XBGR8888_A8 = 0x38414258, - WL_SHM_FORMAT_RGBX8888_A8 = 0x38415852, - WL_SHM_FORMAT_BGRX8888_A8 = 0x38415842, - WL_SHM_FORMAT_RGB888_A8 = 0x38413852, - WL_SHM_FORMAT_BGR888_A8 = 0x38413842, - WL_SHM_FORMAT_RGB565_A8 = 0x38413552, - WL_SHM_FORMAT_BGR565_A8 = 0x38413542, - /** - * non-subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV24 = 0x3432564e, - /** - * non-subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV42 = 0x3234564e, - /** - * 2x1 subsampled Cr:Cb plane, 10 bit per channel - */ - WL_SHM_FORMAT_P210 = 0x30313250, - /** - * 2x2 subsampled Cr:Cb plane 10 bits per channel - */ - WL_SHM_FORMAT_P010 = 0x30313050, - /** - * 2x2 subsampled Cr:Cb plane 12 bits per channel - */ - WL_SHM_FORMAT_P012 = 0x32313050, - /** - * 2x2 subsampled Cr:Cb plane 16 bits per channel - */ - WL_SHM_FORMAT_P016 = 0x36313050, - /** - * [63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian - */ - WL_SHM_FORMAT_AXBXGXRX106106106106 = 0x30314241, - /** - * 2x2 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV15 = 0x3531564e, - WL_SHM_FORMAT_Q410 = 0x30313451, - WL_SHM_FORMAT_Q401 = 0x31303451, - /** - * [63:0] x:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XRGB16161616 = 0x38345258, - /** - * [63:0] x:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XBGR16161616 = 0x38344258, - /** - * [63:0] A:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ARGB16161616 = 0x38345241, - /** - * [63:0] A:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ABGR16161616 = 0x38344241, -}; -#endif /* WL_SHM_FORMAT_ENUM */ - -/** - * @ingroup iface_wl_shm - * @struct wl_shm_listener - */ -struct wl_shm_listener { - /** - * pixel format description - * - * Informs the client about a valid pixel format that can be used - * for buffers. Known formats include argb8888 and xrgb8888. - * @param format buffer pixel format - */ - void (*format)(void *data, - struct wl_shm *wl_shm, - uint32_t format); -}; - -/** - * @ingroup iface_wl_shm - */ -static inline int -wl_shm_add_listener(struct wl_shm *wl_shm, - const struct wl_shm_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_shm, - (void (**)(void)) listener, data); -} - -#define WL_SHM_CREATE_POOL 0 - -/** - * @ingroup iface_wl_shm - */ -#define WL_SHM_FORMAT_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_shm - */ -#define WL_SHM_CREATE_POOL_SINCE_VERSION 1 - -/** @ingroup iface_wl_shm */ -static inline void -wl_shm_set_user_data(struct wl_shm *wl_shm, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shm, user_data); -} - -/** @ingroup iface_wl_shm */ -static inline void * -wl_shm_get_user_data(struct wl_shm *wl_shm) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shm); -} - -static inline uint32_t -wl_shm_get_version(struct wl_shm *wl_shm) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shm); -} - -/** @ingroup iface_wl_shm */ -static inline void -wl_shm_destroy(struct wl_shm *wl_shm) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shm); -} - -/** - * @ingroup iface_wl_shm - * - * Create a new wl_shm_pool object. - * - * The pool can be used to create shared memory based buffer - * objects. The server will mmap size bytes of the passed file - * descriptor, to use as backing memory for the pool. - */ -static inline struct wl_shm_pool * -wl_shm_create_pool(struct wl_shm *wl_shm, int32_t fd, int32_t size) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm, - WL_SHM_CREATE_POOL, &wl_shm_pool_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm), 0, NULL, fd, size); - - return (struct wl_shm_pool *) id; -} - -/** - * @ingroup iface_wl_buffer - * @struct wl_buffer_listener - */ -struct wl_buffer_listener { - /** - * compositor releases buffer - * - * Sent when this wl_buffer is no longer used by the compositor. - * The client is now free to reuse or destroy this buffer and its - * backing storage. - * - * If a client receives a release event before the frame callback - * requested in the same wl_surface.commit that attaches this - * wl_buffer to a surface, then the client is immediately free to - * reuse the buffer and its backing storage, and does not need a - * second buffer for the next surface content update. Typically - * this is possible, when the compositor maintains a copy of the - * wl_surface contents, e.g. as a GL texture. This is an important - * optimization for GL(ES) compositors with wl_shm clients. - */ - void (*release)(void *data, - struct wl_buffer *wl_buffer); -}; - -/** - * @ingroup iface_wl_buffer - */ -static inline int -wl_buffer_add_listener(struct wl_buffer *wl_buffer, - const struct wl_buffer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_buffer, - (void (**)(void)) listener, data); -} - -#define WL_BUFFER_DESTROY 0 - -/** - * @ingroup iface_wl_buffer - */ -#define WL_BUFFER_RELEASE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_buffer - */ -#define WL_BUFFER_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_wl_buffer */ -static inline void -wl_buffer_set_user_data(struct wl_buffer *wl_buffer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_buffer, user_data); -} - -/** @ingroup iface_wl_buffer */ -static inline void * -wl_buffer_get_user_data(struct wl_buffer *wl_buffer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_buffer); -} - -static inline uint32_t -wl_buffer_get_version(struct wl_buffer *wl_buffer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_buffer); -} - -/** - * @ingroup iface_wl_buffer - * - * Destroy a buffer. If and how you need to release the backing - * storage is defined by the buffer factory interface. - * - * For possible side-effects to a surface, see wl_surface.attach. - */ -static inline void -wl_buffer_destroy(struct wl_buffer *wl_buffer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_buffer, - WL_BUFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_buffer), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_DATA_OFFER_ERROR_ENUM -#define WL_DATA_OFFER_ERROR_ENUM -enum wl_data_offer_error { - /** - * finish request was called untimely - */ - WL_DATA_OFFER_ERROR_INVALID_FINISH = 0, - /** - * action mask contains invalid values - */ - WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK = 1, - /** - * action argument has an invalid value - */ - WL_DATA_OFFER_ERROR_INVALID_ACTION = 2, - /** - * offer doesn't accept this request - */ - WL_DATA_OFFER_ERROR_INVALID_OFFER = 3, -}; -#endif /* WL_DATA_OFFER_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_offer - * @struct wl_data_offer_listener - */ -struct wl_data_offer_listener { - /** - * advertise offered mime type - * - * Sent immediately after creating the wl_data_offer object. One - * event per offered mime type. - * @param mime_type offered mime type - */ - void (*offer)(void *data, - struct wl_data_offer *wl_data_offer, - const char *mime_type); - /** - * notify the source-side available actions - * - * This event indicates the actions offered by the data source. - * It will be sent immediately after creating the wl_data_offer - * object, or anytime the source side changes its offered actions - * through wl_data_source.set_actions. - * @param source_actions actions offered by the data source - * @since 3 - */ - void (*source_actions)(void *data, - struct wl_data_offer *wl_data_offer, - uint32_t source_actions); - /** - * notify the selected action - * - * This event indicates the action selected by the compositor - * after matching the source/destination side actions. Only one - * action (or none) will be offered here. - * - * This event can be emitted multiple times during the - * drag-and-drop operation in response to destination side action - * changes through wl_data_offer.set_actions. - * - * This event will no longer be emitted after wl_data_device.drop - * happened on the drag-and-drop destination, the client must honor - * the last action received, or the last preferred one set through - * wl_data_offer.set_actions when handling an "ask" action. - * - * Compositors may also change the selected action on the fly, - * mainly in response to keyboard modifier changes during the - * drag-and-drop operation. - * - * The most recent action received is always the valid one. Prior - * to receiving wl_data_device.drop, the chosen action may change - * (e.g. due to keyboard modifiers being pressed). At the time of - * receiving wl_data_device.drop the drag-and-drop destination must - * honor the last action received. - * - * Action changes may still happen after wl_data_device.drop, - * especially on "ask" actions, where the drag-and-drop destination - * may choose another action afterwards. Action changes happening - * at this stage are always the result of inter-client negotiation, - * the compositor shall no longer be able to induce a different - * action. - * - * Upon "ask" actions, it is expected that the drag-and-drop - * destination may potentially choose a different action and/or - * mime type, based on wl_data_offer.source_actions and finally - * chosen by the user (e.g. popping up a menu with the available - * options). The final wl_data_offer.set_actions and - * wl_data_offer.accept requests must happen before the call to - * wl_data_offer.finish. - * @param dnd_action action selected by the compositor - * @since 3 - */ - void (*action)(void *data, - struct wl_data_offer *wl_data_offer, - uint32_t dnd_action); -}; - -/** - * @ingroup iface_wl_data_offer - */ -static inline int -wl_data_offer_add_listener(struct wl_data_offer *wl_data_offer, - const struct wl_data_offer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_offer, - (void (**)(void)) listener, data); -} - -#define WL_DATA_OFFER_ACCEPT 0 -#define WL_DATA_OFFER_RECEIVE 1 -#define WL_DATA_OFFER_DESTROY 2 -#define WL_DATA_OFFER_FINISH 3 -#define WL_DATA_OFFER_SET_ACTIONS 4 - -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_ACTION_SINCE_VERSION 3 - -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_ACCEPT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_RECEIVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_FINISH_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION 3 - -/** @ingroup iface_wl_data_offer */ -static inline void -wl_data_offer_set_user_data(struct wl_data_offer *wl_data_offer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_offer, user_data); -} - -/** @ingroup iface_wl_data_offer */ -static inline void * -wl_data_offer_get_user_data(struct wl_data_offer *wl_data_offer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_offer); -} - -static inline uint32_t -wl_data_offer_get_version(struct wl_data_offer *wl_data_offer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_offer); -} - -/** - * @ingroup iface_wl_data_offer - * - * Indicate that the client can accept the given mime type, or - * NULL for not accepted. - * - * For objects of version 2 or older, this request is used by the - * client to give feedback whether the client can receive the given - * mime type, or NULL if none is accepted; the feedback does not - * determine whether the drag-and-drop operation succeeds or not. - * - * For objects of version 3 or newer, this request determines the - * final result of the drag-and-drop operation. If the end result - * is that no mime types were accepted, the drag-and-drop operation - * will be cancelled and the corresponding drag source will receive - * wl_data_source.cancelled. Clients may still use this event in - * conjunction with wl_data_source.action for feedback. - */ -static inline void -wl_data_offer_accept(struct wl_data_offer *wl_data_offer, uint32_t serial, const char *mime_type) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_ACCEPT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, serial, mime_type); -} - -/** - * @ingroup iface_wl_data_offer - * - * To transfer the offered data, the client issues this request - * and indicates the mime type it wants to receive. The transfer - * happens through the passed file descriptor (typically created - * with the pipe system call). The source client writes the data - * in the mime type representation requested and then closes the - * file descriptor. - * - * The receiving client reads from the read end of the pipe until - * EOF and then closes its end, at which point the transfer is - * complete. - * - * This request may happen multiple times for different mime types, - * both before and after wl_data_device.drop. Drag-and-drop destination - * clients may preemptively fetch data or examine it more closely to - * determine acceptance. - */ -static inline void -wl_data_offer_receive(struct wl_data_offer *wl_data_offer, const char *mime_type, int32_t fd) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_RECEIVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, mime_type, fd); -} - -/** - * @ingroup iface_wl_data_offer - * - * Destroy the data offer. - */ -static inline void -wl_data_offer_destroy(struct wl_data_offer *wl_data_offer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_data_offer - * - * Notifies the compositor that the drag destination successfully - * finished the drag-and-drop operation. - * - * Upon receiving this request, the compositor will emit - * wl_data_source.dnd_finished on the drag source client. - * - * It is a client error to perform other requests than - * wl_data_offer.destroy after this one. It is also an error to perform - * this request after a NULL mime type has been set in - * wl_data_offer.accept or no action was received through - * wl_data_offer.action. - * - * If wl_data_offer.finish request is received for a non drag and drop - * operation, the invalid_finish protocol error is raised. - */ -static inline void -wl_data_offer_finish(struct wl_data_offer *wl_data_offer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_FINISH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0); -} - -/** - * @ingroup iface_wl_data_offer - * - * Sets the actions that the destination side client supports for - * this operation. This request may trigger the emission of - * wl_data_source.action and wl_data_offer.action events if the compositor - * needs to change the selected action. - * - * This request can be called multiple times throughout the - * drag-and-drop operation, typically in response to wl_data_device.enter - * or wl_data_device.motion events. - * - * This request determines the final result of the drag-and-drop - * operation. If the end result is that no action is accepted, - * the drag source will receive wl_data_source.cancelled. - * - * The dnd_actions argument must contain only values expressed in the - * wl_data_device_manager.dnd_actions enum, and the preferred_action - * argument must only contain one of those values set, otherwise it - * will result in a protocol error. - * - * While managing an "ask" action, the destination drag-and-drop client - * may perform further wl_data_offer.receive requests, and is expected - * to perform one last wl_data_offer.set_actions request with a preferred - * action other than "ask" (and optionally wl_data_offer.accept) before - * requesting wl_data_offer.finish, in order to convey the action selected - * by the user. If the preferred action is not in the - * wl_data_offer.source_actions mask, an error will be raised. - * - * If the "ask" action is dismissed (e.g. user cancellation), the client - * is expected to perform wl_data_offer.destroy right away. - * - * This request can only be made on drag-and-drop offers, a protocol error - * will be raised otherwise. - */ -static inline void -wl_data_offer_set_actions(struct wl_data_offer *wl_data_offer, uint32_t dnd_actions, uint32_t preferred_action) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, dnd_actions, preferred_action); -} - -#ifndef WL_DATA_SOURCE_ERROR_ENUM -#define WL_DATA_SOURCE_ERROR_ENUM -enum wl_data_source_error { - /** - * action mask contains invalid values - */ - WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK = 0, - /** - * source doesn't accept this request - */ - WL_DATA_SOURCE_ERROR_INVALID_SOURCE = 1, -}; -#endif /* WL_DATA_SOURCE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_source - * @struct wl_data_source_listener - */ -struct wl_data_source_listener { - /** - * a target accepts an offered mime type - * - * Sent when a target accepts pointer_focus or motion events. If - * a target does not accept any of the offered types, type is NULL. - * - * Used for feedback during drag-and-drop. - * @param mime_type mime type accepted by the target - */ - void (*target)(void *data, - struct wl_data_source *wl_data_source, - const char *mime_type); - /** - * send the data - * - * Request for data from the client. Send the data as the - * specified mime type over the passed file descriptor, then close - * it. - * @param mime_type mime type for the data - * @param fd file descriptor for the data - */ - void (*send)(void *data, - struct wl_data_source *wl_data_source, - const char *mime_type, - int32_t fd); - /** - * selection was cancelled - * - * This data source is no longer valid. There are several reasons - * why this could happen: - * - * - The data source has been replaced by another data source. - - * The drag-and-drop operation was performed, but the drop - * destination did not accept any of the mime types offered through - * wl_data_source.target. - The drag-and-drop operation was - * performed, but the drop destination did not select any of the - * actions present in the mask offered through - * wl_data_source.action. - The drag-and-drop operation was - * performed but didn't happen over a surface. - The compositor - * cancelled the drag-and-drop operation (e.g. compositor dependent - * timeouts to avoid stale drag-and-drop transfers). - * - * The client should clean up and destroy this data source. - * - * For objects of version 2 or older, wl_data_source.cancelled will - * only be emitted if the data source was replaced by another data - * source. - */ - void (*cancelled)(void *data, - struct wl_data_source *wl_data_source); - /** - * the drag-and-drop operation physically finished - * - * The user performed the drop action. This event does not - * indicate acceptance, wl_data_source.cancelled may still be - * emitted afterwards if the drop destination does not accept any - * mime type. - * - * However, this event might however not be received if the - * compositor cancelled the drag-and-drop operation before this - * event could happen. - * - * Note that the data_source may still be used in the future and - * should not be destroyed here. - * @since 3 - */ - void (*dnd_drop_performed)(void *data, - struct wl_data_source *wl_data_source); - /** - * the drag-and-drop operation concluded - * - * The drop destination finished interoperating with this data - * source, so the client is now free to destroy this data source - * and free all associated data. - * - * If the action used to perform the operation was "move", the - * source can now delete the transferred data. - * @since 3 - */ - void (*dnd_finished)(void *data, - struct wl_data_source *wl_data_source); - /** - * notify the selected action - * - * This event indicates the action selected by the compositor - * after matching the source/destination side actions. Only one - * action (or none) will be offered here. - * - * This event can be emitted multiple times during the - * drag-and-drop operation, mainly in response to destination side - * changes through wl_data_offer.set_actions, and as the data - * device enters/leaves surfaces. - * - * It is only possible to receive this event after - * wl_data_source.dnd_drop_performed if the drag-and-drop operation - * ended in an "ask" action, in which case the final - * wl_data_source.action event will happen immediately before - * wl_data_source.dnd_finished. - * - * Compositors may also change the selected action on the fly, - * mainly in response to keyboard modifier changes during the - * drag-and-drop operation. - * - * The most recent action received is always the valid one. The - * chosen action may change alongside negotiation (e.g. an "ask" - * action can turn into a "move" operation), so the effects of the - * final action must always be applied in - * wl_data_offer.dnd_finished. - * - * Clients can trigger cursor surface changes from this point, so - * they reflect the current action. - * @param dnd_action action selected by the compositor - * @since 3 - */ - void (*action)(void *data, - struct wl_data_source *wl_data_source, - uint32_t dnd_action); -}; - -/** - * @ingroup iface_wl_data_source - */ -static inline int -wl_data_source_add_listener(struct wl_data_source *wl_data_source, - const struct wl_data_source_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_source, - (void (**)(void)) listener, data); -} - -#define WL_DATA_SOURCE_OFFER 0 -#define WL_DATA_SOURCE_DESTROY 1 -#define WL_DATA_SOURCE_SET_ACTIONS 2 - -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_TARGET_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_SEND_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_CANCELLED_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_ACTION_SINCE_VERSION 3 - -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION 3 - -/** @ingroup iface_wl_data_source */ -static inline void -wl_data_source_set_user_data(struct wl_data_source *wl_data_source, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_source, user_data); -} - -/** @ingroup iface_wl_data_source */ -static inline void * -wl_data_source_get_user_data(struct wl_data_source *wl_data_source) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_source); -} - -static inline uint32_t -wl_data_source_get_version(struct wl_data_source *wl_data_source) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_source); -} - -/** - * @ingroup iface_wl_data_source - * - * This request adds a mime type to the set of mime types - * advertised to targets. Can be called several times to offer - * multiple types. - */ -static inline void -wl_data_source_offer(struct wl_data_source *wl_data_source, const char *mime_type) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_OFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, mime_type); -} - -/** - * @ingroup iface_wl_data_source - * - * Destroy the data source. - */ -static inline void -wl_data_source_destroy(struct wl_data_source *wl_data_source) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_data_source - * - * Sets the actions that the source side client supports for this - * operation. This request may trigger wl_data_source.action and - * wl_data_offer.action events if the compositor needs to change the - * selected action. - * - * The dnd_actions argument must contain only values expressed in the - * wl_data_device_manager.dnd_actions enum, otherwise it will result - * in a protocol error. - * - * This request must be made once only, and can only be made on sources - * used in drag-and-drop, so it must be performed before - * wl_data_device.start_drag. Attempting to use the source other than - * for drag-and-drop will raise a protocol error. - */ -static inline void -wl_data_source_set_actions(struct wl_data_source *wl_data_source, uint32_t dnd_actions) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, dnd_actions); -} - -#ifndef WL_DATA_DEVICE_ERROR_ENUM -#define WL_DATA_DEVICE_ERROR_ENUM -enum wl_data_device_error { - /** - * given wl_surface has another role - */ - WL_DATA_DEVICE_ERROR_ROLE = 0, -}; -#endif /* WL_DATA_DEVICE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_device - * @struct wl_data_device_listener - */ -struct wl_data_device_listener { - /** - * introduce a new wl_data_offer - * - * The data_offer event introduces a new wl_data_offer object, - * which will subsequently be used in either the data_device.enter - * event (for drag-and-drop) or the data_device.selection event - * (for selections). Immediately following the - * data_device.data_offer event, the new data_offer object will - * send out data_offer.offer events to describe the mime types it - * offers. - * @param id the new data_offer object - */ - void (*data_offer)(void *data, - struct wl_data_device *wl_data_device, - struct wl_data_offer *id); - /** - * initiate drag-and-drop session - * - * This event is sent when an active drag-and-drop pointer enters - * a surface owned by the client. The position of the pointer at - * enter time is provided by the x and y arguments, in - * surface-local coordinates. - * @param serial serial number of the enter event - * @param surface client surface entered - * @param x surface-local x coordinate - * @param y surface-local y coordinate - * @param id source data_offer object - */ - void (*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); - /** - * end drag-and-drop session - * - * This event is sent when the drag-and-drop pointer leaves the - * surface and the session ends. The client must destroy the - * wl_data_offer introduced at enter time at this point. - */ - void (*leave)(void *data, - struct wl_data_device *wl_data_device); - /** - * drag-and-drop session motion - * - * This event is sent when the drag-and-drop pointer moves within - * the currently focused surface. The new position of the pointer - * is provided by the x and y arguments, in surface-local - * coordinates. - * @param time timestamp with millisecond granularity - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_data_device *wl_data_device, - uint32_t time, - wl_fixed_t x, - wl_fixed_t y); - /** - * end drag-and-drop session successfully - * - * The event is sent when a drag-and-drop operation is ended - * because the implicit grab is removed. - * - * The drag-and-drop destination is expected to honor the last - * action received through wl_data_offer.action, if the resulting - * action is "copy" or "move", the destination can still perform - * wl_data_offer.receive requests, and is expected to end all - * transfers with a wl_data_offer.finish request. - * - * If the resulting action is "ask", the action will not be - * considered final. The drag-and-drop destination is expected to - * perform one last wl_data_offer.set_actions request, or - * wl_data_offer.destroy in order to cancel the operation. - */ - void (*drop)(void *data, - struct wl_data_device *wl_data_device); - /** - * advertise new selection - * - * The selection event is sent out to notify the client of a new - * wl_data_offer for the selection for this device. The - * data_device.data_offer and the data_offer.offer events are sent - * out immediately before this event to introduce the data offer - * object. The selection event is sent to a client immediately - * before receiving keyboard focus and when a new selection is set - * while the client has keyboard focus. The data_offer is valid - * until a new data_offer or NULL is received or until the client - * loses keyboard focus. Switching surface with keyboard focus - * within the same client doesn't mean a new selection will be - * sent. The client must destroy the previous selection data_offer, - * if any, upon receiving this event. - * @param id selection data_offer object - */ - void (*selection)(void *data, - struct wl_data_device *wl_data_device, - struct wl_data_offer *id); -}; - -/** - * @ingroup iface_wl_data_device - */ -static inline int -wl_data_device_add_listener(struct wl_data_device *wl_data_device, - const struct wl_data_device_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_device, - (void (**)(void)) listener, data); -} - -#define WL_DATA_DEVICE_START_DRAG 0 -#define WL_DATA_DEVICE_SET_SELECTION 1 -#define WL_DATA_DEVICE_RELEASE 2 - -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_DATA_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_DROP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_SELECTION_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_START_DRAG_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_SET_SELECTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_RELEASE_SINCE_VERSION 2 - -/** @ingroup iface_wl_data_device */ -static inline void -wl_data_device_set_user_data(struct wl_data_device *wl_data_device, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_device, user_data); -} - -/** @ingroup iface_wl_data_device */ -static inline void * -wl_data_device_get_user_data(struct wl_data_device *wl_data_device) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device); -} - -static inline uint32_t -wl_data_device_get_version(struct wl_data_device *wl_data_device) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_device); -} - -/** @ingroup iface_wl_data_device */ -static inline void -wl_data_device_destroy(struct wl_data_device *wl_data_device) -{ - wl_proxy_destroy((struct wl_proxy *) wl_data_device); -} - -/** - * @ingroup iface_wl_data_device - * - * This request asks the compositor to start a drag-and-drop - * operation on behalf of the client. - * - * The source argument is the data source that provides the data - * for the eventual data transfer. If source is NULL, enter, leave - * and motion events are sent only to the client that initiated the - * drag and the client is expected to handle the data passing - * internally. If source is destroyed, the drag-and-drop session will be - * cancelled. - * - * The origin surface is the surface where the drag originates and - * the client must have an active implicit grab that matches the - * serial. - * - * The icon surface is an optional (can be NULL) surface that - * provides an icon to be moved around with the cursor. Initially, - * the top-left corner of the icon surface is placed at the cursor - * hotspot, but subsequent wl_surface.attach request can move the - * relative position. Attach requests must be confirmed with - * wl_surface.commit as usual. The icon surface is given the role of - * a drag-and-drop icon. If the icon surface already has another role, - * it raises a protocol error. - * - * The input region is ignored for wl_surfaces with the role of a - * drag-and-drop icon. - */ -static inline void -wl_data_device_start_drag(struct wl_data_device *wl_data_device, struct wl_data_source *source, struct wl_surface *origin, struct wl_surface *icon, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_START_DRAG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, origin, icon, serial); -} - -/** - * @ingroup iface_wl_data_device - * - * This request asks the compositor to set the selection - * to the data from the source on behalf of the client. - * - * To unset the selection, set the source to NULL. - */ -static inline void -wl_data_device_set_selection(struct wl_data_device *wl_data_device, struct wl_data_source *source, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_SET_SELECTION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, serial); -} - -/** - * @ingroup iface_wl_data_device - * - * This request destroys the data device. - */ -static inline void -wl_data_device_release(struct wl_data_device *wl_data_device) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM -#define WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM -/** - * @ingroup iface_wl_data_device_manager - * drag and drop actions - * - * This is a bitmask of the available/preferred actions in a - * drag-and-drop operation. - * - * In the compositor, the selected action is a result of matching the - * actions offered by the source and destination sides. "action" events - * with a "none" action will be sent to both source and destination if - * there is no match. All further checks will effectively happen on - * (source actions ∩ destination actions). - * - * In addition, compositors may also pick different actions in - * reaction to key modifiers being pressed. One common design that - * is used in major toolkits (and the behavior recommended for - * compositors) is: - * - * - If no modifiers are pressed, the first match (in bit order) - * will be used. - * - Pressing Shift selects "move", if enabled in the mask. - * - Pressing Control selects "copy", if enabled in the mask. - * - * Behavior beyond that is considered implementation-dependent. - * Compositors may for example bind other modifiers (like Alt/Meta) - * or drags initiated with other buttons than BTN_LEFT to specific - * actions (e.g. "ask"). - */ -enum wl_data_device_manager_dnd_action { - /** - * no action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0, - /** - * copy action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1, - /** - * move action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2, - /** - * ask action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4, -}; -#endif /* WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM */ - -#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE 0 -#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE 1 - - -/** - * @ingroup iface_wl_data_device_manager - */ -#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device_manager - */ -#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE_SINCE_VERSION 1 - -/** @ingroup iface_wl_data_device_manager */ -static inline void -wl_data_device_manager_set_user_data(struct wl_data_device_manager *wl_data_device_manager, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_device_manager, user_data); -} - -/** @ingroup iface_wl_data_device_manager */ -static inline void * -wl_data_device_manager_get_user_data(struct wl_data_device_manager *wl_data_device_manager) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device_manager); -} - -static inline uint32_t -wl_data_device_manager_get_version(struct wl_data_device_manager *wl_data_device_manager) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager); -} - -/** @ingroup iface_wl_data_device_manager */ -static inline void -wl_data_device_manager_destroy(struct wl_data_device_manager *wl_data_device_manager) -{ - wl_proxy_destroy((struct wl_proxy *) wl_data_device_manager); -} - -/** - * @ingroup iface_wl_data_device_manager - * - * Create a new data source. - */ -static inline struct wl_data_source * -wl_data_device_manager_create_data_source(struct wl_data_device_manager *wl_data_device_manager) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, - WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, &wl_data_source_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL); - - return (struct wl_data_source *) id; -} - -/** - * @ingroup iface_wl_data_device_manager - * - * Create a new data device for a given seat. - */ -static inline struct wl_data_device * -wl_data_device_manager_get_data_device(struct wl_data_device_manager *wl_data_device_manager, struct wl_seat *seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, - WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, &wl_data_device_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL, seat); - - return (struct wl_data_device *) id; -} - -#ifndef WL_SHELL_ERROR_ENUM -#define WL_SHELL_ERROR_ENUM -enum wl_shell_error { - /** - * given wl_surface has another role - */ - WL_SHELL_ERROR_ROLE = 0, -}; -#endif /* WL_SHELL_ERROR_ENUM */ - -#define WL_SHELL_GET_SHELL_SURFACE 0 - - -/** - * @ingroup iface_wl_shell - */ -#define WL_SHELL_GET_SHELL_SURFACE_SINCE_VERSION 1 - -/** @ingroup iface_wl_shell */ -static inline void -wl_shell_set_user_data(struct wl_shell *wl_shell, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shell, user_data); -} - -/** @ingroup iface_wl_shell */ -static inline void * -wl_shell_get_user_data(struct wl_shell *wl_shell) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shell); -} - -static inline uint32_t -wl_shell_get_version(struct wl_shell *wl_shell) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shell); -} - -/** @ingroup iface_wl_shell */ -static inline void -wl_shell_destroy(struct wl_shell *wl_shell) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shell); -} - -/** - * @ingroup iface_wl_shell - * - * Create a shell surface for an existing surface. This gives - * the wl_surface the role of a shell surface. If the wl_surface - * already has another role, it raises a protocol error. - * - * Only one shell surface can be associated with a given surface. - */ -static inline struct wl_shell_surface * -wl_shell_get_shell_surface(struct wl_shell *wl_shell, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shell, - WL_SHELL_GET_SHELL_SURFACE, &wl_shell_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_shell), 0, NULL, surface); - - return (struct wl_shell_surface *) id; -} - -#ifndef WL_SHELL_SURFACE_RESIZE_ENUM -#define WL_SHELL_SURFACE_RESIZE_ENUM -/** - * @ingroup iface_wl_shell_surface - * edge values for resizing - * - * These values are used to indicate which edge of a surface - * is being dragged in a resize operation. The server may - * use this information to adapt its behavior, e.g. choose - * an appropriate cursor image. - */ -enum wl_shell_surface_resize { - /** - * no edge - */ - WL_SHELL_SURFACE_RESIZE_NONE = 0, - /** - * top edge - */ - WL_SHELL_SURFACE_RESIZE_TOP = 1, - /** - * bottom edge - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM = 2, - /** - * left edge - */ - WL_SHELL_SURFACE_RESIZE_LEFT = 4, - /** - * top and left edges - */ - WL_SHELL_SURFACE_RESIZE_TOP_LEFT = 5, - /** - * bottom and left edges - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT = 6, - /** - * right edge - */ - WL_SHELL_SURFACE_RESIZE_RIGHT = 8, - /** - * top and right edges - */ - WL_SHELL_SURFACE_RESIZE_TOP_RIGHT = 9, - /** - * bottom and right edges - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT = 10, -}; -#endif /* WL_SHELL_SURFACE_RESIZE_ENUM */ - -#ifndef WL_SHELL_SURFACE_TRANSIENT_ENUM -#define WL_SHELL_SURFACE_TRANSIENT_ENUM -/** - * @ingroup iface_wl_shell_surface - * details of transient behaviour - * - * These flags specify details of the expected behaviour - * of transient surfaces. Used in the set_transient request. - */ -enum wl_shell_surface_transient { - /** - * do not set keyboard focus - */ - WL_SHELL_SURFACE_TRANSIENT_INACTIVE = 0x1, -}; -#endif /* WL_SHELL_SURFACE_TRANSIENT_ENUM */ - -#ifndef WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM -#define WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM -/** - * @ingroup iface_wl_shell_surface - * different method to set the surface fullscreen - * - * Hints to indicate to the compositor how to deal with a conflict - * between the dimensions of the surface and the dimensions of the - * output. The compositor is free to ignore this parameter. - */ -enum wl_shell_surface_fullscreen_method { - /** - * no preference, apply default policy - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT = 0, - /** - * scale, preserve the surface's aspect ratio and center on output - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE = 1, - /** - * switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER = 2, - /** - * no upscaling, center on output and add black borders to compensate size mismatch - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL = 3, -}; -#endif /* WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM */ - -/** - * @ingroup iface_wl_shell_surface - * @struct wl_shell_surface_listener - */ -struct wl_shell_surface_listener { - /** - * ping client - * - * Ping a client to check if it is receiving events and sending - * requests. A client is expected to reply with a pong request. - * @param serial serial number of the ping - */ - void (*ping)(void *data, - struct wl_shell_surface *wl_shell_surface, - uint32_t serial); - /** - * suggest resize - * - * The configure event asks the client to resize its surface. - * - * The size is a hint, in the sense that the client is free to - * ignore it if it doesn't resize, pick a smaller size (to satisfy - * aspect ratio or resize in steps of NxM pixels). - * - * The edges parameter provides a hint about how the surface was - * resized. The client may use this information to decide how to - * adjust its content to the new size (e.g. a scrolling area might - * adjust its content position to leave the viewable content - * unmoved). - * - * The client is free to dismiss all but the last configure event - * it received. - * - * The width and height arguments specify the size of the window in - * surface-local coordinates. - * @param edges how the surface was resized - * @param width new width of the surface - * @param height new height of the surface - */ - void (*configure)(void *data, - struct wl_shell_surface *wl_shell_surface, - uint32_t edges, - int32_t width, - int32_t height); - /** - * popup interaction is done - * - * The popup_done event is sent out when a popup grab is broken, - * that is, when the user clicks a surface that doesn't belong to - * the client owning the popup surface. - */ - void (*popup_done)(void *data, - struct wl_shell_surface *wl_shell_surface); -}; - -/** - * @ingroup iface_wl_shell_surface - */ -static inline int -wl_shell_surface_add_listener(struct wl_shell_surface *wl_shell_surface, - const struct wl_shell_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_shell_surface, - (void (**)(void)) listener, data); -} - -#define WL_SHELL_SURFACE_PONG 0 -#define WL_SHELL_SURFACE_MOVE 1 -#define WL_SHELL_SURFACE_RESIZE 2 -#define WL_SHELL_SURFACE_SET_TOPLEVEL 3 -#define WL_SHELL_SURFACE_SET_TRANSIENT 4 -#define WL_SHELL_SURFACE_SET_FULLSCREEN 5 -#define WL_SHELL_SURFACE_SET_POPUP 6 -#define WL_SHELL_SURFACE_SET_MAXIMIZED 7 -#define WL_SHELL_SURFACE_SET_TITLE 8 -#define WL_SHELL_SURFACE_SET_CLASS 9 - -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_PING_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_POPUP_DONE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_PONG_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_MOVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_RESIZE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TOPLEVEL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TRANSIENT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TITLE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_CLASS_SINCE_VERSION 1 - -/** @ingroup iface_wl_shell_surface */ -static inline void -wl_shell_surface_set_user_data(struct wl_shell_surface *wl_shell_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shell_surface, user_data); -} - -/** @ingroup iface_wl_shell_surface */ -static inline void * -wl_shell_surface_get_user_data(struct wl_shell_surface *wl_shell_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shell_surface); -} - -static inline uint32_t -wl_shell_surface_get_version(struct wl_shell_surface *wl_shell_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shell_surface); -} - -/** @ingroup iface_wl_shell_surface */ -static inline void -wl_shell_surface_destroy(struct wl_shell_surface *wl_shell_surface) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shell_surface); -} - -/** - * @ingroup iface_wl_shell_surface - * - * A client must respond to a ping event with a pong request or - * the client may be deemed unresponsive. - */ -static inline void -wl_shell_surface_pong(struct wl_shell_surface *wl_shell_surface, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, serial); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Start a pointer-driven move of the surface. - * - * This request must be used in response to a button press event. - * The server may ignore move requests depending on the state of - * the surface (e.g. fullscreen or maximized). - */ -static inline void -wl_shell_surface_move(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Start a pointer-driven resizing of the surface. - * - * This request must be used in response to a button press event. - * The server may ignore resize requests depending on the state of - * the surface (e.g. fullscreen or maximized). - */ -static inline void -wl_shell_surface_resize(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, uint32_t edges) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, edges); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a toplevel surface. - * - * A toplevel surface is not fullscreen, maximized or transient. - */ -static inline void -wl_shell_surface_set_toplevel(struct wl_shell_surface *wl_shell_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TOPLEVEL, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface relative to an existing surface. - * - * The x and y arguments specify the location of the upper left - * corner of the surface relative to the upper left corner of the - * parent surface, in surface-local coordinates. - * - * The flags argument controls details of the transient behaviour. - */ -static inline void -wl_shell_surface_set_transient(struct wl_shell_surface *wl_shell_surface, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TRANSIENT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, parent, x, y, flags); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a fullscreen surface. - * - * If an output parameter is given then the surface will be made - * fullscreen on that output. If the client does not specify the - * output then the compositor will apply its policy - usually - * choosing the output on which the surface has the biggest surface - * area. - * - * The client may specify a method to resolve a size conflict - * between the output size and the surface size - this is provided - * through the method parameter. - * - * The framerate parameter is used only when the method is set - * to "driver", to indicate the preferred framerate. A value of 0 - * indicates that the client does not care about framerate. The - * framerate is specified in mHz, that is framerate of 60000 is 60Hz. - * - * A method of "scale" or "driver" implies a scaling operation of - * the surface, either via a direct scaling operation or a change of - * the output mode. This will override any kind of output scaling, so - * that mapping a surface with a buffer size equal to the mode can - * fill the screen independent of buffer_scale. - * - * A method of "fill" means we don't scale up the buffer, however - * any output scale is applied. This means that you may run into - * an edge case where the application maps a buffer with the same - * size of the output mode but buffer_scale 1 (thus making a - * surface larger than the output). In this case it is allowed to - * downscale the results to fit the screen. - * - * The compositor must reply to this request with a configure event - * with the dimensions for the output on which the surface will - * be made fullscreen. - */ -static inline void -wl_shell_surface_set_fullscreen(struct wl_shell_surface *wl_shell_surface, uint32_t method, uint32_t framerate, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, method, framerate, output); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a popup. - * - * A popup surface is a transient surface with an added pointer - * grab. - * - * An existing implicit grab will be changed to owner-events mode, - * and the popup grab will continue after the implicit grab ends - * (i.e. releasing the mouse button does not cause the popup to - * be unmapped). - * - * The popup grab continues until the window is destroyed or a - * mouse button is pressed in any other client's window. A click - * in any of the client's surfaces is reported as normal, however, - * clicks in other clients' surfaces will be discarded and trigger - * the callback. - * - * The x and y arguments specify the location of the upper left - * corner of the surface relative to the upper left corner of the - * parent surface, in surface-local coordinates. - */ -static inline void -wl_shell_surface_set_popup(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_POPUP, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, parent, x, y, flags); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a maximized surface. - * - * If an output parameter is given then the surface will be - * maximized on that output. If the client does not specify the - * output then the compositor will apply its policy - usually - * choosing the output on which the surface has the biggest surface - * area. - * - * The compositor will reply with a configure event telling - * the expected new surface size. The operation is completed - * on the next buffer attach to this surface. - * - * A maximized surface typically fills the entire output it is - * bound to, except for desktop elements such as panels. This is - * the main difference between a maximized shell surface and a - * fullscreen shell surface. - * - * The details depend on the compositor implementation. - */ -static inline void -wl_shell_surface_set_maximized(struct wl_shell_surface *wl_shell_surface, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, output); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Set a short title for the surface. - * - * This string may be used to identify the surface in a task bar, - * window list, or other user interface elements provided by the - * compositor. - * - * The string must be encoded in UTF-8. - */ -static inline void -wl_shell_surface_set_title(struct wl_shell_surface *wl_shell_surface, const char *title) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, title); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Set a class for the surface. - * - * The surface class identifies the general class of applications - * to which the surface belongs. A common convention is to use the - * file name (or the full path if it is a non-standard location) of - * the application's .desktop file as the class. - */ -static inline void -wl_shell_surface_set_class(struct wl_shell_surface *wl_shell_surface, const char *class_) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_CLASS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, class_); -} - -#ifndef WL_SURFACE_ERROR_ENUM -#define WL_SURFACE_ERROR_ENUM -/** - * @ingroup iface_wl_surface - * wl_surface error values - * - * These errors can be emitted in response to wl_surface requests. - */ -enum wl_surface_error { - /** - * buffer scale value is invalid - */ - WL_SURFACE_ERROR_INVALID_SCALE = 0, - /** - * buffer transform value is invalid - */ - WL_SURFACE_ERROR_INVALID_TRANSFORM = 1, - /** - * buffer size is invalid - */ - WL_SURFACE_ERROR_INVALID_SIZE = 2, - /** - * buffer offset is invalid - */ - WL_SURFACE_ERROR_INVALID_OFFSET = 3, - /** - * surface was destroyed before its role object - */ - WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 4, -}; -#endif /* WL_SURFACE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_surface - * @struct wl_surface_listener - */ -struct wl_surface_listener { - /** - * surface enters an output - * - * This is emitted whenever a surface's creation, movement, or - * resizing results in some part of it being within the scanout - * region of an output. - * - * Note that a surface may be overlapping with zero or more - * outputs. - * @param output output entered by the surface - */ - void (*enter)(void *data, - struct wl_surface *wl_surface, - struct wl_output *output); - /** - * surface leaves an output - * - * This is emitted whenever a surface's creation, movement, or - * resizing results in it no longer having any part of it within - * the scanout region of an output. - * - * Clients should not use the number of outputs the surface is on - * for frame throttling purposes. The surface might be hidden even - * if no leave event has been sent, and the compositor might expect - * new surface content updates even if no enter event has been - * sent. The frame event should be used instead. - * @param output output left by the surface - */ - void (*leave)(void *data, - struct wl_surface *wl_surface, - struct wl_output *output); - /** - * preferred buffer scale for the surface - * - * This event indicates the preferred buffer scale for this - * surface. It is sent whenever the compositor's preference - * changes. - * - * It is intended that scaling aware clients use this event to - * scale their content and use wl_surface.set_buffer_scale to - * indicate the scale they have rendered with. This allows clients - * to supply a higher detail buffer. - * @param factor preferred scaling factor - * @since 6 - */ - void (*preferred_buffer_scale)(void *data, - struct wl_surface *wl_surface, - int32_t factor); - /** - * preferred buffer transform for the surface - * - * This event indicates the preferred buffer transform for this - * surface. It is sent whenever the compositor's preference - * changes. - * - * It is intended that transform aware clients use this event to - * apply the transform to their content and use - * wl_surface.set_buffer_transform to indicate the transform they - * have rendered with. - * @param transform preferred transform - * @since 6 - */ - void (*preferred_buffer_transform)(void *data, - struct wl_surface *wl_surface, - uint32_t transform); -}; - -/** - * @ingroup iface_wl_surface - */ -static inline int -wl_surface_add_listener(struct wl_surface *wl_surface, - const struct wl_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_surface, - (void (**)(void)) listener, data); -} - -#define WL_SURFACE_DESTROY 0 -#define WL_SURFACE_ATTACH 1 -#define WL_SURFACE_DAMAGE 2 -#define WL_SURFACE_FRAME 3 -#define WL_SURFACE_SET_OPAQUE_REGION 4 -#define WL_SURFACE_SET_INPUT_REGION 5 -#define WL_SURFACE_COMMIT 6 -#define WL_SURFACE_SET_BUFFER_TRANSFORM 7 -#define WL_SURFACE_SET_BUFFER_SCALE 8 -#define WL_SURFACE_DAMAGE_BUFFER 9 -#define WL_SURFACE_OFFSET 10 - -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION 6 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION 6 - -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_ATTACH_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DAMAGE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_FRAME_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_OPAQUE_REGION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_INPUT_REGION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_COMMIT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_BUFFER_TRANSFORM_SINCE_VERSION 2 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION 3 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION 4 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_OFFSET_SINCE_VERSION 5 - -/** @ingroup iface_wl_surface */ -static inline void -wl_surface_set_user_data(struct wl_surface *wl_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_surface, user_data); -} - -/** @ingroup iface_wl_surface */ -static inline void * -wl_surface_get_user_data(struct wl_surface *wl_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_surface); -} - -static inline uint32_t -wl_surface_get_version(struct wl_surface *wl_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_surface); -} - -/** - * @ingroup iface_wl_surface - * - * Deletes the surface and invalidates its object ID. - */ -static inline void -wl_surface_destroy(struct wl_surface *wl_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_surface - * - * Set a buffer as the content of this surface. - * - * The new size of the surface is calculated based on the buffer - * size transformed by the inverse buffer_transform and the - * inverse buffer_scale. This means that at commit time the supplied - * buffer size must be an integer multiple of the buffer_scale. If - * that's not the case, an invalid_size error is sent. - * - * The x and y arguments specify the location of the new pending - * buffer's upper left corner, relative to the current buffer's upper - * left corner, in surface-local coordinates. In other words, the - * x and y, combined with the new surface size define in which - * directions the surface's size changes. Setting anything other than 0 - * as x and y arguments is discouraged, and should instead be replaced - * with using the separate wl_surface.offset request. - * - * When the bound wl_surface version is 5 or higher, passing any - * non-zero x or y is a protocol violation, and will result in an - * 'invalid_offset' error being raised. The x and y arguments are ignored - * and do not change the pending state. To achieve equivalent semantics, - * use wl_surface.offset. - * - * Surface contents are double-buffered state, see wl_surface.commit. - * - * The initial surface contents are void; there is no content. - * wl_surface.attach assigns the given wl_buffer as the pending - * wl_buffer. wl_surface.commit makes the pending wl_buffer the new - * surface contents, and the size of the surface becomes the size - * calculated from the wl_buffer, as described above. After commit, - * there is no pending buffer until the next attach. - * - * Committing a pending wl_buffer allows the compositor to read the - * pixels in the wl_buffer. The compositor may access the pixels at - * any time after the wl_surface.commit request. When the compositor - * will not access the pixels anymore, it will send the - * wl_buffer.release event. Only after receiving wl_buffer.release, - * the client may reuse the wl_buffer. A wl_buffer that has been - * attached and then replaced by another attach instead of committed - * will not receive a release event, and is not used by the - * compositor. - * - * If a pending wl_buffer has been committed to more than one wl_surface, - * the delivery of wl_buffer.release events becomes undefined. A well - * behaved client should not rely on wl_buffer.release events in this - * case. Alternatively, a client could create multiple wl_buffer objects - * from the same backing storage or use wp_linux_buffer_release. - * - * Destroying the wl_buffer after wl_buffer.release does not change - * the surface contents. Destroying the wl_buffer before wl_buffer.release - * is allowed as long as the underlying buffer storage isn't re-used (this - * can happen e.g. on client process termination). However, if the client - * destroys the wl_buffer before receiving the wl_buffer.release event and - * mutates the underlying buffer storage, the surface contents become - * undefined immediately. - * - * If wl_surface.attach is sent with a NULL wl_buffer, the - * following wl_surface.commit will remove the surface content. - */ -static inline void -wl_surface_attach(struct wl_surface *wl_surface, struct wl_buffer *buffer, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_ATTACH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, buffer, x, y); -} - -/** - * @ingroup iface_wl_surface - * - * This request is used to describe the regions where the pending - * buffer is different from the current surface contents, and where - * the surface therefore needs to be repainted. The compositor - * ignores the parts of the damage that fall outside of the surface. - * - * Damage is double-buffered state, see wl_surface.commit. - * - * The damage rectangle is specified in surface-local coordinates, - * where x and y specify the upper left corner of the damage rectangle. - * - * The initial value for pending damage is empty: no damage. - * wl_surface.damage adds pending damage: the new pending damage - * is the union of old pending damage and the given rectangle. - * - * wl_surface.commit assigns pending damage as the current damage, - * and clears pending damage. The server will clear the current - * damage as it repaints the surface. - * - * Note! New clients should not use this request. Instead damage can be - * posted with wl_surface.damage_buffer which uses buffer coordinates - * instead of surface coordinates. - */ -static inline void -wl_surface_damage(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DAMAGE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_surface - * - * Request a notification when it is a good time to start drawing a new - * frame, by creating a frame callback. This is useful for throttling - * redrawing operations, and driving animations. - * - * When a client is animating on a wl_surface, it can use the 'frame' - * request to get notified when it is a good time to draw and commit the - * next frame of animation. If the client commits an update earlier than - * that, it is likely that some updates will not make it to the display, - * and the client is wasting resources by drawing too often. - * - * The frame request will take effect on the next wl_surface.commit. - * The notification will only be posted for one frame unless - * requested again. For a wl_surface, the notifications are posted in - * the order the frame requests were committed. - * - * The server must send the notifications so that a client - * will not send excessive updates, while still allowing - * the highest possible update rate for clients that wait for the reply - * before drawing again. The server should give some time for the client - * to draw and commit after sending the frame callback events to let it - * hit the next output refresh. - * - * A server should avoid signaling the frame callbacks if the - * surface is not visible in any way, e.g. the surface is off-screen, - * or completely obscured by other opaque surfaces. - * - * The object returned by this request will be destroyed by the - * compositor after the callback is fired and as such the client must not - * attempt to use it after that point. - * - * The callback_data passed in the callback is the current time, in - * milliseconds, with an undefined base. - */ -static inline struct wl_callback * -wl_surface_frame(struct wl_surface *wl_surface) -{ - struct wl_proxy *callback; - - callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_FRAME, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, NULL); - - return (struct wl_callback *) callback; -} - -/** - * @ingroup iface_wl_surface - * - * This request sets the region of the surface that contains - * opaque content. - * - * The opaque region is an optimization hint for the compositor - * that lets it optimize the redrawing of content behind opaque - * regions. Setting an opaque region is not required for correct - * behaviour, but marking transparent content as opaque will result - * in repaint artifacts. - * - * The opaque region is specified in surface-local coordinates. - * - * The compositor ignores the parts of the opaque region that fall - * outside of the surface. - * - * Opaque region is double-buffered state, see wl_surface.commit. - * - * wl_surface.set_opaque_region changes the pending opaque region. - * wl_surface.commit copies the pending region to the current region. - * Otherwise, the pending and current regions are never changed. - * - * The initial value for an opaque region is empty. Setting the pending - * opaque region has copy semantics, and the wl_region object can be - * destroyed immediately. A NULL wl_region causes the pending opaque - * region to be set to empty. - */ -static inline void -wl_surface_set_opaque_region(struct wl_surface *wl_surface, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_OPAQUE_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets the region of the surface that can receive - * pointer and touch events. - * - * Input events happening outside of this region will try the next - * surface in the server surface stack. The compositor ignores the - * parts of the input region that fall outside of the surface. - * - * The input region is specified in surface-local coordinates. - * - * Input region is double-buffered state, see wl_surface.commit. - * - * wl_surface.set_input_region changes the pending input region. - * wl_surface.commit copies the pending region to the current region. - * Otherwise the pending and current regions are never changed, - * except cursor and icon surfaces are special cases, see - * wl_pointer.set_cursor and wl_data_device.start_drag. - * - * The initial value for an input region is infinite. That means the - * whole surface will accept input. Setting the pending input region - * has copy semantics, and the wl_region object can be destroyed - * immediately. A NULL wl_region causes the input region to be set - * to infinite. - */ -static inline void -wl_surface_set_input_region(struct wl_surface *wl_surface, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_INPUT_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); -} - -/** - * @ingroup iface_wl_surface - * - * Surface state (input, opaque, and damage regions, attached buffers, - * etc.) is double-buffered. Protocol requests modify the pending state, - * as opposed to the current state in use by the compositor. A commit - * request atomically applies all pending state, replacing the current - * state. After commit, the new pending state is as documented for each - * related request. - * - * On commit, a pending wl_buffer is applied first, and all other state - * second. This means that all coordinates in double-buffered state are - * relative to the new wl_buffer coming into use, except for - * wl_surface.attach itself. If there is no pending wl_buffer, the - * coordinates are relative to the current surface contents. - * - * All requests that need a commit to become effective are documented - * to affect double-buffered state. - * - * Other interfaces may add further double-buffered surface state. - */ -static inline void -wl_surface_commit(struct wl_surface *wl_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets an optional transformation on how the compositor - * interprets the contents of the buffer attached to the surface. The - * accepted values for the transform parameter are the values for - * wl_output.transform. - * - * Buffer transform is double-buffered state, see wl_surface.commit. - * - * A newly created surface has its buffer transformation set to normal. - * - * wl_surface.set_buffer_transform changes the pending buffer - * transformation. wl_surface.commit copies the pending buffer - * transformation to the current one. Otherwise, the pending and current - * values are never changed. - * - * The purpose of this request is to allow clients to render content - * according to the output transform, thus permitting the compositor to - * use certain optimizations even if the display is rotated. Using - * hardware overlays and scanning out a client buffer for fullscreen - * surfaces are examples of such optimizations. Those optimizations are - * highly dependent on the compositor implementation, so the use of this - * request should be considered on a case-by-case basis. - * - * Note that if the transform value includes 90 or 270 degree rotation, - * the width of the buffer will become the surface height and the height - * of the buffer will become the surface width. - * - * If transform is not one of the values from the - * wl_output.transform enum the invalid_transform protocol error - * is raised. - */ -static inline void -wl_surface_set_buffer_transform(struct wl_surface *wl_surface, int32_t transform) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_BUFFER_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, transform); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets an optional scaling factor on how the compositor - * interprets the contents of the buffer attached to the window. - * - * Buffer scale is double-buffered state, see wl_surface.commit. - * - * A newly created surface has its buffer scale set to 1. - * - * wl_surface.set_buffer_scale changes the pending buffer scale. - * wl_surface.commit copies the pending buffer scale to the current one. - * Otherwise, the pending and current values are never changed. - * - * The purpose of this request is to allow clients to supply higher - * resolution buffer data for use on high resolution outputs. It is - * intended that you pick the same buffer scale as the scale of the - * output that the surface is displayed on. This means the compositor - * can avoid scaling when rendering the surface on that output. - * - * Note that if the scale is larger than 1, then you have to attach - * a buffer that is larger (by a factor of scale in each dimension) - * than the desired surface size. - * - * If scale is not positive the invalid_scale protocol error is - * raised. - */ -static inline void -wl_surface_set_buffer_scale(struct wl_surface *wl_surface, int32_t scale) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_BUFFER_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, scale); -} - -/** - * @ingroup iface_wl_surface - * - * This request is used to describe the regions where the pending - * buffer is different from the current surface contents, and where - * the surface therefore needs to be repainted. The compositor - * ignores the parts of the damage that fall outside of the surface. - * - * Damage is double-buffered state, see wl_surface.commit. - * - * The damage rectangle is specified in buffer coordinates, - * where x and y specify the upper left corner of the damage rectangle. - * - * The initial value for pending damage is empty: no damage. - * wl_surface.damage_buffer adds pending damage: the new pending - * damage is the union of old pending damage and the given rectangle. - * - * wl_surface.commit assigns pending damage as the current damage, - * and clears pending damage. The server will clear the current - * damage as it repaints the surface. - * - * This request differs from wl_surface.damage in only one way - it - * takes damage in buffer coordinates instead of surface-local - * coordinates. While this generally is more intuitive than surface - * coordinates, it is especially desirable when using wp_viewport - * or when a drawing library (like EGL) is unaware of buffer scale - * and buffer transform. - * - * Note: Because buffer transformation changes and damage requests may - * be interleaved in the protocol stream, it is impossible to determine - * the actual mapping between surface and buffer damage until - * wl_surface.commit time. Therefore, compositors wishing to take both - * kinds of damage into account will have to accumulate damage from the - * two requests separately and only transform from one to the other - * after receiving the wl_surface.commit. - */ -static inline void -wl_surface_damage_buffer(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DAMAGE_BUFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_surface - * - * The x and y arguments specify the location of the new pending - * buffer's upper left corner, relative to the current buffer's upper - * left corner, in surface-local coordinates. In other words, the - * x and y, combined with the new surface size define in which - * directions the surface's size changes. - * - * Surface location offset is double-buffered state, see - * wl_surface.commit. - * - * This request is semantically equivalent to and the replaces the x and y - * arguments in the wl_surface.attach request in wl_surface versions prior - * to 5. See wl_surface.attach for details. - */ -static inline void -wl_surface_offset(struct wl_surface *wl_surface, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y); -} - -#ifndef WL_SEAT_CAPABILITY_ENUM -#define WL_SEAT_CAPABILITY_ENUM -/** - * @ingroup iface_wl_seat - * seat capability bitmask - * - * This is a bitmask of capabilities this seat has; if a member is - * set, then it is present on the seat. - */ -enum wl_seat_capability { - /** - * the seat has pointer devices - */ - WL_SEAT_CAPABILITY_POINTER = 1, - /** - * the seat has one or more keyboards - */ - WL_SEAT_CAPABILITY_KEYBOARD = 2, - /** - * the seat has touch devices - */ - WL_SEAT_CAPABILITY_TOUCH = 4, -}; -#endif /* WL_SEAT_CAPABILITY_ENUM */ - -#ifndef WL_SEAT_ERROR_ENUM -#define WL_SEAT_ERROR_ENUM -/** - * @ingroup iface_wl_seat - * wl_seat error values - * - * These errors can be emitted in response to wl_seat requests. - */ -enum wl_seat_error { - /** - * get_pointer, get_keyboard or get_touch called on seat without the matching capability - */ - WL_SEAT_ERROR_MISSING_CAPABILITY = 0, -}; -#endif /* WL_SEAT_ERROR_ENUM */ - -/** - * @ingroup iface_wl_seat - * @struct wl_seat_listener - */ -struct wl_seat_listener { - /** - * seat capabilities changed - * - * This is emitted whenever a seat gains or loses the pointer, - * keyboard or touch capabilities. The argument is a capability - * enum containing the complete set of capabilities this seat has. - * - * When the pointer capability is added, a client may create a - * wl_pointer object using the wl_seat.get_pointer request. This - * object will receive pointer events until the capability is - * removed in the future. - * - * When the pointer capability is removed, a client should destroy - * the wl_pointer objects associated with the seat where the - * capability was removed, using the wl_pointer.release request. No - * further pointer events will be received on these objects. - * - * In some compositors, if a seat regains the pointer capability - * and a client has a previously obtained wl_pointer object of - * version 4 or less, that object may start sending pointer events - * again. This behavior is considered a misinterpretation of the - * intended behavior and must not be relied upon by the client. - * wl_pointer objects of version 5 or later must not send events if - * created before the most recent event notifying the client of an - * added pointer capability. - * - * The above behavior also applies to wl_keyboard and wl_touch with - * the keyboard and touch capabilities, respectively. - * @param capabilities capabilities of the seat - */ - void (*capabilities)(void *data, - struct wl_seat *wl_seat, - uint32_t capabilities); - /** - * unique identifier for this seat - * - * In a multi-seat configuration the seat name can be used by - * clients to help identify which physical devices the seat - * represents. - * - * The seat name is a UTF-8 string with no convention defined for - * its contents. Each name is unique among all wl_seat globals. The - * name is only guaranteed to be unique for the current compositor - * instance. - * - * The same seat names are used for all clients. Thus, the name can - * be shared across processes to refer to a specific wl_seat - * global. - * - * The name event is sent after binding to the seat global. This - * event is only sent once per seat object, and the name does not - * change over the lifetime of the wl_seat global. - * - * Compositors may re-use the same seat name if the wl_seat global - * is destroyed and re-created later. - * @param name seat identifier - * @since 2 - */ - void (*name)(void *data, - struct wl_seat *wl_seat, - const char *name); -}; - -/** - * @ingroup iface_wl_seat - */ -static inline int -wl_seat_add_listener(struct wl_seat *wl_seat, - const struct wl_seat_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_seat, - (void (**)(void)) listener, data); -} - -#define WL_SEAT_GET_POINTER 0 -#define WL_SEAT_GET_KEYBOARD 1 -#define WL_SEAT_GET_TOUCH 2 -#define WL_SEAT_RELEASE 3 - -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_CAPABILITIES_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_NAME_SINCE_VERSION 2 - -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_POINTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_KEYBOARD_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_TOUCH_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_RELEASE_SINCE_VERSION 5 - -/** @ingroup iface_wl_seat */ -static inline void -wl_seat_set_user_data(struct wl_seat *wl_seat, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_seat, user_data); -} - -/** @ingroup iface_wl_seat */ -static inline void * -wl_seat_get_user_data(struct wl_seat *wl_seat) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_seat); -} - -static inline uint32_t -wl_seat_get_version(struct wl_seat *wl_seat) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_seat); -} - -/** @ingroup iface_wl_seat */ -static inline void -wl_seat_destroy(struct wl_seat *wl_seat) -{ - wl_proxy_destroy((struct wl_proxy *) wl_seat); -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_pointer interface - * for this seat. - * - * This request only takes effect if the seat has the pointer - * capability, or has had the pointer capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the pointer capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_pointer * -wl_seat_get_pointer(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_POINTER, &wl_pointer_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_pointer *) id; -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_keyboard interface - * for this seat. - * - * This request only takes effect if the seat has the keyboard - * capability, or has had the keyboard capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the keyboard capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_keyboard * -wl_seat_get_keyboard(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_KEYBOARD, &wl_keyboard_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_keyboard *) id; -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_touch interface - * for this seat. - * - * This request only takes effect if the seat has the touch - * capability, or has had the touch capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the touch capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_touch * -wl_seat_get_touch(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_TOUCH, &wl_touch_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_touch *) id; -} - -/** - * @ingroup iface_wl_seat - * - * Using this request a client can tell the server that it is not going to - * use the seat object anymore. - */ -static inline void -wl_seat_release(struct wl_seat *wl_seat) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_seat), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_POINTER_ERROR_ENUM -#define WL_POINTER_ERROR_ENUM -enum wl_pointer_error { - /** - * given wl_surface has another role - */ - WL_POINTER_ERROR_ROLE = 0, -}; -#endif /* WL_POINTER_ERROR_ENUM */ - -#ifndef WL_POINTER_BUTTON_STATE_ENUM -#define WL_POINTER_BUTTON_STATE_ENUM -/** - * @ingroup iface_wl_pointer - * physical button state - * - * Describes the physical state of a button that produced the button - * event. - */ -enum wl_pointer_button_state { - /** - * the button is not pressed - */ - WL_POINTER_BUTTON_STATE_RELEASED = 0, - /** - * the button is pressed - */ - WL_POINTER_BUTTON_STATE_PRESSED = 1, -}; -#endif /* WL_POINTER_BUTTON_STATE_ENUM */ - -#ifndef WL_POINTER_AXIS_ENUM -#define WL_POINTER_AXIS_ENUM -/** - * @ingroup iface_wl_pointer - * axis types - * - * Describes the axis types of scroll events. - */ -enum wl_pointer_axis { - /** - * vertical axis - */ - WL_POINTER_AXIS_VERTICAL_SCROLL = 0, - /** - * horizontal axis - */ - WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1, -}; -#endif /* WL_POINTER_AXIS_ENUM */ - -#ifndef WL_POINTER_AXIS_SOURCE_ENUM -#define WL_POINTER_AXIS_SOURCE_ENUM -/** - * @ingroup iface_wl_pointer - * axis source types - * - * Describes the source types for axis events. This indicates to the - * client how an axis event was physically generated; a client may - * adjust the user interface accordingly. For example, scroll events - * from a "finger" source may be in a smooth coordinate space with - * kinetic scrolling whereas a "wheel" source may be in discrete steps - * of a number of lines. - * - * The "continuous" axis source is a device generating events in a - * continuous coordinate space, but using something other than a - * finger. One example for this source is button-based scrolling where - * the vertical motion of a device is converted to scroll events while - * a button is held down. - * - * The "wheel tilt" axis source indicates that the actual device is a - * wheel but the scroll event is not caused by a rotation but a - * (usually sideways) tilt of the wheel. - */ -enum wl_pointer_axis_source { - /** - * a physical wheel rotation - */ - WL_POINTER_AXIS_SOURCE_WHEEL = 0, - /** - * finger on a touch surface - */ - WL_POINTER_AXIS_SOURCE_FINGER = 1, - /** - * continuous coordinate space - */ - WL_POINTER_AXIS_SOURCE_CONTINUOUS = 2, - /** - * a physical wheel tilt - * @since 6 - */ - WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3, -}; -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION 6 -#endif /* WL_POINTER_AXIS_SOURCE_ENUM */ - -#ifndef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM -#define WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM -/** - * @ingroup iface_wl_pointer - * axis relative direction - * - * This specifies the direction of the physical motion that caused a - * wl_pointer.axis event, relative to the wl_pointer.axis direction. - */ -enum wl_pointer_axis_relative_direction { - /** - * physical motion matches axis direction - */ - WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL = 0, - /** - * physical motion is the inverse of the axis direction - */ - WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED = 1, -}; -#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */ - -/** - * @ingroup iface_wl_pointer - * @struct wl_pointer_listener - */ -struct wl_pointer_listener { - /** - * enter event - * - * Notification that this seat's pointer is focused on a certain - * surface. - * - * When a seat's focus enters a surface, the pointer image is - * undefined and a client should respond to this event by setting - * an appropriate pointer image with the set_cursor request. - * @param serial serial number of the enter event - * @param surface surface entered by the pointer - * @param surface_x surface-local x coordinate - * @param surface_y surface-local y coordinate - */ - void (*enter)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - struct wl_surface *surface, - wl_fixed_t surface_x, - wl_fixed_t surface_y); - /** - * leave event - * - * Notification that this seat's pointer is no longer focused on - * a certain surface. - * - * The leave notification is sent before the enter notification for - * the new focus. - * @param serial serial number of the leave event - * @param surface surface left by the pointer - */ - void (*leave)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - struct wl_surface *surface); - /** - * pointer motion event - * - * Notification of pointer location change. The arguments - * surface_x and surface_y are the location relative to the focused - * surface. - * @param time timestamp with millisecond granularity - * @param surface_x surface-local x coordinate - * @param surface_y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - wl_fixed_t surface_x, - wl_fixed_t surface_y); - /** - * pointer button event - * - * Mouse button click and release notifications. - * - * The location of the click is given by the last motion or enter - * event. The time argument is a timestamp with millisecond - * granularity, with an undefined base. - * - * The button is a button code as defined in the Linux kernel's - * linux/input-event-codes.h header file, e.g. BTN_LEFT. - * - * Any 16-bit button code value is reserved for future additions to - * the kernel's event code list. All other button codes above - * 0xFFFF are currently undefined but may be used in future - * versions of this protocol. - * @param serial serial number of the button event - * @param time timestamp with millisecond granularity - * @param button button that produced the event - * @param state physical state of the button - */ - void (*button)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - uint32_t time, - uint32_t button, - uint32_t state); - /** - * axis event - * - * Scroll and other axis notifications. - * - * For scroll events (vertical and horizontal scroll axes), the - * value parameter is the length of a vector along the specified - * axis in a coordinate space identical to those of motion events, - * representing a relative movement along the specified axis. - * - * For devices that support movements non-parallel to axes multiple - * axis events will be emitted. - * - * When applicable, for example for touch pads, the server can - * choose to emit scroll events where the motion vector is - * equivalent to a motion event vector. - * - * When applicable, a client can transform its content relative to - * the scroll distance. - * @param time timestamp with millisecond granularity - * @param axis axis type - * @param value length of vector in surface-local coordinate space - */ - void (*axis)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - uint32_t axis, - wl_fixed_t value); - /** - * end of a pointer event sequence - * - * Indicates the end of a set of events that logically belong - * together. A client is expected to accumulate the data in all - * events within the frame before proceeding. - * - * All wl_pointer events before a wl_pointer.frame event belong - * logically together. For example, in a diagonal scroll motion the - * compositor will send an optional wl_pointer.axis_source event, - * two wl_pointer.axis events (horizontal and vertical) and finally - * a wl_pointer.frame event. The client may use this information to - * calculate a diagonal vector for scrolling. - * - * When multiple wl_pointer.axis events occur within the same - * frame, the motion vector is the combined motion of all events. - * When a wl_pointer.axis and a wl_pointer.axis_stop event occur - * within the same frame, this indicates that axis movement in one - * axis has stopped but continues in the other axis. When multiple - * wl_pointer.axis_stop events occur within the same frame, this - * indicates that these axes stopped in the same instance. - * - * A wl_pointer.frame event is sent for every logical event group, - * even if the group only contains a single wl_pointer event. - * Specifically, a client may get a sequence: motion, frame, - * button, frame, axis, frame, axis_stop, frame. - * - * The wl_pointer.enter and wl_pointer.leave events are logical - * events generated by the compositor and not the hardware. These - * events are also grouped by a wl_pointer.frame. When a pointer - * moves from one surface to another, a compositor should group the - * wl_pointer.leave event within the same wl_pointer.frame. - * However, a client must not rely on wl_pointer.leave and - * wl_pointer.enter being in the same wl_pointer.frame. - * Compositor-specific policies may require the wl_pointer.leave - * and wl_pointer.enter event being split across multiple - * wl_pointer.frame groups. - * @since 5 - */ - void (*frame)(void *data, - struct wl_pointer *wl_pointer); - /** - * axis source event - * - * Source information for scroll and other axes. - * - * This event does not occur on its own. It is sent before a - * wl_pointer.frame event and carries the source information for - * all events within that frame. - * - * The source specifies how this event was generated. If the source - * is wl_pointer.axis_source.finger, a wl_pointer.axis_stop event - * will be sent when the user lifts the finger off the device. - * - * If the source is wl_pointer.axis_source.wheel, - * wl_pointer.axis_source.wheel_tilt or - * wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event - * may or may not be sent. Whether a compositor sends an axis_stop - * event for these sources is hardware-specific and - * implementation-dependent; clients must not rely on receiving an - * axis_stop event for these scroll sources and should treat scroll - * sequences from these scroll sources as unterminated by default. - * - * This event is optional. If the source is unknown for a - * particular axis event sequence, no event is sent. Only one - * wl_pointer.axis_source event is permitted per frame. - * - * The order of wl_pointer.axis_discrete and wl_pointer.axis_source - * is not guaranteed. - * @param axis_source source of the axis event - * @since 5 - */ - void (*axis_source)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis_source); - /** - * axis stop event - * - * Stop notification for scroll and other axes. - * - * For some wl_pointer.axis_source types, a wl_pointer.axis_stop - * event is sent to notify a client that the axis sequence has - * terminated. This enables the client to implement kinetic - * scrolling. See the wl_pointer.axis_source documentation for - * information on when this event may be generated. - * - * Any wl_pointer.axis events with the same axis_source after this - * event should be considered as the start of a new axis motion. - * - * The timestamp is to be interpreted identical to the timestamp in - * the wl_pointer.axis event. The timestamp value may be the same - * as a preceding wl_pointer.axis event. - * @param time timestamp with millisecond granularity - * @param axis the axis stopped with this event - * @since 5 - */ - void (*axis_stop)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - uint32_t axis); - /** - * axis click event - * - * Discrete step information for scroll and other axes. - * - * This event carries the axis value of the wl_pointer.axis event - * in discrete steps (e.g. mouse wheel clicks). - * - * This event is deprecated with wl_pointer version 8 - this event - * is not sent to clients supporting version 8 or later. - * - * This event does not occur on its own, it is coupled with a - * wl_pointer.axis event that represents this axis value on a - * continuous scale. The protocol guarantees that each - * axis_discrete event is always followed by exactly one axis event - * with the same axis number within the same wl_pointer.frame. Note - * that the protocol allows for other events to occur between the - * axis_discrete and its coupled axis event, including other - * axis_discrete or axis events. A wl_pointer.frame must not - * contain more than one axis_discrete event per axis type. - * - * This event is optional; continuous scrolling devices like - * two-finger scrolling on touchpads do not have discrete steps and - * do not generate this event. - * - * The discrete value carries the directional information. e.g. a - * value of -2 is two steps towards the negative direction of this - * axis. - * - * The axis number is identical to the axis number in the - * associated axis event. - * - * The order of wl_pointer.axis_discrete and wl_pointer.axis_source - * is not guaranteed. - * @param axis axis type - * @param discrete number of steps - * @since 5 - */ - void (*axis_discrete)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - int32_t discrete); - /** - * axis high-resolution scroll event - * - * Discrete high-resolution scroll information. - * - * This event carries high-resolution wheel scroll information, - * with each multiple of 120 representing one logical scroll step - * (a wheel detent). For example, an axis_value120 of 30 is one - * quarter of a logical scroll step in the positive direction, a - * value120 of -240 are two logical scroll steps in the negative - * direction within the same hardware event. Clients that rely on - * discrete scrolling should accumulate the value120 to multiples - * of 120 before processing the event. - * - * The value120 must not be zero. - * - * This event replaces the wl_pointer.axis_discrete event in - * clients supporting wl_pointer version 8 or later. - * - * Where a wl_pointer.axis_source event occurs in the same - * wl_pointer.frame, the axis source applies to this event. - * - * The order of wl_pointer.axis_value120 and wl_pointer.axis_source - * is not guaranteed. - * @param axis axis type - * @param value120 scroll distance as fraction of 120 - * @since 8 - */ - void (*axis_value120)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - int32_t value120); - /** - * axis relative physical direction event - * - * Relative directional information of the entity causing the - * axis motion. - * - * For a wl_pointer.axis event, the - * wl_pointer.axis_relative_direction event specifies the movement - * direction of the entity causing the wl_pointer.axis event. For - * example: - if a user's fingers on a touchpad move down and this - * causes a wl_pointer.axis vertical_scroll down event, the - * physical direction is 'identical' - if a user's fingers on a - * touchpad move down and this causes a wl_pointer.axis - * vertical_scroll up scroll up event ('natural scrolling'), the - * physical direction is 'inverted'. - * - * A client may use this information to adjust scroll motion of - * components. Specifically, enabling natural scrolling causes the - * content to change direction compared to traditional scrolling. - * Some widgets like volume control sliders should usually match - * the physical direction regardless of whether natural scrolling - * is active. This event enables clients to match the scroll - * direction of a widget to the physical direction. - * - * This event does not occur on its own, it is coupled with a - * wl_pointer.axis event that represents this axis value. The - * protocol guarantees that each axis_relative_direction event is - * always followed by exactly one axis event with the same axis - * number within the same wl_pointer.frame. Note that the protocol - * allows for other events to occur between the - * axis_relative_direction and its coupled axis event. - * - * The axis number is identical to the axis number in the - * associated axis event. - * - * The order of wl_pointer.axis_relative_direction, - * wl_pointer.axis_discrete and wl_pointer.axis_source is not - * guaranteed. - * @param axis axis type - * @param direction physical direction relative to axis motion - * @since 9 - */ - void (*axis_relative_direction)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - uint32_t direction); -}; - -/** - * @ingroup iface_wl_pointer - */ -static inline int -wl_pointer_add_listener(struct wl_pointer *wl_pointer, - const struct wl_pointer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_pointer, - (void (**)(void)) listener, data); -} - -#define WL_POINTER_SET_CURSOR 0 -#define WL_POINTER_RELEASE 1 - -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_BUTTON_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_FRAME_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SOURCE_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_STOP_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_DISCRETE_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_VALUE120_SINCE_VERSION 8 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION 9 - -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_SET_CURSOR_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_pointer */ -static inline void -wl_pointer_set_user_data(struct wl_pointer *wl_pointer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_pointer, user_data); -} - -/** @ingroup iface_wl_pointer */ -static inline void * -wl_pointer_get_user_data(struct wl_pointer *wl_pointer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_pointer); -} - -static inline uint32_t -wl_pointer_get_version(struct wl_pointer *wl_pointer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_pointer); -} - -/** @ingroup iface_wl_pointer */ -static inline void -wl_pointer_destroy(struct wl_pointer *wl_pointer) -{ - wl_proxy_destroy((struct wl_proxy *) wl_pointer); -} - -/** - * @ingroup iface_wl_pointer - * - * Set the pointer surface, i.e., the surface that contains the - * pointer image (cursor). This request gives the surface the role - * of a cursor. If the surface already has another role, it raises - * a protocol error. - * - * The cursor actually changes only if the pointer - * focus for this device is one of the requesting client's surfaces - * or the surface parameter is the current pointer surface. If - * there was a previous surface set with this request it is - * replaced. If surface is NULL, the pointer image is hidden. - * - * The parameters hotspot_x and hotspot_y define the position of - * the pointer surface relative to the pointer location. Its - * top-left corner is always at (x, y) - (hotspot_x, hotspot_y), - * where (x, y) are the coordinates of the pointer location, in - * surface-local coordinates. - * - * On surface.attach requests to the pointer surface, hotspot_x - * and hotspot_y are decremented by the x and y parameters - * passed to the request. Attach must be confirmed by - * wl_surface.commit as usual. - * - * The hotspot can also be updated by passing the currently set - * pointer surface to this request with new values for hotspot_x - * and hotspot_y. - * - * The input region is ignored for wl_surfaces with the role of - * a cursor. When the use as a cursor ends, the wl_surface is - * unmapped. - * - * The serial parameter must match the latest wl_pointer.enter - * serial number sent to the client. Otherwise the request will be - * ignored. - */ -static inline void -wl_pointer_set_cursor(struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, - WL_POINTER_SET_CURSOR, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), 0, serial, surface, hotspot_x, hotspot_y); -} - -/** - * @ingroup iface_wl_pointer - * - * Using this request a client can tell the server that it is not going to - * use the pointer object anymore. - * - * This request destroys the pointer proxy object, so clients must not call - * wl_pointer_destroy() after using this request. - */ -static inline void -wl_pointer_release(struct wl_pointer *wl_pointer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, - WL_POINTER_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_KEYBOARD_KEYMAP_FORMAT_ENUM -#define WL_KEYBOARD_KEYMAP_FORMAT_ENUM -/** - * @ingroup iface_wl_keyboard - * keyboard mapping format - * - * This specifies the format of the keymap provided to the - * client with the wl_keyboard.keymap event. - */ -enum wl_keyboard_keymap_format { - /** - * no keymap; client must understand how to interpret the raw keycode - */ - WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP = 0, - /** - * libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode - */ - WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 = 1, -}; -#endif /* WL_KEYBOARD_KEYMAP_FORMAT_ENUM */ - -#ifndef WL_KEYBOARD_KEY_STATE_ENUM -#define WL_KEYBOARD_KEY_STATE_ENUM -/** - * @ingroup iface_wl_keyboard - * physical key state - * - * Describes the physical state of a key that produced the key event. - */ -enum wl_keyboard_key_state { - /** - * key is not pressed - */ - WL_KEYBOARD_KEY_STATE_RELEASED = 0, - /** - * key is pressed - */ - WL_KEYBOARD_KEY_STATE_PRESSED = 1, -}; -#endif /* WL_KEYBOARD_KEY_STATE_ENUM */ - -/** - * @ingroup iface_wl_keyboard - * @struct wl_keyboard_listener - */ -struct wl_keyboard_listener { - /** - * keyboard mapping - * - * This event provides a file descriptor to the client which can - * be memory-mapped in read-only mode to provide a keyboard mapping - * description. - * - * From version 7 onwards, the fd must be mapped with MAP_PRIVATE - * by the recipient, as MAP_SHARED may fail. - * @param format keymap format - * @param fd keymap file descriptor - * @param size keymap size, in bytes - */ - void (*keymap)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t format, - int32_t fd, - uint32_t size); - /** - * enter event - * - * Notification that this seat's keyboard focus is on a certain - * surface. - * - * The compositor must send the wl_keyboard.modifiers event after - * this event. - * @param serial serial number of the enter event - * @param surface surface gaining keyboard focus - * @param keys the currently pressed keys - */ - void (*enter)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - struct wl_surface *surface, - struct wl_array *keys); - /** - * leave event - * - * Notification that this seat's keyboard focus is no longer on a - * certain surface. - * - * The leave notification is sent before the enter notification for - * the new focus. - * - * After this event client must assume that all keys, including - * modifiers, are lifted and also it must stop key repeating if - * there's some going on. - * @param serial serial number of the leave event - * @param surface surface that lost keyboard focus - */ - void (*leave)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - struct wl_surface *surface); - /** - * key event - * - * A key was pressed or released. The time argument is a - * timestamp with millisecond granularity, with an undefined base. - * - * The key is a platform-specific key code that can be interpreted - * by feeding it to the keyboard mapping (see the keymap event). - * - * If this event produces a change in modifiers, then the resulting - * wl_keyboard.modifiers event must be sent after this event. - * @param serial serial number of the key event - * @param time timestamp with millisecond granularity - * @param key key that produced the event - * @param state physical state of the key - */ - void (*key)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state); - /** - * modifier and group state - * - * Notifies clients that the modifier and/or group state has - * changed, and it should update its local state. - * @param serial serial number of the modifiers event - * @param mods_depressed depressed modifiers - * @param mods_latched latched modifiers - * @param mods_locked locked modifiers - * @param group keyboard layout - */ - void (*modifiers)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group); - /** - * repeat rate and delay - * - * Informs the client about the keyboard's repeat rate and delay. - * - * This event is sent as soon as the wl_keyboard object has been - * created, and is guaranteed to be received by the client before - * any key press event. - * - * Negative values for either rate or delay are illegal. A rate of - * zero will disable any repeating (regardless of the value of - * delay). - * - * This event can be sent later on as well with a new value if - * necessary, so clients should continue listening for the event - * past the creation of wl_keyboard. - * @param rate the rate of repeating keys in characters per second - * @param delay delay in milliseconds since key down until repeating starts - * @since 4 - */ - void (*repeat_info)(void *data, - struct wl_keyboard *wl_keyboard, - int32_t rate, - int32_t delay); -}; - -/** - * @ingroup iface_wl_keyboard - */ -static inline int -wl_keyboard_add_listener(struct wl_keyboard *wl_keyboard, - const struct wl_keyboard_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_keyboard, - (void (**)(void)) listener, data); -} - -#define WL_KEYBOARD_RELEASE 0 - -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_KEYMAP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_KEY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_MODIFIERS_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION 4 - -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_keyboard */ -static inline void -wl_keyboard_set_user_data(struct wl_keyboard *wl_keyboard, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_keyboard, user_data); -} - -/** @ingroup iface_wl_keyboard */ -static inline void * -wl_keyboard_get_user_data(struct wl_keyboard *wl_keyboard) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_keyboard); -} - -static inline uint32_t -wl_keyboard_get_version(struct wl_keyboard *wl_keyboard) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_keyboard); -} - -/** @ingroup iface_wl_keyboard */ -static inline void -wl_keyboard_destroy(struct wl_keyboard *wl_keyboard) -{ - wl_proxy_destroy((struct wl_proxy *) wl_keyboard); -} - -/** - * @ingroup iface_wl_keyboard - */ -static inline void -wl_keyboard_release(struct wl_keyboard *wl_keyboard) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_keyboard, - WL_KEYBOARD_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_keyboard), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_touch - * @struct wl_touch_listener - */ -struct wl_touch_listener { - /** - * touch down event and beginning of a touch sequence - * - * A new touch point has appeared on the surface. This touch - * point is assigned a unique ID. Future events from this touch - * point reference this ID. The ID ceases to be valid after a touch - * up event and may be reused in the future. - * @param serial serial number of the touch down event - * @param time timestamp with millisecond granularity - * @param surface surface touched - * @param id the unique ID of this touch point - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*down)(void *data, - struct wl_touch *wl_touch, - uint32_t serial, - uint32_t time, - struct wl_surface *surface, - int32_t id, - wl_fixed_t x, - wl_fixed_t y); - /** - * end of a touch event sequence - * - * The touch point has disappeared. No further events will be - * sent for this touch point and the touch point's ID is released - * and may be reused in a future touch down event. - * @param serial serial number of the touch up event - * @param time timestamp with millisecond granularity - * @param id the unique ID of this touch point - */ - void (*up)(void *data, - struct wl_touch *wl_touch, - uint32_t serial, - uint32_t time, - int32_t id); - /** - * update of touch point coordinates - * - * A touch point has changed coordinates. - * @param time timestamp with millisecond granularity - * @param id the unique ID of this touch point - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_touch *wl_touch, - uint32_t time, - int32_t id, - wl_fixed_t x, - wl_fixed_t y); - /** - * end of touch frame event - * - * Indicates the end of a set of events that logically belong - * together. A client is expected to accumulate the data in all - * events within the frame before proceeding. - * - * A wl_touch.frame terminates at least one event but otherwise no - * guarantee is provided about the set of events within a frame. A - * client must assume that any state not updated in a frame is - * unchanged from the previously known state. - */ - void (*frame)(void *data, - struct wl_touch *wl_touch); - /** - * touch session cancelled - * - * Sent if the compositor decides the touch stream is a global - * gesture. No further events are sent to the clients from that - * particular gesture. Touch cancellation applies to all touch - * points currently active on this client's surface. The client is - * responsible for finalizing the touch points, future touch points - * on this surface may reuse the touch point ID. - */ - void (*cancel)(void *data, - struct wl_touch *wl_touch); - /** - * update shape of touch point - * - * Sent when a touchpoint has changed its shape. - * - * This event does not occur on its own. It is sent before a - * wl_touch.frame event and carries the new shape information for - * any previously reported, or new touch points of that frame. - * - * Other events describing the touch point such as wl_touch.down, - * wl_touch.motion or wl_touch.orientation may be sent within the - * same wl_touch.frame. A client should treat these events as a - * single logical touch point update. The order of wl_touch.shape, - * wl_touch.orientation and wl_touch.motion is not guaranteed. A - * wl_touch.down event is guaranteed to occur before the first - * wl_touch.shape event for this touch ID but both events may occur - * within the same wl_touch.frame. - * - * A touchpoint shape is approximated by an ellipse through the - * major and minor axis length. The major axis length describes the - * longer diameter of the ellipse, while the minor axis length - * describes the shorter diameter. Major and minor are orthogonal - * and both are specified in surface-local coordinates. The center - * of the ellipse is always at the touchpoint location as reported - * by wl_touch.down or wl_touch.move. - * - * This event is only sent by the compositor if the touch device - * supports shape reports. The client has to make reasonable - * assumptions about the shape if it did not receive this event. - * @param id the unique ID of this touch point - * @param major length of the major axis in surface-local coordinates - * @param minor length of the minor axis in surface-local coordinates - * @since 6 - */ - void (*shape)(void *data, - struct wl_touch *wl_touch, - int32_t id, - wl_fixed_t major, - wl_fixed_t minor); - /** - * update orientation of touch point - * - * Sent when a touchpoint has changed its orientation. - * - * This event does not occur on its own. It is sent before a - * wl_touch.frame event and carries the new shape information for - * any previously reported, or new touch points of that frame. - * - * Other events describing the touch point such as wl_touch.down, - * wl_touch.motion or wl_touch.shape may be sent within the same - * wl_touch.frame. A client should treat these events as a single - * logical touch point update. The order of wl_touch.shape, - * wl_touch.orientation and wl_touch.motion is not guaranteed. A - * wl_touch.down event is guaranteed to occur before the first - * wl_touch.orientation event for this touch ID but both events may - * occur within the same wl_touch.frame. - * - * The orientation describes the clockwise angle of a touchpoint's - * major axis to the positive surface y-axis and is normalized to - * the -180 to +180 degree range. The granularity of orientation - * depends on the touch device, some devices only support binary - * rotation values between 0 and 90 degrees. - * - * This event is only sent by the compositor if the touch device - * supports orientation reports. - * @param id the unique ID of this touch point - * @param orientation angle between major axis and positive surface y-axis in degrees - * @since 6 - */ - void (*orientation)(void *data, - struct wl_touch *wl_touch, - int32_t id, - wl_fixed_t orientation); -}; - -/** - * @ingroup iface_wl_touch - */ -static inline int -wl_touch_add_listener(struct wl_touch *wl_touch, - const struct wl_touch_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_touch, - (void (**)(void)) listener, data); -} - -#define WL_TOUCH_RELEASE 0 - -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_DOWN_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_UP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_FRAME_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_CANCEL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_SHAPE_SINCE_VERSION 6 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_ORIENTATION_SINCE_VERSION 6 - -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_touch */ -static inline void -wl_touch_set_user_data(struct wl_touch *wl_touch, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_touch, user_data); -} - -/** @ingroup iface_wl_touch */ -static inline void * -wl_touch_get_user_data(struct wl_touch *wl_touch) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_touch); -} - -static inline uint32_t -wl_touch_get_version(struct wl_touch *wl_touch) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_touch); -} - -/** @ingroup iface_wl_touch */ -static inline void -wl_touch_destroy(struct wl_touch *wl_touch) -{ - wl_proxy_destroy((struct wl_proxy *) wl_touch); -} - -/** - * @ingroup iface_wl_touch - */ -static inline void -wl_touch_release(struct wl_touch *wl_touch) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_touch, - WL_TOUCH_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_touch), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_OUTPUT_SUBPIXEL_ENUM -#define WL_OUTPUT_SUBPIXEL_ENUM -/** - * @ingroup iface_wl_output - * subpixel geometry information - * - * This enumeration describes how the physical - * pixels on an output are laid out. - */ -enum wl_output_subpixel { - /** - * unknown geometry - */ - WL_OUTPUT_SUBPIXEL_UNKNOWN = 0, - /** - * no geometry - */ - WL_OUTPUT_SUBPIXEL_NONE = 1, - /** - * horizontal RGB - */ - WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB = 2, - /** - * horizontal BGR - */ - WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR = 3, - /** - * vertical RGB - */ - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB = 4, - /** - * vertical BGR - */ - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR = 5, -}; -#endif /* WL_OUTPUT_SUBPIXEL_ENUM */ - -#ifndef WL_OUTPUT_TRANSFORM_ENUM -#define WL_OUTPUT_TRANSFORM_ENUM -/** - * @ingroup iface_wl_output - * transform from framebuffer to output - * - * This describes the transform that a compositor will apply to a - * surface to compensate for the rotation or mirroring of an - * output device. - * - * The flipped values correspond to an initial flip around a - * vertical axis followed by rotation. - * - * The purpose is mainly to allow clients to render accordingly and - * tell the compositor, so that for fullscreen surfaces, the - * compositor will still be able to scan out directly from client - * surfaces. - */ -enum wl_output_transform { - /** - * no transform - */ - WL_OUTPUT_TRANSFORM_NORMAL = 0, - /** - * 90 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_90 = 1, - /** - * 180 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_180 = 2, - /** - * 270 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_270 = 3, - /** - * 180 degree flip around a vertical axis - */ - WL_OUTPUT_TRANSFORM_FLIPPED = 4, - /** - * flip and rotate 90 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_90 = 5, - /** - * flip and rotate 180 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_180 = 6, - /** - * flip and rotate 270 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_270 = 7, -}; -#endif /* WL_OUTPUT_TRANSFORM_ENUM */ - -#ifndef WL_OUTPUT_MODE_ENUM -#define WL_OUTPUT_MODE_ENUM -/** - * @ingroup iface_wl_output - * mode information - * - * These flags describe properties of an output mode. - * They are used in the flags bitfield of the mode event. - */ -enum wl_output_mode { - /** - * indicates this is the current mode - */ - WL_OUTPUT_MODE_CURRENT = 0x1, - /** - * indicates this is the preferred mode - */ - WL_OUTPUT_MODE_PREFERRED = 0x2, -}; -#endif /* WL_OUTPUT_MODE_ENUM */ - -/** - * @ingroup iface_wl_output - * @struct wl_output_listener - */ -struct wl_output_listener { - /** - * properties of the output - * - * The geometry event describes geometric properties of the - * output. The event is sent when binding to the output object and - * whenever any of the properties change. - * - * The physical size can be set to zero if it doesn't make sense - * for this output (e.g. for projectors or virtual outputs). - * - * The geometry event will be followed by a done event (starting - * from version 2). - * - * Note: wl_output only advertises partial information about the - * output position and identification. Some compositors, for - * instance those not implementing a desktop-style output layout or - * those exposing virtual outputs, might fake this information. - * Instead of using x and y, clients should use - * xdg_output.logical_position. Instead of using make and model, - * clients should use name and description. - * @param x x position within the global compositor space - * @param y y position within the global compositor space - * @param physical_width width in millimeters of the output - * @param physical_height height in millimeters of the output - * @param subpixel subpixel orientation of the output - * @param make textual description of the manufacturer - * @param model textual description of the model - * @param transform transform that maps framebuffer to output - */ - void (*geometry)(void *data, - struct wl_output *wl_output, - int32_t x, - int32_t y, - int32_t physical_width, - int32_t physical_height, - int32_t subpixel, - const char *make, - const char *model, - int32_t transform); - /** - * advertise available modes for the output - * - * The mode event describes an available mode for the output. - * - * The event is sent when binding to the output object and there - * will always be one mode, the current mode. The event is sent - * again if an output changes mode, for the mode that is now - * current. In other words, the current mode is always the last - * mode that was received with the current flag set. - * - * Non-current modes are deprecated. A compositor can decide to - * only advertise the current mode and never send other modes. - * Clients should not rely on non-current modes. - * - * The size of a mode is given in physical hardware units of the - * output device. This is not necessarily the same as the output - * size in the global compositor space. For instance, the output - * may be scaled, as described in wl_output.scale, or transformed, - * as described in wl_output.transform. Clients willing to retrieve - * the output size in the global compositor space should use - * xdg_output.logical_size instead. - * - * The vertical refresh rate can be set to zero if it doesn't make - * sense for this output (e.g. for virtual outputs). - * - * The mode event will be followed by a done event (starting from - * version 2). - * - * Clients should not use the refresh rate to schedule frames. - * Instead, they should use the wl_surface.frame event or the - * presentation-time protocol. - * - * Note: this information is not always meaningful for all outputs. - * Some compositors, such as those exposing virtual outputs, might - * fake the refresh rate or the size. - * @param flags bitfield of mode flags - * @param width width of the mode in hardware units - * @param height height of the mode in hardware units - * @param refresh vertical refresh rate in mHz - */ - void (*mode)(void *data, - struct wl_output *wl_output, - uint32_t flags, - int32_t width, - int32_t height, - int32_t refresh); - /** - * sent all information about output - * - * This event is sent after all other properties have been sent - * after binding to the output object and after any other property - * changes done after that. This allows changes to the output - * properties to be seen as atomic, even if they happen via - * multiple events. - * @since 2 - */ - void (*done)(void *data, - struct wl_output *wl_output); - /** - * output scaling properties - * - * This event contains scaling geometry information that is not - * in the geometry event. It may be sent after binding the output - * object or if the output scale changes later. If it is not sent, - * the client should assume a scale of 1. - * - * A scale larger than 1 means that the compositor will - * automatically scale surface buffers by this amount when - * rendering. This is used for very high resolution displays where - * applications rendering at the native resolution would be too - * small to be legible. - * - * It is intended that scaling aware clients track the current - * output of a surface, and if it is on a scaled output it should - * use wl_surface.set_buffer_scale with the scale of the output. - * That way the compositor can avoid scaling the surface, and the - * client can supply a higher detail image. - * - * The scale event will be followed by a done event. - * @param factor scaling factor of output - * @since 2 - */ - void (*scale)(void *data, - struct wl_output *wl_output, - int32_t factor); - /** - * name of this output - * - * Many compositors will assign user-friendly names to their - * outputs, show them to the user, allow the user to refer to an - * output, etc. The client may wish to know this name as well to - * offer the user similar behaviors. - * - * The name is a UTF-8 string with no convention defined for its - * contents. Each name is unique among all wl_output globals. The - * name is only guaranteed to be unique for the compositor - * instance. - * - * The same output name is used for all clients for a given - * wl_output global. Thus, the name can be shared across processes - * to refer to a specific wl_output global. - * - * The name is not guaranteed to be persistent across sessions, - * thus cannot be used to reliably identify an output in e.g. - * configuration files. - * - * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. - * However, do not assume that the name is a reflection of an - * underlying DRM connector, X11 connection, etc. - * - * The name event is sent after binding the output object. This - * event is only sent once per output object, and the name does not - * change over the lifetime of the wl_output global. - * - * Compositors may re-use the same output name if the wl_output - * global is destroyed and re-created later. Compositors should - * avoid re-using the same name if possible. - * - * The name event will be followed by a done event. - * @param name output name - * @since 4 - */ - void (*name)(void *data, - struct wl_output *wl_output, - const char *name); - /** - * human-readable description of this output - * - * Many compositors can produce human-readable descriptions of - * their outputs. The client may wish to know this description as - * well, e.g. for output selection purposes. - * - * The description is a UTF-8 string with no convention defined for - * its contents. The description is not guaranteed to be unique - * among all wl_output globals. Examples might include 'Foocorp 11" - * Display' or 'Virtual X11 output via :1'. - * - * The description event is sent after binding the output object - * and whenever the description changes. The description is - * optional, and may not be sent at all. - * - * The description event will be followed by a done event. - * @param description output description - * @since 4 - */ - void (*description)(void *data, - struct wl_output *wl_output, - const char *description); -}; - -/** - * @ingroup iface_wl_output - */ -static inline int -wl_output_add_listener(struct wl_output *wl_output, - const struct wl_output_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_output, - (void (**)(void)) listener, data); -} - -#define WL_OUTPUT_RELEASE 0 - -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_GEOMETRY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_MODE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_DONE_SINCE_VERSION 2 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_SCALE_SINCE_VERSION 2 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_NAME_SINCE_VERSION 4 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_DESCRIPTION_SINCE_VERSION 4 - -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_output */ -static inline void -wl_output_set_user_data(struct wl_output *wl_output, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_output, user_data); -} - -/** @ingroup iface_wl_output */ -static inline void * -wl_output_get_user_data(struct wl_output *wl_output) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_output); -} - -static inline uint32_t -wl_output_get_version(struct wl_output *wl_output) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_output); -} - -/** @ingroup iface_wl_output */ -static inline void -wl_output_destroy(struct wl_output *wl_output) -{ - wl_proxy_destroy((struct wl_proxy *) wl_output); -} - -/** - * @ingroup iface_wl_output - * - * Using this request a client can tell the server that it is not going to - * use the output object anymore. - */ -static inline void -wl_output_release(struct wl_output *wl_output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_output, - WL_OUTPUT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_output), WL_MARSHAL_FLAG_DESTROY); -} - -#define WL_REGION_DESTROY 0 -#define WL_REGION_ADD 1 -#define WL_REGION_SUBTRACT 2 - - -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_ADD_SINCE_VERSION 1 -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_SUBTRACT_SINCE_VERSION 1 - -/** @ingroup iface_wl_region */ -static inline void -wl_region_set_user_data(struct wl_region *wl_region, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_region, user_data); -} - -/** @ingroup iface_wl_region */ -static inline void * -wl_region_get_user_data(struct wl_region *wl_region) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_region); -} - -static inline uint32_t -wl_region_get_version(struct wl_region *wl_region) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_region); -} - -/** - * @ingroup iface_wl_region - * - * Destroy the region. This will invalidate the object ID. - */ -static inline void -wl_region_destroy(struct wl_region *wl_region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_region - * - * Add the specified rectangle to the region. - */ -static inline void -wl_region_add(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_ADD, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_region - * - * Subtract the specified rectangle from the region. - */ -static inline void -wl_region_subtract(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_SUBTRACT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); -} - -#ifndef WL_SUBCOMPOSITOR_ERROR_ENUM -#define WL_SUBCOMPOSITOR_ERROR_ENUM -enum wl_subcompositor_error { - /** - * the to-be sub-surface is invalid - */ - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE = 0, - /** - * the to-be sub-surface parent is invalid - */ - WL_SUBCOMPOSITOR_ERROR_BAD_PARENT = 1, -}; -#endif /* WL_SUBCOMPOSITOR_ERROR_ENUM */ - -#define WL_SUBCOMPOSITOR_DESTROY 0 -#define WL_SUBCOMPOSITOR_GET_SUBSURFACE 1 - - -/** - * @ingroup iface_wl_subcompositor - */ -#define WL_SUBCOMPOSITOR_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subcompositor - */ -#define WL_SUBCOMPOSITOR_GET_SUBSURFACE_SINCE_VERSION 1 - -/** @ingroup iface_wl_subcompositor */ -static inline void -wl_subcompositor_set_user_data(struct wl_subcompositor *wl_subcompositor, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_subcompositor, user_data); -} - -/** @ingroup iface_wl_subcompositor */ -static inline void * -wl_subcompositor_get_user_data(struct wl_subcompositor *wl_subcompositor) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_subcompositor); -} - -static inline uint32_t -wl_subcompositor_get_version(struct wl_subcompositor *wl_subcompositor) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_subcompositor); -} - -/** - * @ingroup iface_wl_subcompositor - * - * Informs the server that the client will not be using this - * protocol object anymore. This does not affect any other - * objects, wl_subsurface objects included. - */ -static inline void -wl_subcompositor_destroy(struct wl_subcompositor *wl_subcompositor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, - WL_SUBCOMPOSITOR_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_subcompositor - * - * Create a sub-surface interface for the given surface, and - * associate it with the given parent surface. This turns a - * plain wl_surface into a sub-surface. - * - * The to-be sub-surface must not already have another role, and it - * must not have an existing wl_subsurface object. Otherwise the - * bad_surface protocol error is raised. - * - * Adding sub-surfaces to a parent is a double-buffered operation on the - * parent (see wl_surface.commit). The effect of adding a sub-surface - * becomes visible on the next time the state of the parent surface is - * applied. - * - * The parent surface must not be one of the child surface's descendants, - * and the parent must be different from the child surface, otherwise the - * bad_parent protocol error is raised. - * - * This request modifies the behaviour of wl_surface.commit request on - * the sub-surface, see the documentation on wl_subsurface interface. - */ -static inline struct wl_subsurface * -wl_subcompositor_get_subsurface(struct wl_subcompositor *wl_subcompositor, struct wl_surface *surface, struct wl_surface *parent) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, - WL_SUBCOMPOSITOR_GET_SUBSURFACE, &wl_subsurface_interface, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), 0, NULL, surface, parent); - - return (struct wl_subsurface *) id; -} - -#ifndef WL_SUBSURFACE_ERROR_ENUM -#define WL_SUBSURFACE_ERROR_ENUM -enum wl_subsurface_error { - /** - * wl_surface is not a sibling or the parent - */ - WL_SUBSURFACE_ERROR_BAD_SURFACE = 0, -}; -#endif /* WL_SUBSURFACE_ERROR_ENUM */ - -#define WL_SUBSURFACE_DESTROY 0 -#define WL_SUBSURFACE_SET_POSITION 1 -#define WL_SUBSURFACE_PLACE_ABOVE 2 -#define WL_SUBSURFACE_PLACE_BELOW 3 -#define WL_SUBSURFACE_SET_SYNC 4 -#define WL_SUBSURFACE_SET_DESYNC 5 - - -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_POSITION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_PLACE_ABOVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_PLACE_BELOW_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_SYNC_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_DESYNC_SINCE_VERSION 1 - -/** @ingroup iface_wl_subsurface */ -static inline void -wl_subsurface_set_user_data(struct wl_subsurface *wl_subsurface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_subsurface, user_data); -} - -/** @ingroup iface_wl_subsurface */ -static inline void * -wl_subsurface_get_user_data(struct wl_subsurface *wl_subsurface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_subsurface); -} - -static inline uint32_t -wl_subsurface_get_version(struct wl_subsurface *wl_subsurface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_subsurface); -} - -/** - * @ingroup iface_wl_subsurface - * - * The sub-surface interface is removed from the wl_surface object - * that was turned into a sub-surface with a - * wl_subcompositor.get_subsurface request. The wl_surface's association - * to the parent is deleted. The wl_surface is unmapped immediately. - */ -static inline void -wl_subsurface_destroy(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_subsurface - * - * This schedules a sub-surface position change. - * The sub-surface will be moved so that its origin (top left - * corner pixel) will be at the location x, y of the parent surface - * coordinate system. The coordinates are not restricted to the parent - * surface area. Negative values are allowed. - * - * The scheduled coordinates will take effect whenever the state of the - * parent surface is applied. When this happens depends on whether the - * parent surface is in synchronized mode or not. See - * wl_subsurface.set_sync and wl_subsurface.set_desync for details. - * - * If more than one set_position request is invoked by the client before - * the commit of the parent surface, the position of a new request always - * replaces the scheduled position from any previous request. - * - * The initial position is 0, 0. - */ -static inline void -wl_subsurface_set_position(struct wl_subsurface *wl_subsurface, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, x, y); -} - -/** - * @ingroup iface_wl_subsurface - * - * This sub-surface is taken from the stack, and put back just - * above the reference surface, changing the z-order of the sub-surfaces. - * The reference surface must be one of the sibling surfaces, or the - * parent surface. Using any other surface, including this sub-surface, - * will cause a protocol error. - * - * The z-order is double-buffered. Requests are handled in order and - * applied immediately to a pending state. The final pending state is - * copied to the active state the next time the state of the parent - * surface is applied. When this happens depends on whether the parent - * surface is in synchronized mode or not. See wl_subsurface.set_sync and - * wl_subsurface.set_desync for details. - * - * A new sub-surface is initially added as the top-most in the stack - * of its siblings and parent. - */ -static inline void -wl_subsurface_place_above(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_PLACE_ABOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); -} - -/** - * @ingroup iface_wl_subsurface - * - * The sub-surface is placed just below the reference surface. - * See wl_subsurface.place_above. - */ -static inline void -wl_subsurface_place_below(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_PLACE_BELOW, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); -} - -/** - * @ingroup iface_wl_subsurface - * - * Change the commit behaviour of the sub-surface to synchronized - * mode, also described as the parent dependent mode. - * - * In synchronized mode, wl_surface.commit on a sub-surface will - * accumulate the committed state in a cache, but the state will - * not be applied and hence will not change the compositor output. - * The cached state is applied to the sub-surface immediately after - * the parent surface's state is applied. This ensures atomic - * updates of the parent and all its synchronized sub-surfaces. - * Applying the cached state will invalidate the cache, so further - * parent surface commits do not (re-)apply old state. - * - * See wl_subsurface for the recursive effect of this mode. - */ -static inline void -wl_subsurface_set_sync(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); -} - -/** - * @ingroup iface_wl_subsurface - * - * Change the commit behaviour of the sub-surface to desynchronized - * mode, also described as independent or freely running mode. - * - * In desynchronized mode, wl_surface.commit on a sub-surface will - * apply the pending state directly, without caching, as happens - * normally with a wl_surface. Calling wl_surface.commit on the - * parent surface has no effect on the sub-surface's wl_surface - * state. This mode allows a sub-surface to be updated on its own. - * - * If cached state exists when wl_surface.commit is called in - * desynchronized mode, the pending state is added to the cached - * state, and applied as a whole. This invalidates the cache. - * - * Note: even if a sub-surface is set to desynchronized, a parent - * sub-surface may override it to behave as synchronized. For details, - * see wl_subsurface. - * - * If a surface's parent surface behaves as desynchronized, then - * the cached state is applied on set_desync. - */ -static inline void -wl_subsurface_set_desync(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_DESYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h deleted file mode 100644 index ceece5a2b..000000000 --- a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2020 Aleix Pol Gonzalez - * Copyright © 2020 Carlos Garnacho - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_activation_token_v1_interface; - -static const struct wl_interface *xdg_activation_v1_types[] = { - NULL, - &xdg_activation_token_v1_interface, - NULL, - &wl_surface_interface, - NULL, - &wl_seat_interface, - &wl_surface_interface, -}; - -static const struct wl_message xdg_activation_v1_requests[] = { - { "destroy", "", xdg_activation_v1_types + 0 }, - { "get_activation_token", "n", xdg_activation_v1_types + 1 }, - { "activate", "so", xdg_activation_v1_types + 2 }, -}; - -WL_PRIVATE const struct wl_interface xdg_activation_v1_interface = { - "xdg_activation_v1", 1, - 3, xdg_activation_v1_requests, - 0, NULL, -}; - -static const struct wl_message xdg_activation_token_v1_requests[] = { - { "set_serial", "uo", xdg_activation_v1_types + 4 }, - { "set_app_id", "s", xdg_activation_v1_types + 0 }, - { "set_surface", "o", xdg_activation_v1_types + 6 }, - { "commit", "", xdg_activation_v1_types + 0 }, - { "destroy", "", xdg_activation_v1_types + 0 }, -}; - -static const struct wl_message xdg_activation_token_v1_events[] = { - { "done", "s", xdg_activation_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_activation_token_v1_interface = { - "xdg_activation_token_v1", 1, - 5, xdg_activation_token_v1_requests, - 1, xdg_activation_token_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h b/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h deleted file mode 100644 index b26c548c9..000000000 --- a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h +++ /dev/null @@ -1,415 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H -#define XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_activation_v1 The xdg_activation_v1 protocol - * Protocol for requesting activation of surfaces - * - * @section page_desc_xdg_activation_v1 Description - * - * The way for a client to pass focus to another toplevel is as follows. - * - * The client that intends to activate another toplevel uses the - * xdg_activation_v1.get_activation_token request to get an activation token. - * This token is then forwarded to the client, which is supposed to activate - * one of its surfaces, through a separate band of communication. - * - * One established way of doing this is through the XDG_ACTIVATION_TOKEN - * environment variable of a newly launched child process. The child process - * should unset the environment variable again right after reading it out in - * order to avoid propagating it to other child processes. - * - * Another established way exists for Applications implementing the D-Bus - * interface org.freedesktop.Application, which should get their token under - * activation-token on their platform_data. - * - * In general activation tokens may be transferred across clients through - * means not described in this protocol. - * - * The client to be activated will then pass the token - * it received to the xdg_activation_v1.activate request. The compositor can - * then use this token to decide how to react to the activation request. - * - * The token the activating client gets may be ineffective either already at - * the time it receives it, for example if it was not focused, for focus - * stealing prevention. The activating client will have no way to discover - * the validity of the token, and may still forward it to the to be activated - * client. - * - * The created activation token may optionally get information attached to it - * that can be used by the compositor to identify the application that we - * intend to activate. This can for example be used to display a visual hint - * about what application is being started. - * - * Warning! The protocol described in this file is currently in the testing - * phase. Backward compatible changes may be added together with the - * corresponding interface version bump. Backward incompatible changes can - * only be done by creating a new major version of the extension. - * - * @section page_ifaces_xdg_activation_v1 Interfaces - * - @subpage page_iface_xdg_activation_v1 - interface for activating surfaces - * - @subpage page_iface_xdg_activation_token_v1 - an exported activation handle - * @section page_copyright_xdg_activation_v1 Copyright - *
- *
- * Copyright © 2020 Aleix Pol Gonzalez 
- * Copyright © 2020 Carlos Garnacho 
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_seat; -struct wl_surface; -struct xdg_activation_token_v1; -struct xdg_activation_v1; - -#ifndef XDG_ACTIVATION_V1_INTERFACE -#define XDG_ACTIVATION_V1_INTERFACE -/** - * @page page_iface_xdg_activation_v1 xdg_activation_v1 - * @section page_iface_xdg_activation_v1_desc Description - * - * A global interface used for informing the compositor about applications - * being activated or started, or for applications to request to be - * activated. - * @section page_iface_xdg_activation_v1_api API - * See @ref iface_xdg_activation_v1. - */ -/** - * @defgroup iface_xdg_activation_v1 The xdg_activation_v1 interface - * - * A global interface used for informing the compositor about applications - * being activated or started, or for applications to request to be - * activated. - */ -extern const struct wl_interface xdg_activation_v1_interface; -#endif -#ifndef XDG_ACTIVATION_TOKEN_V1_INTERFACE -#define XDG_ACTIVATION_TOKEN_V1_INTERFACE -/** - * @page page_iface_xdg_activation_token_v1 xdg_activation_token_v1 - * @section page_iface_xdg_activation_token_v1_desc Description - * - * An object for setting up a token and receiving a token handle that can - * be passed as an activation token to another client. - * - * The object is created using the xdg_activation_v1.get_activation_token - * request. This object should then be populated with the app_id, surface - * and serial information and committed. The compositor shall then issue a - * done event with the token. In case the request's parameters are invalid, - * the compositor will provide an invalid token. - * @section page_iface_xdg_activation_token_v1_api API - * See @ref iface_xdg_activation_token_v1. - */ -/** - * @defgroup iface_xdg_activation_token_v1 The xdg_activation_token_v1 interface - * - * An object for setting up a token and receiving a token handle that can - * be passed as an activation token to another client. - * - * The object is created using the xdg_activation_v1.get_activation_token - * request. This object should then be populated with the app_id, surface - * and serial information and committed. The compositor shall then issue a - * done event with the token. In case the request's parameters are invalid, - * the compositor will provide an invalid token. - */ -extern const struct wl_interface xdg_activation_token_v1_interface; -#endif - -#define XDG_ACTIVATION_V1_DESTROY 0 -#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN 1 -#define XDG_ACTIVATION_V1_ACTIVATE 2 - - -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_ACTIVATE_SINCE_VERSION 1 - -/** @ingroup iface_xdg_activation_v1 */ -static inline void -xdg_activation_v1_set_user_data(struct xdg_activation_v1 *xdg_activation_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_v1, user_data); -} - -/** @ingroup iface_xdg_activation_v1 */ -static inline void * -xdg_activation_v1_get_user_data(struct xdg_activation_v1 *xdg_activation_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_v1); -} - -static inline uint32_t -xdg_activation_v1_get_version(struct xdg_activation_v1 *xdg_activation_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1); -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Notify the compositor that the xdg_activation object will no longer be - * used. - * - * The child objects created via this interface are unaffected and should - * be destroyed separately. - */ -static inline void -xdg_activation_v1_destroy(struct xdg_activation_v1 *xdg_activation_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Creates an xdg_activation_token_v1 object that will provide - * the initiating client with a unique token for this activation. This - * token should be offered to the clients to be activated. - */ -static inline struct xdg_activation_token_v1 * -xdg_activation_v1_get_activation_token(struct xdg_activation_v1 *xdg_activation_v1) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN, &xdg_activation_token_v1_interface, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, NULL); - - return (struct xdg_activation_token_v1 *) id; -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Requests surface activation. It's up to the compositor to display - * this information as desired, for example by placing the surface above - * the rest. - * - * The compositor may know who requested this by checking the activation - * token and might decide not to follow through with the activation if it's - * considered unwanted. - * - * Compositors can ignore unknown activation tokens when an invalid - * token is passed. - */ -static inline void -xdg_activation_v1_activate(struct xdg_activation_v1 *xdg_activation_v1, const char *token, struct wl_surface *surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_ACTIVATE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, token, surface); -} - -#ifndef XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM -#define XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM -enum xdg_activation_token_v1_error { - /** - * The token has already been used previously - */ - XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED = 0, -}; -#endif /* XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_activation_token_v1 - * @struct xdg_activation_token_v1_listener - */ -struct xdg_activation_token_v1_listener { - /** - * the exported activation token - * - * The 'done' event contains the unique token of this activation - * request and notifies that the provider is done. - * @param token the exported activation token - */ - void (*done)(void *data, - struct xdg_activation_token_v1 *xdg_activation_token_v1, - const char *token); -}; - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -static inline int -xdg_activation_token_v1_add_listener(struct xdg_activation_token_v1 *xdg_activation_token_v1, - const struct xdg_activation_token_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_activation_token_v1, - (void (**)(void)) listener, data); -} - -#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL 0 -#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID 1 -#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE 2 -#define XDG_ACTIVATION_TOKEN_V1_COMMIT 3 -#define XDG_ACTIVATION_TOKEN_V1_DESTROY 4 - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_DONE_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_COMMIT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_xdg_activation_token_v1 */ -static inline void -xdg_activation_token_v1_set_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_token_v1, user_data); -} - -/** @ingroup iface_xdg_activation_token_v1 */ -static inline void * -xdg_activation_token_v1_get_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_token_v1); -} - -static inline uint32_t -xdg_activation_token_v1_get_version(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Provides information about the seat and serial event that requested the - * token. - * - * The serial can come from an input or focus event. For instance, if a - * click triggers the launch of a third-party client, the launcher client - * should send a set_serial request with the serial and seat from the - * wl_pointer.button event. - * - * Some compositors might refuse to activate toplevels when the token - * doesn't have a valid and recent enough event serial. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_serial(struct xdg_activation_token_v1 *xdg_activation_token_v1, uint32_t serial, struct wl_seat *seat) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_SERIAL, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, serial, seat); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * The requesting client can specify an app_id to associate the token - * being created with it. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_app_id(struct xdg_activation_token_v1 *xdg_activation_token_v1, const char *app_id) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, app_id); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * This request sets the surface requesting the activation. Note, this is - * different from the surface that will be activated. - * - * Some compositors might refuse to activate toplevels when the token - * doesn't have a requesting surface. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_surface(struct xdg_activation_token_v1 *xdg_activation_token_v1, struct wl_surface *surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, surface); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Requests an activation token based on the different parameters that - * have been offered through set_serial, set_surface and set_app_id. - */ -static inline void -xdg_activation_token_v1_commit(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Notify the compositor that the xdg_activation_token_v1 object will no - * longer be used. The received token stays valid. - */ -static inline void -xdg_activation_token_v1_destroy(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h deleted file mode 100644 index 85496afa3..000000000 --- a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2018 Simon Ser - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface xdg_toplevel_interface; -extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; - -static const struct wl_interface *xdg_decoration_unstable_v1_types[] = { - NULL, - &zxdg_toplevel_decoration_v1_interface, - &xdg_toplevel_interface, -}; - -static const struct wl_message zxdg_decoration_manager_v1_requests[] = { - { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, - { "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = { - "zxdg_decoration_manager_v1", 1, - 2, zxdg_decoration_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { - { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, - { "set_mode", "u", xdg_decoration_unstable_v1_types + 0 }, - { "unset_mode", "", xdg_decoration_unstable_v1_types + 0 }, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { - { "configure", "u", xdg_decoration_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = { - "zxdg_toplevel_decoration_v1", 1, - 3, zxdg_toplevel_decoration_v1_requests, - 1, zxdg_toplevel_decoration_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h deleted file mode 100644 index 1aa6a0db1..000000000 --- a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h +++ /dev/null @@ -1,378 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol - * @section page_ifaces_xdg_decoration_unstable_v1 Interfaces - * - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager - * - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface - * @section page_copyright_xdg_decoration_unstable_v1 Copyright - *
- *
- * Copyright © 2018 Simon Ser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct xdg_toplevel; -struct zxdg_decoration_manager_v1; -struct zxdg_toplevel_decoration_v1; - -#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE -#define ZXDG_DECORATION_MANAGER_V1_INTERFACE -/** - * @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1 - * @section page_iface_zxdg_decoration_manager_v1_desc Description - * - * This interface allows a compositor to announce support for server-side - * decorations. - * - * A window decoration is a set of window controls as deemed appropriate by - * the party managing them, such as user interface components used to move, - * resize and change a window's state. - * - * A client can use this protocol to request being decorated by a supporting - * compositor. - * - * If compositor and client do not negotiate the use of a server-side - * decoration using this protocol, clients continue to self-decorate as they - * see fit. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - * @section page_iface_zxdg_decoration_manager_v1_api API - * See @ref iface_zxdg_decoration_manager_v1. - */ -/** - * @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface - * - * This interface allows a compositor to announce support for server-side - * decorations. - * - * A window decoration is a set of window controls as deemed appropriate by - * the party managing them, such as user interface components used to move, - * resize and change a window's state. - * - * A client can use this protocol to request being decorated by a supporting - * compositor. - * - * If compositor and client do not negotiate the use of a server-side - * decoration using this protocol, clients continue to self-decorate as they - * see fit. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - */ -extern const struct wl_interface zxdg_decoration_manager_v1_interface; -#endif -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE -#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE -/** - * @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1 - * @section page_iface_zxdg_toplevel_decoration_v1_desc Description - * - * The decoration object allows the compositor to toggle server-side window - * decorations for a toplevel surface. The client can request to switch to - * another mode. - * - * The xdg_toplevel_decoration object must be destroyed before its - * xdg_toplevel. - * @section page_iface_zxdg_toplevel_decoration_v1_api API - * See @ref iface_zxdg_toplevel_decoration_v1. - */ -/** - * @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface - * - * The decoration object allows the compositor to toggle server-side window - * decorations for a toplevel surface. The client can request to switch to - * another mode. - * - * The xdg_toplevel_decoration object must be destroyed before its - * xdg_toplevel. - */ -extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; -#endif - -#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0 -#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1 - - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - */ -#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_decoration_manager_v1 - */ -#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_decoration_manager_v1 */ -static inline void -zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data); -} - -/** @ingroup iface_zxdg_decoration_manager_v1 */ -static inline void * -zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1); -} - -static inline uint32_t -zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1); -} - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - * - * Destroy the decoration manager. This doesn't destroy objects created - * with the manager. - */ -static inline void -zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, - ZXDG_DECORATION_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - * - * Create a new decoration object associated with the given toplevel. - * - * Creating an xdg_toplevel_decoration from an xdg_toplevel which has a - * buffer attached or committed is a client error, and any attempts by a - * client to attach or manipulate a buffer prior to the first - * xdg_toplevel_decoration.configure event must also be treated as - * errors. - */ -static inline struct zxdg_toplevel_decoration_v1 * -zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, - ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), 0, NULL, toplevel); - - return (struct zxdg_toplevel_decoration_v1 *) id; -} - -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM -#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM -enum zxdg_toplevel_decoration_v1_error { - /** - * xdg_toplevel has a buffer attached before configure - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0, - /** - * xdg_toplevel already has a decoration object - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1, - /** - * xdg_toplevel destroyed before the decoration object - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2, -}; -#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */ - -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM -#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * window decoration modes - * - * These values describe window decoration modes. - */ -enum zxdg_toplevel_decoration_v1_mode { - /** - * no server-side window decoration - */ - ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1, - /** - * server-side window decoration - */ - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2, -}; -#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */ - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * @struct zxdg_toplevel_decoration_v1_listener - */ -struct zxdg_toplevel_decoration_v1_listener { - /** - * suggest a surface change - * - * The configure event asks the client to change its decoration - * mode. The configured state should not be applied immediately. - * Clients must send an ack_configure in response to this event. - * See xdg_surface.configure and xdg_surface.ack_configure for - * details. - * - * A configure event can be sent at any time. The specified mode - * must be obeyed by the client. - * @param mode the decoration mode - */ - void (*configure)(void *data, - struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - uint32_t mode); -}; - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -static inline int -zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - const struct zxdg_toplevel_decoration_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1, - (void (**)(void)) listener, data); -} - -#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0 -#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1 -#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2 - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_toplevel_decoration_v1 */ -static inline void -zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data); -} - -/** @ingroup iface_zxdg_toplevel_decoration_v1 */ -static inline void * -zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1); -} - -static inline uint32_t -zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Switch back to a mode without any server-side decorations at the next - * commit. - */ -static inline void -zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Set the toplevel surface decoration mode. This informs the compositor - * that the client prefers the provided decoration mode. - * - * After requesting a decoration mode, the compositor will respond by - * emitting an xdg_surface.configure event. The client should then update - * its content, drawing it without decorations if the received mode is - * server-side decorations. The client must also acknowledge the configure - * when committing the new content (see xdg_surface.ack_configure). - * - * The compositor can decide not to use the client's mode and enforce a - * different mode instead. - * - * Clients whose decoration mode depend on the xdg_toplevel state may send - * a set_mode request in response to an xdg_surface.configure event and wait - * for the next xdg_surface.configure event to prevent unwanted state. - * Such clients are responsible for preventing configure loops and must - * make sure not to send multiple successive set_mode requests with the - * same decoration mode. - */ -static inline void -zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0, mode); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Unset the toplevel surface decoration mode. This informs the compositor - * that the client doesn't prefer a particular decoration mode. - * - * This request has the same semantics as set_mode. - */ -static inline void -zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h deleted file mode 100644 index d698c2ca5..000000000 --- a/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h +++ /dev/null @@ -1,184 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2008-2013 Kristian Høgsberg - * Copyright © 2013 Rafael Antognolli - * Copyright © 2013 Jasper St. Pierre - * Copyright © 2010-2013 Intel Corporation - * Copyright © 2015-2017 Samsung Electronics Co., Ltd - * Copyright © 2015-2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_popup_interface; -extern const struct wl_interface xdg_positioner_interface; -extern const struct wl_interface xdg_surface_interface; -extern const struct wl_interface xdg_toplevel_interface; - -static const struct wl_interface *xdg_shell_types[] = { - NULL, - NULL, - NULL, - NULL, - &xdg_positioner_interface, - &xdg_surface_interface, - &wl_surface_interface, - &xdg_toplevel_interface, - &xdg_popup_interface, - &xdg_surface_interface, - &xdg_positioner_interface, - &xdg_toplevel_interface, - &wl_seat_interface, - NULL, - NULL, - NULL, - &wl_seat_interface, - NULL, - &wl_seat_interface, - NULL, - NULL, - &wl_output_interface, - &wl_seat_interface, - NULL, - &xdg_positioner_interface, - NULL, -}; - -static const struct wl_message xdg_wm_base_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "create_positioner", "n", xdg_shell_types + 4 }, - { "get_xdg_surface", "no", xdg_shell_types + 5 }, - { "pong", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_wm_base_events[] = { - { "ping", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { - "xdg_wm_base", 6, - 4, xdg_wm_base_requests, - 1, xdg_wm_base_events, -}; - -static const struct wl_message xdg_positioner_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_size", "ii", xdg_shell_types + 0 }, - { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, - { "set_anchor", "u", xdg_shell_types + 0 }, - { "set_gravity", "u", xdg_shell_types + 0 }, - { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, - { "set_offset", "ii", xdg_shell_types + 0 }, - { "set_reactive", "3", xdg_shell_types + 0 }, - { "set_parent_size", "3ii", xdg_shell_types + 0 }, - { "set_parent_configure", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_positioner_interface = { - "xdg_positioner", 6, - 10, xdg_positioner_requests, - 0, NULL, -}; - -static const struct wl_message xdg_surface_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "get_toplevel", "n", xdg_shell_types + 7 }, - { "get_popup", "n?oo", xdg_shell_types + 8 }, - { "set_window_geometry", "iiii", xdg_shell_types + 0 }, - { "ack_configure", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_surface_events[] = { - { "configure", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_surface_interface = { - "xdg_surface", 6, - 5, xdg_surface_requests, - 1, xdg_surface_events, -}; - -static const struct wl_message xdg_toplevel_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_parent", "?o", xdg_shell_types + 11 }, - { "set_title", "s", xdg_shell_types + 0 }, - { "set_app_id", "s", xdg_shell_types + 0 }, - { "show_window_menu", "ouii", xdg_shell_types + 12 }, - { "move", "ou", xdg_shell_types + 16 }, - { "resize", "ouu", xdg_shell_types + 18 }, - { "set_max_size", "ii", xdg_shell_types + 0 }, - { "set_min_size", "ii", xdg_shell_types + 0 }, - { "set_maximized", "", xdg_shell_types + 0 }, - { "unset_maximized", "", xdg_shell_types + 0 }, - { "set_fullscreen", "?o", xdg_shell_types + 21 }, - { "unset_fullscreen", "", xdg_shell_types + 0 }, - { "set_minimized", "", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_toplevel_events[] = { - { "configure", "iia", xdg_shell_types + 0 }, - { "close", "", xdg_shell_types + 0 }, - { "configure_bounds", "4ii", xdg_shell_types + 0 }, - { "wm_capabilities", "5a", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { - "xdg_toplevel", 6, - 14, xdg_toplevel_requests, - 4, xdg_toplevel_events, -}; - -static const struct wl_message xdg_popup_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "grab", "ou", xdg_shell_types + 22 }, - { "reposition", "3ou", xdg_shell_types + 24 }, -}; - -static const struct wl_message xdg_popup_events[] = { - { "configure", "iiii", xdg_shell_types + 0 }, - { "popup_done", "", xdg_shell_types + 0 }, - { "repositioned", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_popup_interface = { - "xdg_popup", 6, - 3, xdg_popup_requests, - 3, xdg_popup_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h b/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h deleted file mode 100644 index 1e5a9664b..000000000 --- a/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h +++ /dev/null @@ -1,2307 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_SHELL_CLIENT_PROTOCOL_H -#define XDG_SHELL_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_shell The xdg_shell protocol - * @section page_ifaces_xdg_shell Interfaces - * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces - * - @subpage page_iface_xdg_positioner - child surface positioner - * - @subpage page_iface_xdg_surface - desktop user interface surface base interface - * - @subpage page_iface_xdg_toplevel - toplevel surface - * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus - * @section page_copyright_xdg_shell Copyright - *
- *
- * Copyright © 2008-2013 Kristian Høgsberg
- * Copyright © 2013      Rafael Antognolli
- * Copyright © 2013      Jasper St. Pierre
- * Copyright © 2010-2013 Intel Corporation
- * Copyright © 2015-2017 Samsung Electronics Co., Ltd
- * Copyright © 2015-2017 Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_output; -struct wl_seat; -struct wl_surface; -struct xdg_popup; -struct xdg_positioner; -struct xdg_surface; -struct xdg_toplevel; -struct xdg_wm_base; - -#ifndef XDG_WM_BASE_INTERFACE -#define XDG_WM_BASE_INTERFACE -/** - * @page page_iface_xdg_wm_base xdg_wm_base - * @section page_iface_xdg_wm_base_desc Description - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - * @section page_iface_xdg_wm_base_api API - * See @ref iface_xdg_wm_base. - */ -/** - * @defgroup iface_xdg_wm_base The xdg_wm_base interface - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - */ -extern const struct wl_interface xdg_wm_base_interface; -#endif -#ifndef XDG_POSITIONER_INTERFACE -#define XDG_POSITIONER_INTERFACE -/** - * @page page_iface_xdg_positioner xdg_positioner - * @section page_iface_xdg_positioner_desc Description - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - * @section page_iface_xdg_positioner_api API - * See @ref iface_xdg_positioner. - */ -/** - * @defgroup iface_xdg_positioner The xdg_positioner interface - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - */ -extern const struct wl_interface xdg_positioner_interface; -#endif -#ifndef XDG_SURFACE_INTERFACE -#define XDG_SURFACE_INTERFACE -/** - * @page page_iface_xdg_surface xdg_surface - * @section page_iface_xdg_surface_desc Description - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_surface from a wl_surface which has a buffer attached or - * committed is a client error, and any attempts by a client to attach or - * manipulate a buffer prior to the first xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up, the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - * @section page_iface_xdg_surface_api API - * See @ref iface_xdg_surface. - */ -/** - * @defgroup iface_xdg_surface The xdg_surface interface - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_surface from a wl_surface which has a buffer attached or - * committed is a client error, and any attempts by a client to attach or - * manipulate a buffer prior to the first xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up, the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - */ -extern const struct wl_interface xdg_surface_interface; -#endif -#ifndef XDG_TOPLEVEL_INTERFACE -#define XDG_TOPLEVEL_INTERFACE -/** - * @page page_iface_xdg_toplevel xdg_toplevel - * @section page_iface_xdg_toplevel_desc Description - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by perfoming a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - * @section page_iface_xdg_toplevel_api API - * See @ref iface_xdg_toplevel. - */ -/** - * @defgroup iface_xdg_toplevel The xdg_toplevel interface - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by perfoming a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - */ -extern const struct wl_interface xdg_toplevel_interface; -#endif -#ifndef XDG_POPUP_INTERFACE -#define XDG_POPUP_INTERFACE -/** - * @page page_iface_xdg_popup xdg_popup - * @section page_iface_xdg_popup_desc Description - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - * @section page_iface_xdg_popup_api API - * See @ref iface_xdg_popup. - */ -/** - * @defgroup iface_xdg_popup The xdg_popup interface - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - */ -extern const struct wl_interface xdg_popup_interface; -#endif - -#ifndef XDG_WM_BASE_ERROR_ENUM -#define XDG_WM_BASE_ERROR_ENUM -enum xdg_wm_base_error { - /** - * given wl_surface has another role - */ - XDG_WM_BASE_ERROR_ROLE = 0, - /** - * xdg_wm_base was destroyed before children - */ - XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, - /** - * the client tried to map or destroy a non-topmost popup - */ - XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, - /** - * the client specified an invalid popup parent surface - */ - XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, - /** - * the client provided an invalid surface state - */ - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, - /** - * the client provided an invalid positioner - */ - XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, - /** - * the client didn’t respond to a ping event in time - */ - XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, -}; -#endif /* XDG_WM_BASE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_wm_base - * @struct xdg_wm_base_listener - */ -struct xdg_wm_base_listener { - /** - * check if the client is alive - * - * The ping event asks the client if it's still alive. Pass the - * serial specified in the event back to the compositor by sending - * a "pong" request back with the specified serial. See - * xdg_wm_base.pong. - * - * Compositors can use this to determine if the client is still - * alive. It's unspecified what will happen if the client doesn't - * respond to the ping request, or in what timeframe. Clients - * should try to respond in a reasonable amount of time. The - * “unresponsive” error is provided for compositors that wish - * to disconnect unresponsive clients. - * - * A compositor is free to ping in any way it wants, but a client - * must always respond to any xdg_wm_base object it created. - * @param serial pass this to the pong request - */ - void (*ping)(void *data, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_wm_base - */ -static inline int -xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, - const struct xdg_wm_base_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, - (void (**)(void)) listener, data); -} - -#define XDG_WM_BASE_DESTROY 0 -#define XDG_WM_BASE_CREATE_POSITIONER 1 -#define XDG_WM_BASE_GET_XDG_SURFACE 2 -#define XDG_WM_BASE_PONG 3 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PING_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PONG_SINCE_VERSION 1 - -/** @ingroup iface_xdg_wm_base */ -static inline void -xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); -} - -/** @ingroup iface_xdg_wm_base */ -static inline void * -xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); -} - -static inline uint32_t -xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Destroy this xdg_wm_base object. - * - * Destroying a bound xdg_wm_base object while there are surfaces - * still alive created by this xdg_wm_base object instance is illegal - * and will result in a defunct_surfaces error. - */ -static inline void -xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Create a positioner object. A positioner object is used to position - * surfaces relative to some parent surface. See the interface description - * and xdg_surface.get_popup for details. - */ -static inline struct xdg_positioner * -xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL); - - return (struct xdg_positioner *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * This creates an xdg_surface for the given surface. While xdg_surface - * itself is not a role, the corresponding surface may only be assigned - * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is - * illegal to create an xdg_surface for a wl_surface which already has an - * assigned role and this will result in a role error. - * - * This creates an xdg_surface for the given surface. An xdg_surface is - * used as basis to define a role to a given surface, such as xdg_toplevel - * or xdg_popup. It also manages functionality shared between xdg_surface - * based surface roles. - * - * See the documentation of xdg_surface for more details about what an - * xdg_surface is and how it is used. - */ -static inline struct xdg_surface * -xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL, surface); - - return (struct xdg_surface *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * A client must respond to a ping event with a pong request or - * the client may be deemed unresponsive. See xdg_wm_base.ping - * and xdg_wm_base.error.unresponsive. - */ -static inline void -xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, serial); -} - -#ifndef XDG_POSITIONER_ERROR_ENUM -#define XDG_POSITIONER_ERROR_ENUM -enum xdg_positioner_error { - /** - * invalid input provided - */ - XDG_POSITIONER_ERROR_INVALID_INPUT = 0, -}; -#endif /* XDG_POSITIONER_ERROR_ENUM */ - -#ifndef XDG_POSITIONER_ANCHOR_ENUM -#define XDG_POSITIONER_ANCHOR_ENUM -enum xdg_positioner_anchor { - XDG_POSITIONER_ANCHOR_NONE = 0, - XDG_POSITIONER_ANCHOR_TOP = 1, - XDG_POSITIONER_ANCHOR_BOTTOM = 2, - XDG_POSITIONER_ANCHOR_LEFT = 3, - XDG_POSITIONER_ANCHOR_RIGHT = 4, - XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, - XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, - XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, - XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_ANCHOR_ENUM */ - -#ifndef XDG_POSITIONER_GRAVITY_ENUM -#define XDG_POSITIONER_GRAVITY_ENUM -enum xdg_positioner_gravity { - XDG_POSITIONER_GRAVITY_NONE = 0, - XDG_POSITIONER_GRAVITY_TOP = 1, - XDG_POSITIONER_GRAVITY_BOTTOM = 2, - XDG_POSITIONER_GRAVITY_LEFT = 3, - XDG_POSITIONER_GRAVITY_RIGHT = 4, - XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, - XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, - XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, - XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_GRAVITY_ENUM */ - -#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -/** - * @ingroup iface_xdg_positioner - * constraint adjustments - * - * The constraint adjustment value define ways the compositor will adjust - * the position of the surface, if the unadjusted position would result - * in the surface being partly constrained. - * - * Whether a surface is considered 'constrained' is left to the compositor - * to determine. For example, the surface may be partly outside the - * compositor's defined 'work area', thus necessitating the child surface's - * position be adjusted until it is entirely inside the work area. - * - * The adjustments can be combined, according to a defined precedence: 1) - * Flip, 2) Slide, 3) Resize. - */ -enum xdg_positioner_constraint_adjustment { - /** - * don't move the child surface when constrained - * - * Don't alter the surface position even if it is constrained on - * some axis, for example partially outside the edge of an output. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, - /** - * move along the x axis until unconstrained - * - * Slide the surface along the x axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the x - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the x axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, - /** - * move along the y axis until unconstrained - * - * Slide the surface along the y axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the y - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the y axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, - /** - * invert the anchor and gravity on the x axis - * - * Invert the anchor and gravity on the x axis if the surface is - * constrained on the x axis. For example, if the left edge of the - * surface is constrained, the gravity is 'left' and the anchor is - * 'left', change the gravity to 'right' and the anchor to 'right'. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_x adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, - /** - * invert the anchor and gravity on the y axis - * - * Invert the anchor and gravity on the y axis if the surface is - * constrained on the y axis. For example, if the bottom edge of - * the surface is constrained, the gravity is 'bottom' and the - * anchor is 'bottom', change the gravity to 'top' and the anchor - * to 'top'. - * - * The adjusted position is calculated given the original anchor - * rectangle and offset, but with the new flipped anchor and - * gravity values. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_y adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, - /** - * horizontally resize the surface - * - * Resize the surface horizontally so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, - /** - * vertically resize the surface - * - * Resize the surface vertically so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, -}; -#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ - -#define XDG_POSITIONER_DESTROY 0 -#define XDG_POSITIONER_SET_SIZE 1 -#define XDG_POSITIONER_SET_ANCHOR_RECT 2 -#define XDG_POSITIONER_SET_ANCHOR 3 -#define XDG_POSITIONER_SET_GRAVITY 4 -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 -#define XDG_POSITIONER_SET_OFFSET 6 -#define XDG_POSITIONER_SET_REACTIVE 7 -#define XDG_POSITIONER_SET_PARENT_SIZE 8 -#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 - - -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 - -/** @ingroup iface_xdg_positioner */ -static inline void -xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); -} - -/** @ingroup iface_xdg_positioner */ -static inline void * -xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); -} - -static inline uint32_t -xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); -} - -/** - * @ingroup iface_xdg_positioner - * - * Notify the compositor that the xdg_positioner will no longer be used. - */ -static inline void -xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the size of the surface that is to be positioned with the positioner - * object. The size is in surface-local coordinates and corresponds to the - * window geometry. See xdg_surface.set_window_geometry. - * - * If a zero or negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the anchor rectangle within the parent surface that the child - * surface will be placed relative to. The rectangle is relative to the - * window geometry as defined by xdg_surface.set_window_geometry of the - * parent surface. - * - * When the xdg_positioner object is used to position a child surface, the - * anchor rectangle may not extend outside the window geometry of the - * positioned child's parent surface. - * - * If a negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR_RECT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines the anchor point for the anchor rectangle. The specified anchor - * is used derive an anchor point that the child surface will be - * positioned relative to. If a corner anchor is set (e.g. 'top_left' or - * 'bottom_right'), the anchor point will be at the specified corner; - * otherwise, the derived anchor point will be centered on the specified - * edge, or in the center of the anchor rectangle if no edge is specified. - */ -static inline void -xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, anchor); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines in what direction a surface should be positioned, relative to - * the anchor point of the parent surface. If a corner gravity is - * specified (e.g. 'bottom_right' or 'top_left'), then the child surface - * will be placed towards the specified gravity; otherwise, the child - * surface will be centered over the anchor point on any axis that had no - * gravity specified. If the gravity is not in the ‘gravity’ enum, an - * invalid_input error is raised. - */ -static inline void -xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_GRAVITY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, gravity); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify how the window should be positioned if the originally intended - * position caused the surface to be constrained, meaning at least - * partially outside positioning boundaries set by the compositor. The - * adjustment is set by constructing a bitmask describing the adjustment to - * be made when the surface is constrained on that axis. - * - * If no bit for one axis is set, the compositor will assume that the child - * surface should not change its position on that axis when constrained. - * - * If more than one bit for one axis is set, the order of how adjustments - * are applied is specified in the corresponding adjustment descriptions. - * - * The default adjustment is none. - */ -static inline void -xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, constraint_adjustment); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the surface position offset relative to the position of the - * anchor on the anchor rectangle and the anchor on the surface. For - * example if the anchor of the anchor rectangle is at (x, y), the surface - * has the gravity bottom|right, and the offset is (ox, oy), the calculated - * surface position will be (x + ox, y + oy). The offset position of the - * surface is the one used for constraint testing. See - * set_constraint_adjustment. - * - * An example use case is placing a popup menu on top of a user interface - * element, while aligning the user interface element of the parent surface - * with some user interface element placed somewhere in the popup surface. - */ -static inline void -xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y); -} - -/** - * @ingroup iface_xdg_positioner - * - * When set reactive, the surface is reconstrained if the conditions used - * for constraining changed, e.g. the parent window moved. - * - * If the conditions changed and the popup was reconstrained, an - * xdg_popup.configure event is sent with updated geometry, followed by an - * xdg_surface.configure event. - */ -static inline void -xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_REACTIVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the parent window geometry the compositor should use when - * positioning the popup. The compositor may use this information to - * determine the future state the popup should be constrained using. If - * this doesn't match the dimension of the parent the popup is eventually - * positioned against, the behavior is undefined. - * - * The arguments are given in the surface-local coordinate space. - */ -static inline void -xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, parent_width, parent_height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the serial of an xdg_surface.configure event this positioner will be - * used in response to. The compositor may use this information together - * with set_parent_size to determine what future state the popup should be - * constrained using. - */ -static inline void -xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, serial); -} - -#ifndef XDG_SURFACE_ERROR_ENUM -#define XDG_SURFACE_ERROR_ENUM -enum xdg_surface_error { - /** - * Surface was not fully constructed - */ - XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, - /** - * Surface was already constructed - */ - XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, - /** - * Attaching a buffer to an unconfigured surface - */ - XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, - /** - * Invalid serial number when acking a configure event - */ - XDG_SURFACE_ERROR_INVALID_SERIAL = 4, - /** - * Width or height was zero or negative - */ - XDG_SURFACE_ERROR_INVALID_SIZE = 5, - /** - * Surface was destroyed before its role object - */ - XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, -}; -#endif /* XDG_SURFACE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_surface - * @struct xdg_surface_listener - */ -struct xdg_surface_listener { - /** - * suggest a surface change - * - * The configure event marks the end of a configure sequence. A - * configure sequence is a set of one or more events configuring - * the state of the xdg_surface, including the final - * xdg_surface.configure event. - * - * Where applicable, xdg_surface surface roles will during a - * configure sequence extend this event as a latched state sent as - * events before the xdg_surface.configure event. Such events - * should be considered to make up a set of atomically applied - * configuration states, where the xdg_surface.configure commits - * the accumulated state. - * - * Clients should arrange their surface for the new states, and - * then send an ack_configure request with the serial sent in this - * configure event at some point before committing the new surface. - * - * If the client receives multiple configure events before it can - * respond to one, it is free to discard all but the last event it - * received. - * @param serial serial of the configure event - */ - void (*configure)(void *data, - struct xdg_surface *xdg_surface, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_surface - */ -static inline int -xdg_surface_add_listener(struct xdg_surface *xdg_surface, - const struct xdg_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, - (void (**)(void)) listener, data); -} - -#define XDG_SURFACE_DESTROY 0 -#define XDG_SURFACE_GET_TOPLEVEL 1 -#define XDG_SURFACE_GET_POPUP 2 -#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 -#define XDG_SURFACE_ACK_CONFIGURE 4 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 - -/** @ingroup iface_xdg_surface */ -static inline void -xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); -} - -/** @ingroup iface_xdg_surface */ -static inline void * -xdg_surface_get_user_data(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); -} - -static inline uint32_t -xdg_surface_get_version(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_surface); -} - -/** - * @ingroup iface_xdg_surface - * - * Destroy the xdg_surface object. An xdg_surface must only be destroyed - * after its role object has been destroyed, otherwise - * a defunct_role_object error is raised. - */ -static inline void -xdg_surface_destroy(struct xdg_surface *xdg_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_toplevel object for the given xdg_surface and gives - * the associated wl_surface the xdg_toplevel role. - * - * See the documentation of xdg_toplevel for more details about what an - * xdg_toplevel is and how it is used. - */ -static inline struct xdg_toplevel * -xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL); - - return (struct xdg_toplevel *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_popup object for the given xdg_surface and gives - * the associated wl_surface the xdg_popup role. - * - * If null is passed as a parent, a parent surface must be specified using - * some other protocol, before committing the initial state. - * - * See the documentation of xdg_popup for more details about what an - * xdg_popup is and how it is used. - */ -static inline struct xdg_popup * -xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_POPUP, &xdg_popup_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL, parent, positioner); - - return (struct xdg_popup *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * The window geometry of a surface is its "visible bounds" from the - * user's perspective. Client-side decorations often have invisible - * portions like drop-shadows which should be ignored for the - * purposes of aligning, placing and constraining windows. - * - * The window geometry is double buffered, and will be applied at the - * time wl_surface.commit of the corresponding wl_surface is called. - * - * When maintaining a position, the compositor should treat the (x, y) - * coordinate of the window geometry as the top left corner of the window. - * A client changing the (x, y) window geometry coordinate should in - * general not alter the position of the window. - * - * Once the window geometry of the surface is set, it is not possible to - * unset it, and it will remain the same until set_window_geometry is - * called again, even if a new subsurface or buffer is attached. - * - * If never set, the value is the full bounds of the surface, - * including any subsurfaces. This updates dynamically on every - * commit. This unset is meant for extremely simple clients. - * - * The arguments are given in the surface-local coordinate space of - * the wl_surface associated with this xdg_surface, and may extend outside - * of the wl_surface itself to mark parts of the subsurface tree as part of - * the window geometry. - * - * When applied, the effective window geometry will be the set window - * geometry clamped to the bounding rectangle of the combined - * geometry of the surface of the xdg_surface and the associated - * subsurfaces. - * - * The effective geometry will not be recalculated unless a new call to - * set_window_geometry is done and the new pending surface state is - * subsequently applied. - * - * The width and height of the effective window geometry must be - * greater than zero. Setting an invalid size will raise an - * invalid_size error. - */ -static inline void -xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_SET_WINDOW_GEOMETRY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_surface - * - * When a configure event is received, if a client commits the - * surface in response to the configure event, then the client - * must make an ack_configure request sometime before the commit - * request, passing along the serial of the configure event. - * - * For instance, for toplevel surfaces the compositor might use this - * information to move a surface to the top left only when the client has - * drawn itself for the maximized or fullscreen state. - * - * If the client receives multiple configure events before it - * can respond to one, it only has to ack the last configure event. - * Acking a configure event that was never sent raises an invalid_serial - * error. - * - * A client is not required to commit immediately after sending - * an ack_configure request - it may even ack_configure several times - * before its next surface commit. - * - * A client may send multiple ack_configure requests before committing, but - * only the last request sent before a commit indicates which configure - * event the client really is responding to. - * - * Sending an ack_configure request consumes the serial number sent with - * the request, as well as serial numbers sent by all configure events - * sent on this xdg_surface prior to the configure event referenced by - * the committed serial. - * - * It is an error to issue multiple ack_configure requests referencing a - * serial from the same configure event, or to issue an ack_configure - * request referencing a serial from a configure event issued before the - * event identified by the last ack_configure request for the same - * xdg_surface. Doing so will raise an invalid_serial error. - */ -static inline void -xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, serial); -} - -#ifndef XDG_TOPLEVEL_ERROR_ENUM -#define XDG_TOPLEVEL_ERROR_ENUM -enum xdg_toplevel_error { - /** - * provided value is not a valid variant of the resize_edge enum - */ - XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, - /** - * invalid parent toplevel - */ - XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, - /** - * client provided an invalid min or max size - */ - XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, -}; -#endif /* XDG_TOPLEVEL_ERROR_ENUM */ - -#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM -#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM -/** - * @ingroup iface_xdg_toplevel - * edge values for resizing - * - * These values are used to indicate which edge of a surface - * is being dragged in a resize operation. - */ -enum xdg_toplevel_resize_edge { - XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, - XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, - XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, - XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, -}; -#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ - -#ifndef XDG_TOPLEVEL_STATE_ENUM -#define XDG_TOPLEVEL_STATE_ENUM -/** - * @ingroup iface_xdg_toplevel - * types of state on the surface - * - * The different state values used on the surface. This is designed for - * state values like maximized, fullscreen. It is paired with the - * configure event to ensure that both the client and the compositor - * setting the state can be synchronized. - * - * States set in this way are double-buffered. They will get applied on - * the next commit. - */ -enum xdg_toplevel_state { - /** - * the surface is maximized - * the surface is maximized - * - * The surface is maximized. The window geometry specified in the - * configure event must be obeyed by the client, or the - * xdg_wm_base.invalid_surface_state error is raised. - * - * The client should draw without shadow or other decoration - * outside of the window geometry. - */ - XDG_TOPLEVEL_STATE_MAXIMIZED = 1, - /** - * the surface is fullscreen - * the surface is fullscreen - * - * The surface is fullscreen. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. For a surface to cover the whole fullscreened area, - * the geometry dimensions must be obeyed by the client. For more - * details, see xdg_toplevel.set_fullscreen. - */ - XDG_TOPLEVEL_STATE_FULLSCREEN = 2, - /** - * the surface is being resized - * the surface is being resized - * - * The surface is being resized. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. Clients that have aspect ratio or cell sizing - * configuration can use a smaller size, however. - */ - XDG_TOPLEVEL_STATE_RESIZING = 3, - /** - * the surface is now activated - * the surface is now activated - * - * Client window decorations should be painted as if the window - * is active. Do not assume this means that the window actually has - * keyboard or pointer focus. - */ - XDG_TOPLEVEL_STATE_ACTIVATED = 4, - /** - * the surface’s left edge is tiled - * - * The window is currently in a tiled layout and the left edge is - * considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_LEFT = 5, - /** - * the surface’s right edge is tiled - * - * The window is currently in a tiled layout and the right edge - * is considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, - /** - * the surface’s top edge is tiled - * - * The window is currently in a tiled layout and the top edge is - * considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_TOP = 7, - /** - * the surface’s bottom edge is tiled - * - * The window is currently in a tiled layout and the bottom edge - * is considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, - /** - * surface repaint is suspended - * - * The surface is currently not ordinarily being repainted; for - * example because its content is occluded by another window, or - * its outputs are switched off due to screen locking. - * @since 6 - */ - XDG_TOPLEVEL_STATE_SUSPENDED = 9, -}; -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION 6 -#endif /* XDG_TOPLEVEL_STATE_ENUM */ - -#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -enum xdg_toplevel_wm_capabilities { - /** - * show_window_menu is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, - /** - * set_maximized and unset_maximized are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, - /** - * set_fullscreen and unset_fullscreen are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, - /** - * set_minimized is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, -}; -#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ - -/** - * @ingroup iface_xdg_toplevel - * @struct xdg_toplevel_listener - */ -struct xdg_toplevel_listener { - /** - * suggest a surface change - * - * This configure event asks the client to resize its toplevel - * surface or to change its state. The configured state should not - * be applied immediately. See xdg_surface.configure for details. - * - * The width and height arguments specify a hint to the window - * about how its surface should be resized in window geometry - * coordinates. See set_window_geometry. - * - * If the width or height arguments are zero, it means the client - * should decide its own window dimension. This may happen when the - * compositor needs to configure the state of the surface but - * doesn't have any information about any previous or expected - * dimension. - * - * The states listed in the event specify how the width/height - * arguments should be interpreted, and possibly how it should be - * drawn. - * - * Clients must send an ack_configure in response to this event. - * See xdg_surface.configure and xdg_surface.ack_configure for - * details. - */ - void (*configure)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array *states); - /** - * surface wants to be closed - * - * The close event is sent by the compositor when the user wants - * the surface to be closed. This should be equivalent to the user - * clicking the close button in client-side decorations, if your - * application has any. - * - * This is only a request that the user intends to close the - * window. The client may choose to ignore this request, or show a - * dialog to ask the user to save their data, etc. - */ - void (*close)(void *data, - struct xdg_toplevel *xdg_toplevel); - /** - * recommended window geometry bounds - * - * The configure_bounds event may be sent prior to a - * xdg_toplevel.configure event to communicate the bounds a window - * geometry size is recommended to constrain to. - * - * The passed width and height are in surface coordinate space. If - * width and height are 0, it means bounds is unknown and - * equivalent to as if no configure_bounds event was ever sent for - * this surface. - * - * The bounds can for example correspond to the size of a monitor - * excluding any panels or other shell components, so that a - * surface isn't created in a way that it cannot fit. - * - * The bounds may change at any point, and in such a case, a new - * xdg_toplevel.configure_bounds will be sent, followed by - * xdg_toplevel.configure and xdg_surface.configure. - * @since 4 - */ - void (*configure_bounds)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height); - /** - * compositor capabilities - * - * This event advertises the capabilities supported by the - * compositor. If a capability isn't supported, clients should hide - * or disable the UI elements that expose this functionality. For - * instance, if the compositor doesn't advertise support for - * minimized toplevels, a button triggering the set_minimized - * request should not be displayed. - * - * The compositor will ignore requests it doesn't support. For - * instance, a compositor which doesn't advertise support for - * minimized will ignore set_minimized requests. - * - * Compositors must send this event once before the first - * xdg_surface.configure event. When the capabilities change, - * compositors must send this event again and then send an - * xdg_surface.configure event. - * - * The configured state should not be applied immediately. See - * xdg_surface.configure for details. - * - * The capabilities are sent as an array of 32-bit unsigned - * integers in native endianness. - * @param capabilities array of 32-bit capabilities - * @since 5 - */ - void (*wm_capabilities)(void *data, - struct xdg_toplevel *xdg_toplevel, - struct wl_array *capabilities); -}; - -/** - * @ingroup iface_xdg_toplevel - */ -static inline int -xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, - const struct xdg_toplevel_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, - (void (**)(void)) listener, data); -} - -#define XDG_TOPLEVEL_DESTROY 0 -#define XDG_TOPLEVEL_SET_PARENT 1 -#define XDG_TOPLEVEL_SET_TITLE 2 -#define XDG_TOPLEVEL_SET_APP_ID 3 -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 -#define XDG_TOPLEVEL_MOVE 5 -#define XDG_TOPLEVEL_RESIZE 6 -#define XDG_TOPLEVEL_SET_MAX_SIZE 7 -#define XDG_TOPLEVEL_SET_MIN_SIZE 8 -#define XDG_TOPLEVEL_SET_MAXIMIZED 9 -#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 -#define XDG_TOPLEVEL_SET_FULLSCREEN 11 -#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 -#define XDG_TOPLEVEL_SET_MINIMIZED 13 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 - -/** @ingroup iface_xdg_toplevel */ -static inline void -xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); -} - -/** @ingroup iface_xdg_toplevel */ -static inline void * -xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); -} - -static inline uint32_t -xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); -} - -/** - * @ingroup iface_xdg_toplevel - * - * This request destroys the role surface and unmaps the surface; - * see "Unmapping" behavior in interface section for details. - */ -static inline void -xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set the "parent" of this surface. This surface should be stacked - * above the parent surface and all other ancestor surfaces. - * - * Parent surfaces should be set on dialogs, toolboxes, or other - * "auxiliary" surfaces, so that the parent is raised when the dialog - * is raised. - * - * Setting a null parent for a child surface unsets its parent. Setting - * a null parent for a surface which currently has no parent is a no-op. - * - * Only mapped surfaces can have child surfaces. Setting a parent which - * is not mapped is equivalent to setting a null parent. If a surface - * becomes unmapped, its children's parent is set to the parent of - * the now-unmapped surface. If the now-unmapped surface has no parent, - * its children's parent is unset. If the now-unmapped surface becomes - * mapped again, its parent-child relationship is not restored. - * - * The parent toplevel must not be one of the child toplevel's - * descendants, and the parent must be different from the child toplevel, - * otherwise the invalid_parent protocol error is raised. - */ -static inline void -xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_PARENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, parent); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a short title for the surface. - * - * This string may be used to identify the surface in a task bar, - * window list, or other user interface elements provided by the - * compositor. - * - * The string must be encoded in UTF-8. - */ -static inline void -xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, title); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set an application identifier for the surface. - * - * The app ID identifies the general class of applications to which - * the surface belongs. The compositor can use this to group multiple - * surfaces together, or to determine how to launch a new application. - * - * For D-Bus activatable applications, the app ID is used as the D-Bus - * service name. - * - * The compositor shell will try to group application surfaces together - * by their app ID. As a best practice, it is suggested to select app - * ID's that match the basename of the application's .desktop file. - * For example, "org.freedesktop.FooViewer" where the .desktop file is - * "org.freedesktop.FooViewer.desktop". - * - * Like other properties, a set_app_id request can be sent after the - * xdg_toplevel has been mapped to update the property. - * - * See the desktop-entry specification [0] for more details on - * application identifiers and how they relate to well-known D-Bus - * names and .desktop files. - * - * [0] https://standards.freedesktop.org/desktop-entry-spec/ - */ -static inline void -xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, app_id); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Clients implementing client-side decorations might want to show - * a context menu when right-clicking on the decorations, giving the - * user a menu that they can use to maximize or minimize the window. - * - * This request asks the compositor to pop up such a window menu at - * the given position, relative to the local surface coordinates of - * the parent surface. There are no guarantees as to what menu items - * the window menu contains, or even if a window menu will be drawn - * at all. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. - */ -static inline void -xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SHOW_WINDOW_MENU, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, x, y); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start an interactive, user-driven move of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive move (touch, - * pointer, etc). - * - * The server may ignore move requests depending on the state of - * the surface (e.g. fullscreen or maximized), or if the passed serial - * is no longer valid. - * - * If triggered, the surface will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the move. It is up to the - * compositor to visually indicate that the move is taking place, such as - * updating a pointer cursor, during the move. There is no guarantee - * that the device focus will return when the move is completed. - */ -static inline void -xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start a user-driven, interactive resize of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive resize (touch, - * pointer, etc). - * - * The server may ignore resize requests depending on the state of - * the surface (e.g. fullscreen or maximized). - * - * If triggered, the client will receive configure events with the - * "resize" state enum value and the expected sizes. See the "resize" - * enum value for more details about what is required. The client - * must also acknowledge configure events using "ack_configure". After - * the resize is completed, the client will receive another "configure" - * event without the resize state. - * - * If triggered, the surface also will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the resize. It is up to the - * compositor to visually indicate that the resize is taking place, - * such as updating a pointer cursor, during the resize. There is no - * guarantee that the device focus will return when the resize is - * completed. - * - * The edges parameter specifies how the surface should be resized, and - * is one of the values of the resize_edge enum. Values not matching - * a variant of the enum will cause the invalid_resize_edge protocol error. - * The compositor may use this information to update the surface position - * for example when dragging the top left corner. The compositor may also - * use this information to adapt its behavior, e.g. choose an appropriate - * cursor image. - */ -static inline void -xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, edges); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a maximum size for the window. - * - * The client can specify a maximum size so that the compositor does - * not try to configure the window beyond this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered. They will get applied - * on the next commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the maximum - * size. The compositor may decide to ignore the values set by the - * client and request a larger size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected maximum size in the given dimension. - * As a result, a client wishing to reset the maximum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a maximum size to be smaller than the minimum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width or height will result in a - * invalid_size error. - */ -static inline void -xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAX_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a minimum size for the window. - * - * The client can specify a minimum size so that the compositor does - * not try to configure the window below this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered. They will get applied - * on the next commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the minimum - * size. The compositor may decide to ignore the values set by the - * client and request a smaller size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected minimum size in the given dimension. - * As a result, a client wishing to reset the minimum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a minimum size to be larger than the maximum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width and height will result in a - * invalid_size error. - */ -static inline void -xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MIN_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Maximize the surface. - * - * After requesting that the surface should be maximized, the compositor - * will respond by emitting a configure event. Whether this configure - * actually sets the window maximized is subject to compositor policies. - * The client must then update its content, drawing in the configured - * state. The client must also acknowledge the configure when committing - * the new content (see ack_configure). - * - * It is up to the compositor to decide how and where to maximize the - * surface, for example which output and what region of the screen should - * be used. - * - * If the surface was already maximized, the compositor will still emit - * a configure event with the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Unmaximize the surface. - * - * After requesting that the surface should be unmaximized, the compositor - * will respond by emitting a configure event. Whether this actually - * un-maximizes the window is subject to compositor policies. - * If available and applicable, the compositor will include the window - * geometry dimensions the window had prior to being maximized in the - * configure event. The client must then update its content, drawing it in - * the configured state. The client must also acknowledge the configure - * when committing the new content (see ack_configure). - * - * It is up to the compositor to position the surface after it was - * unmaximized; usually the position the surface had before maximizing, if - * applicable. - * - * If the surface was already not maximized, the compositor will still - * emit a configure event without the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface fullscreen. - * - * After requesting that the surface should be fullscreened, the - * compositor will respond by emitting a configure event. Whether the - * client is actually put into a fullscreen state is subject to compositor - * policies. The client must also acknowledge the configure when - * committing the new content (see ack_configure). - * - * The output passed by the request indicates the client's preference as - * to which display it should be set fullscreen on. If this value is NULL, - * it's up to the compositor to choose which display will be used to map - * this surface. - * - * If the surface doesn't cover the whole output, the compositor will - * position the surface in the center of the output and compensate with - * with border fill covering the rest of the output. The content of the - * border fill is undefined, but should be assumed to be in some way that - * attempts to blend into the surrounding area (e.g. solid black). - * - * If the fullscreened surface is not opaque, the compositor must make - * sure that other screen content not part of the same surface tree (made - * up of subsurfaces, popups or similarly coupled surfaces) are not - * visible below the fullscreened surface. - */ -static inline void -xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, output); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface no longer fullscreen. - * - * After requesting that the surface should be unfullscreened, the - * compositor will respond by emitting a configure event. - * Whether this actually removes the fullscreen state of the client is - * subject to compositor policies. - * - * Making a surface unfullscreen sets states for the surface based on the following: - * * the state(s) it may have had before becoming fullscreen - * * any state(s) decided by the compositor - * * any state(s) requested by the client while the surface was fullscreen - * - * The compositor may include the previous window geometry dimensions in - * the configure event, if applicable. - * - * The client must also acknowledge the configure when committing the new - * content (see ack_configure). - */ -static inline void -xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Request that the compositor minimize your surface. There is no - * way to know if the surface is currently minimized, nor is there - * any way to unset minimization on this surface. - * - * If you are looking to throttle redrawing when minimized, please - * instead use the wl_surface.frame event for this, as this will - * also work with live previews on windows in Alt-Tab, Expose or - * similar compositor features. - */ -static inline void -xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MINIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -#ifndef XDG_POPUP_ERROR_ENUM -#define XDG_POPUP_ERROR_ENUM -enum xdg_popup_error { - /** - * tried to grab after being mapped - */ - XDG_POPUP_ERROR_INVALID_GRAB = 0, -}; -#endif /* XDG_POPUP_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_popup - * @struct xdg_popup_listener - */ -struct xdg_popup_listener { - /** - * configure the popup surface - * - * This event asks the popup surface to configure itself given - * the configuration. The configured state should not be applied - * immediately. See xdg_surface.configure for details. - * - * The x and y arguments represent the position the popup was - * placed at given the xdg_positioner rule, relative to the upper - * left corner of the window geometry of the parent surface. - * - * For version 2 or older, the configure event for an xdg_popup is - * only ever sent once for the initial configuration. Starting with - * version 3, it may be sent again if the popup is setup with an - * xdg_positioner with set_reactive requested, or in response to - * xdg_popup.reposition requests. - * @param x x position relative to parent surface window geometry - * @param y y position relative to parent surface window geometry - * @param width window geometry width - * @param height window geometry height - */ - void (*configure)(void *data, - struct xdg_popup *xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height); - /** - * popup interaction is done - * - * The popup_done event is sent out when a popup is dismissed by - * the compositor. The client should destroy the xdg_popup object - * at this point. - */ - void (*popup_done)(void *data, - struct xdg_popup *xdg_popup); - /** - * signal the completion of a repositioned request - * - * The repositioned event is sent as part of a popup - * configuration sequence, together with xdg_popup.configure and - * lastly xdg_surface.configure to notify the completion of a - * reposition request. - * - * The repositioned event is to notify about the completion of a - * xdg_popup.reposition request. The token argument is the token - * passed in the xdg_popup.reposition request. - * - * Immediately after this event is emitted, xdg_popup.configure and - * xdg_surface.configure will be sent with the updated size and - * position, as well as a new configure serial. - * - * The client should optionally update the content of the popup, - * but must acknowledge the new popup configuration for the new - * position to take effect. See xdg_surface.ack_configure for - * details. - * @param token reposition request token - * @since 3 - */ - void (*repositioned)(void *data, - struct xdg_popup *xdg_popup, - uint32_t token); -}; - -/** - * @ingroup iface_xdg_popup - */ -static inline int -xdg_popup_add_listener(struct xdg_popup *xdg_popup, - const struct xdg_popup_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, - (void (**)(void)) listener, data); -} - -#define XDG_POPUP_DESTROY 0 -#define XDG_POPUP_GRAB 1 -#define XDG_POPUP_REPOSITION 2 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_GRAB_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 - -/** @ingroup iface_xdg_popup */ -static inline void -xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); -} - -/** @ingroup iface_xdg_popup */ -static inline void * -xdg_popup_get_user_data(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); -} - -static inline uint32_t -xdg_popup_get_version(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_popup); -} - -/** - * @ingroup iface_xdg_popup - * - * This destroys the popup. Explicitly destroying the xdg_popup - * object will also dismiss the popup, and unmap the surface. - * - * If this xdg_popup is not the "topmost" popup, the - * xdg_wm_base.not_the_topmost_popup protocol error will be sent. - */ -static inline void -xdg_popup_destroy(struct xdg_popup *xdg_popup) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_popup - * - * This request makes the created popup take an explicit grab. An explicit - * grab will be dismissed when the user dismisses the popup, or when the - * client destroys the xdg_popup. This can be done by the user clicking - * outside the surface, using the keyboard, or even locking the screen - * through closing the lid or a timeout. - * - * If the compositor denies the grab, the popup will be immediately - * dismissed. - * - * This request must be used in response to some sort of user action like a - * button press, key press, or touch down event. The serial number of the - * event should be passed as 'serial'. - * - * The parent of a grabbing popup must either be an xdg_toplevel surface or - * another xdg_popup with an explicit grab. If the parent is another - * xdg_popup it means that the popups are nested, with this popup now being - * the topmost popup. - * - * Nested popups must be destroyed in the reverse order they were created - * in, e.g. the only popup you are allowed to destroy at all times is the - * topmost one. - * - * When compositors choose to dismiss a popup, they may dismiss every - * nested grabbing popup as well. When a compositor dismisses popups, it - * will follow the same dismissing order as required from the client. - * - * If the topmost grabbing popup is destroyed, the grab will be returned to - * the parent of the popup, if that parent previously had an explicit grab. - * - * If the parent is a grabbing popup which has already been dismissed, this - * popup will be immediately dismissed. If the parent is a popup that did - * not take an explicit grab, an error will be raised. - * - * During a popup grab, the client owning the grab will receive pointer - * and touch events for all their surfaces as normal (similar to an - * "owner-events" grab in X11 parlance), while the top most grabbing popup - * will always have keyboard focus. - */ -static inline void -xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_GRAB, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_popup - * - * Reposition an already-mapped popup. The popup will be placed given the - * details in the passed xdg_positioner object, and a - * xdg_popup.repositioned followed by xdg_popup.configure and - * xdg_surface.configure will be emitted in response. Any parameters set - * by the previous positioner will be discarded. - * - * The passed token will be sent in the corresponding - * xdg_popup.repositioned event. The new popup position will not take - * effect until the corresponding configure event is acknowledged by the - * client. See xdg_popup.repositioned for details. The token itself is - * opaque, and has no other special meaning. - * - * If multiple reposition requests are sent, the compositor may skip all - * but the last one. - * - * If the popup is repositioned in response to a configure event for its - * parent, the client should send an xdg_positioner.set_parent_configure - * and possibly an xdg_positioner.set_parent_size request to allow the - * compositor to properly constrain the popup. - * - * If the popup is repositioned together with a parent that is being - * resized, but not in response to a configure event, the client should - * send an xdg_positioner.set_parent_size request. - */ -static inline void -xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_REPOSITION, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, positioner, token); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/apprt.zig b/src/apprt.zig index dd726b3f2..cb542875e 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -3,7 +3,7 @@ //! getting user input (mouse/keyboard), etc. //! //! This enables compile-time interfaces to be built to swap out the underlying -//! application runtime. For example: glfw, pure macOS Cocoa, GTK+, browser, etc. +//! application runtime. For example: pure macOS Cocoa, GTK+, browser, etc. //! //! The goal is to have different implementations share as much of the core //! logic as possible, and to only reach out to platform-specific implementation @@ -15,7 +15,6 @@ const build_config = @import("build_config.zig"); const structs = @import("apprt/structs.zig"); pub const action = @import("apprt/action.zig"); -pub const glfw = @import("apprt/glfw.zig"); pub const gtk = @import("apprt/gtk.zig"); pub const none = @import("apprt/none.zig"); pub const browser = @import("apprt/browser.zig"); @@ -42,7 +41,6 @@ pub const SurfaceSize = structs.SurfaceSize; pub const runtime = switch (build_config.artifact) { .exe => switch (build_config.app_runtime) { .none => none, - .glfw => glfw, .gtk => gtk, }, .lib => embedded, @@ -53,18 +51,12 @@ pub const App = runtime.App; pub const Surface = runtime.Surface; /// Runtime is the runtime to use for Ghostty. All runtimes do not provide -/// equivalent feature sets. For example, GTK offers tabbing and more features -/// that glfw does not provide. However, glfw may require many less -/// dependencies. +/// equivalent feature sets. pub const Runtime = enum { /// Will not produce an executable at all when `zig build` is called. /// This is only useful if you're only interested in the lib only (macOS). none, - /// Glfw-backed. Very simple. Glfw is statically linked. Tabbing and - /// other rich windowing features are not supported. - glfw, - /// GTK-backed. Rich windowed application. GTK is dynamically linked. gtk, @@ -72,12 +64,8 @@ pub const Runtime = enum { // The Linux default is GTK because it is full featured. if (target.os.tag == .linux) return .gtk; - // Windows we currently only support glfw - if (target.os.tag == .windows) return .glfw; - - // Otherwise, we do NONE so we don't create an exe. The GLFW - // build is opt-in because it is missing so many features compared - // to the other builds that are impossible due to the GLFW interface. + // Otherwise, we do NONE so we don't create an exe and we + // create libghostty. return .none; } }; diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index dec1e4135..0121494b7 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -236,7 +236,7 @@ pub const App = struct { var surface = try self.core_app.alloc.create(Surface); errdefer self.core_app.alloc.destroy(surface); - // Create the surface -- because windows are surfaces for glfw. + // Create the surface try surface.init(self, opts); errdefer surface.deinit(); diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig deleted file mode 100644 index b82771d75..000000000 --- a/src/apprt/glfw.zig +++ /dev/null @@ -1,1266 +0,0 @@ -//! Application runtime implementation that uses GLFW (https://www.glfw.org/). -//! -//! This works on macOS and Linux with OpenGL and Metal. -//! (The above sentence may be out of date). - -const std = @import("std"); -const builtin = @import("builtin"); -const build_config = @import("../build_config.zig"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const glfw = @import("glfw"); -const macos = @import("macos"); -const objc = @import("objc"); -const cli = @import("../cli.zig"); -const input = @import("../input.zig"); -const internal_os = @import("../os/main.zig"); -const renderer = @import("../renderer.zig"); -const terminal = @import("../terminal/main.zig"); -const Renderer = renderer.Renderer; -const apprt = @import("../apprt.zig"); -const CoreApp = @import("../App.zig"); -const CoreSurface = @import("../Surface.zig"); -const configpkg = @import("../config.zig"); -const Config = @import("../config.zig").Config; - -// Get native API access on certain platforms so we can do more customization. -const glfwNative = glfw.Native(.{ - .cocoa = builtin.target.os.tag.isDarwin(), - .x11 = builtin.os.tag == .linux, -}); - -/// True if darwin-specific logic is enabled -const darwin_enabled = builtin.target.os.tag.isDarwin() and - build_config.artifact == .exe; - -const log = std.log.scoped(.glfw); - -pub const resourcesDir = internal_os.resourcesDir; - -pub const App = struct { - app: *CoreApp, - config: Config, - - /// Flips to true to quit on the next event loop tick. This - /// never goes false and forces the event loop to exit. - quit: bool = false, - - /// Mac-specific state. - darwin: if (darwin_enabled) Darwin else void, - - pub const Options = struct {}; - - pub fn init(self: *App, core_app: *CoreApp, _: Options) !void { - if (comptime builtin.target.os.tag.isDarwin()) { - log.warn("WARNING WARNING WARNING: GLFW ON MAC HAS BUGS.", .{}); - log.warn("You should use the AppKit-based app instead. The official download", .{}); - log.warn("is properly built and available from GitHub. If you're building from", .{}); - log.warn("source, see the README for details on how to build the AppKit app.", .{}); - } - - if (!glfw.init(.{})) { - if (glfw.getError()) |err| { - log.err("error initializing GLFW err={} msg={s}", .{ - err.error_code, - err.description, - }); - return err.error_code; - } - - return error.GlfwInitFailedUnknownReason; - } - glfw.setErrorCallback(glfwErrorCallback); - - // Mac-specific state. For example, on Mac we enable window tabbing. - var darwin = if (darwin_enabled) try Darwin.init() else {}; - errdefer if (darwin_enabled) darwin.deinit(); - - // Load our configuration - var config = try Config.load(core_app.alloc); - errdefer config.deinit(); - - // If we had configuration errors, then log them. - if (!config._diagnostics.empty()) { - var buf = std.ArrayList(u8).init(core_app.alloc); - defer buf.deinit(); - for (config._diagnostics.items()) |diag| { - try diag.write(buf.writer()); - log.warn("configuration error: {s}", .{buf.items}); - buf.clearRetainingCapacity(); - } - - // If we have any CLI errors, exit. - if (config._diagnostics.containsLocation(.cli)) { - log.warn("CLI errors detected, exiting", .{}); - _ = core_app.mailbox.push(.{ - .quit = {}, - }, .{ .forever = {} }); - } - } - - // Queue a single new window that starts on launch - // Note: above we may send a quit so this may never happen - _ = core_app.mailbox.push(.{ - .new_window = .{}, - }, .{ .forever = {} }); - - // We want the event loop to wake up instantly so we can process our tick. - glfw.postEmptyEvent(); - - self.* = .{ - .app = core_app, - .config = config, - .darwin = darwin, - }; - } - - pub fn terminate(self: *App) void { - self.config.deinit(); - glfw.terminate(); - } - - /// Run the event loop. This doesn't return until the app exits. - pub fn run(self: *App) !void { - while (true) { - // Wait for any events from the app event loop. wakeup will post - // an empty event so that this will return. - // - // Warning: a known issue on macOS is that this will block while - // a resize event is actively happening, which will prevent the - // app tick from happening. I don't know know a way around this - // but its not a big deal since we don't use glfw for the official - // mac app, but noting it in case anyone builds for macos using - // glfw. - glfw.waitEvents(); - - // Tick the terminal app - try self.app.tick(self); - - // If the tick caused us to quit, then we're done. - if (self.quit or self.app.surfaces.items.len == 0) { - for (self.app.surfaces.items) |surface| { - surface.close(false); - } - - return; - } - } - } - - /// Wakeup the event loop. This should be able to be called from any thread. - pub fn wakeup(self: *const App) void { - _ = self; - glfw.postEmptyEvent(); - } - - /// Perform a given action. Returns `true` if the action was able to be - /// performed, `false` otherwise. - pub fn performAction( - self: *App, - target: apprt.Target, - comptime action: apprt.Action.Key, - value: apprt.Action.Value(action), - ) !bool { - switch (action) { - .quit => self.quit = true, - - .new_window => _ = try self.newSurface(switch (target) { - .app => null, - .surface => |v| v, - }), - - .new_tab => try self.newTab(switch (target) { - .app => null, - .surface => |v| v, - }), - - .size_limit => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setSizeLimits(.{ - .width = value.min_width, - .height = value.min_height, - }, if (value.max_width > 0) .{ - .width = value.max_width, - .height = value.max_height, - } else null), - }, - - .initial_size => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setInitialWindowSize( - value.width, - value.height, - ), - }, - - .toggle_fullscreen => self.toggleFullscreen(target), - - .open_config => try configpkg.edit.open(self.app.alloc), - - .set_title => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setTitle(value.title), - }, - - .mouse_shape => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setMouseShape(value), - }, - - .mouse_visibility => switch (target) { - .app => {}, - .surface => |surface| surface.rt_surface.setMouseVisibility(switch (value) { - .visible => true, - .hidden => false, - }), - }, - - .reload_config => try self.reloadConfig(target, value), - - // Unimplemented - .new_split, - .goto_split, - .resize_split, - .equalize_splits, - .toggle_split_zoom, - .present_terminal, - .close_all_windows, - .close_window, - .close_tab, - .toggle_tab_overview, - .toggle_window_decorations, - .toggle_quick_terminal, - .toggle_command_palette, - .toggle_visibility, - .goto_tab, - .move_tab, - .inspector, - .render_inspector, - .quit_timer, - .float_window, - .secure_input, - .key_sequence, - .desktop_notification, - .mouse_over_link, - .cell_size, - .renderer_health, - .color_change, - .pwd, - .config_change, - .toggle_maximize, - .prompt_title, - .reset_window_size, - .ring_bell, - .check_for_updates, - .undo, - .redo, - .show_gtk_inspector, - => { - log.info("unimplemented action={}", .{action}); - return false; - }, - } - - return true; - } - - /// Reload the configuration. This should return the new configuration. - /// The old value can be freed immediately at this point assuming a - /// successful return. - /// - /// The returned pointer value is only valid for a stable self pointer. - fn reloadConfig( - self: *App, - target: apprt.action.Target, - opts: apprt.action.ReloadConfig, - ) !void { - if (opts.soft) { - switch (target) { - .app => try self.app.updateConfig(self, &self.config), - .surface => |core_surface| try core_surface.updateConfig( - &self.config, - ), - } - return; - } - - // Load our configuration - var config = try Config.load(self.app.alloc); - errdefer config.deinit(); - - // Call into our app to update - switch (target) { - .app => try self.app.updateConfig(self, &config), - .surface => |core_surface| try core_surface.updateConfig(&config), - } - - // Update the existing config, be sure to clean up the old one. - self.config.deinit(); - self.config = config; - } - - /// Toggle the window to fullscreen mode. - fn toggleFullscreen(self: *App, target: apprt.Target) void { - _ = self; - const surface: *Surface = switch (target) { - .app => return, - .surface => |v| v.rt_surface, - }; - const win = surface.window; - - if (surface.isFullscreen()) { - win.setMonitor( - null, - @intCast(surface.monitor_dims.position_x), - @intCast(surface.monitor_dims.position_y), - surface.monitor_dims.width, - surface.monitor_dims.height, - 0, - ); - return; - } - - const monitor = win.getMonitor() orelse monitor: { - log.warn("window had null monitor, getting primary monitor", .{}); - break :monitor glfw.Monitor.getPrimary() orelse { - log.warn("window could not get any monitor. will not perform action", .{}); - return; - }; - }; - - const video_mode = monitor.getVideoMode() orelse { - log.warn("failed to get video mode. will not perform action", .{}); - return; - }; - - const position = win.getPos(); - const size = surface.getSize() catch { - log.warn("failed to get window size. will not perform fullscreen action", .{}); - return; - }; - - surface.monitor_dims = .{ - .width = size.width, - .height = size.height, - .position_x = position.x, - .position_y = position.y, - }; - - win.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), 0); - } - - /// Create a new tab in the parent surface. - fn newTab(self: *App, parent_: ?*CoreSurface) !void { - if (comptime !darwin_enabled) { - log.warn("tabbing is not supported on this platform", .{}); - return; - } - - const parent = parent_ orelse { - _ = try self.newSurface(null); - return; - }; - - // Create the new window - const window = try self.newSurface(parent); - - // Add the new window the parent window - const parent_win = glfwNative.getCocoaWindow(parent.rt_surface.window).?; - const other_win = glfwNative.getCocoaWindow(window.window).?; - const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 }; - const nswindow = objc.Object.fromId(parent_win); - nswindow.msgSend(void, objc.sel("addTabbedWindow:ordered:"), .{ - objc.Object.fromId(other_win), - NSWindowOrderingMode.above, - }); - - // Adding a new tab can cause the tab bar to appear which changes - // our viewport size. We need to call the size callback in order to - // update values. For example, we need this to set the proper mouse selection - // point in the grid. - const size = parent.rt_surface.getSize() catch |err| { - log.err("error querying window size for size callback on new tab err={}", .{err}); - return; - }; - parent.sizeCallback(size) catch |err| { - log.err("error in size callback from new tab err={}", .{err}); - return; - }; - } - - fn newSurface(self: *App, parent_: ?*CoreSurface) !*Surface { - // Grab a surface allocation because we're going to need it. - var surface = try self.app.alloc.create(Surface); - errdefer self.app.alloc.destroy(surface); - - // Create the surface -- because windows are surfaces for glfw. - try surface.init(self); - errdefer surface.deinit(); - - // If we have a parent, inherit some properties - if (self.config.@"window-inherit-font-size") { - if (parent_) |parent| { - try surface.core_surface.setFontSize(parent.font_size); - } - } - - return surface; - } - - /// Close the given surface. - pub fn closeSurface(self: *App, surface: *Surface) void { - surface.deinit(); - self.app.alloc.destroy(surface); - } - - pub fn redrawSurface(self: *App, surface: *Surface) void { - _ = self; - _ = surface; - - @panic("This should never be called for GLFW."); - } - - pub fn redrawInspector(self: *App, surface: *Surface) void { - _ = self; - _ = surface; - - // GLFW doesn't support the inspector - } - - fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void { - std.log.warn("glfw error={} message={s}", .{ code, desc }); - - // Workaround for: https://github.com/ocornut/imgui/issues/5908 - // If we get an invalid value with "scancode" in the message we assume - // it is from the glfw key callback that imgui sets and we clear the - // error so that our future code doesn't crash. - if (code == glfw.ErrorCode.InvalidValue and - std.mem.indexOf(u8, desc, "scancode") != null) - { - _ = glfw.getError(); - } - } - - pub fn keyboardLayout(self: *const App) input.KeyboardLayout { - _ = self; - - // Not supported by glfw - return .unknown; - } - - /// Mac-specific settings. This is only enabled when the target is - /// Mac and the artifact is a standalone exe. We don't target libs because - /// the embedded API doesn't do windowing. - const Darwin = struct { - tabbing_id: *macos.foundation.String, - - pub fn init() !Darwin { - const NSWindow = objc.getClass("NSWindow").?; - NSWindow.msgSend(void, objc.sel("setAllowsAutomaticWindowTabbing:"), .{true}); - - // Our tabbing ID allows all of our windows to group together - const tabbing_id = try macos.foundation.String.createWithBytes( - "com.mitchellh.ghostty.window", - .utf8, - false, - ); - errdefer tabbing_id.release(); - - // Setup our Mac settings - return .{ .tabbing_id = tabbing_id }; - } - - pub fn deinit(self: *Darwin) void { - self.tabbing_id.release(); - self.* = undefined; - } - }; -}; - -/// These are used to keep track of the original monitor values so that we can -/// safely toggle on and off of fullscreen. -const MonitorDimensions = struct { - width: u32, - height: u32, - position_x: i64, - position_y: i64, -}; - -/// Surface represents the drawable surface for glfw. In glfw, a surface -/// is always a window because that is the only abstraction that glfw exposes. -/// -/// This means that there is no way for the glfw runtime to support tabs, -/// splits, etc. without considerable effort. In fact, on Darwin, we do -/// support tabs because the minimal tabbing interface is a window abstraction, -/// but this is a bit of a hack. The native Swift runtime should be used instead -/// which uses real native tabbing. -/// -/// Other runtimes a surface usually represents the equivalent of a "view" -/// or "widget" level granularity. -pub const Surface = struct { - /// The glfw window handle - window: glfw.Window, - - /// The glfw mouse cursor handle. - cursor: ?glfw.Cursor, - - /// The app we're part of - app: *App, - - /// A core surface - core_surface: CoreSurface, - - /// This is the key event that was processed in keyCallback. This is only - /// non-null if the event was NOT consumed in keyCallback. This lets us - /// know in charCallback whether we should populate it and call it again. - /// (GLFW guarantees that charCallback is called after keyCallback). - key_event: ?input.KeyEvent = null, - - /// The monitor dimensions so we can toggle fullscreen on and off. - monitor_dims: MonitorDimensions, - - /// Save the title text so that we can return it later when requested. - /// This is allocated from the heap so it must be freed when we deinit the - /// surface. - title_text: ?[:0]const u8 = null, - - pub const Options = struct {}; - - /// Initialize the surface into the given self pointer. This gives a - /// stable pointer to the destination that can be used for callbacks. - pub fn init(self: *Surface, app: *App) !void { - // Create our window - const win = glfw.Window.create( - 640, - 480, - "ghostty", - if (app.config.fullscreen) glfw.Monitor.getPrimary() else null, - null, - Renderer.glfwWindowHints(&app.config), - ) orelse return glfw.mustGetErrorCode(); - errdefer win.destroy(); - - // Setup our - setInitialWindowPosition( - win, - app.config.@"window-position-x", - app.config.@"window-position-y", - ); - - // Get our physical DPI - debug only because we don't have a use for - // this but the logging of it may be useful - if (builtin.mode == .Debug) { - const monitor = win.getMonitor() orelse monitor: { - log.warn("window had null monitor, getting primary monitor", .{}); - break :monitor glfw.Monitor.getPrimary().?; - }; - const video_mode = monitor.getVideoMode() orelse return glfw.mustGetErrorCode(); - const physical_size = monitor.getPhysicalSize(); - const physical_x_dpi = @as(f32, @floatFromInt(video_mode.getWidth())) / (@as(f32, @floatFromInt(physical_size.width_mm)) / 25.4); - const physical_y_dpi = @as(f32, @floatFromInt(video_mode.getHeight())) / (@as(f32, @floatFromInt(physical_size.height_mm)) / 25.4); - log.debug("physical dpi x={} y={}", .{ - physical_x_dpi, - physical_y_dpi, - }); - } - - // On Mac, enable window tabbing - if (comptime darwin_enabled) { - const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 }; - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?); - - // Tabbing mode enables tabbing at all - nswindow.setProperty("tabbingMode", NSWindowTabbingMode.automatic); - - // All windows within a tab bar must have a matching tabbing ID. - // The app sets this up for us. - nswindow.setProperty("tabbingIdentifier", app.darwin.tabbing_id); - } - - // Set our callbacks - win.setUserPointer(&self.core_surface); - win.setSizeCallback(sizeCallback); - win.setCharCallback(charCallback); - win.setKeyCallback(keyCallback); - win.setFocusCallback(focusCallback); - win.setRefreshCallback(refreshCallback); - win.setScrollCallback(scrollCallback); - win.setCursorPosCallback(cursorPosCallback); - win.setMouseButtonCallback(mouseButtonCallback); - win.setDropCallback(dropCallback); - - const dimensions: MonitorDimensions = dimensions: { - const pos = win.getPos(); - const size = win.getFramebufferSize(); - break :dimensions .{ - .width = size.width, - .height = size.height, - .position_x = pos.x, - .position_y = pos.y, - }; - }; - - // Build our result - self.* = .{ - .app = app, - .window = win, - .cursor = null, - .core_surface = undefined, - .monitor_dims = dimensions, - }; - errdefer self.* = undefined; - - // Initialize our cursor - try self.setMouseShape(.text); - - // Add ourselves to the list of surfaces on the app. - try app.app.addSurface(self); - errdefer app.app.deleteSurface(self); - - // Get our new surface config - var config = try apprt.surface.newConfig(app.app, &app.config); - defer config.deinit(); - - // Initialize our surface now that we have the stable pointer. - try self.core_surface.init( - app.app.alloc, - &config, - app.app, - app, - self, - ); - errdefer self.core_surface.deinit(); - } - - pub fn deinit(self: *Surface) void { - if (self.title_text) |t| self.core_surface.alloc.free(t); - - // Remove ourselves from the list of known surfaces in the app. - self.app.app.deleteSurface(self); - - // Clean up our core surface so that all the rendering and IO stop. - self.core_surface.deinit(); - - if (comptime darwin_enabled) { - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?); - const tabgroup = nswindow.getProperty(objc.Object, "tabGroup"); - const windows = tabgroup.getProperty(objc.Object, "windows"); - switch (windows.getProperty(usize, "count")) { - // If we're going down to one window our tab bar is going to be - // destroyed so unset it so that the later logic doesn't try to - // use it. - 1 => {}, - - // If our tab bar is visible and we are going down to 1 window, - // hide the tab bar. The check is "2" because our current window - // is still present. - 2 => if (tabgroup.getProperty(bool, "tabBarVisible")) { - nswindow.msgSend(void, objc.sel("toggleTabBar:"), .{nswindow.value}); - }, - - else => {}, - } - } - - // We can now safely destroy our windows. We have to do this BEFORE - // setting up the new focused window below. - self.window.destroy(); - if (self.cursor) |c| { - c.destroy(); - self.cursor = null; - } - } - - /// Checks if the glfw window is in fullscreen. - pub fn isFullscreen(self: *Surface) bool { - return self.window.getMonitor() != null; - } - - /// Close this surface. - pub fn close(self: *Surface, processActive: bool) void { - _ = processActive; - self.setShouldClose(); - self.deinit(); - self.app.app.alloc.destroy(self); - } - - /// Set the initial window size. This is called exactly once at - /// surface initialization time. This may be called before "self" - /// is fully initialized. - fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void { - const monitor = self.window.getMonitor() orelse glfw.Monitor.getPrimary() orelse { - log.warn("window is not on a monitor, not setting initial size", .{}); - return; - }; - - const workarea = monitor.getWorkarea(); - self.window.setSize(.{ - .width = @min(width, workarea.width), - .height = @min(height, workarea.height), - }); - } - - /// Set the initial window position. This is called exactly once at - /// surface initialization time. This may be called before "self" - /// is fully initialized. - fn setInitialWindowPosition(win: glfw.Window, x: ?i16, y: ?i16) void { - const start_position_x = x orelse return; - const start_position_y = y orelse return; - - log.debug("setting initial window position ({},{})", .{ start_position_x, start_position_y }); - win.setPos(.{ .x = start_position_x, .y = start_position_y }); - } - - /// Set the size limits of the window. - /// Note: this interface is not good, we should redo it if we plan - /// to use this more. i.e. you can't set max width but no max height, - /// or no mins. - fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void { - self.window.setSizeLimits(.{ - .width = min.width, - .height = min.height, - }, if (max_) |max| .{ - .width = max.width, - .height = max.height, - } else .{ - .width = null, - .height = null, - }); - } - - /// Returns the content scale for the created window. - pub fn getContentScale(self: *const Surface) !apprt.ContentScale { - const scale = self.window.getContentScale(); - return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale }; - } - - /// Returns the size of the window in pixels. The pixel size may - /// not match screen coordinate size but we should be able to convert - /// back and forth using getContentScale. - pub fn getSize(self: *const Surface) !apprt.SurfaceSize { - const size = self.window.getFramebufferSize(); - return apprt.SurfaceSize{ .width = size.width, .height = size.height }; - } - - /// Returns the cursor position in scaled pixels relative to the - /// upper-left of the window. - pub fn getCursorPos(self: *const Surface) !apprt.CursorPos { - const unscaled_pos = self.window.getCursorPos(); - const pos = try self.cursorPosToPixels(unscaled_pos); - return apprt.CursorPos{ - .x = @floatCast(pos.xpos), - .y = @floatCast(pos.ypos), - }; - } - - /// Set the flag that notes this window should be closed for the next - /// iteration of the event loop. - pub fn setShouldClose(self: *Surface) void { - self.window.setShouldClose(true); - } - - /// Returns true if the window is flagged to close. - pub fn shouldClose(self: *const Surface) bool { - return self.window.shouldClose(); - } - - /// Set the title of the window. - fn setTitle(self: *Surface, slice: [:0]const u8) !void { - if (self.title_text) |t| self.core_surface.alloc.free(t); - self.title_text = try self.core_surface.alloc.dupeZ(u8, slice); - self.window.setTitle(self.title_text.?.ptr); - } - - /// Return the title of the window. - pub fn getTitle(self: *Surface) ?[:0]const u8 { - return self.title_text; - } - - /// Set the shape of the cursor. - fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void { - if ((comptime builtin.target.os.tag.isDarwin()) and - !internal_os.macos.isAtLeastVersion(13, 0, 0)) - { - // We only set our cursor if we're NOT on Mac, or if we are then the - // macOS version is >= 13 (Ventura). On prior versions, glfw crashes - // since we use a tab group. - return; - } - - const new = glfw.Cursor.createStandard(switch (shape) { - .default => .arrow, - .text => .ibeam, - .crosshair => .crosshair, - .pointer => .pointing_hand, - .ew_resize => .resize_ew, - .ns_resize => .resize_ns, - .nwse_resize => .resize_nwse, - .nesw_resize => .resize_nesw, - .all_scroll => .resize_all, - .not_allowed => .not_allowed, - else => return, // unsupported, ignore - }) orelse { - const err = glfw.mustGetErrorCode(); - log.warn("error creating cursor: {}", .{err}); - return; - }; - errdefer new.destroy(); - - // Set our cursor before we destroy the old one - self.window.setCursor(new); - - if (self.cursor) |c| c.destroy(); - self.cursor = new; - } - - /// Set the visibility of the mouse cursor. - fn setMouseVisibility(self: *Surface, visible: bool) void { - self.window.setInputModeCursor(if (visible) .normal else .hidden); - } - - pub fn supportsClipboard( - self: *const Surface, - clipboard_type: apprt.Clipboard, - ) bool { - _ = self; - return switch (clipboard_type) { - .standard => true, - .selection, .primary => comptime builtin.os.tag == .linux, - }; - } - - /// Start an async clipboard request. - pub fn clipboardRequest( - self: *Surface, - clipboard_type: apprt.Clipboard, - state: apprt.ClipboardRequest, - ) !void { - // GLFW can read clipboards immediately so just do that. - const str: [:0]const u8 = switch (clipboard_type) { - .standard => glfw.getClipboardString() orelse return glfw.mustGetErrorCode(), - .selection, .primary => selection: { - // Not supported except on Linux - if (comptime builtin.os.tag != .linux) break :selection ""; - - const raw = glfwNative.getX11SelectionString() orelse - return glfw.mustGetErrorCode(); - break :selection std.mem.span(raw); - }, - }; - - // Complete our request. We always allow unsafe because we don't - // want to deal with user confirmation in this runtime. - try self.core_surface.completeClipboardRequest(state, str, true); - } - - /// Set the clipboard. - pub fn setClipboardString( - self: *const Surface, - val: [:0]const u8, - clipboard_type: apprt.Clipboard, - confirm: bool, - ) !void { - _ = confirm; - _ = self; - switch (clipboard_type) { - .standard => glfw.setClipboardString(val), - .selection, .primary => { - // Not supported except on Linux - if (comptime builtin.os.tag != .linux) return; - glfwNative.setX11SelectionString(val.ptr); - }, - } - } - - /// The cursor position from glfw directly is in screen coordinates but - /// all our interface works in pixels. - fn cursorPosToPixels(self: *const Surface, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos { - // The cursor position is in screen coordinates but we - // want it in pixels. we need to get both the size of the - // window in both to get the ratio to make the conversion. - const size = self.window.getSize(); - const fb_size = self.window.getFramebufferSize(); - - // If our framebuffer and screen are the same, then there is no scaling - // happening and we can short-circuit by returning the pos as-is. - if (fb_size.width == size.width and fb_size.height == size.height) - return pos; - - const x_scale = @as(f64, @floatFromInt(fb_size.width)) / @as(f64, @floatFromInt(size.width)); - const y_scale = @as(f64, @floatFromInt(fb_size.height)) / @as(f64, @floatFromInt(size.height)); - return .{ - .xpos = pos.xpos * x_scale, - .ypos = pos.ypos * y_scale, - }; - } - - pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap { - return try internal_os.getEnvMap(self.app.app.alloc); - } - - fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { - _ = width; - _ = height; - - // Get the size. We are given a width/height but this is in screen - // coordinates and we want raw pixels. The core window uses the content - // scale to scale appropriately. - const core_win = window.getUserPointer(CoreSurface) orelse return; - const size = core_win.rt_surface.getSize() catch |err| { - log.err("error querying window size for size callback err={}", .{err}); - return; - }; - - // Call the primary callback. - core_win.sizeCallback(size) catch |err| { - log.err("error in size callback err={}", .{err}); - return; - }; - } - - fn charCallback(window: glfw.Window, codepoint: u21) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // We need a key event in order to process the charcallback. If it - // isn't set then the key event was consumed. - var key_event = core_win.rt_surface.key_event orelse return; - core_win.rt_surface.key_event = null; - - // Populate the utf8 value for the event - var buf: [4]u8 = undefined; - const len = std.unicode.utf8Encode(codepoint, &buf) catch |err| { - log.err("error encoding codepoint={} err={}", .{ codepoint, err }); - return; - }; - key_event.utf8 = buf[0..len]; - - // On macOS we need to also disable some modifiers because - // alt+key consumes the alt. - if (comptime builtin.target.os.tag.isDarwin()) { - // For GLFW, we say we always consume alt because - // GLFW doesn't have a way to disable the alt key. - key_event.consumed_mods.alt = true; - } - - _ = core_win.keyCallback(key_event) catch |err| { - log.err("error in key callback err={}", .{err}); - return; - }; - } - - fn keyCallback( - window: glfw.Window, - glfw_key: glfw.Key, - scancode: i32, - glfw_action: glfw.Action, - glfw_mods: glfw.Mods, - ) void { - _ = scancode; - - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert our glfw types into our input types - const mods: input.Mods = .{ - .shift = glfw_mods.shift, - .ctrl = glfw_mods.control, - .alt = glfw_mods.alt, - .super = glfw_mods.super, - }; - const action: input.Action = switch (glfw_action) { - .release => .release, - .press => .press, - .repeat => .repeat, - }; - const key: input.Key = switch (glfw_key) { - .a => .key_a, - .b => .key_b, - .c => .key_c, - .d => .key_d, - .e => .key_e, - .f => .key_f, - .g => .key_g, - .h => .key_h, - .i => .key_i, - .j => .key_j, - .k => .key_k, - .l => .key_l, - .m => .key_m, - .n => .key_n, - .o => .key_o, - .p => .key_p, - .q => .key_q, - .r => .key_r, - .s => .key_s, - .t => .key_t, - .u => .key_u, - .v => .key_v, - .w => .key_w, - .x => .key_x, - .y => .key_y, - .z => .key_z, - .zero => .digit_0, - .one => .digit_1, - .two => .digit_2, - .three => .digit_3, - .four => .digit_4, - .five => .digit_5, - .six => .digit_6, - .seven => .digit_7, - .eight => .digit_8, - .nine => .digit_9, - .up => .arrow_up, - .down => .arrow_down, - .right => .arrow_right, - .left => .arrow_left, - .home => .home, - .end => .end, - .page_up => .page_up, - .page_down => .page_down, - .escape => .escape, - .F1 => .f1, - .F2 => .f2, - .F3 => .f3, - .F4 => .f4, - .F5 => .f5, - .F6 => .f6, - .F7 => .f7, - .F8 => .f8, - .F9 => .f9, - .F10 => .f10, - .F11 => .f11, - .F12 => .f12, - .F13 => .f13, - .F14 => .f14, - .F15 => .f15, - .F16 => .f16, - .F17 => .f17, - .F18 => .f18, - .F19 => .f19, - .F20 => .f20, - .F21 => .f21, - .F22 => .f22, - .F23 => .f23, - .F24 => .f24, - .F25 => .f25, - .kp_0 => .numpad_0, - .kp_1 => .numpad_1, - .kp_2 => .numpad_2, - .kp_3 => .numpad_3, - .kp_4 => .numpad_4, - .kp_5 => .numpad_5, - .kp_6 => .numpad_6, - .kp_7 => .numpad_7, - .kp_8 => .numpad_8, - .kp_9 => .numpad_9, - .kp_decimal => .numpad_decimal, - .kp_divide => .numpad_divide, - .kp_multiply => .numpad_multiply, - .kp_subtract => .numpad_subtract, - .kp_add => .numpad_add, - .kp_enter => .numpad_enter, - .kp_equal => .numpad_equal, - .grave_accent => .backquote, - .minus => .minus, - .equal => .equal, - .space => .space, - .semicolon => .semicolon, - .apostrophe => .quote, - .comma => .comma, - .period => .period, - .slash => .slash, - .left_bracket => .bracket_left, - .right_bracket => .bracket_right, - .backslash => .backslash, - .enter => .enter, - .tab => .tab, - .backspace => .backspace, - .insert => .insert, - .delete => .delete, - .caps_lock => .caps_lock, - .scroll_lock => .scroll_lock, - .num_lock => .num_lock, - .print_screen => .print_screen, - .pause => .pause, - .left_shift => .shift_left, - .left_control => .control_left, - .left_alt => .alt_left, - .left_super => .meta_left, - .right_shift => .shift_right, - .right_control => .control_right, - .right_alt => .alt_right, - .right_super => .meta_right, - .menu => .context_menu, - - .world_1, - .world_2, - .unknown, - => .unidentified, - }; - - // This is a hack for GLFW. We require our apprts to send both - // the UTF8 encoding AND the keypress at the same time. Its critical - // for things like ctrl sequences to work. However, GLFW doesn't - // provide this information all at once. So we just infer based on - // the key press. This isn't portable but GLFW is only for testing. - const utf8 = switch (key) { - inline else => |k| utf8: { - if (mods.shift) break :utf8 ""; - const cp = k.codepoint() orelse break :utf8 ""; - const byte = std.math.cast(u8, cp) orelse break :utf8 ""; - break :utf8 &.{byte}; - }, - }; - - const key_event: input.KeyEvent = .{ - .action = action, - .key = key, - .mods = mods, - .consumed_mods = .{}, - .composing = false, - .utf8 = utf8, - .unshifted_codepoint = if (utf8.len > 0) @intCast(utf8[0]) else 0, - }; - - const effect = core_win.keyCallback(key_event) catch |err| { - log.err("error in key callback err={}", .{err}); - return; - }; - - // Surface closed. - if (effect == .closed) return; - - // If it wasn't consumed, we set it on our self so that charcallback - // can make another attempt. Otherwise, we set null so the charcallback - // is ignored. - core_win.rt_surface.key_event = null; - if (effect == .ignored and - (action == .press or action == .repeat)) - { - core_win.rt_surface.key_event = key_event; - } - } - - fn focusCallback(window: glfw.Window, focused: bool) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.focusCallback(focused) catch |err| { - log.err("error in focus callback err={}", .{err}); - return; - }; - } - - fn refreshCallback(window: glfw.Window) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.refreshCallback() catch |err| { - log.err("error in refresh callback err={}", .{err}); - return; - }; - } - - fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { - // Glfw doesn't support any of the scroll mods. - const scroll_mods: input.ScrollMods = .{}; - - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.scrollCallback(xoff, yoff, scroll_mods) catch |err| { - log.err("error in scroll callback err={}", .{err}); - return; - }; - } - - fn cursorPosCallback( - window: glfw.Window, - unscaled_xpos: f64, - unscaled_ypos: f64, - ) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert our unscaled x/y to scaled. - const pos = core_win.rt_surface.cursorPosToPixels(.{ - .xpos = unscaled_xpos, - .ypos = unscaled_ypos, - }) catch |err| { - log.err( - "error converting cursor pos to scaled pixels in cursor pos callback err={}", - .{err}, - ); - return; - }; - - core_win.cursorPosCallback(.{ - .x = @floatCast(pos.xpos), - .y = @floatCast(pos.ypos), - }, null) catch |err| { - log.err("error in cursor pos callback err={}", .{err}); - return; - }; - } - - fn mouseButtonCallback( - window: glfw.Window, - glfw_button: glfw.MouseButton, - glfw_action: glfw.Action, - glfw_mods: glfw.Mods, - ) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert glfw button to input button - const mods: input.Mods = .{ - .shift = glfw_mods.shift, - .ctrl = glfw_mods.control, - .alt = glfw_mods.alt, - .super = glfw_mods.super, - }; - const button: input.MouseButton = switch (glfw_button) { - .left => .left, - .right => .right, - .middle => .middle, - .four => .four, - .five => .five, - .six => .six, - .seven => .seven, - .eight => .eight, - }; - const action: input.MouseButtonState = switch (glfw_action) { - .press => .press, - .release => .release, - else => unreachable, - }; - - _ = core_win.mouseButtonCallback(action, button, mods) catch |err| { - log.err("error in scroll callback err={}", .{err}); - return; - }; - } - - fn dropCallback(window: glfw.Window, paths: [][*:0]const u8) void { - const surface = window.getUserPointer(CoreSurface) orelse return; - - var list = std.ArrayList(u8).init(surface.alloc); - defer list.deinit(); - - for (paths) |path| { - const path_slice = std.mem.span(path); - - // preallocate worst case of escaping every char + space - list.ensureTotalCapacity(path_slice.len * 2 + 1) catch |err| { - log.err("error in drop callback err={}", .{err}); - return; - }; - - const writer = list.writer(); - for (path_slice) |c| { - if (std.mem.indexOfScalar(u8, "\\ ()[]{}<>\"'`!#$&;|*?\t", c)) |_| { - writer.print("\\{c}", .{c}) catch unreachable; // memory preallocated - } else writer.writeByte(c) catch unreachable; // same here - } - writer.writeByte(' ') catch unreachable; // separate paths - - surface.textCallback(list.items) catch |err| { - log.err("error in drop callback err={}", .{err}); - return; - }; - - list.clearRetainingCapacity(); // avoid unnecessary reallocations - } - } -}; diff --git a/src/build/Config.zig b/src/build/Config.zig index 5f8780af9..3449a9ce3 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -378,11 +378,6 @@ pub fn init(b: *std.Build) !Config { "glslang", "spirv-cross", "simdutf", - - // This is default false because it is used for testing - // primarily and not official packaging. The packaging - // guide advises against building the GLFW backend. - "glfw3", }) |dep| { _ = b.systemIntegrationOption(dep, .{ .default = false }); } diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index f173e4856..5d620ac75 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -515,17 +515,6 @@ pub fn add( switch (self.config.app_runtime) { .none => {}, - - .glfw => if (b.lazyDependency("glfw", .{ - .target = target, - .optimize = optimize, - })) |glfw_dep| { - step.root_module.addImport( - "glfw", - glfw_dep.module("glfw"), - ); - }, - .gtk => try self.addGTK(step), } } diff --git a/src/config/Config.zig b/src/config/Config.zig index fec5b41fc..7a08b5583 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1582,9 +1582,9 @@ keybind: Keybinds = .{}, /// the visible screen area. This means that if the menu bar is visible, the /// window will be placed below the menu bar. /// -/// Note: this is only supported on macOS and Linux GLFW builds. The GTK -/// runtime does not support setting the window position, as windows are -/// only allowed position themselves in X11 and not Wayland. +/// Note: this is only supported on macOS. The GTK runtime does not support +/// setting the window position, as windows are only allowed position +/// themselves in X11 and not Wayland. @"window-position-x": ?i16 = null, @"window-position-y": ?i16 = null, @@ -2504,8 +2504,6 @@ keybind: Keybinds = .{}, /// /// The values `left` or `right` enable this for the left or right *Option* /// key, respectively. -/// -/// This does not work with GLFW builds. @"macos-option-as-alt": ?OptionAsAlt = null, /// Whether to enable the macOS window shadow. The default value is true. diff --git a/src/main_ghostty.zig b/src/main_ghostty.zig index 567eec5f9..b747fe6f0 100644 --- a/src/main_ghostty.zig +++ b/src/main_ghostty.zig @@ -7,7 +7,6 @@ const Allocator = std.mem.Allocator; const posix = std.posix; const build_config = @import("build_config.zig"); const options = @import("build_options"); -const glfw = @import("glfw"); const glslang = @import("glslang"); const macos = @import("macos"); const oni = @import("oniguruma"); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 3899bb8c5..70be1a96b 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -5,7 +5,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); -const glfw = @import("glfw"); const objc = @import("objc"); const macos = @import("macos"); const graphics = macos.graphics; @@ -38,11 +37,6 @@ pub const swap_chain_count = 3; const log = std.log.scoped(.metal); -// Get native API access on certain platforms so we can do more customization. -const glfwNative = glfw.Native(.{ - .cocoa = builtin.os.tag == .macos, -}); - layer: IOSurfaceLayer, /// MTLDevice @@ -87,27 +81,6 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal { // Get the metadata about our underlying view that we'll be rendering to. const info: ViewInfo = switch (apprt.runtime) { - apprt.glfw => info: { - // Everything in glfw is window-oriented so we grab the backing - // window, then derive everything from that. - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow( - opts.rt_surface.window, - ).?); - - const contentView = objc.Object.fromId( - nswindow.getProperty(?*anyopaque, "contentView").?, - ); - const scaleFactor = nswindow.getProperty( - graphics.c.CGFloat, - "backingScaleFactor", - ); - - break :info .{ - .view = contentView, - .scaleFactor = scaleFactor, - }; - }, - apprt.embedded => .{ .scaleFactor = @floatCast(opts.rt_surface.content_scale.x), .view = switch (opts.rt_surface.platform) { diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index cf195361e..00df8e273 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -5,7 +5,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); -const glfw = @import("glfw"); const gl = @import("opengl"); const shadertoy = @import("shadertoy.zig"); const apprt = @import("../apprt.zig"); @@ -60,18 +59,6 @@ pub fn deinit(self: *OpenGL) void { self.* = undefined; } -/// Returns the hints that we want for this -pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints { - _ = config; - return .{ - .context_version_major = MIN_VERSION_MAJOR, - .context_version_minor = MIN_VERSION_MINOR, - .opengl_profile = .opengl_core_profile, - .opengl_forward_compat = true, - .transparent_framebuffer = true, - }; -} - /// 32-bit windows cross-compilation breaks with `.c` for some reason, so... const gl_debug_proc_callconv = @typeInfo( @@ -172,8 +159,7 @@ fn prepareContext(getProcAddress: anytype) !void { /// This is called early right after surface creation. pub fn surfaceInit(surface: *apprt.Surface) !void { - // Treat this like a thread entry - const self: OpenGL = undefined; + _ = surface; switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"), @@ -181,8 +167,6 @@ pub fn surfaceInit(surface: *apprt.Surface) !void { // GTK uses global OpenGL context so we load from null. apprt.gtk => try prepareContext(null), - apprt.glfw => try self.threadEnter(surface), - apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly @@ -205,17 +189,12 @@ pub fn surfaceInit(surface: *apprt.Surface) !void { pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; _ = surface; - - // For GLFW, we grabbed the OpenGL context in surfaceInit and - // we need to release it before we start the renderer thread. - if (apprt.runtime == apprt.glfw) { - glfw.makeContextCurrent(null); - } } /// Callback called by renderer.Thread when it begins. pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; + _ = surface; switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"), @@ -227,21 +206,6 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void { // on the main thread. As such, we don't do anything here. }, - apprt.glfw => { - // We need to make the OpenGL context current. OpenGL requires - // that a single thread own the a single OpenGL context (if any). - // This ensures that the context switches over to our thread. - // Important: the prior thread MUST have detached the context - // prior to calling this entrypoint. - glfw.makeContextCurrent(surface.window); - errdefer glfw.makeContextCurrent(null); - glfw.swapInterval(1); - - // Load OpenGL bindings. This API is context-aware so this sets - // a threadlocal context for these pointers. - try prepareContext(&glfw.getProcAddress); - }, - apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly @@ -262,11 +226,6 @@ pub fn threadExit(self: *const OpenGL) void { // be sharing the global bindings with other windows. }, - apprt.glfw => { - gl.glad.unload(); - glfw.makeContextCurrent(null); - }, - apprt.embedded => { // TODO: see threadEnter }, diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index e7faf633f..1263364d3 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const glfw = @import("glfw"); const xev = @import("xev"); const wuffs = @import("wuffs"); const apprt = @import("../apprt.zig"); @@ -606,20 +605,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { } }; - /// Returns the hints that we want for this window. - pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints { - // If our graphics API provides hints, use them, - // otherwise fall back to generic hints. - if (@hasDecl(GraphicsAPI, "glfwWindowHints")) { - return GraphicsAPI.glfwWindowHints(config); - } - - return .{ - .client_api = .no_api, - .transparent_framebuffer = config.@"background-opacity" < 1, - }; - } - pub fn init(alloc: Allocator, options: renderer.Options) !Self { // Initialize our graphics API wrapper, this will prepare the // surface provided by the apprt and set up any API-specific diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 040132f03..039b11c03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -15,11 +15,6 @@ const posix = std.posix; const log = std.log.scoped(.io_handler); -/// True if we should disable the kitty keyboard protocol. We have to -/// disable this on GLFW because GLFW input events don't support the -/// correct granularity of events. -const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw; - /// This is used as the handler for the terminal.Stream type. This is /// stateful and is expected to live for the entire lifetime of the terminal. /// It is NOT VALID to stop a stream handler, create a new one, and use that @@ -913,8 +908,6 @@ pub const StreamHandler = struct { } pub fn queryKittyKeyboard(self: *StreamHandler) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("querying kitty keyboard mode", .{}); var data: termio.Message.WriteReq.Small.Array = undefined; const resp = try std.fmt.bufPrint(&data, "\x1b[?{}u", .{ @@ -933,15 +926,11 @@ pub const StreamHandler = struct { self: *StreamHandler, flags: terminal.kitty.KeyFlags, ) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("pushing kitty keyboard mode: {}", .{flags}); self.terminal.screen.kitty_keyboard.push(flags); } pub fn popKittyKeyboard(self: *StreamHandler, n: u16) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("popping kitty keyboard mode n={}", .{n}); self.terminal.screen.kitty_keyboard.pop(@intCast(n)); } @@ -951,8 +940,6 @@ pub const StreamHandler = struct { mode: terminal.kitty.KeySetMode, flags: terminal.kitty.KeyFlags, ) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("setting kitty keyboard mode: {} {}", .{ mode, flags }); self.terminal.screen.kitty_keyboard.set(mode, flags); } From e441094af036ba3df6047331543e8868e63c1ab4 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 3 Jul 2025 16:02:12 -0600 Subject: [PATCH 063/119] font: add constraint logic to rasterizers This is in preparation to move constraint off the GPU to simplify our shaders, instead we only need to constrain once at raster time and never again. This also significantly reworks the freetype renderGlyph function to be generally much cleaner and more straightforward. This commit doesn't actually apply the constraints to anything yet, that will be in following commits. --- pkg/freetype/freetype-zig.h | 2 + pkg/macos/graphics/context.zig | 16 + src/config/Config.zig | 19 +- src/font/face.zig | 203 ++++++++++++ src/font/face/coretext.zig | 138 +++++---- src/font/face/freetype.zig | 551 ++++++++++++++++++--------------- 6 files changed, 608 insertions(+), 321 deletions(-) diff --git a/pkg/freetype/freetype-zig.h b/pkg/freetype/freetype-zig.h index 29e546154..dcc65e514 100644 --- a/pkg/freetype/freetype-zig.h +++ b/pkg/freetype/freetype-zig.h @@ -5,3 +5,5 @@ #include #include #include +#include +#include diff --git a/pkg/macos/graphics/context.zig b/pkg/macos/graphics/context.zig index d1c6c943f..77e4344e0 100644 --- a/pkg/macos/graphics/context.zig +++ b/pkg/macos/graphics/context.zig @@ -141,6 +141,22 @@ pub fn Context(comptime T: type) type { @bitCast(rect), ); } + + pub fn scaleCTM(self: *T, sx: c.CGFloat, sy: c.CGFloat) void { + c.CGContextScaleCTM( + @ptrCast(self), + sx, + sy, + ); + } + + pub fn translateCTM(self: *T, tx: c.CGFloat, ty: c.CGFloat) void { + c.CGContextTranslateCTM( + @ptrCast(self), + tx, + ty, + ); + } }; } diff --git a/src/config/Config.zig b/src/config/Config.zig index f36132ea9..68b456d7a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -425,13 +425,16 @@ pub const compatibility = std.StaticStringMap( /// /// Available flags: /// -/// * `hinting` - Enable or disable hinting, enabled by default. -/// * `force-autohint` - Use the freetype auto-hinter rather than the -/// font's native hinter. Enabled by default. -/// * `monochrome` - Instructs renderer to use 1-bit monochrome -/// rendering. This option doesn't impact the hinter. -/// Enabled by default. -/// * `autohint` - Use the freetype auto-hinter. Enabled by default. +/// * `hinting` - Enable or disable hinting. Enabled by default. +/// +/// * `force-autohint` - Always use the freetype auto-hinter instead of +/// the font's native hinter. Enabled by default. +/// +/// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering. +/// This will disable anti-aliasing, and probably not look very good unless +/// you're using a pixel font. Disabled by default. +/// +/// * `autohint` - Enable the freetype auto-hinter. Enabled by default. /// /// Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint` @"freetype-load-flags": FreetypeLoadFlags = .{}, @@ -6961,7 +6964,7 @@ pub const FreetypeLoadFlags = packed struct { // to these defaults. hinting: bool = true, @"force-autohint": bool = true, - monochrome: bool = true, + monochrome: bool = false, autohint: bool = true, }; diff --git a/src/font/face.zig b/src/font/face.zig index 6355578db..363576ff0 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -94,6 +94,17 @@ pub const RenderOptions = struct { /// optionally by the rasterizer to better layout the glyph. cell_width: ?u2 = null, + /// Constraint and alignment properties for the glyph. The rasterizer + /// should call the `constrain` function on this with the original size + /// and bearings of the glyph to get remapped values that the glyph + /// should be scaled/moved to. + constraint: Constraint = .none, + + /// The number of cells, horizontally that the glyph is free to take up + /// when resized and aligned by `constraint`. This is usually 1, but if + /// there's whitespace to the right of the cell then it can be 2. + constraint_width: u2 = 1, + /// Thicken the glyph. This draws the glyph with a thicker stroke width. /// This is purely an aesthetic setting. /// @@ -108,6 +119,198 @@ pub const RenderOptions = struct { /// /// CoreText only. thicken_strength: u8 = 255, + + /// See the `constraint` field. + pub const Constraint = struct { + /// Don't constrain the glyph in any way. + pub const none: Constraint = .{}; + + /// Vertical sizing rule. + size_vertical: Size = .none, + /// Horizontal sizing rule. + size_horizontal: Size = .none, + + /// Vertical alignment rule. + align_vertical: Align = .none, + /// Horizontal alignment rule. + align_horizontal: Align = .none, + + /// Top padding when resizing. + pad_top: f64 = 0.0, + /// Left padding when resizing. + pad_left: f64 = 0.0, + /// Right padding when resizing. + pad_right: f64 = 0.0, + /// Bottom padding when resizing. + pad_bottom: f64 = 0.0, + + /// Maximum ratio of width to height when resizing. + max_xy_ratio: ?f64 = null, + + pub const Size = enum { + /// Don't change the size of this glyph. + none, + /// Move the glyph and optionally scale it down + /// proportionally to fit within the given axis. + fit, + /// Move and resize the glyph proportionally to + /// cover the given axis. + cover, + /// Same as `cover` but not proportional. + stretch, + }; + + pub const Align = enum { + /// Don't move the glyph on this axis. + none, + /// Move the glyph so that its leading (bottom/left) + /// edge aligns with the leading edge of the axis. + start, + /// Move the glyph so that its trailing (top/right) + /// edge aligns with the trailing edge of the axis. + end, + /// Move the glyph so that it is centered on this axis. + center, + }; + + /// The size and position of a glyph. + pub const GlyphSize = struct { + width: f64, + height: f64, + x: f64, + y: f64, + }; + + /// Apply this constraint to the provided glyph + /// size, given the available width and height. + pub fn constrain( + self: Constraint, + glyph: GlyphSize, + /// Available width + cell_width: f64, + /// Available height + cell_height: f64, + ) GlyphSize { + var g = glyph; + + const w = cell_width - + self.pad_left * cell_width - + self.pad_right * cell_width; + const h = cell_height - + self.pad_top * cell_height - + self.pad_bottom * cell_height; + + // Subtract padding from the bearings so that our + // alignment and sizing code works correctly. We + // re-add before returning. + g.x -= self.pad_left * cell_width; + g.y -= self.pad_bottom * cell_height; + + switch (self.size_horizontal) { + .none => {}, + .fit => if (g.width > w) { + const orig_height = g.height; + // Adjust our height and width to proportionally + // scale them to fit the glyph to the cell width. + g.height *= w / g.width; + g.width = w; + // Set our x to 0 since anything else would mean + // the glyph extends outside of the cell width. + g.x = 0; + // Compensate our y to keep things vertically + // centered as they're scaled down. + g.y += (orig_height - g.height) / 2; + } else if (g.width + g.x > w) { + // If the width of the glyph can fit in the cell but + // is currently outside due to the left bearing, then + // we reduce the left bearing just enough to fit it + // back in the cell. + g.x = w - g.width; + } else if (g.x < 0) { + g.x = 0; + }, + .cover => { + const orig_height = g.height; + + g.height *= w / g.width; + g.width = w; + + g.x = 0; + + g.y += (orig_height - g.height) / 2; + }, + .stretch => { + g.width = w; + g.x = 0; + }, + } + + switch (self.size_vertical) { + .none => {}, + .fit => if (g.height > h) { + const orig_width = g.width; + // Adjust our height and width to proportionally + // scale them to fit the glyph to the cell height. + g.width *= h / g.height; + g.height = h; + // Set our y to 0 since anything else would mean + // the glyph extends outside of the cell height. + g.y = 0; + // Compensate our x to keep things horizontally + // centered as they're scaled down. + g.x += (orig_width - g.width) / 2; + } else if (g.height + g.y > h) { + // If the height of the glyph can fit in the cell but + // is currently outside due to the bottom bearing, then + // we reduce the bottom bearing just enough to fit it + // back in the cell. + g.y = h - g.height; + } else if (g.y < 0) { + g.y = 0; + }, + .cover => { + const orig_width = g.width; + + g.width *= h / g.height; + g.height = h; + + g.y = 0; + + g.x += (orig_width - g.width) / 2; + }, + .stretch => { + g.height = h; + g.y = 0; + }, + } + + if (self.max_xy_ratio) |ratio| if (g.width > g.height * ratio) { + const orig_width = g.width; + g.width = g.height * ratio; + g.x += (orig_width - g.width) / 2; + }; + + switch (self.align_horizontal) { + .none => {}, + .start => g.x = 0, + .end => g.x = w - g.width, + .center => g.x = (w - g.width) / 2, + } + + switch (self.align_vertical) { + .none => {}, + .start => g.y = 0, + .end => g.y = h - g.height, + .center => g.y = (h - g.height) / 2, + } + + // Re-add our padding before returning. + g.x += self.pad_left * cell_width; + g.y += self.pad_bottom * cell_height; + + return g; + } + }; }; test { diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 06bba661f..35f094848 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -291,22 +291,29 @@ pub const Face = struct { // in the bottom left and +Y pointing up. var rect = self.font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null); + // Determine whether this is a color glyph. + const is_color = self.isColorGlyph(glyph_index); + // And whether it's (probably) a bitmap (sbix). + const sbix = is_color and self.color != null and self.color.?.sbix; + // If we're rendering a synthetic bold then we will gain 50% of // the line width on every edge, which means we should increase // our width and height by the line width and subtract half from // our origin points. - if (self.synthetic_bold) |line_width| { + // + // We don't add extra size if it's a sbix color font though, + // since bitmaps aren't affected by synthetic bold. + if (!sbix) if (self.synthetic_bold) |line_width| { rect.size.width += line_width; rect.size.height += line_width; rect.origin.x -= line_width / 2; rect.origin.y -= line_width / 2; - } + }; // We make an assumption that font smoothing ("thicken") // adds no more than 1 extra pixel to any edge. We don't // add extra size if it's a sbix color font though, since // bitmaps aren't affected by smoothing. - const sbix = self.color != null and self.color.?.sbix; if (opts.thicken and !sbix) { rect.size.width += 2.0; rect.size.height += 2.0; @@ -314,29 +321,43 @@ pub const Face = struct { rect.origin.y -= 1.0; } - // We compute the minimum and maximum x and y values. - // We round our min points down and max points up. - const x0: i32, const x1: i32, const y0: i32, const y1: i32 = .{ - @intFromFloat(@floor(rect.origin.x)), - @intFromFloat(@ceil(rect.origin.x) + @ceil(rect.size.width)), - @intFromFloat(@floor(rect.origin.y)), - @intFromFloat(@ceil(rect.origin.y) + @ceil(rect.size.height)), - }; + // If our rect is smaller than a quarter pixel in either axis + // then it has no outlines or they're too small to render. + // + // In this case we just return 0-sized glyph struct. + if (rect.size.width < 0.25 or rect.size.height < 0.25) + return font.Glyph{ + .width = 0, + .height = 0, + .offset_x = 0, + .offset_y = 0, + .atlas_x = 0, + .atlas_y = 0, + .advance_x = 0, + }; - // This bitmap is blank. I've seen it happen in a font, I don't know why. - // If it is empty, we just return a valid glyph struct that does nothing. - if (x1 <= x0 or y1 <= y0) return font.Glyph{ - .width = 0, - .height = 0, - .offset_x = 0, - .offset_y = 0, - .atlas_x = 0, - .atlas_y = 0, - .advance_x = 0, - }; + const metrics = opts.grid_metrics; + const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width); + const cell_height: f64 = @floatFromInt(metrics.cell_height); - const width: u32 = @intCast(x1 - x0); - const height: u32 = @intCast(y1 - y0); + const glyph_size = opts.constraint.constrain( + .{ + .width = rect.size.width, + .height = rect.size.height, + .x = rect.origin.x, + .y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)), + }, + cell_width, + cell_height, + ); + + const width = glyph_size.width; + const height = glyph_size.height; + const x = glyph_size.x; + const y = glyph_size.y; + + const px_width: u32 = @intFromFloat(@ceil(width)); + const px_height: u32 = @intFromFloat(@ceil(height)); // Settings that are specific to if we are rendering text or emoji. const color: struct { @@ -344,7 +365,7 @@ pub const Face = struct { depth: u32, space: *macos.graphics.ColorSpace, context_opts: c_uint, - } = if (!self.isColorGlyph(glyph_index)) .{ + } = if (!is_color) .{ .color = false, .depth = 1, .space = try macos.graphics.ColorSpace.createNamed(.linearGray), @@ -371,17 +392,17 @@ pub const Face = struct { // usually stabilizes pretty quickly and is very infrequent so I think // the allocation overhead is acceptable compared to the cost of // caching it forever or having to deal with a cache lifetime. - const buf = try alloc.alloc(u8, width * height * color.depth); + const buf = try alloc.alloc(u8, px_width * px_height * color.depth); defer alloc.free(buf); @memset(buf, 0); const context = macos.graphics.BitmapContext.context; const ctx = try macos.graphics.BitmapContext.create( buf, - width, - height, + px_width, + px_height, 8, - width * color.depth, + px_width * color.depth, color.space, color.context_opts, ); @@ -390,14 +411,14 @@ pub const Face = struct { // Perform an initial fill. This ensures that we don't have any // uninitialized pixels in the bitmap. if (color.color) - context.setRGBFillColor(ctx, 1, 1, 1, 0) + context.setRGBFillColor(ctx, 0, 0, 0, 0) else - context.setGrayFillColor(ctx, 1, 0); + context.setGrayFillColor(ctx, 0, 0); context.fillRect(ctx, .{ .origin = .{ .x = 0, .y = 0 }, .size = .{ - .width = @floatFromInt(width), - .height = @floatFromInt(height), + .width = @floatFromInt(px_width), + .height = @floatFromInt(px_height), }, }); @@ -427,49 +448,34 @@ pub const Face = struct { context.setLineWidth(ctx, line_width); } + context.scaleCTM( + ctx, + width / rect.size.width, + height / rect.size.height, + ); + // We want to render the glyphs at (0,0), but the glyphs themselves // are offset by bearings, so we have to undo those bearings in order // to get them to 0,0. - self.font.drawGlyphs(&glyphs, &.{ - .{ - .x = @floatFromInt(-x0), - .y = @floatFromInt(-y0), - }, - }, ctx); + self.font.drawGlyphs(&glyphs, &.{.{ + .x = -@floor(rect.origin.x), + .y = -@floor(rect.origin.y), + }}, ctx); - const region = region: { - // We reserve a region that's 1px wider and taller than we need - // in order to create a 1px separation between adjacent glyphs - // to prevent interpolation with adjacent glyphs while sampling - // from the atlas. - var region = try atlas.reserve( - alloc, - width + 1, - height + 1, - ); - - // We adjust the region width and height back down since we - // don't need the extra pixel, we just needed to reserve it - // so that it isn't used for other glyphs in the future. - region.width -= 1; - region.height -= 1; - break :region region; - }; + // Write our rasterized glyph to the atlas. + const region = try atlas.reserve(alloc, px_width, px_height); atlas.set(region, buf); - const metrics = opts.grid_metrics; - // This should be the distance from the bottom of // the cell to the top of the glyph's bounding box. - // - // The calculation is distance from bottom of cell to - // baseline plus distance from baseline to top of glyph. - const offset_y: i32 = @as(i32, @intCast(metrics.cell_baseline)) + y1; + const offset_y: i32 = + @as(i32, @intFromFloat(@floor(y))) + + @as(i32, @intCast(px_height)); // This should be the distance from the left of // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = x0; + var result: i32 = @intFromFloat(@round(x)); // If our cell was resized then we adjust our glyph's // position relative to the new center. This keeps glyphs @@ -490,8 +496,8 @@ pub const Face = struct { _ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances); return .{ - .width = width, - .height = height, + .width = px_width, + .height = px_height, .offset_x = offset_x, .offset_y = offset_y, .atlas_x = region.x, diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index accb891a4..9e057ceea 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -21,6 +21,8 @@ const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); const config = @import("../../config.zig"); +const F26Dot6 = opentype.sfnt.F26Dot6; + const log = std.log.scoped(.font_face); pub const Face = struct { @@ -58,14 +60,6 @@ pub const Face = struct { bold: bool = false, } = .{}, - /// The matrix applied to a regular font to create a synthetic italic. - const italic_matrix: freetype.c.FT_Matrix = .{ - .xx = 0x10000, - .xy = 0x044ED, // approx. tan(15) - .yx = 0, - .yy = 0x10000, - }; - /// Initialize a new font face with the given source in-memory. pub fn initFile( lib: Library, @@ -330,26 +324,32 @@ pub const Face = struct { self.ft_mutex.lock(); defer self.ft_mutex.unlock(); - const metrics = opts.grid_metrics; + // We enable hinting by default, and disable it if either of the + // constraint alignments are not center or none, since this means + // that the glyph needs to be aligned flush to the cell edge, and + // hinting can mess that up. + const do_hinting = self.load_flags.hinting and + switch (opts.constraint.align_horizontal) { + .start, .end => false, + .center, .none => true, + } and + switch (opts.constraint.align_vertical) { + .start, .end => false, + .center, .none => true, + }; - // If we have synthetic italic, then we apply a transformation matrix. - // We have to undo this because synthetic italic works by increasing - // the ref count of the base face. - if (self.synthetic.italic) self.face.setTransform(&italic_matrix, null); - defer if (self.synthetic.italic) self.face.setTransform(null, null); - - // If our glyph has color, we want to render the color + // Load the glyph. try self.face.loadGlyph(glyph_index, .{ + // If our glyph has color, we want to render the color .color = self.face.hasColor(), - // If we have synthetic bold, we have to set some additional - // glyph properties before render so we don't render here. - .render = !self.synthetic.bold, + // We don't render, because we'll invoke the render + // manually after applying constraints further down. + .render = false, // use options from config - .no_hinting = !self.load_flags.hinting, + .no_hinting = !do_hinting, .force_autohint = !self.load_flags.@"force-autohint", - .monochrome = !self.load_flags.monochrome, .no_autohint = !self.load_flags.autohint, // NO_SVG set to true because we don't currently support rendering @@ -359,260 +359,310 @@ pub const Face = struct { }); const glyph = self.face.handle.*.glyph; - // For synthetic bold, we embolden the glyph and render it. + const glyph_width: f64 = f26dot6ToF64(glyph.*.metrics.width); + const glyph_height: f64 = f26dot6ToF64(glyph.*.metrics.height); + + // If our glyph is smaller than a quarter pixel in either axis + // then it has no outlines or they're too small to render. + // + // In this case we just return 0-sized glyph struct. + if (glyph_width < 0.25 or glyph_height < 0.25) + return font.Glyph{ + .width = 0, + .height = 0, + .offset_x = 0, + .offset_y = 0, + .atlas_x = 0, + .atlas_y = 0, + .advance_x = 0, + }; + + // For synthetic bold, we embolden the glyph. if (self.synthetic.bold) { // We need to scale the embolden amount based on the font size. // This is a heuristic I found worked well across a variety of // founts: 1 pixel per 64 units of height. - const height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height); + const font_height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height); const ratio: f64 = 64.0 / 2048.0; - const amount = @ceil(height * ratio); + const amount = @ceil(font_height * ratio); _ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount)); - try self.face.renderGlyph(.normal); } - // This bitmap is blank. I've seen it happen in a font, I don't know why. - // If it is empty, we just return a valid glyph struct that does nothing. - const bitmap_ft = glyph.*.bitmap; - if (bitmap_ft.rows == 0) return .{ - .width = 0, - .height = 0, - .offset_x = 0, - .offset_y = 0, - .atlas_x = 0, - .atlas_y = 0, - .advance_x = 0, - }; + // Next we need to apply any constraints. + const metrics = opts.grid_metrics; - // Ensure we know how to work with the font format. And assure that - // or color depth is as expected on the texture atlas. If format is null - // it means there is no native color format for our Atlas and we must try - // conversion. - const format: ?font.Atlas.Format = switch (bitmap_ft.pixel_mode) { - freetype.c.FT_PIXEL_MODE_MONO => null, - freetype.c.FT_PIXEL_MODE_GRAY => .grayscale, - freetype.c.FT_PIXEL_MODE_BGRA => .bgra, + const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width); + const cell_height: f64 = @floatFromInt(metrics.cell_height); + + const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX); + const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height; + + const glyph_size = opts.constraint.constrain( + .{ + .width = glyph_width, + .height = glyph_height, + .x = glyph_x, + .y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)), + }, + cell_width, + cell_height, + ); + + const width = glyph_size.width; + const height = glyph_size.height; + // This may need to be adjusted later on. + var x = glyph_size.x; + const y = glyph_size.y; + + // Now we can render the glyph. + var bitmap: freetype.c.FT_Bitmap = undefined; + _ = freetype.c.FT_Bitmap_Init(&bitmap); + defer _ = freetype.c.FT_Bitmap_Done(self.lib.lib.handle, &bitmap); + switch (glyph.*.format) { + freetype.c.FT_GLYPH_FORMAT_OUTLINE => { + // Manually adjust the glyph outline with this transform. + // + // This offers better precision than using the freetype transform + // matrix, since that has 16.16 coefficients, and also I was having + // weird issues that I can only assume where due to freetype doing + // some bad caching or something when I did this using the matrix. + const scale_x = width / glyph_width; + const scale_y = height / glyph_height; + const skew: f64 = + if (self.synthetic.italic) + // We skew by 12 degrees to synthesize italics. + @tan(std.math.degreesToRadians(12)) + else + 0.0; + + var bbox_before: freetype.c.FT_BBox = undefined; + _ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_before); + + const outline = &glyph.*.outline; + for (outline.points[0..@intCast(outline.n_points)]) |*p| { + // Convert to f64 for processing + var px = f26dot6ToF64(p.x); + var py = f26dot6ToF64(p.y); + + // Scale + px *= scale_x; + py *= scale_y; + + // Skew + px += py * skew; + + // Convert back and store + p.x = @as(i32, @bitCast(F26Dot6.from(px))); + p.y = @as(i32, @bitCast(F26Dot6.from(py))); + } + + var bbox_after: freetype.c.FT_BBox = undefined; + _ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_after); + + // If our bounding box changed, account for the lsb difference. + // + // This can happen when we skew glyphs that have a bit sticking + // out to the left higher up, like the top of the T or the serif + // on the lower case l in many monospace fonts. + x += f26dot6ToF64(bbox_after.xMin) - f26dot6ToF64(bbox_before.xMin); + + try self.face.renderGlyph( + if (self.load_flags.monochrome) + .mono + else + .normal, + ); + + // Copy the glyph's bitmap, making sure + // that it's 8bpp and densely packed. + if (freetype.c.FT_Bitmap_Convert( + self.lib.lib.handle, + &glyph.*.bitmap, + &bitmap, + 1, + ) != 0) { + return error.BitmapHandlingError; + } + }, + + freetype.c.FT_GLYPH_FORMAT_BITMAP => { + // If our glyph has a non-color bitmap, we need + // to convert it to dense 8bpp so that the scale + // operation works correctly. + switch (glyph.*.bitmap.pixel_mode) { + freetype.c.FT_PIXEL_MODE_BGRA, + freetype.c.FT_PIXEL_MODE_GRAY, + => {}, + else => { + var converted: freetype.c.FT_Bitmap = undefined; + freetype.c.FT_Bitmap_Init(&converted); + if (freetype.c.FT_Bitmap_Convert( + self.lib.lib.handle, + &glyph.*.bitmap, + &converted, + 1, + ) != 0) { + return error.BitmapHandlingError; + } + // Free the existing glyph bitmap and + // replace it with the converted one. + _ = freetype.c.FT_Bitmap_Done( + self.lib.lib.handle, + &glyph.*.bitmap, + ); + glyph.*.bitmap = converted; + }, + } + + const glyph_bitmap = glyph.*.bitmap; + + // Round our target width and height + // as the size for our scaled bitmap. + const w: u32 = @intFromFloat(@round(width)); + const h: u32 = @intFromFloat(@round(height)); + const pitch = w * atlas.format.depth(); + + // Allocate a buffer for our scaled bitmap. + // + // We'll copy this to the original bitmap once we're + // done so we can free it at the end of this scope. + const buf = try alloc.alloc(u8, pitch * h); + defer alloc.free(buf); + + // Resize + if (stb.stbir_resize_uint8( + glyph_bitmap.buffer, + @intCast(glyph_bitmap.width), + @intCast(glyph_bitmap.rows), + glyph_bitmap.pitch, + buf.ptr, + @intCast(w), + @intCast(h), + @intCast(pitch), + atlas.format.depth(), + ) == 0) { + // This should never fail because this is a + // fairly straightforward in-memory operation... + return error.GlyphResizeFailed; + } + + const scaled_bitmap: freetype.c.FT_Bitmap = .{ + .buffer = buf.ptr, + .width = @intCast(w), + .rows = @intCast(h), + .pitch = @intCast(pitch), + .pixel_mode = glyph_bitmap.pixel_mode, + .num_grays = glyph_bitmap.num_grays, + }; + + // Replace the bitmap's buffer and size info. + if (freetype.c.FT_Bitmap_Copy( + self.lib.lib.handle, + &scaled_bitmap, + &bitmap, + ) != 0) { + return error.BitmapHandlingError; + } + }, + + else => |f| { + // Glyph formats are tags, so we can + // output a semi-readable error here. + log.err( + "Can't render glyph with unsupported glyph format \"{s}\"", + .{[4]u8{ + @truncate(f >> 24), + @truncate(f >> 16), + @truncate(f >> 8), + @truncate(f >> 0), + }}, + ); + return error.UnsupportedGlyphFormat; + }, + } + + // If this is a color glyph but we're trying to render it to the + // grayscale atlas, or vice versa, then we throw and error. Maybe + // in the future we could convert, but for now it should be fine. + switch (bitmap.pixel_mode) { + freetype.c.FT_PIXEL_MODE_GRAY => if (atlas.format != .grayscale) { + return error.WrongAtlas; + }, + freetype.c.FT_PIXEL_MODE_BGRA => if (atlas.format != .bgra) { + return error.WrongAtlas; + }, else => { - log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode }); + log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap.pixel_mode }); @panic("unsupported pixel mode"); }, - }; - - // If our atlas format doesn't match, look for conversions if possible. - const bitmap_converted = if (format == null or atlas.format != format.?) blk: { - const func = convert.map[bitmap_ft.pixel_mode].get(atlas.format) orelse { - log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode }); - return error.UnsupportedPixelMode; - }; - - log.debug("converting from pixel_mode={} to atlas_format={}", .{ - bitmap_ft.pixel_mode, - atlas.format, - }); - break :blk try func(alloc, bitmap_ft); - } else null; - defer if (bitmap_converted) |bm| { - const len = @as(usize, @intCast(bm.pitch)) * @as(usize, @intCast(bm.rows)); - alloc.free(bm.buffer[0..len]); - }; - - // Now we need to see if we need to resize this bitmap. This can happen - // in scenarios where we have fixed size glyphs. For example, emoji - // can be quite large (i.e. 128x128) when we have a cell width of 24! - // The issue with large bitmaps is they take a huge amount of space in - // the atlas and force resizes quite frequently. We pay some CPU cost - // up front to resize the glyph to avoid significant CPU cost to resize - // and copy the atlas. - const bitmap_original = bitmap_converted orelse bitmap_ft; - const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: { - const original_width = bitmap_original.width; - const original_height = bitmap_original.rows; - var result = bitmap_original; - // TODO: We are limiting this to only color glyphs, so mainly emoji. - // We can rework this after a future improvement (promised by Qwerasd) - // which implements more flexible resizing rules. - if (atlas.format != .grayscale and opts.cell_width != null) { - const cell_width = opts.cell_width orelse unreachable; - // If we have a cell_width, we constrain - // the glyph to fit within the cell(s). - result.width = metrics.cell_width * @as(u32, cell_width); - result.rows = (result.width * original_height) / original_width; - } else { - // If we don't have a cell_width, we scale to fill vertically - result.rows = metrics.cell_height; - result.width = (metrics.cell_height * original_width) / original_height; - } - - // If we already fit, we don't need to resize - if (original_height <= result.rows and original_width <= result.width) { - break :resized null; - } - - result.pitch = @as(c_int, @intCast(result.width)) * atlas.format.depth(); - - const buf = try alloc.alloc( - u8, - @as(usize, @intCast(result.pitch)) * @as(usize, @intCast(result.rows)), - ); - result.buffer = buf.ptr; - errdefer alloc.free(buf); - - if (stb.stbir_resize_uint8( - bitmap_original.buffer, - @intCast(original_width), - @intCast(original_height), - bitmap_original.pitch, - result.buffer, - @intCast(result.width), - @intCast(result.rows), - result.pitch, - atlas.format.depth(), - ) == 0) { - // This should never fail because this is a fairly straightforward - // in-memory operation... - return error.GlyphResizeFailed; - } - - break :resized result; - }; - defer if (bitmap_resized) |bm| { - const len = @as(usize, @intCast(bm.pitch)) * @as(usize, @intCast(bm.rows)); - alloc.free(bm.buffer[0..len]); - }; - - const bitmap = bitmap_resized orelse (bitmap_converted orelse bitmap_ft); - const tgt_w = bitmap.width; - const tgt_h = bitmap.rows; - - // Must have non-empty bitmap because we return earlier - // if zero. We assume the rest of this that it is nont-zero so - // this is important. - assert(tgt_w > 0 and tgt_h > 0); - - // If we resized our bitmap, we need to recalculate some metrics that - // we use such as the top/left offsets. These need to be scaled by the - // same ratio as the resize. - const glyph_metrics = if (bitmap_resized) |bm| metrics: { - // Our ratio for the resize - const ratio = ratio: { - const new: f64 = @floatFromInt(bm.rows); - const old: f64 = @floatFromInt(bitmap_original.rows); - break :ratio new / old; - }; - - var copy = glyph.*; - copy.bitmap_top = @as(c_int, @intFromFloat(@round(@as(f64, @floatFromInt(copy.bitmap_top)) * ratio))); - copy.bitmap_left = @as(c_int, @intFromFloat(@round(@as(f64, @floatFromInt(copy.bitmap_left)) * ratio))); - break :metrics copy; - } else glyph.*; - - // Allocate our texture atlas region - const region = region: { - // We need to add a 1px padding to the font so that we don't - // get fuzzy issues when blending textures. - const padding = 1; - - // Get the full padded region - var region = try atlas.reserve( - alloc, - tgt_w + (padding * 2), // * 2 because left+right - tgt_h + (padding * 2), // * 2 because top+bottom - ); - - // Modify the region so that we remove the padding so that - // we write to the non-zero location. The data in an Altlas - // is always initialized to zero (Atlas.clear) so we don't - // need to worry about zero-ing that. - region.x += padding; - region.y += padding; - region.width -= padding * 2; - region.height -= padding * 2; - break :region region; - }; - - // Copy the image into the region. - assert(region.width > 0 and region.height > 0); - { - const depth = atlas.format.depth(); - - // We can avoid a buffer copy if our atlas width and bitmap - // width match and the bitmap pitch is just the width (meaning - // the data is tightly packed). - const needs_copy = !(tgt_w == bitmap.width and (bitmap.width * depth) == bitmap.pitch); - - // If we need to copy the data, we copy it into a temporary buffer. - const buffer = if (needs_copy) buffer: { - const temp = try alloc.alloc(u8, tgt_w * tgt_h * depth); - var dst_ptr = temp; - var src_ptr = bitmap.buffer; - var i: usize = 0; - while (i < bitmap.rows) : (i += 1) { - fastmem.copy(u8, dst_ptr, src_ptr[0 .. bitmap.width * depth]); - dst_ptr = dst_ptr[tgt_w * depth ..]; - src_ptr += @as(usize, @intCast(bitmap.pitch)); - } - break :buffer temp; - } else bitmap.buffer[0..(tgt_w * tgt_h * depth)]; - defer if (buffer.ptr != bitmap.buffer) alloc.free(buffer); - - // Write the glyph information into the atlas - assert(region.width == tgt_w); - assert(region.height == tgt_h); - atlas.set(region, buffer); } - const offset_y: c_int = offset_y: { - // For non-scalable colorized fonts, we assume they are pictographic - // and just center the glyph. So far this has only applied to emoji - // fonts. Emoji fonts don't always report a correct ascender/descender - // (mainly Apple Emoji) so we just center them. Also, since emoji font - // aren't scalable, cell_baseline is incorrect anyways. - // - // NOTE(mitchellh): I don't know if this is right, this doesn't - // _feel_ right, but it makes all my limited test cases work. - if (self.face.hasColor() and !self.face.isScalable()) { - break :offset_y @intCast(tgt_h + (metrics.cell_height -| tgt_h) / 2); + const px_width = bitmap.width; + const px_height = bitmap.rows; + const len: usize = @intCast( + @as(c_uint, @intCast(@abs(bitmap.pitch))) * bitmap.rows, + ); + + // If our bitmap is grayscale, make sure to multiply all pixel + // values by the right factor to bring `num_grays` up to 256. + // + // This is necessary because FT_Bitmap_Convert doesn't do this, + // it just sets num_grays to the correct number and uses the + // original smaller pixel values. + if (bitmap.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY and + bitmap.num_grays < 256) + { + const factor: u8 = @intCast(255 / (bitmap.num_grays - 1)); + for (bitmap.buffer[0..len]) |*p| { + p.* *= factor; } + bitmap.num_grays = 256; + } - // The Y offset is the offset of the top of our bitmap PLUS our - // baseline calculation. The baseline calculation is so that everything - // is properly centered when we render it out into a monospace grid. - // Note: we add here because our X/Y is actually reversed, adding goes UP. - break :offset_y glyph_metrics.bitmap_top + @as(c_int, @intCast(metrics.cell_baseline)); - }; + // Must have non-empty bitmap because we return earlier if zero. + // We assume the rest of this that it is non-zero so this is important. + assert(px_width > 0 and px_height > 0); + // If this doesn't match then something is wrong. + assert(px_width * atlas.format.depth() == bitmap.pitch); + + // Allocate our texture atlas region and copy our bitmap in to it. + const region = try atlas.reserve(alloc, px_width, px_height); + atlas.set(region, bitmap.buffer[0..len]); + + // This should be the distance from the bottom of + // the cell to the top of the glyph's bounding box. + const offset_y: i32 = + @as(i32, @intFromFloat(@floor(y))) + + @as(i32, @intCast(px_height)); + + // This should be the distance from the left of + // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = glyph_metrics.bitmap_left; + var result: i32 = @intFromFloat(@floor(x)); - // If our cell was resized to be wider then we center our - // glyph in the cell. + // If our cell was resized then we adjust our glyph's + // position relative to the new center. This keeps glyphs + // centered in the cell whether it was made wider or narrower. if (metrics.original_cell_width) |original_width| { - if (original_width < metrics.cell_width) { - const diff = (metrics.cell_width - original_width) / 2; - result += @intCast(diff); - } + const before: i32 = @intCast(original_width); + const after: i32 = @intCast(metrics.cell_width); + // Increase the offset by half of the difference + // between the widths to keep things centered. + result += @divTrunc(after - before, 2); } break :offset_x result; }; - // log.warn("renderGlyph width={} height={} offset_x={} offset_y={} glyph_metrics={}", .{ - // tgt_w, - // tgt_h, - // glyph_metrics.bitmap_left, - // offset_y, - // glyph_metrics, - // }); - - // Store glyph metadata return Glyph{ - .width = tgt_w, - .height = tgt_h, + .width = px_width, + .height = px_height, .offset_x = offset_x, .offset_y = offset_y, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = f26dot6ToFloat(glyph_metrics.advance.x), + .advance_x = f26dot6ToFloat(glyph.*.advance.x), }; } @@ -631,7 +681,7 @@ pub const Face = struct { } fn f26dot6ToF64(v: freetype.c.FT_F26Dot6) f64 { - return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64); + return @as(F26Dot6, @bitCast(@as(i32, @intCast(v)))).to(f64); } pub const GetMetricsError = error{ @@ -950,13 +1000,15 @@ test "color emoji" { } // resize + // TODO: Comprehensive tests for constraints, + // this is just an adapted legacy test. { const glyph = try ft_font.renderGlyph( alloc, &atlas, ft_font.glyphIndex('🥸').?, .{ .grid_metrics = .{ - .cell_width = 10, + .cell_width = 13, .cell_height = 24, .cell_baseline = 0, .underline_position = 0, @@ -967,6 +1019,11 @@ test "color emoji" { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, + }, .constraint_width = 2, .constraint = .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .center, + .align_vertical = .center, } }, ); try testing.expectEqual(@as(u32, 24), glyph.height); From 1775b75f2ce24027089de090541db59d264696e9 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 3 Jul 2025 16:03:42 -0600 Subject: [PATCH 064/119] font: generate glyph constraints based on nerd font patcher --- src/font/nerd_font_attributes.zig | 349 +++++ src/font/nerd_font_codegen.py | 259 ++++ vendor/nerd-fonts/LICENSE | 126 ++ vendor/nerd-fonts/README.md | 10 + vendor/nerd-fonts/font-patcher.py | 2296 +++++++++++++++++++++++++++++ 5 files changed, 3040 insertions(+) create mode 100644 src/font/nerd_font_attributes.zig create mode 100644 src/font/nerd_font_codegen.py create mode 100644 vendor/nerd-fonts/LICENSE create mode 100644 vendor/nerd-fonts/README.md create mode 100644 vendor/nerd-fonts/font-patcher.py diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig new file mode 100644 index 000000000..1465a8466 --- /dev/null +++ b/src/font/nerd_font_attributes.zig @@ -0,0 +1,349 @@ +//! This is a generate file, produced by nerd_font_codegen.py +//! DO NOT EDIT BY HAND! +//! +//! This file provides info extracted from the nerd fonts patcher script, +//! specifying the scaling/positioning attributes of various glyphs. + +const Constraint = @import("face.zig").RenderOptions.Constraint; + +/// Get the a constraints for the provided codepoint. +pub fn getConstraint(cp: u21) Constraint { + return switch (cp) { + 0x2500...0x259f, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .center, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0x2630, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .align_horizontal = .center, + .align_vertical = .center, + .pad_left = 0.1, + .pad_right = 0.1, + .pad_top = 0.01, + .pad_bottom = 0.01, + }, + 0x276c...0x2771, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .align_horizontal = .center, + .align_vertical = .center, + }, + 0xe0b0, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0b1, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.7, + }, + 0xe0b2, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0b3, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .max_xy_ratio = 0.7, + }, + 0xe0b4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.59, + }, + 0xe0b5, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.5, + }, + 0xe0b6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.59, + }, + 0xe0b7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .max_xy_ratio = 0.5, + }, + 0xe0b8, + 0xe0bc, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0b9, + 0xe0bd, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0ba, + 0xe0be, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0bb, + 0xe0bf, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + }, + 0xe0c0, + 0xe0c8, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0c1, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0c2, + 0xe0ca, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0c3, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + }, + 0xe0c4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.01, + .pad_bottom = 0.01, + .max_xy_ratio = 0.86, + }, + 0xe0c5, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.01, + .pad_bottom = 0.01, + .max_xy_ratio = 0.86, + }, + 0xe0c6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.01, + .pad_bottom = 0.01, + .max_xy_ratio = 0.78, + }, + 0xe0c7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.01, + .pad_bottom = 0.01, + .max_xy_ratio = 0.78, + }, + 0xe0cc, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.85, + }, + 0xe0cd, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.865, + }, + 0xe0ce, + 0xe0d0...0xe0d1, + => .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0cf, + 0xe0d3, + 0xe0d5, + => .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .center, + .align_vertical = .center, + }, + 0xe0d2, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0x23fb...0x23fe, + 0x2665, + 0x26a1, + 0x2b58, + 0xe000...0xe0a9, + 0xe4fa...0xe7ef, + 0xea60...0xec1e, + 0xed00...0xf847, + 0xf0001...0xf1af0, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .align_horizontal = .center, + .align_vertical = .center, + }, + else => .none, + }; +} diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py new file mode 100644 index 000000000..c2dd7314f --- /dev/null +++ b/src/font/nerd_font_codegen.py @@ -0,0 +1,259 @@ +""" +This file is mostly vibe coded because I don't like Python. It extracts the +patch sets from the nerd fonts font patcher file in order to extract scaling +rules and attributes for different codepoint ranges which it then codegens +in to a Zig file with a function that switches over codepoints and returns +the attributes and scaling rules. + +This does include an `eval` call! This is spooky, but we trust +the nerd fonts code to be safe and not malicious or anything. +""" + +import ast +import math +from pathlib import Path +from collections import defaultdict + + +class PatchSetExtractor(ast.NodeVisitor): + def __init__(self): + self.symbol_table = {} + self.patch_set_values = [] + + def visit_ClassDef(self, node): + if node.name == "font_patcher": + for item in node.body: + if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set": + self.visit_setup_patch_set(item) + + def visit_setup_patch_set(self, node): + # First pass: gather variable assignments + for stmt in node.body: + if isinstance(stmt, ast.Assign): + # Store simple variable assignments in the symbol table + if len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name): + var_name = stmt.targets[0].id + self.symbol_table[var_name] = stmt.value + + # Second pass: process self.patch_set + for stmt in node.body: + if isinstance(stmt, ast.Assign): + for target in stmt.targets: + if isinstance(target, ast.Attribute) and target.attr == "patch_set": + if isinstance(stmt.value, ast.List): + for elt in stmt.value.elts: + if isinstance(elt, ast.Dict): + self.process_patch_entry(elt) + + def resolve_symbol(self, node): + """Resolve named variables to their actual values from the symbol table.""" + if isinstance(node, ast.Name) and node.id in self.symbol_table: + return self.safe_literal_eval(self.symbol_table[node.id]) + return self.safe_literal_eval(node) + + def safe_literal_eval(self, node): + """Try to evaluate or stringify an AST node.""" + try: + return ast.literal_eval(node) + except Exception: + # Spooky eval! But we trust nerd fonts to be safe... + if hasattr(ast, "unparse"): + return eval( + ast.unparse(node), {"box_keep": True}, {"self": SpoofSelf()} + ) + else: + return f"" + + def process_patch_entry(self, dict_node): + entry = {} + for key_node, value_node in zip(dict_node.keys, dict_node.values): + if isinstance(key_node, ast.Constant) and key_node.value in ( + "Enabled", + "Name", + "Filename", + "Exact", + ): + continue + key = ast.literal_eval(key_node) + value = self.resolve_symbol(value_node) + entry[key] = value + self.patch_set_values.append(entry) + + +def extract_patch_set_values(source_code): + tree = ast.parse(source_code) + extractor = PatchSetExtractor() + extractor.visit(tree) + return extractor.patch_set_values + + +# We have to spoof `self` and `self.args` for the eval. +class SpoofArgs: + careful = True + + +class SpoofSelf: + args = SpoofArgs() + + +def parse_alignment(val): + return { + "l": ".start", + "r": ".end", + "c": ".center", + "": None, + }.get(val, ".none") + + +def get_param(d, key, default): + return float(d.get(key, default)) + + +def attr_key(attr): + """Convert attributes to a hashable key for grouping.""" + stretch = attr.get("stretch", "") + return ( + parse_alignment(attr.get("align", "")), + parse_alignment(attr.get("valign", "")), + stretch, + float(attr.get("params", {}).get("overlap", 0.0)), + float(attr.get("params", {}).get("xy-ratio", -1.0)), + float(attr.get("params", {}).get("ypadding", 0.0)), + ) + + +def coalesce_codepoints_to_ranges(codepoints): + """Convert a sorted list of integers to a list of single values and ranges.""" + ranges = [] + cp_iter = iter(sorted(codepoints)) + try: + start = prev = next(cp_iter) + for cp in cp_iter: + if cp == prev + 1: + prev = cp + else: + ranges.append((start, prev)) + start = prev = cp + ranges.append((start, prev)) + except StopIteration: + pass + return ranges + + +def emit_zig_entry_multikey(codepoints, attr): + align = parse_alignment(attr.get("align", "")) + valign = parse_alignment(attr.get("valign", "")) + stretch = attr.get("stretch", "") + params = attr.get("params", {}) + + overlap = get_param(params, "overlap", 0.0) + xy_ratio = get_param(params, "xy-ratio", -1.0) + y_padding = get_param(params, "ypadding", 0.0) + + ranges = coalesce_codepoints_to_ranges(codepoints) + keys = "\n".join( + f" 0x{start:x}...0x{end:x}," if start != end else f" 0x{start:x}," + for start, end in ranges + ) + + s = f"""{keys} + => .{{\n""" + + # These translations don't quite capture the way + # the actual patcher does scaling, but they're a + # good enough compromise. + if ("xy" in stretch): + s += " .size_horizontal = .stretch,\n" + s += " .size_vertical = .stretch,\n" + elif ("!" in stretch): + s += " .size_horizontal = .cover,\n" + s += " .size_vertical = .fit,\n" + elif ("^" in stretch): + s += " .size_horizontal = .cover,\n" + s += " .size_vertical = .cover,\n" + else: + s += " .size_horizontal = .fit,\n" + s += " .size_vertical = .fit,\n" + + if (align is not None): + s += f" .align_horizontal = {align},\n" + if (valign is not None): + s += f" .align_vertical = {valign},\n" + + if (overlap != 0.0): + pad = -overlap + s += f" .pad_left = {pad},\n" + s += f" .pad_right = {pad},\n" + v_pad = y_padding - math.copysign(min(0.01, abs(overlap)), overlap) + s += f" .pad_top = {v_pad},\n" + s += f" .pad_bottom = {v_pad},\n" + + if (xy_ratio > 0): + s += f" .max_xy_ratio = {xy_ratio},\n" + + s += " }," + + return s + +def generate_zig_switch_arms(patch_set): + entries = {} + for entry in patch_set: + attributes = entry["Attributes"] + + for cp in range(entry["SymStart"], entry["SymEnd"] + 1): + entries[cp] = attributes["default"] + + for k, v in attributes.items(): + if isinstance(k, int): + entries[k] = v + + del entries[0] + + # Group codepoints by attribute key + grouped = defaultdict(list) + for cp, attr in entries.items(): + grouped[attr_key(attr)].append(cp) + + # Emit zig switch arms + result = [] + for _, codepoints in sorted(grouped.items(), key=lambda x: x[1]): + # Use one of the attrs in the group to emit the value + attr = entries[codepoints[0]] + result.append(emit_zig_entry_multikey(codepoints, attr)) + + return "\n".join(result) + + +if __name__ == "__main__": + path = ( + Path(__file__).resolve().parent + / ".." + / ".." + / "vendor" + / "nerd-fonts" + / "font-patcher.py" + ) + with open(path, "r", encoding="utf-8") as f: + source = f.read() + + patch_set = extract_patch_set_values(source) + + out_path = Path(__file__).resolve().parent / "nerd_font_attributes.zig" + + with open(out_path, "w", encoding="utf-8") as f: + f.write("""//! This is a generate file, produced by nerd_font_codegen.py +//! DO NOT EDIT BY HAND! +//! +//! This file provides info extracted from the nerd fonts patcher script, +//! specifying the scaling/positioning attributes of various glyphs. + +const Constraint = @import("face.zig").RenderOptions.Constraint; + +/// Get the a constraints for the provided codepoint. +pub fn getConstraint(cp: u21) Constraint { + return switch (cp) { +""") + f.write(generate_zig_switch_arms(patch_set)) + f.write("\n") + + f.write(" else => .none,\n };\n}\n") diff --git a/vendor/nerd-fonts/LICENSE b/vendor/nerd-fonts/LICENSE new file mode 100644 index 000000000..d163912b3 --- /dev/null +++ b/vendor/nerd-fonts/LICENSE @@ -0,0 +1,126 @@ +# Nerd Fonts Licensing + +There are various sources used under various licenses: + +* Nerd Fonts source fonts, patched fonts, and folders with explict OFL SIL files are licensed under SIL OPEN FONT LICENSE Version 1.1 (see below). +* Nerd Fonts original source code files (such as `.sh`, `.py`, `font-patcher` and others) are licensed under the MIT License (MIT) (see below). +* Many other licenses are present in this project for even more detailed breakdown see: [License Audit](https://github.com/ryanoasis/nerd-fonts/blob/-/license-audit.md). + +## Source files not in folders containing an explicit license are using the MIT License (MIT) + +The MIT License (MIT) + +Copyright (c) 2014 Ryan L McIntyre + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## Various Fonts, Patched Fonts, SVGs, Glyph Fonts, and any files in a folder with explicit SIL OFL 1.1 License + +Copyright (c) 2014, Ryan L McIntyre (https://ryanlmcintyre.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/vendor/nerd-fonts/README.md b/vendor/nerd-fonts/README.md new file mode 100644 index 000000000..66dec54cb --- /dev/null +++ b/vendor/nerd-fonts/README.md @@ -0,0 +1,10 @@ +We have a copy of the `font-patcher` file from `nerd-fonts` here, fetched from +https://github.com/ryanoasis/nerd-fonts/blob/master/font-patcher. + +This is MIT licensed, see `LICENSE` in this directory. + +We use parse a section of this file to codegen a lookup table of the nerd font +scaling rules. See `src/font/nerd_font_codegen.py` in the main Ghostty source +tree for more info. + +Last fetched commit: ebc376cbd43f609d8084f47dd348646595ce066e diff --git a/vendor/nerd-fonts/font-patcher.py b/vendor/nerd-fonts/font-patcher.py new file mode 100644 index 000000000..6c7ebfe37 --- /dev/null +++ b/vendor/nerd-fonts/font-patcher.py @@ -0,0 +1,2296 @@ +#!/usr/bin/env python +# coding=utf8 +# Nerd Fonts Version: 3.4.0 +# Script version is further down + +from __future__ import absolute_import, print_function, unicode_literals + +# Change the script version when you edit this script: +script_version = "4.20.5" + +version = "3.4.0" +projectName = "Nerd Fonts" +projectNameAbbreviation = "NF" +projectNameSingular = projectName[:-1] + +import sys +import re +import os +import argparse +from argparse import RawTextHelpFormatter +import errno +import subprocess +import json +from enum import Enum +import logging +try: + import configparser +except ImportError: + sys.exit(projectName + ": configparser module is probably not installed. Try `pip install configparser` or equivalent") +try: + import psMat + import fontforge +except ImportError: + sys.exit( + projectName + ( + ": FontForge module could not be loaded. Try installing fontforge python bindings " + "[e.g. on Linux Debian or Ubuntu: `sudo apt install fontforge python3-fontforge`]" + ) + ) + +sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'bin', 'scripts', 'name_parser')) +try: + from FontnameParser import FontnameParser + from FontnameTools import FontnameTools + FontnameParserOK = True +except ImportError: + FontnameParserOK = False + +class TableHEADWriter: + """ Access to the HEAD table without external dependencies """ + def getlong(self, pos = None): + """ Get four bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 24) + (ord(self.f.read(1)) << 16) + (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def getshort(self, pos = None): + """ Get two bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def putlong(self, num, pos = None): + """ Put number as four bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 24) & 0xFF, (num >> 16) & 0xFF ,(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def putshort(self, num, pos = None): + """ Put number as two bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def calc_checksum(self, start, end, checksum = 0): + """ Calculate a font table checksum, optionally ignoring another embedded checksum value (for table 'head') """ + self.f.seek(start) + for i in range(start, end - 4, 4): + checksum += self.getlong() + checksum &= 0xFFFFFFFF + i += 4 + extra = 0 + for j in range(4): + extra = extra << 8 + if i + j <= end: + extra += ord(self.f.read(1)) + checksum = (checksum + extra) & 0xFFFFFFFF + return checksum + + def find_table(self, tablenames, idx): + """ Search all tables for one of the tables in tablenames and store its metadata """ + # Use font with index idx if this is a font collection file + self.f.seek(0, 0) + tag = self.f.read(4) + if tag == b'ttcf': + self.f.seek(2*2, 1) + self.num_fonts = self.getlong() + if (idx >= self.num_fonts): + raise Exception('Trying to access subfont index {} but have only {} fonts'.format(idx, num_fonts)) + for _ in range(idx + 1): + offset = self.getlong() + self.f.seek(offset, 0) + elif idx != 0: + raise Exception('Trying to access subfont but file is no collection') + else: + self.f.seek(0, 0) + self.num_fonts = 1 + + self.f.seek(4, 1) + numtables = self.getshort() + self.f.seek(3*2, 1) + + for i in range(numtables): + tab_name = self.f.read(4) + self.tab_check_offset = self.f.tell() + self.tab_check = self.getlong() + self.tab_offset = self.getlong() + self.tab_length = self.getlong() + if tab_name in tablenames: + return True + return False + + def find_head_table(self, idx): + """ Search all tables for the HEAD table and store its metadata """ + # Use font with index idx if this is a font collection file + found = self.find_table([ b'head' ], idx) + if not found: + raise Exception('No HEAD table found in font idx {}'.format(idx)) + + + def goto(self, where): + """ Go to a named location in the file or to the specified index """ + if isinstance(where, str): + positions = {'checksumAdjustment': 2+2+4, + 'flags': 2+2+4+4+4, + 'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2, + 'avgWidth': 2, + } + where = self.tab_offset + positions[where] + self.f.seek(where) + + + def calc_full_checksum(self, check = False): + """ Calculate the whole file's checksum """ + self.f.seek(0, 2) + self.end = self.f.tell() + full_check = self.calc_checksum(0, self.end, (-self.checksum_adj) & 0xFFFFFFFF) + if check and (0xB1B0AFBA - full_check) & 0xFFFFFFFF != self.checksum_adj: + sys.exit("Checksum of whole font is bad") + return full_check + + def calc_table_checksum(self, check = False): + tab_check_new = self.calc_checksum(self.tab_offset, self.tab_offset + self.tab_length - 1, (-self.checksum_adj) & 0xFFFFFFFF) + if check and tab_check_new != self.tab_check: + sys.exit("Checksum of 'head' in font is bad") + return tab_check_new + + def reset_table_checksum(self): + new_check = self.calc_table_checksum() + self.putlong(new_check, self.tab_check_offset) + + def reset_full_checksum(self): + new_adj = (0xB1B0AFBA - self.calc_full_checksum()) & 0xFFFFFFFF + self.putlong(new_adj, 'checksumAdjustment') + + def close(self): + self.f.close() + + + def __init__(self, filename): + self.modified = False + self.f = open(filename, 'r+b') + + self.find_head_table(0) + + self.flags = self.getshort('flags') + self.lowppem = self.getshort('lowestRecPPEM') + self.checksum_adj = self.getlong('checksumAdjustment') + +def check_panose_monospaced(font): + """ Check if the font's Panose flags say it is monospaced """ + # https://forum.high-logic.com/postedfiles/Panose.pdf + panose = list(font.os2_panose) + if panose[0] < 2 or panose[0] > 5: + return -1 # invalid Panose info + panose_mono = ((panose[0] == 2 and panose[3] == 9) or + (panose[0] == 3 and panose[3] == 3)) + return 1 if panose_mono else 0 + +def panose_check_to_text(value, panose = False): + """ Convert value from check_panose_monospaced() to human readable string """ + if value == 0: + return "Panose says \"not monospaced\"" + if value == 1: + return "Panose says \"monospaced\"" + return "Panose is invalid" + (" ({})".format(list(panose)) if panose else "") + +def panose_proportion_to_text(value): + """ Interpret a Panose proportion value (4th value) for family 2 (latin text) """ + proportion = { + 0: "Any", 1: "No Fit", 2: "Old Style", 3: "Modern", 4: "Even Width", + 5: "Extended", 6: "Condensed", 7: "Very Extended", 8: "Very Condensed", + 9: "Monospaced" } + return proportion.get(value, "??? {}".format(value)) + +def is_monospaced(font): + """ Check if a font is probably monospaced """ + # Some fonts lie (or have not any Panose flag set), spot check monospaced: + width = -1 + width_mono = True + for glyph in [ 0x49, 0x4D, 0x57, 0x61, 0x69, 0x6d, 0x2E ]: # wide and slim glyphs 'I', 'M', 'W', 'a', 'i', 'm', '.' + if not glyph in font: + # A 'strange' font, believe Panose + return (check_panose_monospaced(font) == 1, None) + # print(" -> {} {}".format(glyph, font[glyph].width)) + if width < 0: + width = font[glyph].width + continue + if font[glyph].width != width: + # Exception for fonts like Code New Roman Regular or Hermit Light/Bold: + # Allow small 'i' and dot to be smaller than normal + # I believe the source fonts are buggy + if glyph in [ 0x69, 0x2E ]: + if width > font[glyph].width: + continue + (xmin, _, xmax, _) = font[glyph].boundingBox() + if width > xmax - xmin: + continue + width_mono = False + break + # We believe our own check more then Panose ;-D + return (width_mono, None if width_mono else glyph) + +def force_panose_monospaced(font): + """ Forces the Panose flag to monospaced if they are unset or halfway ok already """ + # For some Windows applications (e.g. 'cmd'), they seem to honour the Panose table + # https://forum.high-logic.com/postedfiles/Panose.pdf + panose = list(font.os2_panose) + if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default) + panose[0] = 2 # make kind latin text and display + logger.info("Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')") + font.os2_panose = tuple(panose) + if panose[0] == 2 and panose[3] != 9: + logger.info("Setting Panose 'Proportion' to 'Monospaced' (was '%s')", panose_proportion_to_text(panose[3])) + panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced + font.os2_panose = tuple(panose) + +def get_advance_width(font, extended, minimum): + """ Get the maximum/minimum advance width in the extended(?) range """ + width = 0 + if not extended: + r = range(0x021, 0x07e) + else: + r = range(0x07f, 0x17f) + for glyph in r: + if not glyph in font: + continue + if glyph in range(0x7F, 0xBF): + continue # ignore special characters like '1/4' etc + if width == 0: + width = font[glyph].width + continue + if not minimum and width < font[glyph].width: + width = font[glyph].width + elif minimum and width > font[glyph].width: + width = font[glyph].width + return width + +def report_advance_widths(font): + return "Advance widths (base/extended): {} - {} / {} - {}".format( + get_advance_width(font, False, True), get_advance_width(font, False, False), + get_advance_width(font, True, True), get_advance_width(font, True, False)) + +def get_btb_metrics(font): + """ Get the baseline to baseline distance for all three metrics """ + hhea_height = font.hhea_ascent - font.hhea_descent + typo_height = font.os2_typoascent - font.os2_typodescent + win_height = font.os2_winascent + font.os2_windescent + win_gap = max(0, font.hhea_linegap - win_height + hhea_height) + hhea_btb = hhea_height + font.hhea_linegap + typo_btb = typo_height + font.os2_typolinegap + win_btb = win_height + win_gap + return (hhea_btb, typo_btb, win_btb, win_gap) + +def get_metrics_names(): + """ Helper to get the line metrics names consistent """ + return ['HHEA','TYPO','WIN'] + +def get_old_average_x_width(font): + """ Determine xAvgCharWidth of the OS/2 table """ + # Fontforge can not create fonts with old (i.e. prior to OS/2 version 3) + # table values, but some very old applications do need them sometimes + # https://learn.microsoft.com/en-us/typography/opentype/spec/os2#xavgcharwidth + s = 0 + weights = { + 'a': 64, 'b': 14, 'c': 27, 'd': 35, 'e': 100, 'f': 20, 'g': 14, 'h': 42, 'i': 63, + 'j': 3, 'k': 6, 'l': 35, 'm': 20, 'n': 56, 'o': 56, 'p': 17, 'q': 4, 'r': 49, + 's': 56, 't': 71, 'u': 31, 'v': 10, 'w': 18, 'x': 3, 'y': 18, 'z': 2, 32: 166, + } + for g in weights: + if g not in font: + logger.critical("Can not determine ancient style xAvgCharWidth") + sys.exit(1) + s += font[g].width * weights[g] + return int(s / 1000) + +def create_filename(fonts): + """ Determine filename from font object(s) """ + # Only consider the standard (i.e. English-US) names + sfnt = { k: v for l, k, v in fonts[0].sfnt_names if l == 'English (US)' } + sfnt_pfam = sfnt.get('Preferred Family', sfnt['Family']) + sfnt_psubfam = sfnt.get('Preferred Styles', sfnt['SubFamily']) + if len(fonts) > 1: + return sfnt_pfam + if len(sfnt_psubfam) > 0: + sfnt_psubfam = '-' + sfnt_psubfam + return (sfnt_pfam + sfnt_psubfam).replace(' ', '') + +def fetch_glyphnames(): + """ Read the glyphname database and put it into a dictionary """ + try: + glyphnamefile = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), 'glyphnames.json')) + with open(glyphnamefile, 'rb') as f: + namelist = json.load(f) + return { int(v['code'], 16): k for k, v in namelist.items() if 'code' in v } + except Exception as error: + logger.warning("Can not read glyphnames file (%s)", repr(error)) + return {} + +class font_patcher: + def __init__(self, args, conf): + self.args = args # class 'argparse.Namespace' + self.sym_font_args = [] + self.config = conf # class 'configparser.ConfigParser' + self.sourceFont = None # class 'fontforge.font' + self.patch_set = None # class 'list' + self.font_dim = None # class 'dict' + self.font_extrawide = False + self.source_monospaced = None # Later True or False + self.symbolsonly = False # Are we generating the SymbolsOnly font? + self.onlybitmaps = 0 + self.essential = set() + self.xavgwidth = [] # list of ints + self.glyphnames = fetch_glyphnames() + + def patch(self, font): + self.sourceFont = font + self.setup_version() + self.assert_monospace() + self.remove_ligatures() + self.manipulate_hints() + self.get_essential_references() + self.get_sourcefont_dimensions() + self.setup_patch_set() + self.improve_line_dimensions() + self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available + self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used + + if self.args.forcemono: + # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. + # This needs to be done on all characters, as some information seems to be lost from the original font file. + self.set_sourcefont_glyph_widths() + + # For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs + if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2: + logger.warning("Very wide and short font, disabling 2 cell Powerline glyphs") + self.font_extrawide = True + + # Prevent opening and closing the fontforge font. Makes things faster when patching + # multiple ranges using the same symbol font. + PreviousSymbolFilename = "" + symfont = None + + if not os.path.isdir(self.args.glyphdir): + logger.critical("Can not find symbol glyph directory %s " + "(probably you need to download the src/glyphs/ directory?)", self.args.glyphdir) + sys.exit(1) + + if self.args.dry_run: + return + + for patch in self.patch_set: + if patch['Enabled']: + if PreviousSymbolFilename != patch['Filename']: + # We have a new symbol font, so close the previous one if it exists + if symfont: + symfont.close() + symfont = None + symfont_file = os.path.join(self.args.glyphdir, patch['Filename']) + if not os.path.isfile(symfont_file): + logger.critical("Can not find symbol source for '%s' (i.e. %s)", + patch['Name'], symfont_file) + sys.exit(1) + if not os.access(symfont_file, os.R_OK): + logger.critical("Can not open symbol source for '%s' (i.e. %s)", + patch['Name'], symfont_file) + sys.exit(1) + symfont = fontforge.open(symfont_file) + symfont.encoding = 'UnicodeFull' + + # Match the symbol font size to the source font size + symfont.em = self.sourceFont.em + PreviousSymbolFilename = patch['Filename'] + + # If patch table doesn't include a source start, re-use the symbol font values + SrcStart = patch['SrcStart'] + if not SrcStart: + SrcStart = patch['SymStart'] + self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleRules'], patch['Name'], patch['Attributes']) + + if symfont: + symfont.close() + + # The grave accent and fontforge: + # If the type is 'auto' fontforge changes it to 'mark' on export. + # We can not prevent this. So set it to 'baseglyph' instead, as + # that resembles the most common expectations. + # This is not needed with fontforge March 2022 Release anymore. + if "grave" in self.sourceFont: + self.sourceFont["grave"].glyphclass="baseglyph" + + + def generate(self, sourceFonts): + sourceFont = sourceFonts[0] + # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. + if int(fontforge.version()) >= 20201107: + gen_flags = (str('opentype'), str('PfEd-comments'), str('no-FFTM-table')) + else: + gen_flags = (str('opentype'), str('PfEd-comments')) + if len(sourceFonts) > 1: + layer = None + # use first non-background layer + for l in sourceFont.layers: + if not sourceFont.layers[l].is_background: + layer = l + break + outfile = os.path.normpath(os.path.join( + sanitize_filename(self.args.outputdir, True), + sanitize_filename(create_filename(sourceFonts)) + ".ttc")) + sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer) + message = " Generated {} fonts\n \\===> '{}'".format(len(sourceFonts), outfile) + else: + fontname = create_filename(sourceFonts) + if not fontname: + fontname = sourceFont.cidfontname + outfile = os.path.normpath(os.path.join( + sanitize_filename(self.args.outputdir, True), + sanitize_filename(fontname) + self.args.extension)) + bitmaps = str() + if len(sourceFont.bitmapSizes): + logger.debug("Preserving bitmaps %s", repr(sourceFont.bitmapSizes)) + bitmaps = str('otf') # otf/ttf, both is bf_ttf + if self.args.dry_run: + logger.debug("=====> Filename '%s'", outfile) + return + sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags) + message = " {}\n \\===> '{}'".format(sourceFont.fullname, outfile) + + # Adjust flags that can not be changed via fontforge + if re.search(r'\.[ot]tf$', self.args.font, re.IGNORECASE) and re.search(r'\.[ot]tf$', outfile, re.IGNORECASE): + if not os.path.isfile(outfile) or os.path.getsize(outfile) < 1: + logger.critical("Something went wrong and Fontforge did not generate the new font - look for messages above") + sys.exit(1) + try: + source_font = TableHEADWriter(self.args.font) + dest_font = TableHEADWriter(outfile) + for idx in range(source_font.num_fonts): + logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts) + xwidth_s = '' + xwidth = self.xavgwidth[idx] if len(self.xavgwidth) > idx else None + if isinstance(xwidth, int): + if isinstance(xwidth, bool) and xwidth: + source_font.find_table([b'OS/2'], idx) + xwidth = source_font.getshort('avgWidth') + xwidth_s = ' (copied from source)' + dest_font.find_table([b'OS/2'], idx) + d_xwidth = dest_font.getshort('avgWidth') + if d_xwidth != xwidth: + logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s) + dest_font.putshort(xwidth, 'avgWidth') + dest_font.reset_table_checksum() + source_font.find_head_table(idx) + dest_font.find_head_table(idx) + if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: + logger.debug("Changing flags from 0x%X to 0x%X", dest_font.flags, dest_font.flags & ~0x08) + dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int' + if source_font.lowppem != dest_font.lowppem: + logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem) + dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') + if dest_font.modified: + dest_font.reset_table_checksum() + if dest_font.modified: + dest_font.reset_full_checksum() + except Exception as error: + logger.error("Can not handle font flags (%s)", repr(error)) + finally: + try: + source_font.close() + dest_font.close() + except: + pass + if self.args.is_variable: + logger.critical("Source font is a variable open type font (VF) and the patch results will most likely not be what you want") + print(message) + + if self.args.postprocess: + subprocess.call([self.args.postprocess, outfile]) + print("\n") + logger.info("Post Processed: %s", outfile) + + + def setup_name_backup(self, font): + """ Store the original font names to be able to rename the font multiple times """ + font.persistent = { + "fontname": font.fontname, + "fullname": font.fullname, + "familyname": font.familyname, + } + + + def setup_font_names(self, font): + font.fontname = font.persistent["fontname"] + if isinstance(font.persistent["fullname"], str): + font.fullname = font.persistent["fullname"] + if isinstance(font.persistent["familyname"], str): + font.familyname = font.persistent["familyname"] + verboseAdditionalFontNameSuffix = "" + additionalFontNameSuffix = "" + if not self.args.complete: + # NOTE not all symbol fonts have appended their suffix here + if self.args.fontawesome: + additionalFontNameSuffix += " A" + verboseAdditionalFontNameSuffix += " Plus Font Awesome" + if self.args.fontawesomeextension: + additionalFontNameSuffix += " AE" + verboseAdditionalFontNameSuffix += " Plus Font Awesome Extension" + if self.args.octicons: + additionalFontNameSuffix += " O" + verboseAdditionalFontNameSuffix += " Plus Octicons" + if self.args.powersymbols: + additionalFontNameSuffix += " PS" + verboseAdditionalFontNameSuffix += " Plus Power Symbols" + if self.args.codicons: + additionalFontNameSuffix += " C" + verboseAdditionalFontNameSuffix += " Plus Codicons" + if self.args.pomicons: + additionalFontNameSuffix += " P" + verboseAdditionalFontNameSuffix += " Plus Pomicons" + if self.args.fontlogos: + additionalFontNameSuffix += " L" + verboseAdditionalFontNameSuffix += " Plus Font Logos" + if self.args.material: + additionalFontNameSuffix += " MDI" + verboseAdditionalFontNameSuffix += " Plus Material Design Icons" + if self.args.weather: + additionalFontNameSuffix += " WEA" + verboseAdditionalFontNameSuffix += " Plus Weather Icons" + + # add mono signifier to beginning of name suffix + if self.args.single: + variant_abbrev = "M" + variant_full = " Mono" + elif self.args.nonmono and not self.symbolsonly: + variant_abbrev = "P" + variant_full = " Propo" + else: + variant_abbrev = "" + variant_full = "" + + ps_suffix = projectNameAbbreviation + variant_abbrev + additionalFontNameSuffix + + # add 'Nerd Font' to beginning of name suffix + verboseAdditionalFontNameSuffix = " " + projectNameSingular + variant_full + verboseAdditionalFontNameSuffix + additionalFontNameSuffix = " " + projectNameSingular + variant_full + additionalFontNameSuffix + + if FontnameParserOK and self.args.makegroups > 0: + user_supplied_name = False # User supplied names are kept unchanged + if not isinstance(self.args.force_name, str): + use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse + # Use fullname if it is 'equal' to the fontname + if font.fullname: + use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.fullname).lower() + # Use fullname for any of these source fonts (that are impossible to disentangle from the fontname, we need the blanks) + for hit in [ 'Meslo' ]: + use_fullname |= font.fontname.lower().startswith(hit.lower()) + parser_name = font.fullname if use_fullname else font.fontname + # Gohu fontnames hide the weight, but the file names are ok... + if parser_name.startswith('Gohu'): + parser_name = os.path.splitext(os.path.basename(self.args.font))[0] + else: + if self.args.force_name == 'full': + parser_name = font.fullname + elif self.args.force_name == 'postscript': + parser_name = font.fontname + elif self.args.force_name == 'filename': + parser_name = os.path.basename(font.path).split('.')[0] + else: + parser_name = self.args.force_name + user_supplied_name = True + if not isinstance(parser_name, str) or len(parser_name) < 1: + logger.critical("Specified --name not usable because the name will be empty") + sys.exit(2) + n = FontnameParser(parser_name, logger) + if not n.parse_ok: + logger.warning("Have only minimal naming information, check resulting name. Maybe specify --makegroups 0") + n.drop_for_powerline() + n.enable_short_families(not user_supplied_name, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ]) + if not n.set_expect_no_italic(self.args.noitalic): + logger.critical("Detected 'Italic' slant but --has-no-italic specified") + sys.exit(1) + + # All the following stuff is ignored in makegroups-mode + + # basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold) + # this does not seem very reliable so only use the style here as a fallback if the font does not + # have an internal style defined (in sfnt_names) + # using '([^-]*?)' to get the item before the first dash "-" + # using '([^-]*(?!.*-))' to get the item after the last dash "-" + fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", font.fontname).groups() + + # dont trust 'font.familyname' + familyname = fontname + + # fullname (filename) can always use long/verbose font name, even in windows + if font.fullname != None: + fullname = font.fullname + verboseAdditionalFontNameSuffix + else: + fullname = font.cidfontname + verboseAdditionalFontNameSuffix + + fontname = fontname + additionalFontNameSuffix.replace(" ", "") + + # let us try to get the 'style' from the font info in sfnt_names and fallback to the + # parse fontname if it fails: + try: + # search tuple: + subFamilyTupleIndex = [x[1] for x in font.sfnt_names].index("SubFamily") + + # String ID is at the second index in the Tuple lists + sfntNamesStringIDIndex = 2 + + # now we have the correct item: + subFamily = font.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex] + except IndexError: + sys.stderr.write("{}: Could not find 'SubFamily' for given font, falling back to parsed fontname\n".format(projectName)) + subFamily = fallbackStyle + + # some fonts have inaccurate 'SubFamily', if it is Regular let us trust the filename more: + if subFamily == "Regular" and len(fallbackStyle): + subFamily = fallbackStyle + + # This is meant to cover the case where the SubFamily is "Italic" and the filename is *-BoldItalic. + if len(subFamily) < len(fallbackStyle): + subFamily = fallbackStyle + + if len(subFamily) == 0: + subFamily = "Regular" + + familyname += " " + projectNameSingular + variant_full + + # Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with + # the same name as the same font, even if subFamily is different. Make sure to + # keep the resulting fontname (PostScript name) valid by removing spaces. + fontname += '-' + subFamily.replace(' ', '') + + # rename font + # + # comply with SIL Open Font License (OFL) + reservedFontNameReplacements = { + 'source' : 'sauce', + 'Source' : 'Sauce', + 'Bitstream Vera Sans Mono' : 'Bitstrom Wera', + 'BitstreamVeraSansMono' : 'BitstromWera', + 'bitstream vera sans mono' : 'bitstrom wera', + 'bitstreamverasansmono' : 'bitstromwera', + 'hermit' : 'hurmit', + 'Hermit' : 'Hurmit', + 'hasklig' : 'hasklug', + 'Hasklig' : 'Hasklug', + 'Share' : 'Shure', + 'share' : 'shure', + 'IBMPlex' : 'Blex', + 'ibmplex' : 'blex', + 'IBM-Plex' : 'Blex', + 'IBM Plex' : 'Blex', + 'terminus' : 'terminess', + 'Terminus' : 'Terminess', + 'liberation' : 'literation', + 'Liberation' : 'Literation', + 'iAWriter' : 'iMWriting', + 'iA Writer' : 'iM Writing', + 'iA-Writer' : 'iM-Writing', + 'Anka/Coder' : 'AnaConder', + 'anka/coder' : 'anaconder', + 'Cascadia Code' : 'Caskaydia Cove', + 'cascadia code' : 'caskaydia cove', + 'CascadiaCode' : 'CaskaydiaCove', + 'cascadiacode' : 'caskaydiacove', + 'Cascadia Mono' : 'Caskaydia Mono', + 'cascadia mono' : 'caskaydia mono', + 'CascadiaMono' : 'CaskaydiaMono', + 'cascadiamono' : 'caskaydiamono', + 'Fira Mono' : 'Fura Mono', + 'Fira Sans' : 'Fura Sans', + 'FiraMono' : 'FuraMono', + 'FiraSans' : 'FuraSans', + 'fira mono' : 'fura mono', + 'fira sans' : 'fura sans', + 'firamono' : 'furamono', + 'firasans' : 'furasans', + 'IntelOneMono' : 'IntoneMono', + 'IntelOne Mono' : 'Intone Mono', + 'Intel One Mono' : 'Intone Mono', + } + + # remove overly verbose font names + # particularly regarding Powerline sourced Fonts (https://github.com/powerline/fonts) + additionalFontNameReplacements = { + 'for Powerline': '', + 'ForPowerline': '' + } + + additionalFontNameReplacements2 = { + 'Powerline': '' + } + + projectInfo = ( + "Patched with '" + projectName + " Patcher' (https://github.com/ryanoasis/nerd-fonts)\n\n" + "* Website: https://www.nerdfonts.com\n" + "* Version: " + version + "\n" + "* Development Website: https://github.com/ryanoasis/nerd-fonts\n" + "* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/-/changelog.md" + ) + + familyname = replace_font_name(familyname, reservedFontNameReplacements) + fullname = replace_font_name(fullname, reservedFontNameReplacements) + fontname = replace_font_name(fontname, reservedFontNameReplacements) + familyname = replace_font_name(familyname, additionalFontNameReplacements) + fullname = replace_font_name(fullname, additionalFontNameReplacements) + fontname = replace_font_name(fontname, additionalFontNameReplacements) + familyname = replace_font_name(familyname, additionalFontNameReplacements2) + fullname = replace_font_name(fullname, additionalFontNameReplacements2) + fontname = replace_font_name(fontname, additionalFontNameReplacements2) + + if self.args.makegroups < 0: + logger.warning("Renaming disabled! Make sure to comply with font license, esp RFN clause!") + elif not (FontnameParserOK and self.args.makegroups > 0): + # replace any extra whitespace characters: + font.familyname = " ".join(familyname.split()) + font.fullname = " ".join(fullname.split()) + font.fontname = " ".join(fontname.split()) + + font.appendSFNTName(str('English (US)'), str('Preferred Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname) + font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) + else: + # Add Nerd Font suffix unless user specifically asked for some excplicit name via --name + if not user_supplied_name: + short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full + # inject_suffix(family, ps_fontname, short_family) + n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family) + n.rename_font(font) + + font.comment = projectInfo + font.fontlog = projectInfo + + + def setup_version(self): + """ Add the Nerd Font version to the original version """ + # print("Version was {}".format(sourceFont.version)) + if self.sourceFont.version != None: + self.sourceFont.version += ";" + projectName + " " + version + else: + self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version + self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge + self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version) + # The Version SFNT name is later reused by the NameParser for UniqueID + # print("Version now is {}".format(sourceFont.version)) + + + def remove_ligatures(self): + # let's deal with ligatures (mostly for monospaced fonts) + # Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot' + if self.args.removeligatures: + logger.info("Removing ligatures from configfile `Subtables` section") + if 'Subtables' not in self.config: + logger.warning("No ligature data (config file missing?)") + return + ligature_subtables = json.loads(self.config.get('Subtables', 'ligatures', fallback='[]')) + for subtable in ligature_subtables: + logger.debug("Removing subtable: %s", subtable) + try: + self.sourceFont.removeLookupSubtable(subtable) + logger.debug("Successfully removed subtable: %s", subtable) + except Exception: + logger.error("Failed to remove subtable: %s", subtable) + + + def manipulate_hints(self): + """ Redo the hinting on some problematic glyphs """ + if 'Hinting' not in self.config: + return + redo = json.loads(self.config.get('Hinting', 're_hint', fallback='[]')) + if not len(redo): + return + logger.debug("Working on {} rehinting rules (this may create a lot of fontforge warnings)".format(len(redo))) + count = 0 + for gname in self.sourceFont: + for regex in redo: + if re.fullmatch(regex, gname): + glyph = self.sourceFont[gname] + glyph.autoHint() + glyph.autoInstr() + count += 1 + break + logger.info("Rehinted {} glyphs".format(count)) + + def assert_monospace(self): + # Check if the sourcefont is monospaced + width_mono, offending_char = is_monospaced(self.sourceFont) + self.source_monospaced = width_mono + if self.args.nonmono: + return + panose_mono = check_panose_monospaced(self.sourceFont) + logger.debug("Monospace check: %s; glyph-width-mono %s", + panose_check_to_text(panose_mono, self.sourceFont.os2_panose), repr(width_mono)) + # The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown' + if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1): + logger.warning("Monospaced check: Panose assumed to be wrong") + logger.warning("Monospaced check: %s and %s", + report_advance_widths(self.sourceFont), + panose_check_to_text(panose_mono, self.sourceFont.os2_panose)) + if self.args.forcemono and not width_mono: + logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, " + "results might be useless%s", + " - offending char: {:X}".format(offending_char) if offending_char is not None else "") + if self.args.forcemono <= 1: + logger.critical("Font will not be patched! Give --mono (or -s) twice to force patching") + sys.exit(1) + if width_mono: + force_panose_monospaced(self.sourceFont) + + + def setup_patch_set(self): + """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ + + box_enabled = self.source_monospaced and not self.symbolsonly # Box glyph only for monospaced and not for Symbols Only + box_keep = False + if box_enabled or self.args.forcebox: + self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f) + box_glyphs_target = len(list(self.sourceFont.selection)) + box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs)) + if box_glyphs_target > box_glyphs_current or self.args.forcebox: + # Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing) + if box_glyphs_current > 0: + logger.debug("%d/%d box drawing glyphs will be replaced", + box_glyphs_current, box_glyphs_target) + box_enabled = True + else: + # Sourcefont does have all of these glyphs + # box_keep = True # just scale do not copy (need to scale to fit new cell size) + box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this + + # Stretch 'xz' or 'pa' (preserve aspect ratio) + # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding + # Overlap value is used horizontally but vertically limited to 0.01 + # Careful does not overwrite/modify existing glyphs + # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) + # '1' means occupu 1 cell (default for 'xy') + # '2' means occupy 2 cells (default for 'pa') + # '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up) + # '^' means that scaling shall fill the whole cell and not only the icon-cap-height (for mono fonts, other always use the whole cell) + # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones + # + # Be careful, stretch may not change within a ScaleRule! + + SYM_ATTR_DEFAULT = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} + } + SYM_ATTR_POWERLINE = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + + # Arrow tips + 0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.7}}, + 0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.7}}, + 0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.7}}, + 0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.7}}, + + # Inverse arrow tips + 0xe0d6: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'xy-ratio': 0.7}}, + 0xe0d7: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'xy-ratio': 0.7}}, + + # Rounded arcs + 0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.59}}, + 0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.5}}, + 0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.59}}, + 0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.5}}, + + # Bottom Triangles + 0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + 0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + + # Top Triangles + 0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + 0xe0be: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + + # Flames + 0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {}}, + 0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {}}, + + # Small squares + 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}}, + 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}}, + + # Bigger squares + 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}}, + 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}}, + + # Waveform + 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0ca: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + + # Hexagons + 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.02, 'xy-ratio': 0.85}}, + 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'xy-ratio': 0.865}}, + + # Legos + 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0d0: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + + # Top and bottom trapezoid + 0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}, + 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}} + } + SYM_ATTR_TRIGRAPH = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa1!', 'params': {'overlap': -0.10, 'careful': True}} + } + SYM_ATTR_FONTA = { + # 'pa' == preserve aspect ratio + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, + + # Don't center these arrows vertically + 0xf0dc: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}} + } + SYM_ATTR_HEAVYBRACKETS = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa1!', 'params': {'ypadding': 0.3, 'careful': True}} + } + SYM_ATTR_BOX = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}}, + # No overlap with checkered greys (commented out because that raises problems on rescaling clients) + # 0x2591: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2592: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + } + SYM_ATTR_PROGRESS = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa1!', 'params': {'overlap': -0.03, 'careful': True}}, # Cirles + # All the squares: + 0xee00: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee01: {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.10, 'careful': True}}, + 0xee02: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee03: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee04: {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.10, 'careful': True}}, + 0xee05: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + } + CUSTOM_ATTR = { + # previous custom scaling => do not touch the icons + # 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': self.args.careful}} + } + + # Most glyphs we want to maximize (individually) during the scale + # However, there are some that need to be small or stay relative in + # size to each other. + # The glyph-specific behavior can be given as ScaleRules in the patch-set. + # + # ScaleRules can contain two different kind of rules (possibly in parallel): + # - ScaleGlyph: + # Here one specific glyph is used as 'scale blueprint'. Other glyphs are + # scaled by the same factor as this glyph. This is useful if you have one + # 'biggest' glyph and all others should stay relatively in size. + # Shifting in addition to scaling can be selected too (see below). + # - ScaleGroups: + # Here you specify a group of glyphs that should be handled together + # with the same scaling and shifting (see bottom). The basis for it is + # a 'combined bounding box' of all glyphs in that group. All glyphs are + # handled as if they fill that combined bounding box. + # (- ScaleGroupsVert: Removed with this commit) + # + # The ScaleGlyph method: You set 'ScaleGlyph' to the unicode of the reference glyph. + # Note that there can be only one per patch-set. + # Additionally you set 'GlyphsToScale' that contains all the glyphs that shall be + # handled (scaled) like the reference glyph. + # It is a List of: ((glyph code) or (tuple of two glyph codes that form a closed range)) + # 'GlyphsToScale': [ + # 0x0100, 0x0300, 0x0400, # The single glyphs 0x0100, 0x0300, and 0x0400 + # (0x0200, 0x0210), # All glyphs 0x0200 to 0x0210 including both 0x0200 and 0x0210 + # ]} + # If you want to not only scale but also shift as the reference glyph you give the + # data as 'GlyphsToScale+'. Note that only one set is used and the plus version is preferred. + # + # For the ScaleGroup method you define any number groups of glyphs and each group is + # handled separately. The combined bounding box of all glyphs in the group is determined + # and based on that the scale and shift (see bottom) for all the glyphs in the group. + # You define the groups as value of 'ScaleGroups'. + # It is a List of: ((lists of glyph codes) or (ranges of glyph codes)) + # 'ScaleGroups': [ + # [0x0100, 0x0300, 0x0400], # One group consists of glyphs 0x0100, 0x0300, and 0x0400 + # range(0x0200, 0x0210 + 1), # Another group contains glyphs 0x0200 to 0x0210 incl. + # + # Note the subtle differences: tuple vs. range; closed vs open range; etc + # See prepareScaleRules() for some more details. + # For historic reasons ScaleGroups is sometimes called 'new method' and ScaleGlyph 'old'. + # The codepoints mentioned here are symbol-font-codepoints. + # + # Shifting: + # If we have a combined bounding box stored in a range, that + # box is used to align all symbols in the range identically. + # - If the symbol font is proportinal only the v alignment is synced. + # - If the symbol font is monospaced v and h alignemnts are synced. + # To make sure the behavior is as expected you are required to set a ShiftMode property + # accordingly. It just checks, you can not (!) select what is done with that property. + + BOX_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + [*range(0x2500, 0x2570 + 1), *range(0x2574, 0x257f + 1)], # box drawing + range(0x2571, 0x2573 + 1), # diagonals + range(0x2580, 0x259f + 1), # blocks and greys (greys are less tall originally, so overlap will be less) + ]} + CODI_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + [0xea61, 0xeb13], # lightbulb + range(0xeab4, 0xeab7 + 1), # chevrons + [0xea7d, *range(0xea99, 0xeaa1 + 1), 0xebcb], # arrows + [0xeaa2, 0xeb9a, 0xec08, 0xec09], # bells + range(0xead4, 0xead6 + 1), # dot and arrow + [0xeb43, 0xec0b, 0xec0c], # (pull) request changes + range(0xeb6e, 0xeb71 + 1), # triangles + [*range(0xeb89, 0xeb8b + 1), 0xec07], # smallish dots + range(0xebd5, 0xebd7 + 1), # compasses + ]} + DEVI_SCALE_LIST = None + FONTA_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [0xf005, 0xf006, 0xf089], # star, star empty, half star + range(0xf026, 0xf028 + 1), # volume off, down, up + range(0xf02b, 0xf02c + 1), # tag, tags + range(0xf031, 0xf035 + 1), # font et al + range(0xf044, 0xf046 + 1), # edit, share, check (boxes) + range(0xf048, 0xf052 + 1), # multimedia buttons + range(0xf060, 0xf063 + 1), # arrows + [0xf053, 0xf054, 0xf077, 0xf078], # chevron all directions + range(0xf07d, 0xf07e + 1), # resize + range(0xf0a4, 0xf0a7 + 1), # pointing hands + [0xf0d7, 0xf0d8, 0xf0d9, 0xf0da, 0xf0dc, 0xf0dd, 0xf0de], # caret all directions and same looking sort + range(0xf100, 0xf107 + 1), # angle + range(0xf130, 0xf131 + 1), # mic + range(0xf141, 0xf142 + 1), # ellipsis + range(0xf153, 0xf15a + 1), # currencies + range(0xf175, 0xf178 + 1), # long arrows + range(0xf182, 0xf183 + 1), # male and female + range(0xf221, 0xf22d + 1), # gender or so + range(0xf255, 0xf25b + 1), # hand symbols + ]} + HEAVY_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + range(0x276c, 0x2771+1) + ]} + OCTI_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [*range(0xf03d, 0xf040 + 1), 0xf019, 0xf030, 0xf04a, 0xf051, 0xf071, 0xf08c ], # arrows + [0xF0E7, # Smily and ... + 0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles + 0xf052, 0xf053, 0xf296, 0xf2f0, # small stuff + 0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons + 0xf0ca, 0xf081, 0xf092, # dash, X, github-text + ], + [0xf09c, 0xf09f, 0xf0de], # bells + range(0xf2c2, 0xf2c5 + 1), # move to + [0xf07b, 0xf0a1, 0xf0d6, 0xf306], # bookmarks + ]} + PROGR_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + range(0xedff, 0xee05 + 1), # boxes... with helper glyph EDFF for Y padding + range(0xee06, 0xee0b + 1), # circles + ]} + WEATH_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [0xf03c, 0xf042, 0xf045 ], # degree signs + [0xf043, 0xf044, 0xf048, 0xf04b, 0xf04c, 0xf04d, 0xf057, 0xf058, 0xf087, 0xf088], # arrows + range(0xf053, 0xf055 + 1), # thermometers + [*range(0xf059, 0xf061 + 1), 0xf0b1], # wind directions + range(0xf089, 0xf094 + 1), # clocks + range(0xf095, 0xf0b0 + 1), # moon phases + range(0xf0b7, 0xf0c3 + 1), # wind strengths + [0xf06e, 0xf070 ], # solar/lunar eclipse + [0xf051, 0xf052, 0xf0c9, 0xf0ca, 0xf072 ], # sun/moon up/down + [0xf049, 0xf056, 0xf071, *range(0xf073, 0xf07c + 1), 0xf08a], # other things + # Note: Codepoints listed before that are also in the following range + # will take the scaling of the previous group (the ScaleGroups are + # searched through in definition order). + # But be careful, the combined bounding box for the following group + # _will_ include all glyphs in its definition: Make sure the exempt + # glyphs from above are smaller (do not extend) the combined bounding + # box of this range: + [ *range(0xf000, 0xf041 + 1), + *range(0xf064, 0xf06d + 1), + *range(0xf07d, 0xf083 + 1), + *range(0xf085, 0xf086 + 1), + *range(0xf0b2, 0xf0b6 + 1) + ], # lots of clouds (weather states) (Please read note above!) + ]} + MDI_SCALE_LIST = None # Maybe later add some selected ScaleGroups + + + # Define the character ranges + # Symbol font ranges + self.patch_set = [ + {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': HEAVY_SCALE_LIST, 'Attributes': SYM_ATTR_HEAVYBRACKETS}, + {'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX}, + {'Enabled': True, 'Name': "Progress Indicators", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0xEE00, 'SymEnd': 0xEE0B, 'SrcStart': None, 'ScaleRules': PROGR_SCALE_LIST, 'Attributes': SYM_ATTR_PROGRESS}, + {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons/devicons.otf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE7EF, 'SrcStart': 0xE700, 'ScaleRules': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D7, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0x2630, 'SymEnd': 0x2630, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_TRIGRAPH}, + {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "pomicons/Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xED00, 'SymEnd': 0xF2FF, 'SrcStart': None, 'ScaleRules': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, + {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) + {'Enabled': False , 'Name': "Material legacy", 'Filename': "materialdesign/materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF381, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF306, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEC1E, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR} + ] + + def improve_line_dimensions(self): + # Make the total line size even. This seems to make the powerline separators + # center more evenly. + if self.args.adjustLineHeight: + if (self.sourceFont.os2_winascent + self.sourceFont.os2_windescent) % 2 != 0: + # All three are equal before due to get_sourcefont_dimensions() + self.sourceFont.hhea_ascent += 1 + self.sourceFont.os2_typoascent += 1 + self.sourceFont.os2_winascent += 1 + + def add_glyphrefs_to_essential(self, unicode): + self.essential.add(unicode) + # According to fontforge spec, altuni is either None or a tuple of tuples + # Those tuples contained in altuni are of the following "format": + # (unicode-value, variation-selector, reserved-field) + altuni = self.sourceFont[unicode].altuni + if altuni is not None: + for altcode in [ v for v, s, r in altuni if v >= 0 ]: + # If alternate unicode already exists in self.essential, + # that means it has gone through this function before. + # Therefore we skip it to avoid infinite loop. + # A unicode value of -1 basically means unused and is also worth skipping. + if altcode not in self.essential: + self.add_glyphrefs_to_essential(altcode) + # From fontforge documentation: + # glyph.references return a tuple of tuples containing, for each reference in foreground, + # a glyph name, a transformation matrix, and (depending on ff version) whether the + # reference is currently selected. + references = self.sourceFont[unicode].references + for refcode in [ self.sourceFont[n].unicode for n, *_ in references ]: # tuple of 2 or 3 depending on ff version + if refcode not in self.essential and refcode >= 0: + self.add_glyphrefs_to_essential(refcode) + + def get_essential_references(self): + """Find glyphs that are needed for the basic glyphs""" + # Sometimes basic glyphs are constructed from multiple other glyphs. + # Find out which other glyphs are also needed to keep the basic + # glyphs intact. + # 0x0000-0x017f is the Latin Extended-A range + # 0xfb00-0xfb06 are 'fi' and other ligatures + basic_glyphs = { c for c in range(0x21, 0x17f + 1) if c in self.sourceFont } + # Collect substitution destinations + for glyph in list(basic_glyphs) + [*range(0xfb00, 0xfb06 + 1)]: + if not glyph in self.sourceFont: + continue + for possub in self.sourceFont[glyph].getPosSub('*'): + if possub[1] == 'Substitution' or possub[1] == 'Ligature': + basic_glyphs.add(glyph) + basic_glyphs.add(self.sourceFont[possub[2]].unicode) + basic_glyphs.discard(-1) # the .notdef glyph + for glyph in basic_glyphs: + self.add_glyphrefs_to_essential(glyph) + + def get_sourcefont_dimensions(self): + """ This gets the font dimensions (cell width and height), and makes them equal on all platforms """ + # Step 1 + # There are three ways to describe the baseline to baseline distance + # (a.k.a. line spacing) of a font. That is all a kuddelmuddel + # and we try to sort this out here + # See also https://glyphsapp.com/learn/vertical-metrics + # See also https://github.com/source-foundry/font-line + (hhea_btb, typo_btb, win_btb, win_gap) = get_btb_metrics(self.sourceFont) + use_typo = self.sourceFont.os2_use_typo_metrics != 0 + + Metric = Enum('Metric', get_metrics_names()) + + if not self.args.metrics: + # We use either TYPO (1) or WIN (2) and compare with HHEA + # and use HHEA (0) if the fonts seems broken - no WIN, see #1056 + our_btb = typo_btb if use_typo else win_btb + if our_btb == hhea_btb: + metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font + elif abs(our_btb - hhea_btb) / our_btb < 0.03: + logger.info("Font vertical metrics slightly off (%.1f%%)", (our_btb - hhea_btb) / our_btb * 100.0) + metrics = Metric.TYPO if use_typo else Metric.WIN + else: + # Try the other metric + our_btb = typo_btb if not use_typo else win_btb + if our_btb == hhea_btb: + use_typo = not use_typo + logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", repr(use_typo)) + self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0 + metrics = Metric.TYPO if use_typo else Metric.WIN + else: + # We trust the WIN metric more, see experiments in #1056 + logger.warning("Font vertical metrics inconsistent (HHEA %d / TYPO %d / WIN %d), using WIN", hhea_btb, typo_btb, win_btb) + our_btb = win_btb + metrics = Metric.WIN + else: + metrics = Metric[self.args.metrics] + logger.debug("Metrics in the font: HHEA %d / TYPO %d / WIN %d", hhea_btb, typo_btb, win_btb) + if metrics == Metric.HHEA: + our_btb = hhea_btb + elif metrics == Metric.TYPO: + our_btb = typo_btb + else: + our_btb = win_btb + logger.info("Manually selected metrics: %s (%d)", self.args.metrics, our_btb) + + # print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) + + self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0, 'iconheight': 0, 'ypadding': 0} + + if metrics == Metric.HHEA: + self.font_dim['ymin'] = self.sourceFont.hhea_descent - half_gap(self.sourceFont.hhea_linegap, False) + self.font_dim['ymax'] = self.sourceFont.hhea_ascent + half_gap(self.sourceFont.hhea_linegap, True) + elif metrics == Metric.TYPO: + self.font_dim['ymin'] = self.sourceFont.os2_typodescent - half_gap(self.sourceFont.os2_typolinegap, False) + self.font_dim['ymax'] = self.sourceFont.os2_typoascent + half_gap(self.sourceFont.os2_typolinegap, True) + elif metrics == Metric.WIN: + self.font_dim['ymin'] = -self.sourceFont.os2_windescent - half_gap(win_gap, False) + self.font_dim['ymax'] = self.sourceFont.os2_winascent + half_gap(win_gap, True) + else: + logger.debug("Metrics is strange") + pass # Will fail the metrics check some line later + + if isinstance(self.args.cellopt, list): + logger.debug("Overriding cell Y{%d:%d} with Y{%d:%d}", + self.font_dim['ymin'], self.font_dim['ymax'], + self.args.cellopt[2], self.args.cellopt[3]) + self.font_dim['ymin'] = self.args.cellopt[2] + self.font_dim['ymax'] = self.args.cellopt[3] + our_btb = self.args.cellopt[3] - self.args.cellopt[2] + + # Calculate font height + self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax'] + if self.font_dim['height'] == 0: + # This can only happen if the input font is empty + # Assume we are using our prepared templates + self.symbolsonly = True + self.font_dim = { + 'xmin' : 0, + 'ymin' : -self.sourceFont.descent, + 'xmax' : self.sourceFont.em, + 'ymax' : self.sourceFont.ascent, + 'width' : self.sourceFont.em, + 'height' : self.sourceFont.descent + self.sourceFont.ascent, + 'iconheight': self.sourceFont.descent + self.sourceFont.ascent, + 'ypadding' : 0, + } + our_btb = self.sourceFont.descent + self.sourceFont.ascent + if self.font_dim['height'] <= 0: + logger.critical("Can not detect sane font height") + sys.exit(1) + + self.font_dim['iconheight'] = self.font_dim['height'] + if self.args.single and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): + # Limit the icon height on monospaced fonts because very slender and tall icons render + # excessively tall otherwise. We ignore that effect for the other variants because it + # does not look so much out of place there. + # Icons can be bigger than the letter capitals, but not the whole cell: + self.font_dim['iconheight'] = (self.sourceFont.capHeight * 2 + self.font_dim['height']) / 3 + + # Make all metrics equal + self.sourceFont.os2_typolinegap = 0 + self.sourceFont.os2_typoascent = self.font_dim['ymax'] + self.sourceFont.os2_typodescent = self.font_dim['ymin'] + self.sourceFont.os2_winascent = self.sourceFont.os2_typoascent + self.sourceFont.os2_windescent = -self.sourceFont.os2_typodescent + self.sourceFont.hhea_ascent = self.sourceFont.os2_typoascent + self.sourceFont.hhea_descent = self.sourceFont.os2_typodescent + self.sourceFont.hhea_linegap = self.sourceFont.os2_typolinegap + self.sourceFont.os2_use_typo_metrics = 1 + (check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont) + if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb: + logger.critical("Error in baseline to baseline code detected") + sys.exit(1) + + # Step 2 + # Find the biggest char width and advance width + # 0x00-0x17f is the Latin Extended-A range + warned1 = self.args.nonmono # Do not warn if proportional target + warned2 = warned1 + for glyph in range(0x21, 0x17f): + if glyph in range(0x7F, 0xBF) or glyph in [ + 0x132, 0x133, # IJ, ij (in Overpass Mono) + 0x022, 0x027, 0x060, # Single and double quotes in Inconsolata LGC + 0x0D0, 0x10F, 0x110, 0x111, 0x127, 0x13E, 0x140, 0x165, # Eth and others with stroke or caron in RobotoMono + 0x149, # napostrophe in DaddyTimeMono + 0x02D, # hyphen for Monofur + ]: + continue # ignore special characters like '1/4' etc and some specifics + try: + (_, _, xmax, _) = self.sourceFont[glyph].boundingBox() + except TypeError: + continue + # print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if self.font_dim['width'] < self.sourceFont[glyph].width: + self.font_dim['width'] = self.sourceFont[glyph].width + if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z + logger.debug("Extended glyphs wider than basic glyphs, results might be useless") + logger.debug("%s", report_advance_widths(self.sourceFont)) + warned1 = True + # print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if xmax > self.font_dim['xmax']: + self.font_dim['xmax'] = xmax + if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z + logger.debug("Extended glyphs wider bounding box than basic glyphs") + warned2 = True + # print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if self.font_dim['width'] < self.font_dim['xmax']: + logger.debug("Font has negative right side bearing in extended glyphs") + self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used + if self.font_dim['width'] <= 0: + logger.critical("Can not detect sane font width") + sys.exit(1) + if isinstance(self.args.cellopt, list): + logger.debug("Overriding cell X{%d:%d} with X{%d:%d}", + self.font_dim['xmin'], self.font_dim['xmin'] + self.font_dim['width'], + self.args.cellopt[0], self.args.cellopt[1]) + self.font_dim['xmin'] = self.args.cellopt[0] + self.font_dim['xmax'] = self.args.cellopt[1] + self.font_dim['width'] = self.args.cellopt[1] + if self.args.cellopt: + logger.info("Cell coordinates (Xmin:Xmax:Ymin:Ymax) %s%d:%d:%d:%d", + '' if not isinstance(self.args.cellopt, list) else 'overridden with ', + self.font_dim['xmin'], self.font_dim['width'], + self.font_dim['ymax'] - self.font_dim['height'], self.font_dim['ymax']) + logger.debug("Final font cell dimensions %d w x %d h%s", + self.font_dim['width'], self.font_dim['height'], + ' (with icon cell {} h)'.format(int(self.font_dim['iconheight'])) if self.font_dim['iconheight'] != self.font_dim['height'] else '') + try: + middle = lambda x, y: abs(x - y) / 2 + min(x, y) + x_bb = self.sourceFont['x'].boundingBox(); + X_bb = self.sourceFont['X'].boundingBox(); + logger.debug("Center x-height/cell/capitals %d/%d/%d", + middle(x_bb[1], x_bb[3]), + middle(self.font_dim['ymin'], self.font_dim['ymax']), + middle(X_bb[1], X_bb[3])) + except: + pass + + self.xavgwidth.append(self.args.xavgwidth) + if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: + self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont) + + + def get_target_width(self, stretch): + """ Get the target width (1 or 2 'cell') for a given stretch parameter """ + # For monospaced fonts all chars need to be maximum 'one' space wide + # other fonts allows double width glyphs for 'pa' or if requested with '2' + if self.args.single or ('pa' not in stretch and '2' not in stretch) or '1' in stretch: + return 1 + return 2 + + def get_scale_factors(self, sym_dim, stretch, overlap=None): + """ Get scale in x and y as tuple """ + # It is possible to have empty glyphs, so we need to skip those. + if not sym_dim['width'] or not sym_dim['height']: + return (1.0, 1.0) + + target_width = self.font_dim['width'] * self.get_target_width(stretch) + if overlap: + target_width += self.font_dim['width'] * overlap + scale_ratio_x = target_width / sym_dim['width'] + + # font_dim['height'] represents total line height, keep our symbols sized based upon font's em + # Use the font_dim['height'] only for explicit 'y' scaling (not 'pa') + target_height = self.font_dim['height'] if '^' in stretch else self.font_dim['iconheight'] + target_height *= 1.0 - self.font_dim['ypadding'] + if overlap: + target_height *= 1.0 + min(0.01, overlap) # never aggressive vertical overlap + scale_ratio_y = target_height / sym_dim['height'] + + if 'pa' in stretch: + # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit + scale_ratio_x = min(scale_ratio_x, scale_ratio_y) + if not self.args.single and not '!' in stretch and not overlap: + # non monospaced fonts just scale down on 'pa', not up + scale_ratio_x = min(scale_ratio_x, 1.0) + scale_ratio_y = scale_ratio_x + else: + # Keep the not-stretched direction + if not 'x' in stretch: + scale_ratio_x = 1.0 + if not 'y' in stretch: + scale_ratio_y = 1.0 + + return (scale_ratio_x, scale_ratio_y) + + + def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleRules, setName, attributes): + """ Copies symbol glyphs into self.sourceFont """ + progressText = '' + careful = False + sourceFontCounter = 0 + + if self.args.careful: + careful = True + + # Create glyphs from symbol font + # + # If we are going to copy all Glyphs, then assume we want to be careful + # and only copy those that are not already contained in the source font + if symbolFontStart == 0: + symbolFont.selection.all() + careful = True + else: + symbolFont.selection.select((str("ranges"), str("unicode")), symbolFontStart, symbolFontEnd) + + # Get number of selected non-empty glyphs with codes >=0 (i.e. not -1 == notdef) + symbolFontSelection = [ x for x in symbolFont.selection.byGlyphs if x.unicode >= 0 ] + glyphSetLength = len(symbolFontSelection) + + if not self.args.quiet: + modify = attributes['default']['params'].get('dont_copy') + sys.stdout.write("{} {} Glyphs from {} Set\n".format( + "Adding" if not modify else "Rescaling", glyphSetLength, setName)) + + currentSourceFontGlyph = -1 # initialize for the exactEncoding case + width_warning = False + + for index, sym_glyph in enumerate(symbolFontSelection): + sym_attr = attributes.get(sym_glyph.unicode) + if sym_attr is None: + sym_attr = attributes['default'] + + if self.font_extrawide: + # Do not allow 'xy2' scaling + sym_attr['stretch'] = sym_attr['stretch'].replace('2', '') + + if exactEncoding: + # Use the exact same hex values for the source font as for the symbol font. + # Problem is we do not know the codepoint of the sym_glyph and because it + # came from a selection.byGlyphs there might be skipped over glyphs. + # The iteration is still in the order of the selection by codepoint, + # so we take the next allowed codepoint of the current glyph + possible_codes = [ ] + if sym_glyph.unicode > currentSourceFontGlyph: + possible_codes += [ sym_glyph.unicode ] + if sym_glyph.altuni: + possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] + if len(possible_codes) == 0: + logger.warning("Can not determine codepoint of %X. Skipping...", sym_glyph.unicode) + continue + currentSourceFontGlyph = min(possible_codes) + else: + # use source font defined hex values based on passed in start (fills gaps; symbols are packed) + currentSourceFontGlyph = sourceFontStart + sourceFontCounter + sourceFontCounter += 1 + + # For debugging process only limited glyphs + # if currentSourceFontGlyph != 0xe7bd: + # continue + + ypadding = sym_attr['params'].get('ypadding') + self.font_dim['ypadding'] = ypadding or 0.0 + + if not self.args.quiet: + if self.args.progressbars: + update_progress(round(float(index + 1) / glyphSetLength, 2)) + else: + progressText = "\nUpdating glyph: {} {} putting at: {:X}".format(sym_glyph, sym_glyph.glyphname, currentSourceFontGlyph) + sys.stdout.write(progressText) + sys.stdout.flush() + + # check if a glyph already exists in this location + do_careful = sym_attr['params'].get('careful', careful) # params take precedence + if do_careful or currentSourceFontGlyph in self.essential: + if currentSourceFontGlyph in self.sourceFont: + careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' + logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph) + # We don't want to touch anything so move to next Glyph + continue + else: + # If we overwrite an existing glyph all subtable entries regarding it will be wrong + # (Probably; at least if we add a symbol and do not substitute a ligature or such) + if currentSourceFontGlyph in self.sourceFont: + self.sourceFont[currentSourceFontGlyph].removePosSub("*") + + stretch = sym_attr['stretch'] + dont_copy = sym_attr['params'].get('dont_copy') + + if dont_copy: + # Just prepare scaling of existing glyphs + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None + else: + # Break apart multiple unicodes linking to one glyph + if currentSourceFontGlyph in self.sourceFont: + altuni = self.sourceFont[currentSourceFontGlyph].altuni + if altuni: + codes = { v for v, s, r in altuni if v >= 0 } + codes.add(self.sourceFont[currentSourceFontGlyph].unicode) + codes.remove(currentSourceFontGlyph) + codes = [ "{:04X}".format(c) for c in sorted(list(codes)) ] + logger.debug("Removing alternate unicode on %X (%s)", currentSourceFontGlyph, ' '.join(codes)); + self.sourceFont[currentSourceFontGlyph].altuni = None + self.sourceFont.encoding = 'UnicodeFull' # Rebuild encoding table (needed after altuni changes) + + # This will destroy any content currently in currentSourceFontGlyph, so do it first + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None + + # Select and copy symbol from its encoding point + # We need to do this select after the careful check, this way we don't + # reset our selection before starting the next loop + symbolFont.selection.select(sym_glyph.encoding) + symbolFont.copy() + + # Paste it + self.sourceFont.selection.select(currentSourceFontGlyph) + self.sourceFont.paste() + self.sourceFont[currentSourceFontGlyph].glyphname = \ + self.glyphnames.get(currentSourceFontGlyph, sym_glyph.glyphname) if setName != 'Custom' else sym_glyph.glyphname + self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols + + # Prepare symbol glyph dimensions + sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) + overlap = sym_attr['params'].get('overlap') + if overlap and ypadding: + logger.critical("Conflicting params: overlap and ypadding") + sys.exit(1) + + if glyph_scale_data is not None: + if glyph_scale_data[1] is not None: + sym_dim = glyph_scale_data[1] # Use combined bounding box + (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch, overlap) + else: + # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') + # Except we do not have glyph_scale_data[1] always... + (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) + if overlap: + scale_ratio_x *= 1.0 + (self.font_dim['width'] / (sym_dim['width'] * scale_ratio_x)) * overlap + y_overlap = min(0.01, overlap) # never aggressive vertical overlap + scale_ratio_y *= 1.0 + (self.font_dim['height'] / (sym_dim['height'] * scale_ratio_y)) * y_overlap + else: + (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch, overlap) + + + # Size in x to size in y ratio limit (to prevent over-wide glyphs) + xy_ratio_max = sym_attr['params'].get('xy-ratio') + if (xy_ratio_max): + xy_ratio = sym_dim['width'] * scale_ratio_x / (sym_dim['height'] * scale_ratio_y) + if xy_ratio > xy_ratio_max: + scale_ratio_x = scale_ratio_x * xy_ratio_max / xy_ratio + + if scale_ratio_x != 1.0 or scale_ratio_y != 1.0: + scale_ratio_x *= self.sourceFont.em / (self.sourceFont.em + 1) # scale a tiny bit too small to avoid rounding problems + self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) + + # Drop nonintegral part of nodes' coordinates; ttf will do it anyhow, otf will be much smaller + self.sourceFont[currentSourceFontGlyph].round() + + if self.args.single: + # Check and correct the scaling after rounding (if all 3 tries fail we will get a warning later on) + destmaxsize = self.font_dim['width'] * max(1, 1 + (overlap or 0)) + for increaser in range(3): + (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() + sizeerror = (xmax - xmin) - destmaxsize + if sizeerror <= 0: + break + # Start from scratch with a new unscaled glyph + scale_ratio_x /= 1 + ((sizeerror + increaser) / destmaxsize) + self.sourceFont.paste() + self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) + self.sourceFont[currentSourceFontGlyph].round() + + # We pasted and scaled now we want to align/move + # Use the dimensions from the newly pasted and stretched glyph to avoid any rounding errors + sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) + # Use combined bounding box? + if glyph_scale_data is not None and glyph_scale_data[1] is not None: + scaleglyph_dim = scale_bounding_box(glyph_scale_data[1], scale_ratio_x, scale_ratio_y) + if scaleglyph_dim['advance'] is None: + # On monospaced symbol collections use their advance with, otherwise align horizontally individually + scaleglyph_dim['xmin'] = sym_dim['xmin'] + scaleglyph_dim['xmax'] = sym_dim['xmax'] + scaleglyph_dim['width'] = sym_dim['width'] + sym_dim = scaleglyph_dim + + y_align_distance = 0 + if sym_attr['valign'] == 'c': + # Center the symbol vertically by matching the center of the line height and center of symbol + sym_ycenter = sym_dim['ymax'] - (sym_dim['height'] / 2) + font_ycenter = self.font_dim['ymax'] - (self.font_dim['height'] / 2) + y_align_distance = font_ycenter - sym_ycenter + + # Handle glyph l/r/c alignment + x_align_distance = 0 + simple_nonmono = self.args.nonmono and sym_dim['advance'] is None + if simple_nonmono: + # Remove left side bearing + # (i.e. do not remove left side bearing when combined BB is in use) + x_align_distance = -self.sourceFont[currentSourceFontGlyph].left_side_bearing + elif sym_attr['align']: + # First find the baseline x-alignment (left alignment amount) + x_align_distance = self.font_dim['xmin'] - sym_dim['xmin'] + if self.args.nonmono and 'pa' in stretch: + cell_width = sym_dim['advance'] or sym_dim['width'] + else: + cell_width = self.font_dim['width'] + if sym_attr['align'] == 'c': + # Center align + x_align_distance += (cell_width / 2) - (sym_dim['width'] / 2) + elif sym_attr['align'] == 'r': + # Right align + # (not really supported with pa scaling and 2x stretch in NFP) + x_align_distance += cell_width * self.get_target_width(stretch) - sym_dim['width'] + if not overlap: + # If symbol glyph is wider than target font cell, just left-align + x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance) + + if overlap: + overlap_width = self.font_dim['width'] * overlap + if sym_attr['align'] == 'l': + x_align_distance -= overlap_width + elif sym_attr['align'] == 'c': + # center aligned keeps being center aligned even with overlap + if overlap_width < 0 and simple_nonmono: # Keep positive bearing due to negative overlap (propo) + x_align_distance -= overlap_width / 2 + elif sym_attr['align'] == 'r' and not simple_nonmono: + # Check and correct overlap; it can go wrong if we have a xy-ratio limit + target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(stretch) + target_xmax += overlap_width + glyph_xmax = sym_dim['xmax'] + x_align_distance + correction = target_xmax - glyph_xmax + x_align_distance += correction + + align_matrix = psMat.translate(x_align_distance, y_align_distance) + self.sourceFont[currentSourceFontGlyph].transform(align_matrix) + + # Ensure after horizontal adjustments and centering that the glyph + # does not overlap the bearings (edges) + if not overlap: + self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) + + # Needed for setting 'advance width' on each glyph so they do not overlap, + # also ensures the font is considered monospaced on Windows by setting the + # same width for all character glyphs. This needs to be done for all glyphs, + # even the ones that are empty and didn't go through the scaling operations. + # It should come after setting the glyph bearings + if not self.args.nonmono: + self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph]) + else: + # Target font with variable advance width get the icons with their native widths + # and keeping possible (right and/or negative) bearings in effect + if sym_dim['advance'] is not None: + # 'Width' from monospaced scale group + width = sym_dim['advance'] + else: + width = sym_dim['width'] + # If we have overlap we need to subtract that to keep/get negative bearings + if overlap: + width -= overlap_width + # Fontforge handles the width change like this: + # - Keep existing left_side_bearing + # - Set width + # - Calculate and set new right_side_bearing + self.sourceFont[currentSourceFontGlyph].width = int(width) + + # Check if the inserted glyph is scaled correctly for monospace + if self.args.single: + (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() + if (xmax - xmin) > self.font_dim['width'] * max(1, 1 + (overlap or 0)): + logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %s))", + currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], repr(overlap)) + + # end for + + if not self.args.quiet: + sys.stdout.write("\n") + + + def set_sourcefont_glyph_widths(self): + """ Makes self.sourceFont monospace compliant """ + + for glyph in self.sourceFont.glyphs(): + if (glyph.width == self.font_dim['width']): + # Don't touch the (negative) bearings if the width is ok + # Ligatures will have these. + continue + + if (glyph.width != 0): + # If the width is zero this glyph is intended to be printed on top of another one. + # In this case we need to keep the negative bearings to shift it 'left'. + # Things like Ä have these: composed of U+0041 'A' and U+0308 'double dot above' + # + # If width is not zero, correct the bearings such that they are within the width: + self.remove_glyph_neg_bearings(glyph) + + self.set_glyph_width_mono(glyph) + + + def remove_glyph_neg_bearings(self, glyph): + """ Sets passed glyph's bearings 0 if they are negative. """ + try: + if glyph.left_side_bearing < 0: + glyph.left_side_bearing = 0 + if glyph.right_side_bearing < 0: + glyph.right_side_bearing = 0 + except: + pass + + + def set_glyph_width_mono(self, glyph): + """ Sets passed glyph.width to self.font_dim.width. + + self.font_dim.width is set with self.get_sourcefont_dimensions(). + """ + try: + # Fontforge handles the width change like this: + # - Keep existing left_side_bearing + # - Set width + # - Calculate and set new right_side_bearing + glyph.width = self.font_dim['width'] + except: + pass + + def prepareScaleRules(self, scaleRules, stretch, symbolFont, destGlyph): + """ Prepare raw ScaleRules data for use """ + # The scaleRules is/will be a dict with these (possible) entries: + # 'ScaleGroups': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled + # 'scales': List of associated scale factors, one for each entry in 'ScaleGroups' (generated by this function) + # 'bbdims': List of associated sym_dim dicts, one for each entry in 'ScaleGroups' (generated by this function) + # Each dim_dict describes the combined bounding box of all glyphs in one ScaleGroups group + # Example: + # { 'ScaleGroups': [ range(1, 3), [ 7, 10 ], ], + # 'scales': [ 1.23, 1.33, ], + # 'bbdims': [ dim_dict1, dim_dict2, ] } + # + # Each item in 'ScaleGroups' (a range or an explicit list) forms a group of glyphs that shall be + # as rescaled all with the same and maximum possible (for the included glyphs) 'pa' factor. + # If the 'bbdims' is present they all shall be shifted in the same way. + # + # Previously this structure has been used: + # 'ScaleGlyph' Lead glyph, which scaling factor is taken + # 'GlyphsToScale': List of ((glyph code) or (tuple of two glyph codes that form a closed range)) that shall be scaled + # Note that this allows only one group for the whle symbol font, and that the scaling factor is defined by + # a specific character, which needs to be manually selected (on each symbol font update). + # Previous entries are automatically rewritten to the new style. + # + # Note that scaleRules is overwritten with the added data. + if 'scales' in scaleRules: + # Already prepared... must not happen, ignore call + return + + scaleRules['scales'] = [] + scaleRules['bbdims'] = [] + if 'ScaleGroups' not in scaleRules: + scaleRules['ScaleGroups'] = [] + + mode = scaleRules['ShiftMode'] # Mode is only documentary + for group in scaleRules['ScaleGroups']: + sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ], destGlyph) + scale = self.get_scale_factors(sym_dim, stretch)[0] + scaleRules['scales'].append(scale) + scaleRules['bbdims'].append(sym_dim) + if (mode): + if ('x' in mode) != (sym_dim['advance'] is not None): + d = '0x{:X} - 0x{:X}'.format(group[0], group[-1]) + if ('x' in mode) : + logger.critical("Scaling in group %s is expected to do horizontal shifts but can not", d) + else: + logger.critical("Scaling in group %s is expected to not do horizontal shifts but will", d) + sys.exit(1) + + if 'ScaleGlyph' in scaleRules: + # Rewrite to equivalent ScaleGroup + group_list = [] + if 'GlyphsToScale+' in scaleRules: + key = 'GlyphsToScale+' + plus = True + else: + key = 'GlyphsToScale' + plus = False + for i in scaleRules[key]: + if isinstance(i, tuple): + group_list.append(range(i[0], i[1] + 1)) + else: + group_list.append(i) + sym_dim = get_glyph_dimensions(symbolFont[scaleRules['ScaleGlyph']]) + scale = self.get_scale_factors(sym_dim, stretch)[0] + scaleRules['ScaleGroups'].append(group_list) + scaleRules['scales'].append(scale) + if plus: + scaleRules['bbdims'].append(sym_dim) + else: + scaleRules['bbdims'].append(None) # The 'old' style keeps just the scale, not the positioning + + def get_glyph_scale(self, symbol_unicode, scaleRules, stretch, symbolFont, dest_unicode): + """ Determines whether or not to use scaled glyphs for glyph in passed symbol_unicode """ + # Potentially destroys the contents of self.sourceFont[dest_unicode] + if not 'scales' in scaleRules: + if not dest_unicode in self.sourceFont: + self.sourceFont.createChar(dest_unicode) + self.prepareScaleRules(scaleRules, stretch, symbolFont, self.sourceFont[dest_unicode]) + for glyph_list, scale, box in zip(scaleRules['ScaleGroups'], scaleRules['scales'], scaleRules['bbdims']): + for e in glyph_list: + if isinstance(e, range): + if symbol_unicode in e: + return (scale, box) + elif symbol_unicode == e: + return (scale, box) + return None + + +def half_gap(gap, top): + """ Divides integer value into two new integers """ + # Line gap add extra space on the bottom of the line which + # doesn't allow the powerline glyphs to fill the entire line. + # Put half of the gap into the 'cell', each top and bottom + if gap <= 0: + return 0 + gap_top = int(gap / 2) + gap_bottom = gap - gap_top + if top: + logger.info("Redistributing line gap of %d (%d top and %d bottom)", gap, gap_top, gap_bottom) + return gap_top + return gap_bottom + +def replace_font_name(font_name, replacement_dict): + """ Replaces all keys with vals from replacement_dict in font_name. """ + for key, val in replacement_dict.items(): + font_name = font_name.replace(key, val) + return font_name + + +def make_sure_path_exists(path): + """ Verifies path passed to it exists. """ + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise + +def sanitize_filename(filename, allow_dirs = False): + """ Enforces to not use forbidden characters in a filename/path. """ + if filename == '.' and not allow_dirs: + return '_' + restore_colon = sys.platform == 'win32' and re.match('[a-z]:', filename, re.I) + trans = filename.maketrans('<>:"|?*', '_______') + for i in range(0x00, 0x20): + trans[i] = ord('_') + if not allow_dirs: + trans[ord('/')] = ord('_') + trans[ord('\\')] = ord('_') + else: + trans[ord('\\')] = ord('/') # We use Posix paths + new_filename = filename.translate(trans) + if restore_colon: + new_filename = new_filename[ :1] + ':' + new_filename[2: ] + return new_filename + +def get_multiglyph_boundingBox(glyphs, destGlyph = None): + """ Returns dict of the dimensions of multiple glyphs combined(, as if they are copied into destGlyph) """ + # If destGlyph is given the glyph(s) are first copied over into that + # glyph and measured in that font (to avoid rounding errors) + # Leaves the destGlyph in unknown state! + bbox = [ None, None, None, None, None ] + for glyph in glyphs: + if glyph is None: + # Glyph has been in defining range but is not in the actual font + continue + if destGlyph and glyph.font != destGlyph.font: + glyph.font.selection.select(glyph) + glyph.font.copy() + destGlyph.font.selection.select(destGlyph) + destGlyph.font.paste() + glyph = destGlyph + gbb = glyph.boundingBox() + gadvance = glyph.width + if len(glyphs) > 1 and gbb[0] == gbb[2] and gbb[1] == gbb[3]: + # Ignore empty glyphs if we examine more than one glyph + continue + bbox[0] = gbb[0] if bbox[0] is None or bbox[0] > gbb[0] else bbox[0] + bbox[1] = gbb[1] if bbox[1] is None or bbox[1] > gbb[1] else bbox[1] + bbox[2] = gbb[2] if bbox[2] is None or bbox[2] < gbb[2] else bbox[2] + bbox[3] = gbb[3] if bbox[3] is None or bbox[3] < gbb[3] else bbox[3] + if not bbox[4]: + bbox[4] = -gadvance # Negative for one/first glyph + else: + if abs(bbox[4]) != gadvance: + bbox[4] = -1 # Marker for not-monospaced + else: + bbox[4] = gadvance # Positive for 2 or more glyphs + if bbox[4] and bbox[4] < 0: + # Not monospaced when only one glyph is used or multiple glyphs with different advance widths + bbox[4] = None + return { + 'xmin' : bbox[0], + 'ymin' : bbox[1], + 'xmax' : bbox[2], + 'ymax' : bbox[3], + 'width' : bbox[2] + (-bbox[0]), + 'height' : bbox[3] + (-bbox[1]), + 'advance': bbox[4], # advance width if monospaced + } + +def get_glyph_dimensions(glyph): + """ Returns dict of the dimensions of the glyph passed to it. """ + return get_multiglyph_boundingBox([ glyph ]) + +def scale_bounding_box(bbox, scale_x, scale_y): + """ Return a scaled version of a glyph dimensions dict """ + # Simulate scaling on combined bounding box, round values for better simulation + new_dim = { + 'xmin' : int(bbox['xmin'] * scale_x), + 'ymin' : int(bbox['ymin'] * scale_y), + 'xmax' : int(bbox['xmax'] * scale_x), + 'ymax' : int(bbox['ymax'] * scale_y), + 'advance': int(bbox['advance'] * scale_x) if bbox['advance'] is not None else None, + } + new_dim['width'] = new_dim['xmax'] + (-new_dim['xmin']) + new_dim['height'] = new_dim['ymax'] + (-new_dim['ymin']) + return new_dim + +def update_progress(progress): + """ Updates progress bar length. + + Accepts a float between 0.0 and 1.0. Any int will be converted to a float. + A value at 1 or bigger represents 100% + modified from: https://stackoverflow.com/questions/3160699/python-progress-bar + """ + barLength = 40 # Modify this to change the length of the progress bar + if isinstance(progress, int): + progress = float(progress) + if progress >= 1: + progress = 1 + status = "Done...\r\n" # NOTE: status initialized and never used + block = int(round(barLength * progress)) + text = "\r╢{0}╟ {1}%".format("█" * block + "░" * (barLength - block), int(progress * 100)) + sys.stdout.write(text) + sys.stdout.flush() + + +def check_fontforge_min_version(): + """ Verifies installed FontForge version meets minimum requirement. """ + minimumVersion = 20141231 + actualVersion = int(fontforge.version()) + + # un-comment following line for testing invalid version error handling + # actualVersion = 20120731 + + # versions tested: 20150612, 20150824 + if actualVersion < minimumVersion: + logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion) + logger.critical("Please use at least version: %d", minimumVersion) + sys.exit(1) + +def check_version_with_git(version): + """ Upgraded the version to the current git tag version (starting with 'v') """ + git = subprocess.run("git describe --tags", + cwd=os.path.dirname(__file__), + shell=True, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ).stdout.decode('utf-8') + if len(git) == 0: + return False + tag = git.strip() + if len(tag) == 0 or not tag.startswith('v'): + return False + tag = tag[1:] + r = re.search('(.*?)(-[0-9]+)-g[0-9a-fA-F]+$', tag) + if r: + tag = r.group(1) + patchlevel = r.group(2) + else: + patchlevel = "" + # Inspired by Phaxmohdem's versiontuple https://stackoverflow.com/a/28568003 + + versiontuple = lambda v: tuple( p.zfill(8) for p in v.split(".") ) + if versiontuple(tag) > versiontuple(version): + return tag + patchlevel + if versiontuple(tag) == versiontuple(version) and len(patchlevel) > 0: + return tag + patchlevel + return False + +def setup_arguments(): + """ Parse the command line parameters and load the config file if needed """ + parser = argparse.ArgumentParser( + description=( + 'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n' + '* Website: https://www.nerdfonts.com\n' + '* Version: ' + version + '\n' + '* Development Website: https://github.com/ryanoasis/nerd-fonts\n' + '* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/-/changelog.md'), + formatter_class=RawTextHelpFormatter, + add_help=False, + ) + + parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)') + # optional arguments + parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected') + parser.add_argument('--debug', dest='debugmode', default=0, type=int, nargs='?', help='Verbose mode (optional: 1=just to file; 2*=just to terminal; 3=display and file)', const=2, choices=range(0, 3 + 1)) + parser.add_argument('--extension', '-ext', dest='extension', default="", type=str, help='Change font file type to create (e.g., ttf, otf)') + parser.add_argument('--help', '-h', action='help', default=argparse.SUPPRESS, help='Show this help message and exit') + parser.add_argument('--makegroups', dest='makegroups', default=1, type=int, nargs='?', help='Use alternative method to name patched fonts (default=1)', const=1, choices=range(-1, 6 + 1)) + parser.add_argument('--mono', '-s', dest='forcemono', default=False, action='count', help='Create monospaced font, existing and added glyphs are single-width (implies --single-width-glyphs)') + parser.add_argument('--outputdir', '-out', dest='outputdir', default=".", type=str, help='The directory to output the patched font file to') + parser.add_argument('--quiet', '-q', dest='quiet', default=False, action='store_true', help='Do not generate verbose output') + parser.add_argument('--single-width-glyphs', dest='single', default=False, action='store_true', help='Whether to generate the glyphs as single-width not double-width (default is double-width) (Nerd Font Mono)') + parser.add_argument('--use-single-width-glyphs', dest='forcemono', default=False, action='count', help=argparse.SUPPRESS) + parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang") (Nerd Font Propo)') + parser.add_argument('--version', '-v', action='version', version=projectName + ': %(prog)s (' + version + ')', help='Show program\'s version number and exit') + # --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'. + # Original font name: Hugo Sans Mono ExtraCondensed Light Italic + # NF Fam agg. + # -1 no renaming at all (keep old names and versions etc) --- --- --- + # 0 turned off, use old naming scheme [-] [-] [-] + # 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ] + # 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ] + # 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X] + # 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ] + # 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ] + # 6 HugoSansMono NF XCn Lt It [X] [X] [X] + + sym_font_group = parser.add_argument_group('Symbol Fonts') + sym_font_group.add_argument('--complete', '-c', dest='complete', default=False, action='store_true', help='Add all available Glyphs') + sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)') + sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') + sym_font_group.add_argument('--fontawesomeext', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)') + sym_font_group.add_argument('--fontlogos', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)') + sym_font_group.add_argument('--material', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)') + sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') + sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)') + sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs') + sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Extra Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)') + sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') + sym_font_group.add_argument('--weather', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)') + + expert_group = parser.add_argument_group('Expert Options') + expert_group.add_argument('--adjust-line-height', '-l', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)') + expert_group.add_argument('--boxdrawing', dest='forcebox', default=False, action='store_true', help='Force patching in (over existing) box drawing glyphs') + expert_group.add_argument('--cell', dest='cellopt', default=None, type=str, help='Adjust or query the cell size, e.g. use "0:1000:-200:800" or "?"') + expert_group.add_argument('--configfile', dest='configfile', default=False, type=str, help='Specify a file path for configuration file (see sample: src/config.sample.cfg)') + expert_group.add_argument('--custom', dest='custom', default=False, type=str, help='Specify a custom symbol font, all glyphs will be copied; absolute path suggested') + expert_group.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming') + expert_group.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, help='Path to glyphs to be used for patching') + expert_group.add_argument('--has-no-italic', dest='noitalic', default=False, action='store_true', help='Font family does not have Italic (but Oblique), to help create correct RIBBI set') + expert_group.add_argument('--metrics', dest='metrics', default=None, choices=get_metrics_names(), help='Select vertical metrics source (for problematic cases)') + expert_group.add_argument('--name', dest='force_name', default=None, type=str, help='Specify naming source (\'full\', \'postscript\', \'filename\', or concrete free name-string)') + expert_group.add_argument('--postprocess', dest='postprocess', default=False, type=str, help='Specify a Script for Post Processing') + progressbars_group_parser = expert_group.add_mutually_exclusive_group(required=False) + expert_group.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specified in configuration file (needs --configfile)') + expert_group.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True) + # --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts + # Possible values with examples: + # - copy from sourcefont (default) + # 0 - calculate from font according to OS/2-version-2 + # 500 - set to 500 + + # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse + progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)') + progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') + expert_group.set_defaults(progressbars=True) + + args = parser.parse_args() + setup_global_logger(args) + + # if we have a config file: fetch commandline arguments from there and process again with all arguments + config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) + if args.configfile: + if not os.path.isfile(args.configfile): + logger.critical("Configfile does not exist: %s", args.configfile) + sys.exit(1) + if not os.access(args.configfile, os.R_OK): + logger.critical("Can not open configfile for reading: %s", args.configfile) + sys.exit(1) + config.read(args.configfile) + extraflags = config.get("Config", "commandline", fallback='') + if len(extraflags): + logger.info("Adding config commandline options: %s", extraflags) + extraflags += ' ' + args.font # Need to re-add the mandatory argument + args = parser.parse_args(extraflags.split(), args) + + if args.makegroups > 0 and not FontnameParserOK: + logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0") + sys.exit(1) + + # if you add a new font, set it to True here inside the if condition + if args.complete: + args.fontawesome = True + args.fontawesomeextension = True + args.fontlogos = True + args.octicons = True + args.codicons = True + args.powersymbols = True + args.pomicons = True + args.powerline = True + args.powerlineextra = True + args.material = True + args.weather = True + + if not args.complete: + sym_font_args = [] + # add the list of arguments for each symbol font to the list sym_font_args + for action in sym_font_group._group_actions: + sym_font_args.append(action.__dict__['option_strings']) + + # determine whether or not all symbol fonts are to be used + font_complete = True + for sym_font_arg_aliases in sym_font_args: + found = False + for alias in sym_font_arg_aliases: + if alias in sys.argv: + found = True + if not found: + font_complete = False + args.complete = font_complete + + if args.forcemono: + args.single = True + if args.nonmono and args.single: + logger.warning("Specified contradicting --variable-width-glyphs together with --mono or --single-width-glyphs. Ignoring --variable-width-glyphs.") + args.nonmono = False + + if args.cellopt: + if args.cellopt != '?': + try: + parts = [ int(v) for v in args.cellopt.split(':') ] + if len(parts) != 4: + raise + except: + logger.critical("Parameter for --cell is not 4 colon separated integer numbers: '%s'", args.cellopt) + sys.exit(2) + if parts[0] >= parts[1] or parts[2] >= parts[3]: + logger.critical("Parameter for --cell do not result in positive cell size: %d x %d", + parts[1] - parts[0], parts[3] - parts[2]) + sys.exit(2) + if parts[0] != 0: + logger.warn("First parameter for --cell should be zero, this is probably not working") + args.cellopt = parts + + make_sure_path_exists(args.outputdir) + if not os.path.isfile(args.font): + logger.critical("Font file does not exist: %s", args.font) + sys.exit(1) + if not os.access(args.font, os.R_OK): + logger.critical("Can not open font file for reading: %s", args.font) + sys.exit(1) + is_ttc = len(fontforge.fontsInFile(args.font)) > 1 + try: + source_font_test = TableHEADWriter(args.font) + args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0) + if args.is_variable: + logger.warning("Source font is a variable open type font (VF), opening might fail...") + except: + args.is_variable = False + finally: + try: + source_font_test.close() + except: + pass + + if args.extension == "": + args.extension = os.path.splitext(args.font)[1] + else: + args.extension = '.' + args.extension + if re.match(r'\.ttc$', args.extension, re.IGNORECASE): + if not is_ttc: + logger.critical("Can not create True Type Collections from single font files") + sys.exit(1) + else: + if is_ttc: + logger.critical("Can not create single font files from True Type Collections") + sys.exit(1) + + # The if might look ridiculous, but isinstance(False, int) is True! + if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool): + if args.xavgwidth < 0: + logger.critical("--xavgcharwidth takes no negative numbers") + sys.exit(2) + if args.xavgwidth > 16384: + logger.critical("--xavgcharwidth takes only numbers up to 16384") + sys.exit(2) + + return (args, config) + +def setup_global_logger(args): + """ Set up the logger and take options into account """ + global logger + logger = logging.getLogger(os.path.basename(args.font)) + logger.setLevel(logging.DEBUG) + log_to_file = (args.debugmode & 1 == 1) + if log_to_file: + try: + f_handler = logging.FileHandler('font-patcher-log.txt') + f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s')) + logger.addHandler(f_handler) + except: + log_to_file = False + logger.debug(allversions) + logger.debug("Options %s", repr(sys.argv[1:])) + c_handler = logging.StreamHandler(stream=sys.stdout) + c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + if not (args.debugmode & 2 == 2): + c_handler.setLevel(logging.INFO) + logger.addHandler(c_handler) + if (args.debugmode & 1 == 1) and not log_to_file: + logger.info("Can not write logfile, disabling") + +def main(): + global logger + logger = logging.getLogger("start") # Use start logger until we can set up something sane + s_handler = logging.StreamHandler(stream=sys.stdout) + s_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + logger.addHandler(s_handler) + + global version + git_version = check_version_with_git(version) + global allversions + allversions = "Patcher v{} ({}) (ff {})".format( + git_version if git_version else version, script_version, fontforge.version()) + print("{} {}".format(projectName, allversions)) + if git_version: + version = git_version + check_fontforge_min_version() + (args, conf) = setup_arguments() + logger.debug("Naming mode %d", args.makegroups) + + patcher = font_patcher(args, conf) + + sourceFonts = [] + all_fonts = fontforge.fontsInFile(args.font) + if not all_fonts: + if re.match(".*\\.woff2?", args.font, re.I): + all_fonts=[ "" ] + else: + logger.critical("Can not find any fonts in '%s'", args.font) + sys.exit(1) + for i, subfont in enumerate(all_fonts): + if len(all_fonts) > 1: + print("\n") + logger.info("Processing %s (%d/%d)", subfont, i + 1, len(all_fonts)) + try: + sourceFonts.append(fontforge.open("{}({})".format(args.font, i), 1)) # 1 = ("fstypepermitted",)) + except Exception: + logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information", + subfont) + sys.exit(1) + + patcher.setup_name_backup(sourceFonts[-1]) + patcher.patch(sourceFonts[-1]) + + print("Done with Patch Sets, generating font...") + for f in sourceFonts: + patcher.setup_font_names(f) + patcher.generate(sourceFonts) + + for f in sourceFonts: + f.close() + + +if __name__ == "__main__": + __dir__ = os.path.dirname(os.path.abspath(__file__)) + main() From 95c8747ab54773c838fc8f4e98352833866d6386 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 3 Jul 2025 16:04:04 -0600 Subject: [PATCH 065/119] renderer: apply glyph constraints when rasterizing glyphs --- src/renderer/cell.zig | 54 ++++++++++++++++++++++++++++++++++++++++ src/renderer/generic.zig | 6 +++++ 2 files changed, 60 insertions(+) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index ef7122699..6d5bcbaf6 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -223,6 +223,60 @@ pub const FgMode = enum { powerline, }; +/// Returns the appropriate `constraint_width` for +/// the provided cell when rendering its glyph(s). +pub fn constraintWidth(cell_pin: terminal.Pin) u2 { + const cell = cell_pin.rowAndCell().cell; + const cp = cell.codepoint(); + + if (!ziglyph.general_category.isPrivateUse(cp) and + !ziglyph.blocks.isDingbats(cp)) + { + return cell.gridWidth(); + } + + // If we are at the end of the screen it must be constrained to one cell. + if (cell_pin.x == cell_pin.node.data.size.cols - 1) return 1; + + // If we have a previous cell and it was PUA then we need to + // also constrain. This is so that multiple PUA glyphs align. + // As an exception, we ignore powerline glyphs since they are + // used for box drawing and we consider them whitespace. + if (cell_pin.x > 0) prev: { + const prev_cp = prev_cp: { + var copy = cell_pin; + copy.x -= 1; + const prev_cell = copy.rowAndCell().cell; + break :prev_cp prev_cell.codepoint(); + }; + + // We consider powerline glyphs whitespace. + if (isPowerline(prev_cp)) break :prev; + + if (ziglyph.general_category.isPrivateUse(prev_cp)) { + return 1; + } + } + + // If the next cell is whitespace, then + // we allow it to be up to two cells wide. + const next_cp = next_cp: { + var copy = cell_pin; + copy.x += 1; + const next_cell = copy.rowAndCell().cell; + break :next_cp next_cell.codepoint(); + }; + if (next_cp == 0 or + isSpace(next_cp) or + isPowerline(next_cp)) + { + return 2; + } + + // Must be constrained + return 1; +} + /// Returns the appropriate foreground mode for the given cell. This is /// meant to be called from the typical updateCell function within a /// renderer. diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 810e17686..539b478c0 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -14,6 +14,7 @@ const Surface = @import("../Surface.zig"); const link = @import("link.zig"); const cellpkg = @import("cell.zig"); const fgMode = cellpkg.fgMode; +const constraintWidth = cellpkg.constraintWidth; const isCovering = cellpkg.isCovering; const imagepkg = @import("image.zig"); const Image = imagepkg.Image; @@ -26,6 +27,8 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Terminal = terminal.Terminal; const Health = renderer.Health; +const getConstraint = @import("../font/nerd_font_attributes.zig").getConstraint; + const FileType = @import("../file_type.zig").FileType; const macos = switch (builtin.os.tag) { @@ -3030,6 +3033,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, .thicken_strength = self.config.font_thicken_strength, + .cell_width = cell.gridWidth(), + .constraint = getConstraint(cell.codepoint()), + .constraint_width = constraintWidth(cell_pin), }, ); From 5553f7bf688b69b006f437223724d6cf33f969ce Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 3 Jul 2025 16:49:51 -0600 Subject: [PATCH 066/119] font: always maximize size of emoji and center them --- src/font/SharedGrid.zig | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index dcfa0a551..48c7239b2 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -265,13 +265,36 @@ pub fn renderGlyph( .emoji => &self.atlas_color, }; + var render_opts = opts; + + // Always use these constraints for emoji. + if (p == .emoji) { + render_opts.constraint = .{ + // Make the emoji as wide as possible, scaling proportionally, + // but then scale it down as necessary if its new size exceeds + // the cell height. + .size_horizontal = .cover, + .size_vertical = .fit, + + // Center the emoji in its cells. + .align_horizontal = .center, + .align_vertical = .center, + + // Add a small bit of padding so the emoji + // doesn't quite touch the edges of the cells. + .pad_left = 0.025, + .pad_right = 0.025, + }; + } + + // Render into the atlas const glyph = self.resolver.renderGlyph( alloc, atlas, index, glyph_index, - opts, + render_opts, ) catch |err| switch (err) { // If the atlas is full, we resize it error.AtlasFull => blk: { @@ -281,7 +304,7 @@ pub fn renderGlyph( atlas, index, glyph_index, - opts, + render_opts, ); }, From 1a8a04813652e2d3fbbcbdd73ab5dbfe6ee544af Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 4 Jul 2025 09:28:13 -0600 Subject: [PATCH 067/119] font/sfnt: simpler and more efficient FixedType conversions --- src/font/opentype/sfnt.zig | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/font/opentype/sfnt.zig b/src/font/opentype/sfnt.zig index 14a3b795a..82c118bce 100644 --- a/src/font/opentype/sfnt.zig +++ b/src/font/opentype/sfnt.zig @@ -76,24 +76,22 @@ fn FixedPoint(comptime T: type, int_bits: u64, frac_bits: u64) type { )); const half = @as(T, 1) << @intCast(frac_bits - 1); - frac: std.meta.Int(.unsigned, frac_bits), - int: std.meta.Int(type_info.signedness, int_bits), + const Frac = std.meta.Int(.unsigned, frac_bits); + const Int = std.meta.Int(type_info.signedness, int_bits); + + frac: Frac, + int: Int, pub fn to(self: Self, comptime FloatType: type) FloatType { - const i: FloatType = @floatFromInt(self.int); - const f: FloatType = @floatFromInt(self.frac); - - return i + f / frac_factor; + return @as(FloatType, @floatFromInt( + @as(T, @bitCast(self)), + )) / frac_factor; } pub fn from(float: anytype) Self { - const int = @floor(float); - const frac = @abs(float - int); - - return .{ - .int = @intFromFloat(int), - .frac = @intFromFloat(@round(frac * frac_factor)), - }; + return @bitCast( + @as(T, @intFromFloat(@round(float * frac_factor))), + ); } /// Round to the nearest integer, .5 rounds away from 0. From f29213276215ac5b8a03bf0676c6dce8e8a65718 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 4 Jul 2025 13:44:58 -0600 Subject: [PATCH 068/119] font: add constraint width to glyph cache key --- src/font/SharedGrid.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 48c7239b2..3ccac7fa1 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -287,7 +287,6 @@ pub fn renderGlyph( }; } - // Render into the atlas const glyph = self.resolver.renderGlyph( alloc, @@ -348,7 +347,8 @@ const GlyphKey = struct { cell_width: u2, thicken: bool, thicken_strength: u8, - _padding: u5 = 0, + constraint_width: u2, + _padding: u3 = 0, }, inline fn from(key: GlyphKey) Packed { @@ -359,6 +359,7 @@ const GlyphKey = struct { .cell_width = key.opts.cell_width orelse 0, .thicken = key.opts.thicken, .thicken_strength = key.opts.thicken_strength, + .constraint_width = key.opts.constraint_width, }, }; } From ec20c455c773f9c6f7fe8b4e9a77efe4d299bf24 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 4 Jul 2025 15:39:56 -0600 Subject: [PATCH 069/119] renderer: remove all gpu-side glyph constraint logic Now that it's done at the rasterization stage, we don't need to handle it on the GPU. This also means that we can switch to nearest neighbor interpolation in the Metal shader since we're guaranteed to be pixel perfect. Accidentally, we were already nearest neighbor in the OpenGL shaders because I used the Rectangle texture mode in the big renderer rework, which doesn't support interpolation- anyway, that's no longer problematic since we won't be scaling glyphs on the GPU anymore. --- src/renderer/cell.zig | 101 +-------------------- src/renderer/generic.zig | 44 +++------ src/renderer/metal/shaders.zig | 17 ++-- src/renderer/opengl/shaders.zig | 17 ++-- src/renderer/shaders/glsl/cell_text.f.glsl | 23 ++--- src/renderer/shaders/glsl/cell_text.v.glsl | 56 ++++-------- src/renderer/shaders/shaders.metal | 75 +++++---------- 7 files changed, 86 insertions(+), 247 deletions(-) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 6d5bcbaf6..632eab3fe 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -205,24 +205,6 @@ pub fn isCovering(cp: u21) bool { }; } -pub const FgMode = enum { - /// Normal non-colored text rendering. The text can leave the cell - /// size if it is larger than the cell to allow for ligatures. - normal, - - /// Colored text rendering, specifically Emoji. - color, - - /// Similar to normal but the text must be constrained to the cell - /// size. If a glyph is larger than the cell then it must be resized - /// to fit. - constrained, - - /// Similar to normal, but the text consists of Powerline glyphs and is - /// optionally exempt from padding color extension and minimum contrast requirements. - powerline, -}; - /// Returns the appropriate `constraint_width` for /// the provided cell when rendering its glyph(s). pub fn constraintWidth(cell_pin: terminal.Pin) u2 { @@ -277,85 +259,10 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 { return 1; } -/// Returns the appropriate foreground mode for the given cell. This is -/// meant to be called from the typical updateCell function within a -/// renderer. -pub fn fgMode( - presentation: font.Presentation, - cell_pin: terminal.Pin, -) FgMode { - return switch (presentation) { - // Emoji is always full size and color. - .emoji => .color, - - // If it is text it is slightly more complex. If we are a codepoint - // in the private use area and we are at the end or the next cell - // is not empty, we need to constrain rendering. - // - // We do this specifically so that Nerd Fonts can render their - // icons without overlapping with subsequent characters. But if - // the subsequent character is empty, then we allow it to use - // the full glyph size. See #1071. - .text => text: { - const cell = cell_pin.rowAndCell().cell; - const cp = cell.codepoint(); - - if (!ziglyph.general_category.isPrivateUse(cp) and - !ziglyph.blocks.isDingbats(cp)) - { - break :text .normal; - } - - // Special-case Powerline glyphs. They exhibit box drawing behavior - // and should not be constrained. They have their own special category - // though because they're used for other logic (i.e. disabling - // min contrast). - if (isPowerline(cp)) { - break :text .powerline; - } - - // If we are at the end of the screen its definitely constrained - if (cell_pin.x == cell_pin.node.data.size.cols - 1) break :text .constrained; - - // If we have a previous cell and it was PUA then we need to - // also constrain. This is so that multiple PUA glyphs align. - // As an exception, we ignore powerline glyphs since they are - // used for box drawing and we consider them whitespace. - if (cell_pin.x > 0) prev: { - const prev_cp = prev_cp: { - var copy = cell_pin; - copy.x -= 1; - const prev_cell = copy.rowAndCell().cell; - break :prev_cp prev_cell.codepoint(); - }; - - // Powerline is whitespace - if (isPowerline(prev_cp)) break :prev; - - if (ziglyph.general_category.isPrivateUse(prev_cp)) { - break :text .constrained; - } - } - - // If the next cell is empty, then we allow it to use the - // full glyph size. - const next_cp = next_cp: { - var copy = cell_pin; - copy.x += 1; - const next_cell = copy.rowAndCell().cell; - break :next_cp next_cell.codepoint(); - }; - if (next_cp == 0 or - isSpace(next_cp) or - isPowerline(next_cp)) - { - break :text .normal; - } - - // Must be constrained - break :text .constrained; - }, - }; +/// Whether min contrast should be disabled for a given glyph. +pub fn noMinContrast(cp: u21) bool { + // TODO: We should disable for all box drawing type characters. + return isPowerline(cp); } // Some general spaces, others intentionally kept diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 539b478c0..3b9879019 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -13,7 +13,7 @@ const math = @import("../math.zig"); const Surface = @import("../Surface.zig"); const link = @import("link.zig"); const cellpkg = @import("cell.zig"); -const fgMode = cellpkg.fgMode; +const noMinContrast = cellpkg.noMinContrast; const constraintWidth = cellpkg.constraintWidth; const isCovering = cellpkg.isCovering; const imagepkg = @import("image.zig"); @@ -2933,9 +2933,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .underline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2965,9 +2964,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .overline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2997,9 +2995,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .strikethrough, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3024,6 +3021,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { const rac = cell_pin.rowAndCell(); const cell = rac.cell; + const cp = cell.codepoint(); + // Render const render = try self.font_grid.renderGlyph( self.alloc, @@ -3034,7 +3033,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .thicken = self.config.font_thicken, .thicken_strength = self.config.font_thicken_strength, .cell_width = cell.gridWidth(), - .constraint = getConstraint(cell.codepoint()), + .constraint = getConstraint(cp), .constraint_width = constraintWidth(cell_pin), }, ); @@ -3045,27 +3044,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type { return; } - // We always use fg mode for sprite glyphs, since we know we never - // need to constrain them, and we don't have any color sprites. - // - // Otherwise we defer to `fgMode`. - const mode: shaderpkg.CellText.Mode = - if (render.glyph.sprite) - .fg - else switch (fgMode( - render.presentation, - cell_pin, - )) { - .normal => .fg, - .color => .fg_color, - .constrained => .fg_constrained, - .powerline => .fg_powerline, - }; - try self.cells.add(self.alloc, .text, .{ - .mode = mode, + .atlas = switch (render.presentation) { + .emoji => .color, + .text => .grayscale, + }, + .bools = .{ .no_min_contrast = noMinContrast(cp) }, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = cell.gridWidth(), .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3150,7 +3135,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; self.cells.setCursor(.{ - .mode = .cursor, + .atlas = .grayscale, + .bools = .{ .is_cursor_glyph = true }, .grid_pos = .{ x, screen.cursor.y }, .color = .{ cursor_color.r, cursor_color.g, cursor_color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, @@ -3199,7 +3185,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Add our text try self.cells.add(self.alloc, .text, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(coord.x), @intCast(coord.y) }, .color = .{ fg.r, fg.g, fg.b, 255 }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 9fe0862ed..bf3bcc6e4 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -269,15 +269,16 @@ pub const CellText = extern struct { bearings: [2]i16 align(4) = .{ 0, 0 }, grid_pos: [2]u16 align(4), color: [4]u8 align(4), - mode: Mode align(1), - constraint_width: u8 align(1) = 0, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u8) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; test { diff --git a/src/renderer/opengl/shaders.zig b/src/renderer/opengl/shaders.zig index 0b67eaff0..80980bac7 100644 --- a/src/renderer/opengl/shaders.zig +++ b/src/renderer/opengl/shaders.zig @@ -237,15 +237,16 @@ pub const CellText = extern struct { bearings: [2]i16 align(4) = .{ 0, 0 }, grid_pos: [2]u16 align(4), color: [4]u8 align(4), - mode: Mode align(4), - constraint_width: u32 align(4) = 0, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u32) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; // test { diff --git a/src/renderer/shaders/glsl/cell_text.f.glsl b/src/renderer/shaders/glsl/cell_text.f.glsl index fda6d8134..176efcbde 100644 --- a/src/renderer/shaders/glsl/cell_text.f.glsl +++ b/src/renderer/shaders/glsl/cell_text.f.glsl @@ -4,21 +4,15 @@ layout(binding = 0) uniform sampler2DRect atlas_grayscale; layout(binding = 1) uniform sampler2DRect atlas_color; in CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; } in_data; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -// -// NOTE: this must be kept in sync with the fragment shader -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; @@ -27,12 +21,9 @@ void main() { bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0; bool use_linear_correction = (bools & USE_LINEAR_CORRECTION) != 0; - switch (in_data.mode) { + switch (in_data.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: + case ATLAS_GRAYSCALE: { // Our input color is always linear. vec4 color = in_data.color; @@ -84,7 +75,7 @@ void main() { return; } - case MODE_TEXT_COLOR: + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. diff --git a/src/renderer/shaders/glsl/cell_text.v.glsl b/src/renderer/shaders/glsl/cell_text.v.glsl index 10965ddd2..7e38e2f0c 100644 --- a/src/renderer/shaders/glsl/cell_text.v.glsl +++ b/src/renderer/shaders/glsl/cell_text.v.glsl @@ -15,22 +15,22 @@ layout(location = 3) in uvec2 grid_pos; // The color of the rendered text glyph. layout(location = 4) in uvec4 color; -// The mode for this cell. -layout(location = 5) in uint mode; +// Which atlas this glyph is in. +layout(location = 5) in uint atlas; -// The width to constrain the glyph to, in cells, or 0 for no constraint. -layout(location = 6) in uint constraint_width; +// Misc glyph properties. +layout(location = 6) in uint glyph_bools; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; + +// Masks for the `glyph_bools` attribute +const uint NO_MIN_CONTRAST = 1u; +const uint IS_CURSOR_GLYPH = 2u; out CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; @@ -69,7 +69,7 @@ void main() { corner.x = float(vid == 1 || vid == 3); corner.y = float(vid == 2 || vid == 3); - out_data.mode = mode; + out_data.atlas = atlas; // === Grid Cell === // +X @@ -102,25 +102,6 @@ void main() { offset.y = cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (mode == MODE_TEXT_CONSTRAINED) { - float max_width = cell_size.x * constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2.0; - offset.x = 0.0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.x; - } - } - // Calculate the final position of the cell which uses our glyph size // and glyph offset to create the correct bounding box for the glyph. cell_pos = cell_pos + size * corner + offset; @@ -149,11 +130,7 @@ void main() { // If we have a minimum contrast, we need to check if we need to // change the color of the text to ensure it has enough contrast // with the background. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // since we want color glyphs to appear in their original color - // and Powerline glyphs to be unaffected (else parts of the line would - // have different colors as some parts are displayed via background colors). - if (min_contrast > 1.0f && mode == MODE_TEXT) { + if (min_contrast > 1.0f && (glyph_bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out_data.color = contrasted_color(min_contrast, out_data.color, out_data.bg_color); } @@ -161,8 +138,9 @@ void main() { // Check if current position is under cursor (including wide cursor) bool is_cursor_pos = ((grid_pos.x == cursor_pos.x) || (cursor_wide && (grid_pos.x == (cursor_pos.x + 1)))) && (grid_pos.y == cursor_pos.y); - // If this cell is the cursor cell, then we need to change the color. - if (mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((glyph_bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out_data.color = load_color(unpack4u8(cursor_color_packed_4u8), use_linear_blending); } } diff --git a/src/renderer/shaders/shaders.metal b/src/renderer/shaders/shaders.metal index b62e0c3cf..4797f89e4 100644 --- a/src/renderer/shaders/shaders.metal +++ b/src/renderer/shaders/shaders.metal @@ -509,13 +509,17 @@ fragment float4 cell_bg_fragment( //------------------------------------------------------------------- #pragma mark - Cell Text Shader -// The possible modes that a cell fg entry can take. -enum CellTextMode : uint8_t { - MODE_TEXT = 1u, - MODE_TEXT_CONSTRAINED = 2u, - MODE_TEXT_COLOR = 3u, - MODE_TEXT_CURSOR = 4u, - MODE_TEXT_POWERLINE = 5u, +enum CellTextAtlas : uint8_t { + ATLAS_GRAYSCALE = 0u, + ATLAS_COLOR = 1u, +}; + +// We use a packed struct of bools for misc properties of the glyph. +enum CellTextBools : uint8_t { + // Don't apply min contrast to this glyph. + NO_MIN_CONTRAST = 1u, + // This is the cursor glyph. + IS_CURSOR_GLYPH = 2u, }; struct CellTextVertexIn { @@ -534,16 +538,16 @@ struct CellTextVertexIn { // The color of the rendered text glyph. uchar4 color [[attribute(4)]]; - // The mode for this cell. - uint8_t mode [[attribute(5)]]; + // Which atlas to sample for our glyph. + uint8_t atlas [[attribute(5)]]; - // The width to constrain the glyph to, in cells, or 0 for no constraint. - uint8_t constraint_width [[attribute(6)]]; + // Misc properties of the glyph. + uint8_t bools [[attribute(6)]]; }; struct CellTextVertexOut { float4 position [[position]]; - uint8_t mode [[flat]]; + uint8_t atlas [[flat]]; float4 color [[flat]]; float4 bg_color [[flat]]; float2 tex_coord; @@ -577,7 +581,7 @@ vertex CellTextVertexOut cell_text_vertex( corner.y = float(vid == 2 || vid == 3); CellTextVertexOut out; - out.mode = in.mode; + out.atlas = in.atlas; // === Grid Cell === // +X @@ -610,25 +614,6 @@ vertex CellTextVertexOut cell_text_vertex( offset.y = uniforms.cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (in.mode == MODE_TEXT_CONSTRAINED) { - float max_width = uniforms.cell_size.x * in.constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2; - offset.x = 0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.x; - } - } - // Calculate the final position of the cell which uses our glyph size // and glyph offset to create the correct bounding box for the glyph. cell_pos = cell_pos + size * corner + offset; @@ -665,11 +650,7 @@ vertex CellTextVertexOut cell_text_vertex( // If we have a minimum contrast, we need to check if we need to // change the color of the text to ensure it has enough contrast // with the background. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // since we want color glyphs to appear in their original color - // and Powerline glyphs to be unaffected (else parts of the line would - // have different colors as some parts are displayed via background colors). - if (uniforms.min_contrast > 1.0f && in.mode == MODE_TEXT) { + if (uniforms.min_contrast > 1.0f && (in.bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out.color = contrasted_color(uniforms.min_contrast, out.color, out.bg_color); } @@ -681,8 +662,9 @@ vertex CellTextVertexOut cell_text_vertex( in.grid_pos.x == uniforms.cursor_pos.x + 1 ) && in.grid_pos.y == uniforms.cursor_pos.y; - // If this cell is the cursor cell, then we need to change the color. - if (in.mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((in.bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out.color = load_color( uniforms.cursor_color, uniforms.use_display_p3, @@ -702,19 +684,12 @@ fragment float4 cell_text_fragment( constexpr sampler textureSampler( coord::pixel, address::clamp_to_edge, - // TODO(qwerasd): This can be changed back to filter::nearest when - // we move the constraint logic out of the GPU code - // which should once again guarantee pixel perfect - // sizing. - filter::linear + filter::nearest ); - switch (in.mode) { + switch (in.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: { + case ATLAS_GRAYSCALE: { // Our input color is always linear. float4 color = in.color; @@ -764,7 +739,7 @@ fragment float4 cell_text_fragment( return color; } - case MODE_TEXT_COLOR: { + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. float4 color = textureColor.sample(textureSampler, in.tex_coord); From 1ab2603e0f07df543ca69a41ecd580ec0c4402d9 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 4 Jul 2025 15:43:00 -0600 Subject: [PATCH 070/119] font/freetype: remove freetype_convert.zig This is no longer needed since we're now using the FT_Bitmap_Convert function from FreeType to do any conversions we need instead. --- src/font/face/freetype.zig | 1 - src/font/face/freetype_convert.zig | 88 ------------------------------ 2 files changed, 89 deletions(-) delete mode 100644 src/font/face/freetype_convert.zig diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 9e057ceea..c23ede04a 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -15,7 +15,6 @@ const Allocator = std.mem.Allocator; const font = @import("../main.zig"); const Glyph = font.Glyph; const Library = font.Library; -const convert = @import("freetype_convert.zig"); const opentype = @import("../opentype.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); diff --git a/src/font/face/freetype_convert.zig b/src/font/face/freetype_convert.zig deleted file mode 100644 index 3a7cf8c98..000000000 --- a/src/font/face/freetype_convert.zig +++ /dev/null @@ -1,88 +0,0 @@ -//! Various conversions from Freetype formats to Atlas formats. These are -//! currently implemented naively. There are definitely MUCH faster ways -//! to do this (likely using SIMD), but I started simple. -const std = @import("std"); -const freetype = @import("freetype"); -const font = @import("../main.zig"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -/// The mapping from freetype format to atlas format. -pub const map = genMap(); - -/// The map type. -pub const Map = [freetype.c.FT_PIXEL_MODE_MAX]AtlasArray; - -/// Conversion function type. The returning bitmap buffer is guaranteed -/// to be exactly `width * rows * depth` long for freeing it. The caller must -/// free the bitmap buffer. The depth is the depth of the atlas format in the -/// map. -pub const Func = *const fn (Allocator, Bitmap) Allocator.Error!Bitmap; - -/// Alias for the freetype FT_Bitmap type to make it easier to type. -pub const Bitmap = freetype.c.struct_FT_Bitmap_; - -const AtlasArray = std.EnumArray(font.Atlas.Format, ?Func); - -fn genMap() Map { - var result: Map = undefined; - - // Initialize to no converter - var i: usize = 0; - while (i < freetype.c.FT_PIXEL_MODE_MAX) : (i += 1) { - result[i] = .initFill(null); - } - - // Map our converters - result[freetype.c.FT_PIXEL_MODE_MONO].set(.grayscale, monoToGrayscale); - - return result; -} - -pub fn monoToGrayscale(alloc: Allocator, bm: Bitmap) Allocator.Error!Bitmap { - var buf = try alloc.alloc(u8, bm.width * bm.rows); - errdefer alloc.free(buf); - - for (0..bm.rows) |y| { - const row_offset = y * @as(usize, @intCast(bm.pitch)); - for (0..bm.width) |x| { - const byte_offset = row_offset + @divTrunc(x, 8); - const mask = @as(u8, 1) << @intCast(7 - (x % 8)); - const bit: u8 = @intFromBool((bm.buffer[byte_offset] & mask) != 0); - buf[y * bm.width + x] = bit * 255; - } - } - - var copy = bm; - copy.buffer = buf.ptr; - copy.pixel_mode = freetype.c.FT_PIXEL_MODE_GRAY; - copy.pitch = @as(c_int, @intCast(bm.width)); - return copy; -} - -test { - // Force comptime to run - _ = map; -} - -test "mono to grayscale" { - const testing = std.testing; - const alloc = testing.allocator; - - var mono_data = [_]u8{0b1010_0101}; - const source: Bitmap = .{ - .rows = 1, - .width = 8, - .pitch = 1, - .buffer = @ptrCast(&mono_data), - .num_grays = 0, - .pixel_mode = freetype.c.FT_PIXEL_MODE_MONO, - .palette_mode = 0, - .palette = null, - }; - - const result = try monoToGrayscale(alloc, source); - defer alloc.free(result.buffer[0..(result.width * result.rows)]); - try testing.expect(result.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY); - try testing.expectEqual(@as(u8, 255), result.buffer[0]); -} From 3793dac3130c0a699702bce4dc69cf003b07d4e2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 14:56:21 -0700 Subject: [PATCH 071/119] build: zig build run only builds xcframework for current arch --- build.zig | 30 +++++++++++++------ src/build/GhosttyXCFramework.zig | 51 ++++++++++++++++++++++---------- src/build/GhosttyXcodebuild.zig | 18 +++++++++++ 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/build.zig b/build.zig index 4bd6e0b46..d72bdc28c 100644 --- a/build.zig +++ b/build.zig @@ -95,7 +95,11 @@ pub fn build(b: *std.Build) !void { if (!config.emit_xcframework) break :none; // Build the xcframework - const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); + const xcframework = try buildpkg.GhosttyXCFramework.init( + b, + &deps, + .universal, + ); xcframework.install(); // The xcframework build always installs resources because our @@ -113,15 +117,23 @@ pub fn build(b: *std.Build) !void { } // Build our macOS app - const app = try buildpkg.GhosttyXcodebuild.init( - b, - &config, - &xcframework, - ); + { + const xcframework_native = try buildpkg.GhosttyXCFramework.init( + b, + &deps, + .native, + ); - // Add a run command that opens our mac app. - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&app.open.step); + const app = try buildpkg.GhosttyXcodebuild.init( + b, + &config, + &xcframework_native, + ); + + // Add a run command that opens our mac app. + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&app.open.step); + } } // Tests diff --git a/src/build/GhosttyXCFramework.zig b/src/build/GhosttyXCFramework.zig index 0dc4f5762..707d5e4e2 100644 --- a/src/build/GhosttyXCFramework.zig +++ b/src/build/GhosttyXCFramework.zig @@ -7,11 +7,23 @@ const GhosttyLib = @import("GhosttyLib.zig"); const XCFrameworkStep = @import("XCFrameworkStep.zig"); xcframework: *XCFrameworkStep, -macos: GhosttyLib, +target: Target, -pub fn init(b: *std.Build, deps: *const SharedDeps) !GhosttyXCFramework { - // Create our universal macOS static library. - const macos = try GhosttyLib.initMacOSUniversal(b, deps); +pub const Target = enum { native, universal }; + +pub fn init( + b: *std.Build, + deps: *const SharedDeps, + target: Target, +) !GhosttyXCFramework { + // Universal macOS build + const macos_universal = try GhosttyLib.initMacOSUniversal(b, deps); + + // Native macOS build + const macos_native = try GhosttyLib.initStatic(b, &try deps.retarget( + b, + Config.genericMacOSTarget(b, null), + )); // iOS const ios = try GhosttyLib.initStatic(b, &try deps.retarget( @@ -47,25 +59,32 @@ pub fn init(b: *std.Build, deps: *const SharedDeps) !GhosttyXCFramework { const xcframework = XCFrameworkStep.create(b, .{ .name = "GhosttyKit", .out_path = "macos/GhosttyKit.xcframework", - .libraries = &.{ - .{ - .library = macos.output, - .headers = b.path("include"), + .libraries = switch (target) { + .universal => &.{ + .{ + .library = macos_universal.output, + .headers = b.path("include"), + }, + .{ + .library = ios.output, + .headers = b.path("include"), + }, + .{ + .library = ios_sim.output, + .headers = b.path("include"), + }, }, - .{ - .library = ios.output, + + .native => &.{.{ + .library = macos_native.output, .headers = b.path("include"), - }, - .{ - .library = ios_sim.output, - .headers = b.path("include"), - }, + }}, }, }); return .{ .xcframework = xcframework, - .macos = macos, + .target = target, }; } diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 83ab0aed3..8ed067b6b 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -1,6 +1,7 @@ const Ghostty = @This(); const std = @import("std"); +const builtin = @import("builtin"); const RunStep = std.Build.Step.Run; const Config = @import("Config.zig"); const XCFramework = @import("GhosttyXCFramework.zig"); @@ -40,6 +41,23 @@ pub fn init( xc_config, }); + switch (xcframework.target) { + // Universal is our default target, so we don't have to + // add anything. + .universal => {}, + + // Native we need to override the architecture in the Xcode + // project with the -arch flag. + .native => build.addArgs(&.{ + "-arch", + switch (builtin.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x86_64", + else => @panic("unsupported macOS arch"), + }, + }), + } + // We need the xcframework build.step.dependOn(xcframework.xcframework.step); From 91ee75ae4d1bd9da3d95a56dfdc080e5e96cd77c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 15:01:14 -0700 Subject: [PATCH 072/119] build: add -Dxcframework-target to specify the target for builds --- build.zig | 2 +- src/build/Config.zig | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index d72bdc28c..668904336 100644 --- a/build.zig +++ b/build.zig @@ -98,7 +98,7 @@ pub fn build(b: *std.Build) !void { const xcframework = try buildpkg.GhosttyXCFramework.init( b, &deps, - .universal, + config.xcframework_target, ); xcframework.install(); diff --git a/src/build/Config.zig b/src/build/Config.zig index 3449a9ce3..e5e090a6d 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -9,6 +9,7 @@ const apprt = @import("../apprt.zig"); const font = @import("../font/main.zig"); const rendererpkg = @import("../renderer.zig"); const Command = @import("../Command.zig"); +const XCFramework = @import("GhosttyXCFramework.zig"); const WasmTarget = @import("../os/wasm/target.zig").Target; const gtk = @import("gtk.zig"); @@ -24,6 +25,7 @@ const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 4 } /// Standard build configuration options. optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget, +xcframework_target: XCFramework.Target = .universal, wasm_target: WasmTarget, /// Comptime interfaces @@ -109,6 +111,14 @@ pub fn init(b: *std.Build) !Config { .env = env, }; + //--------------------------------------------------------------- + // Target-specific properties + config.xcframework_target = b.option( + XCFramework.Target, + "xcframework-target", + "The target for the xcframework.", + ) orelse .universal; + //--------------------------------------------------------------- // Comptime Interfaces config.font_backend = b.option( From 41ff0b440b7742e0b708efd3e88365cf7c520a19 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 4 Jul 2025 16:10:36 -0600 Subject: [PATCH 073/119] fix tests --- src/renderer/cell.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 632eab3fe..1c3c77eac 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -309,7 +309,7 @@ test Contents { // Add some contents. const bg_cell: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -330,7 +330,8 @@ test Contents { // Add a cursor. const cursor_cell: shaderpkg.CellText = .{ - .mode = .cursor, + .atlas = .grayscale, + .bools = .{ .is_cursor_glyph = true }, .grid_pos = .{ 2, 3 }, .color = .{ 0, 0, 0, 1 }, }; @@ -357,7 +358,7 @@ test "Contents clear retains other content" { // bg and fg cells in row 1 const bg_cell_1: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_1: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -366,7 +367,7 @@ test "Contents clear retains other content" { // bg and fg cells in row 2 const bg_cell_2: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_2: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 2 }, .color = .{ 0, 0, 0, 1 }, }; @@ -397,7 +398,7 @@ test "Contents clear last added content" { // bg and fg cells in row 1 const bg_cell_1: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_1: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -406,7 +407,7 @@ test "Contents clear last added content" { // bg and fg cells in row 2 const bg_cell_2: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_2: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 2 }, .color = .{ 0, 0, 0, 1 }, }; From de3e77570e479033853733736a025ae05100f6bd Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Jul 2025 18:26:27 -0400 Subject: [PATCH 074/119] add second cursor list --- src/renderer/cell.zig | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index ef7122699..3e5ae92c0 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -103,11 +103,12 @@ pub const Contents = struct { // form a single grapheme, and multi-substitutions in fonts, the number // of glyphs in a row is theoretically unlimited. // - // We have size.rows + 1 lists because index 0 is used for a special - // list containing the cursor cell which needs to be first in the buffer. + // We have size.rows + 2 lists because indexes 0 and size.rows - 1 are + // used for special lists containing the cursor cell which need to + // be first and last in the buffer, respectively. var fg_rows = try ArrayListCollection(shaderpkg.CellText).init( alloc, - size.rows + 1, + size.rows + 2, size.columns * 3, ); errdefer fg_rows.deinit(alloc); @@ -118,14 +119,19 @@ pub const Contents = struct { self.bg_cells = bg_cells; self.fg_rows = fg_rows; - // We don't need 3*cols worth of cells for the cursor list, so we can - // replace it with a smaller list. This is technically a tiny bit of + // We don't need 3*cols worth of cells for the cursor lists, so we can + // replace them with smaller lists. This is technically a tiny bit of // extra work but resize is not a hot function so it's worth it to not // waste the memory. self.fg_rows.lists[0].deinit(alloc); self.fg_rows.lists[0] = try std.ArrayListUnmanaged( shaderpkg.CellText, ).initCapacity(alloc, 1); + + self.fg_rows.lists[size.rows + 1].deinit(alloc); + self.fg_rows.lists[size.rows + 1] = try std.ArrayListUnmanaged( + shaderpkg.CellText, + ).initCapacity(alloc, 1); } /// Reset the cell contents to an empty state without resizing. @@ -137,6 +143,7 @@ pub const Contents = struct { /// Set the cursor value. If the value is null then the cursor is hidden. pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText) void { self.fg_rows.lists[0].clearRetainingCapacity(); + self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity(); if (v) |cell| { self.fg_rows.lists[0].appendAssumeCapacity(cell); From a9fc3b6fa0f862f00825664c44e6dcd8305e2141 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Jul 2025 18:50:23 -0400 Subject: [PATCH 075/119] enable drawing cursor on top or bottom based on style --- src/renderer/cell.zig | 17 ++++++++++++----- src/renderer/generic.zig | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 3e5ae92c0..821ac1c7c 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -141,12 +141,19 @@ pub const Contents = struct { } /// Set the cursor value. If the value is null then the cursor is hidden. - pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText) void { + pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText, cursor_style: ?renderer.CursorStyle) void { self.fg_rows.lists[0].clearRetainingCapacity(); self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity(); if (v) |cell| { - self.fg_rows.lists[0].appendAssumeCapacity(cell); + if (cursor_style) |style| { + switch (style) { + // Block cursors should be drawn first + .block => self.fg_rows.lists[0].appendAssumeCapacity(cell), + // Other cursor styles should be drawn last + .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell), + } + } } } @@ -374,17 +381,17 @@ test Contents { } } - // Add a cursor. + // Add a block cursor. const cursor_cell: shaderpkg.CellText = .{ .mode = .cursor, .grid_pos = .{ 2, 3 }, .color = .{ 0, 0, 0, 1 }, }; - c.setCursor(cursor_cell); + c.setCursor(cursor_cell, .block); try testing.expectEqual(cursor_cell, c.fg_rows.lists[0].items[0]); // And remove it. - c.setCursor(null); + c.setCursor(null, null); try testing.expectEqual(0, c.fg_rows.lists[0].items.len); } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index e7faf633f..3a65b9ac5 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -2791,7 +2791,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Setup our cursor rendering information. cursor: { // By default, we don't handle cursor inversion on the shader. - self.cells.setCursor(null); + self.cells.setCursor(null, null); self.uniforms.cursor_pos = .{ std.math.maxInt(u16), std.math.maxInt(u16), @@ -3162,7 +3162,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { @intCast(render.glyph.offset_x), @intCast(render.glyph.offset_y), }, - }); + }, cursor_style); } fn addPreeditCell( From 0b4a1e21540b8368507bd0f4e0ef2eb328ce670a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Jul 2025 18:54:55 -0400 Subject: [PATCH 076/119] added test for other cursor style --- src/renderer/cell.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 821ac1c7c..083adee40 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -393,6 +393,10 @@ test Contents { // And remove it. c.setCursor(null, null); try testing.expectEqual(0, c.fg_rows.lists[0].items.len); + + // Add a hollow cursor. + c.setCursor(cursor_cell, .block_hollow); + try testing.expectEqual(cursor_cell, c.fg_rows.lists[rows + 1].items[0]); } test "Contents clear retains other content" { From b8931dd1db69421bd747059457d1de8f9531f346 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Fri, 4 Jul 2025 16:08:28 -0400 Subject: [PATCH 077/119] bash: stop using PS0 for the 'cursor' feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our use of PS0 (which bash runs before command execution) was causing raw command sequences to be printed between multiple commands in a sequence. $ alias garbage='echo start > echo end' $ garbage start �\���dend I wasn't able to definitely track down all of the reasons for why this only happens in the command sequence case, but I suspect it's related to the way that __ghostty_preexec runs from within the bash DEBUG trap (by way of bash-preexec). This problem occurs when PS0 is set to _any_ string (even "") inside of __ghostty_preexec, which also rules out most/any Ghostty-specific code. PS1 and PS2 appear to be safe to (re)set in this context. Fortunately, we can avoid using PS0 entirely by instead printing the cursor reset escape sequence directly from __ghostty_precmd because it also runs just before command execution. --- src/shell-integration/bash/ghostty.bash | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 0766198f9..21a6965ca 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -106,7 +106,6 @@ _ghostty_last_reported_cwd="" function __ghostty_precmd() { local ret="$?" if test "$_ghostty_executing" != "0"; then - _GHOSTTY_SAVE_PS0="$PS0" _GHOSTTY_SAVE_PS1="$PS1" _GHOSTTY_SAVE_PS2="$PS2" @@ -123,8 +122,8 @@ function __ghostty_precmd() { # Cursor if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then - PS1=$PS1'\[\e[5 q\]' - PS0=$PS0'\[\e[0 q\]' + PS1=$PS1'\[\e[5 q\]' # blinking bar for input + builtin printf "\e[0 q" # reset to default cursor fi # Title (working directory) @@ -154,7 +153,6 @@ function __ghostty_precmd() { function __ghostty_preexec() { builtin local cmd="$1" - PS0="$_GHOSTTY_SAVE_PS0" PS1="$_GHOSTTY_SAVE_PS1" PS2="$_GHOSTTY_SAVE_PS2" From 1a4b128af336d497deab540ec976db7f7926064e Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Jul 2025 21:36:17 -0400 Subject: [PATCH 078/119] replace nested if for readability --- src/renderer/cell.zig | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 083adee40..97b24aa90 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -145,15 +145,14 @@ pub const Contents = struct { self.fg_rows.lists[0].clearRetainingCapacity(); self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity(); - if (v) |cell| { - if (cursor_style) |style| { - switch (style) { - // Block cursors should be drawn first - .block => self.fg_rows.lists[0].appendAssumeCapacity(cell), - // Other cursor styles should be drawn last - .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell), - } - } + const cell = v orelse return; + const style = cursor_style orelse return; + + switch (style) { + // Block cursors should be drawn first + .block => self.fg_rows.lists[0].appendAssumeCapacity(cell), + // Other cursor styles should be drawn last + .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell), } } From 5fc0bbc58f68ff4445f9e16335a29add5027f425 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 21:16:23 -0700 Subject: [PATCH 079/119] build: add -Demit-macos-app --- build.zig | 140 +++++++++++++++++++------------- src/build/Config.zig | 15 +++- src/build/GhosttyXcodebuild.zig | 4 +- 3 files changed, 95 insertions(+), 64 deletions(-) diff --git a/build.zig b/build.zig index 668904336..bf7221aad 100644 --- a/build.zig +++ b/build.zig @@ -23,7 +23,22 @@ pub fn build(b: *std.Build) !void { // Ghostty docs const docs = try buildpkg.GhosttyDocs.init(b, &deps); - if (config.emit_docs) docs.install(); + if (config.emit_docs) { + docs.install(); + } else if (config.target.result.os.tag.isDarwin()) { + // If we aren't emitting docs we need to emit a placeholder so + // our macOS xcodeproject builds since it expects the `share/man` + // directory to exist to copy into the app bundle. + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + b.getInstallStep().dependOn(&b.addInstallFile( + wf.add( + path, + "emit-docs not true so no man pages", + ), + path, + ).step); + } // Ghostty webdata const webdata = try buildpkg.GhosttyWebdata.init(b, &deps); @@ -43,15 +58,70 @@ pub fn build(b: *std.Build) !void { check_step.dependOn(dist.install_step); } - // If we're not building libghostty, then install the exe and resources. + // libghostty + const libghostty_shared = try buildpkg.GhosttyLib.initShared( + b, + &deps, + ); + const libghostty_static = try buildpkg.GhosttyLib.initStatic( + b, + &deps, + ); + + // Runtime "none" is libghostty, anything else is an executable. if (config.app_runtime != .none) { exe.install(); resources.install(); i18n.install(); + } else { + // Libghostty + // + // Note: libghostty is not stable for general purpose use. It is used + // heavily by Ghostty on macOS but it isn't built to be reusable yet. + // As such, these build steps are lacking. For example, the Darwin + // build only produces an xcframework. - // Run runs the Ghostty exe. We only do this if we are building - // an apprt. - { + // We shouldn't have this guard but we don't currently + // build on macOS this way ironically so we need to fix that. + if (!config.target.result.os.tag.isDarwin()) { + libghostty_shared.installHeader(); // Only need one header + libghostty_shared.install("libghostty.so"); + libghostty_static.install("libghostty.a"); + } + } + + // macOS only artifacts. These will error if they're initialized for + // other targets. + if (config.target.result.os.tag.isDarwin()) { + // Ghostty xcframework + const xcframework = try buildpkg.GhosttyXCFramework.init( + b, + &deps, + config.xcframework_target, + ); + if (config.emit_xcframework) { + xcframework.install(); + + // The xcframework build always installs resources because our + // macOS xcode project contains references to them. + resources.install(); + i18n.install(); + } + + // Ghostty macOS app + const macos_app = try buildpkg.GhosttyXcodebuild.init( + b, + &config, + &xcframework, + ); + if (config.emit_macos_app) { + macos_app.install(); + } + } + + // Run step + run: { + if (config.app_runtime != .none) { const run_cmd = b.addRunArtifact(exe.exe); if (b.args) |args| run_cmd.addArgs(args); @@ -66,73 +136,27 @@ pub fn build(b: *std.Build) !void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - } - } - - // Libghostty - // - // Note: libghostty is not stable for general purpose use. It is used - // heavily by Ghostty on macOS but it isn't built to be reusable yet. - // As such, these build steps are lacking. For example, the Darwin - // build only produces an xcframework. - if (config.app_runtime == .none) none: { - if (!config.target.result.os.tag.isDarwin()) { - const libghostty_shared = try buildpkg.GhosttyLib.initShared( - b, - &deps, - ); - const libghostty_static = try buildpkg.GhosttyLib.initStatic( - b, - &deps, - ); - libghostty_shared.installHeader(); // Only need one header - libghostty_shared.install("libghostty.so"); - libghostty_static.install("libghostty.a"); - break :none; + break :run; } - assert(config.target.result.os.tag.isDarwin()); - if (!config.emit_xcframework) break :none; + assert(config.app_runtime == .none); - // Build the xcframework - const xcframework = try buildpkg.GhosttyXCFramework.init( - b, - &deps, - config.xcframework_target, - ); - xcframework.install(); - - // The xcframework build always installs resources because our - // macOS xcode project contains references to them. - resources.install(); - i18n.install(); - - // If we aren't emitting docs we need to emit a placeholder so - // our macOS xcodeproject builds. - if (!config.emit_docs) { - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - const placeholder = wf.add(path, "emit-docs not true so no man pages"); - b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); - } - - // Build our macOS app - { + // On macOS we can run the macOS app. For "run" we always force + // a native-only build so that we can run as quickly as possible. + if (config.target.result.os.tag.isDarwin()) { const xcframework_native = try buildpkg.GhosttyXCFramework.init( b, &deps, .native, ); - - const app = try buildpkg.GhosttyXcodebuild.init( + const macos_app_native_only = try buildpkg.GhosttyXcodebuild.init( b, &config, &xcframework_native, ); - // Add a run command that opens our mac app. const run_step = b.step("run", "Run the app"); - run_step.dependOn(&app.open.step); + run_step.dependOn(&macos_app_native_only.open.step); } } diff --git a/src/build/Config.zig b/src/build/Config.zig index e5e090a6d..a9a79fb53 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -50,14 +50,15 @@ patch_rpath: ?[]const u8 = null, /// Artifacts flatpak: bool = false, -emit_test_exe: bool = false, emit_bench: bool = false, -emit_helpgen: bool = false, emit_docs: bool = false, -emit_webdata: bool = false, -emit_xcframework: bool = false, +emit_helpgen: bool = false, +emit_macos_app: bool = false, emit_terminfo: bool = false, emit_termcap: bool = false, +emit_test_exe: bool = false, +emit_xcframework: bool = false, +emit_webdata: bool = false, /// Environmental properties env: std.process.EnvMap, @@ -350,6 +351,12 @@ pub fn init(b: *std.Build) !Config { !config.emit_test_exe and !config.emit_helpgen); + config.emit_macos_app = b.option( + bool, + "emit-macos-app", + "Build and install the macOS app bundle.", + ) orelse config.emit_xcframework; + //--------------------------------------------------------------- // System Packages diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 8ed067b6b..87c060976 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -65,8 +65,8 @@ pub fn init( build.expectExitCode(0); // Capture stdout/stderr so we don't pollute our zig build - _ = build.captureStdOut(); - _ = build.captureStdErr(); + // _ = build.captureStdOut(); + // _ = build.captureStdErr(); break :build build; }; From 8b44d0b3bb5fa47b546dbb1e5d1388e38fb9e982 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 07:22:35 -0700 Subject: [PATCH 080/119] build: make `zig build run` on macOS work with an empty zig-out --- build.zig | 16 ++++++------- src/build/GhosttyDocs.zig | 29 ++++++++++++++++++++++- src/build/GhosttyI18n.zig | 9 +++++++- src/build/GhosttyResources.zig | 9 +++++++- src/build/GhosttyXcodebuild.zig | 41 ++++++++++++++++++++++++--------- 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/build.zig b/build.zig index bf7221aad..a5e5407b6 100644 --- a/build.zig +++ b/build.zig @@ -29,15 +29,7 @@ pub fn build(b: *std.Build) !void { // If we aren't emitting docs we need to emit a placeholder so // our macOS xcodeproject builds since it expects the `share/man` // directory to exist to copy into the app bundle. - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - b.getInstallStep().dependOn(&b.addInstallFile( - wf.add( - path, - "emit-docs not true so no man pages", - ), - path, - ).step); + docs.installDummy(b.getInstallStep()); } // Ghostty webdata @@ -155,6 +147,12 @@ pub fn build(b: *std.Build) !void { &xcframework_native, ); + // The app depends on the resources and i18n to be in the + // install directory too. + resources.addStepDependencies(&macos_app_native_only.build.step); + i18n.addStepDependencies(&macos_app_native_only.build.step); + docs.installDummy(&macos_app_native_only.build.step); + const run_step = b.step("run", "Run the app"); run_step.dependOn(&macos_app_native_only.open.step); } diff --git a/src/build/GhosttyDocs.zig b/src/build/GhosttyDocs.zig index 4b5dbfd92..b95b56f74 100644 --- a/src/build/GhosttyDocs.zig +++ b/src/build/GhosttyDocs.zig @@ -93,5 +93,32 @@ pub fn init( pub fn install(self: *const GhosttyDocs) void { const b = self.steps[0].owner; - for (self.steps) |step| b.getInstallStep().dependOn(step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyDocs, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); +} + +/// Installs some dummy files to satisfy the folder structure of docs +/// without actually generating any documentation. This is useful +/// when the `emit-docs` option is not set to true, but we still +/// need the rough directory structure to exist, such as for the macOS +/// app. +pub fn installDummy(self: *const GhosttyDocs, step: *std.Build.Step) void { + _ = self; + + const b = step.owner; + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + step.dependOn(&b.addInstallFile( + wf.add( + path, + "emit-docs not true so no man pages", + ), + path, + ).step); } diff --git a/src/build/GhosttyI18n.zig b/src/build/GhosttyI18n.zig index e0f6b5611..7667d30c3 100644 --- a/src/build/GhosttyI18n.zig +++ b/src/build/GhosttyI18n.zig @@ -50,7 +50,14 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyI18n { } pub fn install(self: *const GhosttyI18n) void { - for (self.steps) |step| self.owner.getInstallStep().dependOn(step); + self.addStepDependencies(self.owner.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyI18n, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); } fn createUpdateStep(b: *std.Build) !*std.Build.Step { diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig index 34b5e35f8..ef04b21fd 100644 --- a/src/build/GhosttyResources.zig +++ b/src/build/GhosttyResources.zig @@ -397,5 +397,12 @@ fn addLinuxAppResources( pub fn install(self: *const GhosttyResources) void { const b = self.steps[0].owner; - for (self.steps) |step| b.getInstallStep().dependOn(step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyResources, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); } diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 87c060976..38596b474 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -6,8 +6,9 @@ const RunStep = std.Build.Step.Run; const Config = @import("Config.zig"); const XCFramework = @import("GhosttyXCFramework.zig"); -xcodebuild: *std.Build.Step.Run, +build: *std.Build.Step.Run, open: *std.Build.Step.Run, +copy: *std.Build.Step.Run, pub fn init( b: *std.Build, @@ -22,6 +23,8 @@ pub fn init( => "Release", }; + const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config}); + // Our step to build the Ghostty macOS app. const build = build: { // External environment variables can mess up xcodebuild, so @@ -74,13 +77,11 @@ pub fn init( const open = open: { const open = RunStep.create(b, "run Ghostty app"); open.has_side_effects = true; - open.cwd = b.path("macos"); - open.addArgs(&.{ - b.fmt( - "build/{s}/Ghostty.app/Contents/MacOS/ghostty", - .{xc_config}, - ), - }); + open.cwd = b.path(""); + open.addArgs(&.{b.fmt( + "{s}/Contents/MacOS/ghostty", + .{app_path}, + )}); // Open depends on the app open.step.dependOn(&build.step); @@ -105,13 +106,31 @@ pub fn init( break :open open; }; + // Our step to copy the app bundle to the install path. + // We have to use `cp -R` because there are symlinks in the + // bundle. + const copy = copy: { + const step = RunStep.create(b, "copy app bundle"); + step.addArgs(&.{ "cp", "-R" }); + step.addFileArg(b.path(app_path)); + step.addArg(b.fmt("{s}", .{b.install_path})); + step.step.dependOn(&build.step); + break :copy step; + }; + return .{ - .xcodebuild = build, + .build = build, .open = open, + .copy = copy, }; } pub fn install(self: *const Ghostty) void { - const b = self.xcodebuild.step.owner; - b.getInstallStep().dependOn(&self.xcodebuild.step); + const b = self.copy.step.owner; + b.getInstallStep().dependOn(&self.copy.step); +} + +pub fn installXcframework(self: *const Ghostty) void { + const b = self.build.step.owner; + b.getInstallStep().dependOn(&self.build.step); } From 8653229607572c2e591f8d4e44c1bc4f853ce456 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 07:26:00 -0700 Subject: [PATCH 081/119] ci: don't use the new macOS app emit yet --- .github/workflows/test.yml | 14 +++++++------- src/build/GhosttyXcodebuild.zig | 3 --- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1894e81fe..b34327f7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -294,7 +294,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. - name: Build GhosttyKit - run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} + run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false # The native app is built with native Xcode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -334,7 +334,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. - name: Build GhosttyKit - run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} + run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false # The native app is built with native Xcode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -381,11 +381,11 @@ jobs: - name: Build All run: | - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_harfbuzz - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_noshape + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_harfbuzz + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_noshape build-snap: strategy: diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 38596b474..b340619e8 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -67,9 +67,6 @@ pub fn init( // Expect success build.expectExitCode(0); - // Capture stdout/stderr so we don't pollute our zig build - // _ = build.captureStdOut(); - // _ = build.captureStdErr(); break :build build; }; From 0929f39e890438126b599a9937d1222b31a906f6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 13:36:26 -0700 Subject: [PATCH 082/119] build: xcodebuild properly sets up resources --- build.zig | 20 ++++++++++++-------- src/build/GhosttyXCFramework.zig | 9 ++++++++- src/build/GhosttyXcodebuild.zig | 22 +++++++++++++++++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/build.zig b/build.zig index a5e5407b6..024e2db61 100644 --- a/build.zig +++ b/build.zig @@ -104,7 +104,12 @@ pub fn build(b: *std.Build) !void { const macos_app = try buildpkg.GhosttyXcodebuild.init( b, &config, - &xcframework, + .{ + .xcframework = &xcframework, + .docs = &docs, + .i18n = &i18n, + .resources = &resources, + }, ); if (config.emit_macos_app) { macos_app.install(); @@ -144,15 +149,14 @@ pub fn build(b: *std.Build) !void { const macos_app_native_only = try buildpkg.GhosttyXcodebuild.init( b, &config, - &xcframework_native, + .{ + .xcframework = &xcframework_native, + .docs = &docs, + .i18n = &i18n, + .resources = &resources, + }, ); - // The app depends on the resources and i18n to be in the - // install directory too. - resources.addStepDependencies(&macos_app_native_only.build.step); - i18n.addStepDependencies(&macos_app_native_only.build.step); - docs.installDummy(&macos_app_native_only.build.step); - const run_step = b.step("run", "Run the app"); run_step.dependOn(&macos_app_native_only.open.step); } diff --git a/src/build/GhosttyXCFramework.zig b/src/build/GhosttyXCFramework.zig index 707d5e4e2..7debd6906 100644 --- a/src/build/GhosttyXCFramework.zig +++ b/src/build/GhosttyXCFramework.zig @@ -90,5 +90,12 @@ pub fn init( pub fn install(self: *const GhosttyXCFramework) void { const b = self.xcframework.step.owner; - b.getInstallStep().dependOn(self.xcframework.step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyXCFramework, + other_step: *std.Build.Step, +) void { + other_step.dependOn(self.xcframework.step); } diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index b340619e8..9b472eda8 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -4,16 +4,26 @@ const std = @import("std"); const builtin = @import("builtin"); const RunStep = std.Build.Step.Run; const Config = @import("Config.zig"); +const Docs = @import("GhosttyDocs.zig"); +const I18n = @import("GhosttyI18n.zig"); +const Resources = @import("GhosttyResources.zig"); const XCFramework = @import("GhosttyXCFramework.zig"); build: *std.Build.Step.Run, open: *std.Build.Step.Run, copy: *std.Build.Step.Run, +pub const Deps = struct { + xcframework: *const XCFramework, + docs: *const Docs, + i18n: *const I18n, + resources: *const Resources, +}; + pub fn init( b: *std.Build, config: *const Config, - xcframework: *const XCFramework, + deps: Deps, ) !Ghostty { const xc_config = switch (config.optimize) { .Debug => "Debug", @@ -44,7 +54,7 @@ pub fn init( xc_config, }); - switch (xcframework.target) { + switch (deps.xcframework.target) { // Universal is our default target, so we don't have to // add anything. .universal => {}, @@ -62,7 +72,13 @@ pub fn init( } // We need the xcframework - build.step.dependOn(xcframework.xcframework.step); + deps.xcframework.addStepDependencies(&build.step); + + // We also need all these resources because the xcode project + // references them via symlinks. + deps.resources.addStepDependencies(&build.step); + deps.i18n.addStepDependencies(&build.step); + deps.docs.installDummy(&build.step); // Expect success build.expectExitCode(0); From 28b54fe22b6b992186b39b31ddcfc5cff25c5319 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 14:09:39 -0700 Subject: [PATCH 083/119] build: fix JBM zip to tar.gz --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 38beb70fa..e39d4e589 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -102,8 +102,8 @@ // Fonts .jetbrains_mono = .{ - .url = "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", - .hash = "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6", + .url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + .hash = "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm", }, .nerd_fonts_symbols_only = .{ .url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", diff --git a/build.zig.zon.json b/build.zig.zon.json index 4d60be2c7..703cfb57d 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -59,10 +59,10 @@ "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz", "hash": "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4=" }, - "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6": { + "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm": { "name": "jetbrains_mono", - "url": "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", - "hash": "sha256-b2N2xu0pYOqKljzXOH7J124/YpElvDPR/c1+twEve78=" + "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + "hash": "sha256-C9T2ZxmKKzRIafWQzSzj5FOoKglc8cAQ2+vhTiWg3Qs=" }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index eaf15bbb0..37c6c92fa 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -178,11 +178,11 @@ in }; } { - name = "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6"; + name = "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm"; path = fetchZigArtifact { name = "jetbrains_mono"; - url = "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip"; - hash = "sha256-b2N2xu0pYOqKljzXOH7J124/YpElvDPR/c1+twEve78="; + url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz"; + hash = "sha256-C9T2ZxmKKzRIafWQzSzj5FOoKglc8cAQ2+vhTiWg3Qs="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 738ab4ccc..449a9f310 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -2,6 +2,7 @@ git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23 https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz +https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz @@ -25,7 +26,6 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6 https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz -https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index dcc75776a..109389aed 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -73,9 +73,9 @@ }, { "type": "archive", - "url": "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", - "dest": "vendor/p/N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6", - "sha256": "6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf" + "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + "dest": "vendor/p/N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm", + "sha256": "0bd4f667198a2b344869f590cd2ce3e453a82a095cf1c010dbebe14e25a0dd0b" }, { "type": "archive", From d772b2ce39fe9e4d6364515ad6dd567130483af0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 14:14:09 -0700 Subject: [PATCH 084/119] ci: fix release workflows to not build macos app --- .github/workflows/release-pr.yml | 4 ++-- .github/workflows/release-tag.yml | 1 + .github/workflows/release-tip.yml | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index a1cc2af19..37d5ba79b 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -86,7 +86,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseSafe + run: nix develop -c zig build -Doptimize=ReleaseSafe -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -238,7 +238,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=Debug + run: nix develop -c zig build -Doptimize=Debug -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 3deafd066..33cf9f3a8 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -158,6 +158,7 @@ jobs: nix develop -c \ zig build \ -Doptimize=ReleaseFast \ + -Demit-macos-app=false \ -Dversion-string=${GHOSTTY_VERSION} # The native app is built with native XCode tooling. This also does diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 2a3277ea6..4d009ab7b 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -196,7 +196,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseFast + run: nix develop -c zig build -Doptimize=ReleaseFast -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -411,7 +411,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=Debug + run: nix develop -c zig build -Doptimize=Debug -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -586,7 +586,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseSafe + run: nix develop -c zig build -Doptimize=ReleaseSafe -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. From 43404bf4dc4ffc2e047307545bb3f7a0a422a706 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 14:20:45 -0700 Subject: [PATCH 085/119] build: try again, also do symbol packages --- build.zig.zon | 2 +- build.zig.zon.json | 4 ++-- build.zig.zon.nix | 4 ++-- flatpak/zig-packages.json | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index e39d4e589..047f23149 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -103,7 +103,7 @@ // Fonts .jetbrains_mono = .{ .url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", - .hash = "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm", + .hash = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x", }, .nerd_fonts_symbols_only = .{ .url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", diff --git a/build.zig.zon.json b/build.zig.zon.json index 703cfb57d..b14602e42 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -59,10 +59,10 @@ "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz", "hash": "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4=" }, - "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm": { + "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": { "name": "jetbrains_mono", "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", - "hash": "sha256-C9T2ZxmKKzRIafWQzSzj5FOoKglc8cAQ2+vhTiWg3Qs=" + "hash": "sha256-xXppHouCrQmLWWPzlZAy5AOPORCHr3cViFulkEYQXMQ=" }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 37c6c92fa..952c1c8ec 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -178,11 +178,11 @@ in }; } { - name = "N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm"; + name = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x"; path = fetchZigArtifact { name = "jetbrains_mono"; url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz"; - hash = "sha256-C9T2ZxmKKzRIafWQzSzj5FOoKglc8cAQ2+vhTiWg3Qs="; + hash = "sha256-xXppHouCrQmLWWPzlZAy5AOPORCHr3cViFulkEYQXMQ="; }; } { diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 109389aed..5bb24a9ac 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -74,8 +74,8 @@ { "type": "archive", "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", - "dest": "vendor/p/N-V-__8AAGnElwD7frqELjgfXcwExs4UZuMDQQA_iXEe4Fsm", - "sha256": "0bd4f667198a2b344869f590cd2ce3e453a82a095cf1c010dbebe14e25a0dd0b" + "dest": "vendor/p/N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x", + "sha256": "c57a691e8b82ad098b5963f3959032e4038f391087af7715885ba59046105cc4" }, { "type": "archive", From 9ff77a56226ad482ad4740726c8a85ca834d37fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 14:22:23 -0700 Subject: [PATCH 086/119] build: switch symbols over --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 047f23149..6254745da 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -106,8 +106,8 @@ .hash = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x", }, .nerd_fonts_symbols_only = .{ - .url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", - .hash = "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI", + .url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s", }, // Other diff --git a/build.zig.zon.json b/build.zig.zon.json index b14602e42..36e547b29 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -79,10 +79,10 @@ "url": "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz", "hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU=" }, - "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI": { + "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s": { "name": "nerd_fonts_symbols_only", - "url": "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", - "hash": "sha256-f4wJDaOw6qcQhka/NMu7btE9U1inJGBSIQiwbH7NcWo=" + "url": "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + "hash": "sha256-EWTRuVbUveJI17LwmYxDzJT1ICQxoVZKeTiVsec7DQQ=" }, "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c": { "name": "oniguruma", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 952c1c8ec..f3d0e7a5d 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -210,11 +210,11 @@ in }; } { - name = "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI"; + name = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s"; path = fetchZigArtifact { name = "nerd_fonts_symbols_only"; - url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz"; - hash = "sha256-f4wJDaOw6qcQhka/NMu7btE9U1inJGBSIQiwbH7NcWo="; + url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz"; + hash = "sha256-EWTRuVbUveJI17LwmYxDzJT1ICQxoVZKeTiVsec7DQQ="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 449a9f310..503328fae 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -3,6 +3,7 @@ git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23 https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz +https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz @@ -32,5 +33,4 @@ https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025c https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz -https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 5bb24a9ac..502a1d35f 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -97,9 +97,9 @@ }, { "type": "archive", - "url": "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", - "dest": "vendor/p/N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI", - "sha256": "7f8c090da3b0eaa7108646bf34cbbb6ed13d5358a72460522108b06c7ecd716a" + "url": "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + "dest": "vendor/p/N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s", + "sha256": "1164d1b956d4bde248d7b2f0998c43cc94f5202431a1564a793895b1e73b0d04" }, { "type": "archive", From 4b6c461d6fc47699e50a56f2612a8b0a98634bf2 Mon Sep 17 00:00:00 2001 From: trag1c Date: Sun, 6 Jul 2025 01:59:06 +0200 Subject: [PATCH 087/119] refactor nerd font codegen script --- src/font/nerd_font_attributes.zig | 2 +- src/font/nerd_font_codegen.py | 226 +++++++++++++++--------------- 2 files changed, 115 insertions(+), 113 deletions(-) diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index 1465a8466..dfb11c5a5 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -1,4 +1,4 @@ -//! This is a generate file, produced by nerd_font_codegen.py +//! This is a generated file, produced by nerd_font_codegen.py //! DO NOT EDIT BY HAND! //! //! This file provides info extracted from the nerd fonts patcher script, diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index c2dd7314f..99915c9f2 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -1,102 +1,122 @@ """ -This file is mostly vibe coded because I don't like Python. It extracts the -patch sets from the nerd fonts font patcher file in order to extract scaling -rules and attributes for different codepoint ranges which it then codegens -in to a Zig file with a function that switches over codepoints and returns -the attributes and scaling rules. +This file extracts the patch sets from the nerd fonts font patcher file in order to +extract scaling rules and attributes for different codepoint ranges which it then +codegens in to a Zig file with a function that switches over codepoints and returns the +attributes and scaling rules. -This does include an `eval` call! This is spooky, but we trust -the nerd fonts code to be safe and not malicious or anything. +This does include an `eval` call! This is spooky, but we trust the nerd fonts code to +be safe and not malicious or anything. """ import ast import math -from pathlib import Path from collections import defaultdict +from contextlib import suppress +from pathlib import Path +from types import SimpleNamespace +from typing import Literal, TypedDict, cast + +type PatchSetAttributes = dict[Literal["default"] | int, PatchSetAttributeEntry] +type AttributeHash = tuple[str | None, str | None, str, float, float, float] +type ResolvedSymbol = PatchSetAttributes | PatchSetScaleRules | int | None + + +class PatchSetScaleRules(TypedDict): + ShiftMode: str + ScaleGroups: list[list[int] | range] + + +class PatchSetAttributeEntry(TypedDict): + align: str + valign: str + stretch: str + params: dict[str, float | bool] + + +class PatchSet(TypedDict): + SymStart: int + SymEnd: int + SrcStart: int | None + ScaleRules: PatchSetScaleRules | None + Attributes: PatchSetAttributes class PatchSetExtractor(ast.NodeVisitor): - def __init__(self): - self.symbol_table = {} - self.patch_set_values = [] + def __init__(self) -> None: + self.symbol_table: dict[str, ast.expr] = {} + self.patch_set_values: list[PatchSet] = [] - def visit_ClassDef(self, node): - if node.name == "font_patcher": - for item in node.body: - if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set": - self.visit_setup_patch_set(item) + def visit_ClassDef(self, node: ast.ClassDef) -> None: + if node.name != "font_patcher": + return + for item in node.body: + if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set": + self.visit_setup_patch_set(item) - def visit_setup_patch_set(self, node): + def visit_setup_patch_set(self, node: ast.FunctionDef) -> None: # First pass: gather variable assignments for stmt in node.body: - if isinstance(stmt, ast.Assign): - # Store simple variable assignments in the symbol table - if len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name): - var_name = stmt.targets[0].id - self.symbol_table[var_name] = stmt.value + match stmt: + case ast.Assign(targets=[ast.Name(id=symbol)]): + # Store simple variable assignments in the symbol table + self.symbol_table[symbol] = stmt.value # Second pass: process self.patch_set for stmt in node.body: - if isinstance(stmt, ast.Assign): - for target in stmt.targets: - if isinstance(target, ast.Attribute) and target.attr == "patch_set": - if isinstance(stmt.value, ast.List): - for elt in stmt.value.elts: - if isinstance(elt, ast.Dict): - self.process_patch_entry(elt) + if not isinstance(stmt, ast.Assign): + continue + for target in stmt.targets: + if ( + isinstance(target, ast.Attribute) + and target.attr == "patch_set" + and isinstance(stmt.value, ast.List) + ): + for elt in stmt.value.elts: + if isinstance(elt, ast.Dict): + self.process_patch_entry(elt) - def resolve_symbol(self, node): + def resolve_symbol(self, node: ast.expr) -> ResolvedSymbol: """Resolve named variables to their actual values from the symbol table.""" if isinstance(node, ast.Name) and node.id in self.symbol_table: return self.safe_literal_eval(self.symbol_table[node.id]) return self.safe_literal_eval(node) - def safe_literal_eval(self, node): + def safe_literal_eval(self, node: ast.expr) -> ResolvedSymbol: """Try to evaluate or stringify an AST node.""" try: return ast.literal_eval(node) - except Exception: + except ValueError: # Spooky eval! But we trust nerd fonts to be safe... if hasattr(ast, "unparse"): return eval( - ast.unparse(node), {"box_keep": True}, {"self": SpoofSelf()} + ast.unparse(node), + {"box_keep": True}, + {"self": SimpleNamespace(args=SimpleNamespace(careful=True))}, ) - else: - return f"" + msg = f"" + raise ValueError(msg) from None - def process_patch_entry(self, dict_node): + def process_patch_entry(self, dict_node: ast.Dict) -> None: entry = {} + disallowed_key_nodes = frozenset({"Enabled", "Name", "Filename", "Exact"}) for key_node, value_node in zip(dict_node.keys, dict_node.values): - if isinstance(key_node, ast.Constant) and key_node.value in ( - "Enabled", - "Name", - "Filename", - "Exact", + if ( + isinstance(key_node, ast.Constant) + and key_node.value not in disallowed_key_nodes ): - continue - key = ast.literal_eval(key_node) - value = self.resolve_symbol(value_node) - entry[key] = value - self.patch_set_values.append(entry) + key = ast.literal_eval(cast("ast.Constant", key_node)) + entry[key] = self.resolve_symbol(value_node) + self.patch_set_values.append(cast("PatchSet", entry)) -def extract_patch_set_values(source_code): +def extract_patch_set_values(source_code: str) -> list[PatchSet]: tree = ast.parse(source_code) extractor = PatchSetExtractor() extractor.visit(tree) return extractor.patch_set_values -# We have to spoof `self` and `self.args` for the eval. -class SpoofArgs: - careful = True - - -class SpoofSelf: - args = SpoofArgs() - - -def parse_alignment(val): +def parse_alignment(val: str) -> str | None: return { "l": ".start", "r": ".end", @@ -105,28 +125,24 @@ def parse_alignment(val): }.get(val, ".none") -def get_param(d, key, default): - return float(d.get(key, default)) - - -def attr_key(attr): +def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash: """Convert attributes to a hashable key for grouping.""" - stretch = attr.get("stretch", "") + params = attr.get("params", {}) return ( parse_alignment(attr.get("align", "")), parse_alignment(attr.get("valign", "")), - stretch, - float(attr.get("params", {}).get("overlap", 0.0)), - float(attr.get("params", {}).get("xy-ratio", -1.0)), - float(attr.get("params", {}).get("ypadding", 0.0)), + attr.get("stretch", ""), + float(params.get("overlap", 0.0)), + float(params.get("xy-ratio", -1.0)), + float(params.get("ypadding", 0.0)), ) -def coalesce_codepoints_to_ranges(codepoints): +def coalesce_codepoints_to_ranges(codepoints: list[int]) -> list[tuple[int, int]]: """Convert a sorted list of integers to a list of single values and ranges.""" - ranges = [] + ranges: list[tuple[int, int]] = [] cp_iter = iter(sorted(codepoints)) - try: + with suppress(StopIteration): start = prev = next(cp_iter) for cp in cp_iter: if cp == prev + 1: @@ -135,52 +151,49 @@ def coalesce_codepoints_to_ranges(codepoints): ranges.append((start, prev)) start = prev = cp ranges.append((start, prev)) - except StopIteration: - pass return ranges -def emit_zig_entry_multikey(codepoints, attr): +def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) -> str: align = parse_alignment(attr.get("align", "")) valign = parse_alignment(attr.get("valign", "")) stretch = attr.get("stretch", "") params = attr.get("params", {}) - overlap = get_param(params, "overlap", 0.0) - xy_ratio = get_param(params, "xy-ratio", -1.0) - y_padding = get_param(params, "ypadding", 0.0) + overlap = params.get("overlap", 0.0) + xy_ratio = params.get("xy-ratio", -1.0) + y_padding = params.get("ypadding", 0.0) ranges = coalesce_codepoints_to_ranges(codepoints) keys = "\n".join( - f" 0x{start:x}...0x{end:x}," if start != end else f" 0x{start:x}," + f" {start:#x}...{end:#x}," if start != end else f" {start:#x}," for start, end in ranges ) - s = f"""{keys} - => .{{\n""" + s = f"{keys}\n => .{{\n" # These translations don't quite capture the way # the actual patcher does scaling, but they're a # good enough compromise. - if ("xy" in stretch): + if "xy" in stretch: s += " .size_horizontal = .stretch,\n" s += " .size_vertical = .stretch,\n" - elif ("!" in stretch): + elif "!" in stretch: s += " .size_horizontal = .cover,\n" s += " .size_vertical = .fit,\n" - elif ("^" in stretch): + elif "^" in stretch: s += " .size_horizontal = .cover,\n" s += " .size_vertical = .cover,\n" else: s += " .size_horizontal = .fit,\n" s += " .size_vertical = .fit,\n" - if (align is not None): + if align is not None: s += f" .align_horizontal = {align},\n" - if (valign is not None): + if valign is not None: s += f" .align_vertical = {valign},\n" - if (overlap != 0.0): + if overlap: pad = -overlap s += f" .pad_left = {pad},\n" s += f" .pad_right = {pad},\n" @@ -188,35 +201,33 @@ def emit_zig_entry_multikey(codepoints, attr): s += f" .pad_top = {v_pad},\n" s += f" .pad_bottom = {v_pad},\n" - if (xy_ratio > 0): + if xy_ratio > 0: s += f" .max_xy_ratio = {xy_ratio},\n" s += " }," - return s -def generate_zig_switch_arms(patch_set): - entries = {} - for entry in patch_set: + +def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: + entries: dict[int, PatchSetAttributeEntry] = {} + for entry in patch_sets: attributes = entry["Attributes"] for cp in range(entry["SymStart"], entry["SymEnd"] + 1): entries[cp] = attributes["default"] - for k, v in attributes.items(): - if isinstance(k, int): - entries[k] = v + entries |= {k: v for k, v in attributes.items() if isinstance(k, int)} del entries[0] # Group codepoints by attribute key - grouped = defaultdict(list) + grouped = defaultdict[AttributeHash, list[int]](list) for cp, attr in entries.items(): grouped[attr_key(attr)].append(cp) # Emit zig switch arms - result = [] - for _, codepoints in sorted(grouped.items(), key=lambda x: x[1]): + result: list[str] = [] + for codepoints in sorted(grouped.values()): # Use one of the attrs in the group to emit the value attr = entries[codepoints[0]] result.append(emit_zig_entry_multikey(codepoints, attr)) @@ -225,23 +236,16 @@ def generate_zig_switch_arms(patch_set): if __name__ == "__main__": - path = ( - Path(__file__).resolve().parent - / ".." - / ".." - / "vendor" - / "nerd-fonts" - / "font-patcher.py" - ) - with open(path, "r", encoding="utf-8") as f: - source = f.read() + project_root = Path(__file__).resolve().parents[2] + patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py" + source = patcher_path.read_text(encoding="utf-8") patch_set = extract_patch_set_values(source) - out_path = Path(__file__).resolve().parent / "nerd_font_attributes.zig" + out_path = project_root / "src" / "font" / "nerd_font_attributes.zig" - with open(out_path, "w", encoding="utf-8") as f: - f.write("""//! This is a generate file, produced by nerd_font_codegen.py + with out_path.open("w", encoding="utf-8") as f: + f.write("""//! This is a generated file, produced by nerd_font_codegen.py //! DO NOT EDIT BY HAND! //! //! This file provides info extracted from the nerd fonts patcher script, @@ -254,6 +258,4 @@ pub fn getConstraint(cp: u21) Constraint { return switch (cp) { """) f.write(generate_zig_switch_arms(patch_set)) - f.write("\n") - - f.write(" else => .none,\n };\n}\n") + f.write("\n else => .none,\n };\n}\n") From 2fca0477bc7f3c955daf40a0d4663d63ef3d76a1 Mon Sep 17 00:00:00 2001 From: trag1c Date: Sun, 6 Jul 2025 02:28:28 +0200 Subject: [PATCH 088/119] rely on stdin/stdout instead of hardcoded paths --- src/font/nerd_font_codegen.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index 99915c9f2..52d70ac01 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -10,9 +10,9 @@ be safe and not malicious or anything. import ast import math +import sys from collections import defaultdict from contextlib import suppress -from pathlib import Path from types import SimpleNamespace from typing import Literal, TypedDict, cast @@ -236,16 +236,9 @@ def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: if __name__ == "__main__": - project_root = Path(__file__).resolve().parents[2] - - patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py" - source = patcher_path.read_text(encoding="utf-8") + source = sys.stdin.read() patch_set = extract_patch_set_values(source) - - out_path = project_root / "src" / "font" / "nerd_font_attributes.zig" - - with out_path.open("w", encoding="utf-8") as f: - f.write("""//! This is a generated file, produced by nerd_font_codegen.py + print("""//! This is a generated file, produced by nerd_font_codegen.py //! DO NOT EDIT BY HAND! //! //! This file provides info extracted from the nerd fonts patcher script, @@ -255,7 +248,6 @@ const Constraint = @import("face.zig").RenderOptions.Constraint; /// Get the a constraints for the provided codepoint. pub fn getConstraint(cp: u21) Constraint { - return switch (cp) { -""") - f.write(generate_zig_switch_arms(patch_set)) - f.write("\n else => .none,\n };\n}\n") + return switch (cp) {""") + print(generate_zig_switch_arms(patch_set)) + print(" else => .none,\n };\n}") From fff16bff6928e14a6e5f342f5578f73b0b998733 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sat, 5 Jul 2025 20:36:35 -0600 Subject: [PATCH 089/119] font/coretext: fix bitmap size calculation, prevent clipping Previously, many glyphs were having their top and right row/column of pixels clipped off due to not accounting for the slight bearing in the width and height calculation here. --- src/font/face/coretext.zig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 35f094848..89d771d95 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -356,8 +356,14 @@ pub const Face = struct { const x = glyph_size.x; const y = glyph_size.y; - const px_width: u32 = @intFromFloat(@ceil(width)); - const px_height: u32 = @intFromFloat(@ceil(height)); + // We have to include the fractional pixels that we won't be offsetting + // in our width and height calculations, that is, we offset by the floor + // of the bearings when we render the glyph, meaning there's still a bit + // of extra width to the area that's drawn in beyond just the width of + // the glyph itself, so we include that extra fraction of a pixel when + // calculating the width and height here. + const px_width: u32 = @intFromFloat(@ceil(width + rect.origin.x - @floor(rect.origin.x))); + const px_height: u32 = @intFromFloat(@ceil(height + rect.origin.y - @floor(rect.origin.y))); // Settings that are specific to if we are rendering text or emoji. const color: struct { From 02d82720d26b222ad0c768d95395d7f4bea864c6 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sat, 5 Jul 2025 20:40:12 -0600 Subject: [PATCH 090/119] font/freetype: fix negated force-autohint flag The behavior of this flag was the opposite of its description in the docs- luckily, at the same time, the default (true) was the opposite from what the default actually is in freetype, so users who haven't explicitly set this flag won't see a behavior difference from this. --- src/config/Config.zig | 4 ++-- src/font/face/freetype.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 653ce4178..2910372f3 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -435,7 +435,7 @@ pub const compatibility = std.StaticStringMap( /// * `hinting` - Enable or disable hinting. Enabled by default. /// /// * `force-autohint` - Always use the freetype auto-hinter instead of -/// the font's native hinter. Enabled by default. +/// the font's native hinter. Disabled by default. /// /// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering. /// This will disable anti-aliasing, and probably not look very good unless @@ -7084,7 +7084,7 @@ pub const FreetypeLoadFlags = packed struct { // for Freetype itself. Ghostty hasn't made any opinionated changes // to these defaults. hinting: bool = true, - @"force-autohint": bool = true, + @"force-autohint": bool = false, monochrome: bool = false, autohint: bool = true, }; diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index c23ede04a..585d21c5b 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -348,7 +348,7 @@ pub const Face = struct { // use options from config .no_hinting = !do_hinting, - .force_autohint = !self.load_flags.@"force-autohint", + .force_autohint = self.load_flags.@"force-autohint", .no_autohint = !self.load_flags.autohint, // NO_SVG set to true because we don't currently support rendering From 8c3caee15cce43c8d671a8c1f63e99816a92cc92 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 19:54:01 -0700 Subject: [PATCH 091/119] macOS: zig build run disables window saved state --- macos/Sources/App/macOS/AppDelegate.swift | 5 ++--- macos/Sources/Ghostty/Package.swift | 20 ++++++++++++++++++++ src/build/GhosttyXcodebuild.zig | 20 ++++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index efc09ede9..53b6dce88 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -256,9 +256,8 @@ class AppDelegate: NSObject, // Setup signal handlers setupSignals() - // This is a hack used by our build scripts, specifically `zig build run`, - // to force our app to the foreground. - if ProcessInfo.processInfo.environment["GHOSTTY_MAC_ACTIVATE"] == "1" { + // If we launched via zig run then we need to force foreground. + if Ghostty.launchSource == .zig_run { // This never gets called until we click the dock icon. This forces it // activate immediately. applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification)) diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 125a09825..e96f555d3 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -48,6 +48,26 @@ extension Ghostty { } } +// MARK: General Helpers + +extension Ghostty { + enum LaunchSource: String { + case cli + case app + case zig_run + } + + /// Returns the mechanism that launched the app. This is based on an env var so + /// its up to the env var being set in the correct circumstance. + static var launchSource: LaunchSource { + guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else { + return .app + } + + return LaunchSource(rawValue: envValue) ?? .app + } +} + // MARK: Swift Types for C Types extension Ghostty { diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 9b472eda8..052c9f3e4 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -88,6 +88,19 @@ pub fn init( // Our step to open the resulting Ghostty app. const open = open: { + const disable_save_state = RunStep.create(b, "disable save state"); + disable_save_state.has_side_effects = true; + disable_save_state.addArgs(&.{ + "/usr/libexec/PlistBuddy", + "-c", + // We'll have to change this to `Set` if we ever put this + // into our Info.plist. + "Add :NSQuitAlwaysKeepsWindows bool false", + b.fmt("{s}/Contents/Info.plist", .{app_path}), + }); + disable_save_state.expectExitCode(0); + disable_save_state.step.dependOn(&build.step); + const open = RunStep.create(b, "run Ghostty app"); open.has_side_effects = true; open.cwd = b.path(""); @@ -98,15 +111,14 @@ pub fn init( // Open depends on the app open.step.dependOn(&build.step); + open.step.dependOn(&disable_save_state.step); // This overrides our default behavior and forces logs to show // up on stderr (in addition to the centralized macOS log). open.setEnvironmentVariable("GHOSTTY_LOG", "1"); - // This is hack so that we can activate the app and bring it to - // the front forcibly even though we're executing directly - // via the binary and not launch services. - open.setEnvironmentVariable("GHOSTTY_MAC_ACTIVATE", "1"); + // Configure how we're launching + open.setEnvironmentVariable("GHOSTTY_MAC_LAUNCH_SOURCE", "zig_run"); if (b.args) |args| { open.addArgs(args); From 87f35bd1c12dd8b50221163f9fc10d17d09eb32a Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sat, 5 Jul 2025 20:58:15 -0600 Subject: [PATCH 092/119] renderer/opengl: explicit texture options This sets up for a couple improvments (see TODO comments) and also sets the glyph atlas textures to nearest neighbor sampling since we can do that now that we never scale glyphs. --- pkg/opengl/Texture.zig | 24 ++++++++++++++++++++++++ src/renderer/OpenGL.zig | 18 ++++++++++++++++++ src/renderer/opengl/Texture.zig | 12 ++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/pkg/opengl/Texture.zig b/pkg/opengl/Texture.zig index 2c8e05eff..03e794855 100644 --- a/pkg/opengl/Texture.zig +++ b/pkg/opengl/Texture.zig @@ -92,6 +92,30 @@ pub const Format = enum(c_uint) { _, }; +/// Minification filter for textures. +pub const MinFilter = enum(c_int) { + nearest = c.GL_NEAREST, + linear = c.GL_LINEAR, + nearest_mipmap_nearest = c.GL_NEAREST_MIPMAP_NEAREST, + linear_mipmap_nearest = c.GL_LINEAR_MIPMAP_NEAREST, + nearest_mipmap_linear = c.GL_NEAREST_MIPMAP_LINEAR, + linear_mipmap_linear = c.GL_LINEAR_MIPMAP_LINEAR, +}; + +/// Magnification filter for textures. +pub const MagFilter = enum(c_int) { + nearest = c.GL_NEAREST, + linear = c.GL_LINEAR, +}; + +/// Texture coordinate wrapping mode. +pub const Wrap = enum(c_int) { + clamp_to_edge = c.GL_CLAMP_TO_EDGE, + clamp_to_border = c.GL_CLAMP_TO_BORDER, + mirrored_repeat = c.GL_MIRRORED_REPEAT, + repeat = c.GL_REPEAT, +}; + /// Data type for texture images. pub const DataType = enum(c_uint) { UnsignedByte = c.GL_UNSIGNED_BYTE, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 00df8e273..882d6fc03 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -356,6 +356,10 @@ pub inline fn textureOptions(self: OpenGL) Texture.Options { .format = .rgba, .internal_format = .srgba, .target = .@"2D", + .min_filter = .linear, + .mag_filter = .linear, + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }; } @@ -388,6 +392,16 @@ pub inline fn imageTextureOptions( .format = format.toPixelFormat(), .internal_format = if (srgb) .srgba else .rgba, .target = .@"2D", + // TODO: Generate mipmaps for image textures and use + // linear_mipmap_linear filtering so that they + // look good even when scaled way down. + .min_filter = .linear, + .mag_filter = .linear, + // TODO: Separate out background image options, use + // repeating coordinate modes so we don't have + // to do the modulus in the shader. + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }; } @@ -409,6 +423,10 @@ pub fn initAtlasTexture( .format = format, .internal_format = internal_format, .target = .Rectangle, + .min_filter = .nearest, + .mag_filter = .nearest, + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }, atlas.size, atlas.size, diff --git a/src/renderer/opengl/Texture.zig b/src/renderer/opengl/Texture.zig index 9be2b7078..2f3e7f46a 100644 --- a/src/renderer/opengl/Texture.zig +++ b/src/renderer/opengl/Texture.zig @@ -16,6 +16,10 @@ pub const Options = struct { format: gl.Texture.Format, internal_format: gl.Texture.InternalFormat, target: gl.Texture.Target, + min_filter: gl.Texture.MinFilter, + mag_filter: gl.Texture.MagFilter, + wrap_s: gl.Texture.Wrap, + wrap_t: gl.Texture.Wrap, }; texture: gl.Texture, @@ -48,10 +52,10 @@ pub fn init( { const texbind = tex.bind(opts.target) catch return error.OpenGLFailed; defer texbind.unbind(); - texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed; - texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed; - texbind.parameter(.MinFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed; - texbind.parameter(.MagFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed; + texbind.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed; + texbind.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed; + texbind.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed; + texbind.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed; texbind.image2D( 0, opts.internal_format, From 8f50c7f2699bb09ec85a43776c8fb6f59e8abe1e Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sat, 5 Jul 2025 22:13:26 -0600 Subject: [PATCH 093/119] font/sprite: no more margin in atlas region We no longer need a margin in the atlas because we always sample with nearest neighbor and our glyphs are always pixel perfect, no worry about interpolation between adjacent glyphs anymore! --- src/font/sprite/canvas.zig | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index b981449bc..a77b90a56 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -140,24 +140,7 @@ pub const Canvas = struct { const region_height = sfc_height -| self.clip_top -| self.clip_bottom; // Allocate our texture atlas region - const region = region: { - // Reserve a region with a 1px margin on the bottom and right edges - // so that we can avoid interpolation between adjacent glyphs during - // texture sampling. - var region = try atlas.reserve( - alloc, - region_width + 1, - region_height + 1, - ); - - // Modify the region to remove the margin so that we write to the - // non-zero location. The data in an Altlas is always initialized - // to zero (Atlas.clear) so we don't need to worry about zero-ing - // that. - region.width -= 1; - region.height -= 1; - break :region region; - }; + const region = try atlas.reserve(alloc, region_width, region_height); if (region.width > 0 and region.height > 0) { const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf); From 984d123fe48e03f8b828c036b2dcadb79b627624 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Jul 2025 21:04:59 -0700 Subject: [PATCH 094/119] macos: support configuration via CLI arguments This makes it so `zig build run` can take arguments such as `--config-default-files=false` or any other configuration. Previously, it only accepted commands such as `+version`. Incidentally, this also makes it so that the app in general can now take configuration arguments via the CLI if it is launched as a new instance via `open`. For example: open -n Ghostty.app --args --config-default-files=false This previously didn't work. This is kind of cool. To make this work, the libghostty C API was modified so that initialization requires the CLI args, and there is a new C API to try to execute an action if it was set. --- include/ghostty.h | 4 +-- macos/Ghostty-Info.plist | 4 +-- macos/Ghostty.xcodeproj/project.pbxproj | 4 +++ macos/Sources/App/macOS/main.swift | 33 +++++++++++++++---- macos/Sources/Ghostty/Ghostty.App.swift | 6 ---- macos/Sources/Ghostty/Package.swift | 5 ++- .../Extensions/FileHandle+Extension.swift | 9 +++++ src/apprt/embedded.zig | 2 +- src/build/GhosttyXcodebuild.zig | 4 --- src/main_c.zig | 30 ++++++++--------- src/os/desktop.zig | 11 +++++-- 11 files changed, 70 insertions(+), 42 deletions(-) create mode 100644 macos/Sources/Helpers/Extensions/FileHandle+Extension.swift diff --git a/include/ghostty.h b/include/ghostty.h index 181f7b7f8..73c708c6b 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -778,8 +778,8 @@ typedef struct { //------------------------------------------------------------------- // Published API -int ghostty_init(void); -void ghostty_cli_main(uintptr_t, char**); +int ghostty_init(uintptr_t, char**); +void ghostty_cli_try_action(void); ghostty_info_s ghostty_info(void); const char* ghostty_translate(const char*); diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist index dcce61373..ff391c0f8 100644 --- a/macos/Ghostty-Info.plist +++ b/macos/Ghostty-Info.plist @@ -48,8 +48,8 @@ LSEnvironment - GHOSTTY_MAC_APP - 1 + GHOSTTY_MAC_LAUNCH_SOURCE + app MDItemKeywords Terminal diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index cf806c7bd..08c3ef3b3 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; 9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; }; A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; }; + A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */; }; A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; }; A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; }; A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; }; @@ -158,6 +159,7 @@ 857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = ""; }; A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; + A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+Extension.swift"; sourceTree = ""; }; A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = ""; }; A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = ""; }; A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; @@ -516,6 +518,7 @@ A586366A2DF0A98900E04A10 /* Array+Extension.swift */, A50297342DFA0F3300B4E924 /* Double+Extension.swift */, A586366E2DF25D8300E04A10 /* Duration+Extension.swift */, + A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */, A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */, A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */, A51194122E05D003007258CC /* Optional+Extension.swift */, @@ -799,6 +802,7 @@ A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */, A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */, A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */, + A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */, A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */, CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */, A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */, diff --git a/macos/Sources/App/macOS/main.swift b/macos/Sources/App/macOS/main.swift index 990ef8ef1..ad32f4e70 100644 --- a/macos/Sources/App/macOS/main.swift +++ b/macos/Sources/App/macOS/main.swift @@ -2,13 +2,32 @@ import AppKit import Cocoa import GhosttyKit -// We put the GHOSTTY_MAC_APP env var into the Info.plist to detect -// whether we launch from the app or not. A user can fake this if -// they want but they're doing so at their own detriment... -let process = ProcessInfo.processInfo -if ((process.environment["GHOSTTY_MAC_APP"] ?? "") == "") { - ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv) - exit(1) +// Initialize Ghostty global state. We do this once right away because the +// CLI APIs require it and it lets us ensure it is done immediately for the +// rest of the app. +if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS { + Ghostty.logger.critical("ghostty_init failed") + + // We also write to stderr if this is executed from the CLI or zig run + switch Ghostty.launchSource { + case .cli, .zig_run: + let stderrHandle = FileHandle.standardError + stderrHandle.write( + "Ghostty failed to initialize! If you're executing Ghostty from the command line\n" + + "then this is usually because an invalid action or multiple actions were specified.\n" + + "Actions start with the `+` character.\n\n" + + "View all available actions by running `ghostty +help`.\n") + exit(1) + + case .app: + // For the app we exit immediately. We should handle this case more + // gracefully in the future. + exit(1) + } } +// This will run the CLI action and exit if one was specified. A CLI +// action is a command starting with a `+`, such as `ghostty +boo`. +ghostty_cli_try_action(); + _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index ba0b95212..17abe2b0e 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -45,12 +45,6 @@ extension Ghostty { } init() { - // Initialize ghostty global state. This happens once per process. - if ghostty_init() != GHOSTTY_SUCCESS { - logger.critical("ghostty_init failed, weird things may happen") - readiness = .error - } - // Initialize the global configuration. self.config = Config() if self.config.config == nil { diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index e96f555d3..f30f2f6f9 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -61,9 +61,12 @@ extension Ghostty { /// its up to the env var being set in the correct circumstance. static var launchSource: LaunchSource { guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else { - return .app + // We default to the CLI because the app bundle always sets the + // source. If its unset we assume we're in a CLI environment. + return .cli } + // If the env var is set but its unknown then we default back to the app. return LaunchSource(rawValue: envValue) ?? .app } } diff --git a/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift b/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift new file mode 100644 index 000000000..b6df4a60f --- /dev/null +++ b/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift @@ -0,0 +1,9 @@ +import Foundation + +extension FileHandle: @retroactive TextOutputStream { + /// Write a string to a filehandle. + public func write(_ string: String) { + let data = Data(string.utf8) + self.write(data) + } +} diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 0121494b7..30a2d9ff6 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -884,7 +884,7 @@ pub const Surface = struct { } // Remove this so that running `ghostty` within Ghostty works. - env.remove("GHOSTTY_MAC_APP"); + env.remove("GHOSTTY_MAC_LAUNCH_SOURCE"); // If we were launched from the desktop then we want to // remove the LANGUAGE env var so that we don't inherit diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 052c9f3e4..7fa2d2f95 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -122,10 +122,6 @@ pub fn init( if (b.args) |args| { open.addArgs(args); - } else { - // This tricks the app into thinking it's running from the - // app bundle so we don't execute our CLI mode. - open.setEnvironmentVariable("GHOSTTY_MAC_APP", "1"); } break :open open; diff --git a/src/main_c.zig b/src/main_c.zig index 1b73d7327..0722900e7 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -46,17 +46,11 @@ const Info = extern struct { }; }; -/// Initialize ghostty global state. It is possible to have more than -/// one global state but it has zero practical benefit. -export fn ghostty_init() c_int { +/// Initialize ghostty global state. +export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int { assert(builtin.link_libc); - // Since in the lib we don't go through start.zig, we need - // to populate argv so that inspecting std.os.argv doesn't - // touch uninitialized memory. - var argv: [0][*:0]u8 = .{}; - std.os.argv = &argv; - + std.os.argv = argv[0..argc]; state.init() catch |err| { std.log.err("failed to initialize ghostty error={}", .{err}); return 1; @@ -65,15 +59,17 @@ export fn ghostty_init() c_int { return 0; } -/// This is the entrypoint for the CLI version of Ghostty. This -/// is mutually exclusive to ghostty_init. Do NOT run ghostty_init -/// if you are going to run this. This will not return. -export fn ghostty_cli_main(argc: usize, argv: [*][*:0]u8) noreturn { - std.os.argv = argv[0..argc]; - main.main() catch |err| { - std.log.err("failed to run ghostty error={}", .{err}); +/// Runs an action if it is specified. If there is no action this returns +/// false. If there is an action then this doesn't return. +export fn ghostty_cli_try_action() void { + const action = state.action orelse return; + std.log.info("executing CLI action={}", .{action}); + posix.exit(action.run(state.alloc) catch |err| { + std.log.err("CLI action failed error={}", .{err}); posix.exit(1); - }; + }); + + posix.exit(0); } /// Return metadata about Ghostty, such as version, build mode, etc. diff --git a/src/os/desktop.zig b/src/os/desktop.zig index 3bc843e5c..93bfb74bc 100644 --- a/src/os/desktop.zig +++ b/src/os/desktop.zig @@ -24,8 +24,15 @@ pub fn launchedFromDesktop() bool { // This special case is so that if we launch the app via the // app bundle (i.e. via open) then we still treat it as if it // was launched from the desktop. - if (build_config.artifact == .lib and - posix.getenv("GHOSTTY_MAC_APP") != null) break :macos true; + if (build_config.artifact == .lib) lib: { + const env = "GHOSTTY_MAC_LAUNCH_SOURCE"; + const source = posix.getenv(env) orelse break :lib; + + // Source can be "app", "cli", or "zig_run". We assume + // its the desktop only if its "app". We may want to do + // "zig_run" but at the moment there's no reason. + if (std.mem.eql(u8, source, "app")) break :macos true; + } break :macos c.getppid() == 1; }, From b752ade1128f7f4acd6a0a349ff0ddaa19d69e99 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 01:05:17 -0500 Subject: [PATCH 095/119] gtk: don't allow focusing on tab overview or menu buttons This prevents GTK from focusing on those buttons, which would prevent focus from returning to the terminal after those buttons had been used. --- src/apprt/gtk/Window.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 555edb1e4..e6b502c80 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -214,6 +214,7 @@ pub fn init(self: *Window, app: *App) !void { { const btn = gtk.MenuButton.new(); btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu")); + btn.as(gtk.Widget).setCanFocus(0); btn.setIconName("open-menu-symbolic"); btn.setPopover(self.titlebar_menu.asWidget()); _ = gobject.Object.signals.notify.connect( @@ -253,6 +254,7 @@ pub fn init(self: *Window, app: *App) !void { }, }; + btn.setCanFocus(0); btn.setFocusOnClick(0); self.headerbar.packEnd(btn); } From 64dd10b98f2d41a240bc4fbe56b7b174b4e6d1d6 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 6 Jul 2025 08:28:24 -0500 Subject: [PATCH 096/119] Add termio to terminal CODEOWNERS group The termio directory contains the implementation of many terminal features that those in the terminal reviewer group may want to be notified about. --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 56768d5ae..7995650b7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -155,6 +155,7 @@ /src/input/KeyEncoder.zig @ghostty-org/terminal /src/terminal/ @ghostty-org/terminal /src/terminfo/ @ghostty-org/terminal +/src/termio/ @ghostty-org/terminal /src/unicode/ @ghostty-org/terminal /src/Surface.zig @ghostty-org/terminal /src/surface_mouse.zig @ghostty-org/terminal From 9f3f9205d8d04a077e35ceaa37ef3aca3d5bb10c Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 29 Jun 2025 10:52:24 -0500 Subject: [PATCH 097/119] Add link-previews option --- src/Surface.zig | 36 +++++++++++++++++++++++++----------- src/config.zig | 1 + src/config/Config.zig | 14 ++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 6d0f1584b..769512606 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -270,6 +270,7 @@ const DerivedConfig = struct { title: ?[:0]const u8, title_report: bool, links: []Link, + link_previews: configpkg.LinkPreviews, const Link = struct { regex: oni.Regex, @@ -336,6 +337,7 @@ const DerivedConfig = struct { .title = config.title, .title_report = config.@"title-report", .links = links, + .link_previews = config.@"link-previews", // Assignments happen sequentially so we have to do this last // so that the memory is captured from allocs above. @@ -1242,7 +1244,7 @@ fn mouseRefreshLinks( // Get our link at the current position. This returns null if there // isn't a link OR if we shouldn't be showing links for some reason // (see further comments for cases). - const link_: ?apprt.action.MouseOverLink = link: { + const link_: ?apprt.action.MouseOverLink, const preview: bool = link: { // If we clicked and our mouse moved cells then we never // highlight links until the mouse is unclicked. This follows // standard macOS and Linux behavior where a click and drag cancels @@ -1257,18 +1259,21 @@ fn mouseRefreshLinks( if (!click_pt.coord().eql(pos_vp)) { log.debug("mouse moved while left click held, ignoring link hover", .{}); - break :link null; + break :link .{ null, false }; } } - const link = (try self.linkAtPos(pos)) orelse break :link null; + const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false }; switch (link[0]) { .open => { const str = try self.io.terminal.screen.selectionString(alloc, .{ .sel = link[1], .trim = false, }); - break :link .{ .url = str }; + break :link .{ + .{ .url = str }, + self.config.link_previews == .true, + }; }, ._open_osc8 => { @@ -1276,9 +1281,14 @@ fn mouseRefreshLinks( const pin = link[1].start(); const uri = self.osc8URI(pin) orelse { log.warn("failed to get URI for OSC8 hyperlink", .{}); - break :link null; + break :link .{ null, false }; + }; + break :link .{ + .{ + .url = uri, + }, + self.config.link_previews != .false, }; - break :link .{ .url = uri }; }, } }; @@ -1294,11 +1304,15 @@ fn mouseRefreshLinks( .mouse_shape, .pointer, ); - _ = try self.rt_app.performAction( - .{ .surface = self }, - .mouse_over_link, - link, - ); + + if (preview) { + _ = try self.rt_app.performAction( + .{ .surface = self }, + .mouse_over_link, + link, + ); + } + try self.queueRender(); return; } diff --git a/src/config.zig b/src/config.zig index ac38eb89c..b6fecde4e 100644 --- a/src/config.zig +++ b/src/config.zig @@ -37,6 +37,7 @@ pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures; pub const WindowPaddingColor = Config.WindowPaddingColor; pub const BackgroundImagePosition = Config.BackgroundImagePosition; pub const BackgroundImageFit = Config.BackgroundImageFit; +pub const LinkPreviews = Config.LinkPreviews; // Alternate APIs pub const CAPI = @import("config/CAPI.zig"); diff --git a/src/config/Config.zig b/src/config/Config.zig index 2910372f3..31d749348 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1046,6 +1046,14 @@ link: RepeatableLink = .{}, /// `link`). If you want to customize URL matching, use `link` and disable this. @"link-url": bool = true, +/// Show link previews for a matched URL. +/// +/// When true, link previews are shown for all matched URLs. When false, link +/// previews are never shown. When set to "osc8", link previews are only shown +/// for hyperlinks created with the OSC 8 sequence (in this case, the link text +/// can differ from the link destination). +@"link-previews": LinkPreviews = .true, + /// Whether to start the window in a maximized state. This setting applies /// to new windows and does not apply to tabs, splits, etc. However, this setting /// will apply to all new windows, not just the first one. @@ -4326,6 +4334,12 @@ pub const WindowSubtitle = enum { @"working-directory", }; +pub const LinkPreviews = enum { + false, + true, + osc8, +}; + /// Color represents a color using RGB. /// /// This is a packed struct so that the C API to read color values just From eb5694794cac5d0f10f3d2663914c8b9278e409e Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 00:21:01 -0500 Subject: [PATCH 098/119] keybind: add set_font_size Fixes #7795 This allows the font size to be set to an absolute value. --- src/Surface.zig | 8 ++++++++ src/config/Config.zig | 10 ++++++++- src/input/Binding.zig | 47 +++++++++++++++++++++++++++++++++++++++++++ src/input/command.zig | 2 ++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index 6d0f1584b..d9d6a3012 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4484,6 +4484,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool try self.setFontSize(size); }, + .set_font_size => |points| { + log.debug("set font size={d}", .{points}); + + var size = self.font_size; + size.points = std.math.clamp(points, 1.0, 255.0); + try self.setFontSize(size); + }, + .prompt_surface_title => return try self.rt_app.performAction( .{ .surface = self }, .prompt_title, diff --git a/src/config/Config.zig b/src/config/Config.zig index 2910372f3..be76820d2 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -6531,8 +6531,9 @@ pub const RepeatableCommand = struct { try list.parseCLI(alloc, "title:Foo,action:ignore"); try list.parseCLI(alloc, "title:Bar,description:bobr,action:text:ale bydle"); try list.parseCLI(alloc, "title:Quux,description:boo,action:increase_font_size:2.5"); + try list.parseCLI(alloc, "title:Baz,description:Raspberry Pie,action:set_font_size:3.14"); - try testing.expectEqual(@as(usize, 3), list.value.items.len); + try testing.expectEqual(@as(usize, 4), list.value.items.len); try testing.expectEqual(inputpkg.Binding.Action.ignore, list.value.items[0].action); try testing.expectEqualStrings("Foo", list.value.items[0].title); @@ -6549,6 +6550,13 @@ pub const RepeatableCommand = struct { try testing.expectEqualStrings("Quux", list.value.items[2].title); try testing.expectEqualStrings("boo", list.value.items[2].description); + try testing.expectEqual( + inputpkg.Binding.Action{ .set_font_size = 3.14 }, + list.value.items[3].action, + ); + try testing.expectEqualStrings("Baz", list.value.items[3].title); + try testing.expectEqualStrings("Raspberry Pie", list.value.items[3].description); + try list.parseCLI(alloc, ""); try testing.expectEqual(@as(usize, 0), list.value.items.len); } diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 7cdb8047c..c342c9cc2 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -296,6 +296,12 @@ pub const Action = union(enum) { /// Reset the font size to the original configured size. reset_font_size, + /// Set the font size to the specified size in points (pt). + /// + /// For example, `set_font_size:14.5` will set the font size + /// to 14.5 points. + set_font_size: f32, + /// Clear the screen and all scrollback. clear_screen, @@ -1004,6 +1010,7 @@ pub const Action = union(enum) { .increase_font_size, .decrease_font_size, .reset_font_size, + .set_font_size, .prompt_surface_title, .clear_screen, .select_all, @@ -3065,6 +3072,7 @@ test "set: getEvent codepoint case folding" { try testing.expect(action == null); } } + test "Action: clone" { const testing = std.testing; var arena = std.heap.ArenaAllocator.init(testing.allocator); @@ -3083,3 +3091,42 @@ test "Action: clone" { try testing.expect(b == .text); } } + +test "parse: increase_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=increase_font_size:1.5"); + try testing.expect(binding.action == .increase_font_size); + try testing.expectEqual(1.5, binding.action.increase_font_size); + } +} + +test "parse: decrease_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=decrease_font_size:2.5"); + try testing.expect(binding.action == .decrease_font_size); + try testing.expectEqual(2.5, binding.action.decrease_font_size); + } +} + +test "parse: reset_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=reset_font_size"); + try testing.expect(binding.action == .reset_font_size); + } +} + +test "parse: set_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=set_font_size:13.5"); + try testing.expect(binding.action == .set_font_size); + try testing.expectEqual(13.5, binding.action.set_font_size); + } +} diff --git a/src/input/command.zig b/src/input/command.zig index 693d5c8d4..8938835d0 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Action = @import("Binding.zig").Action; @@ -460,6 +461,7 @@ fn actionCommands(action: Action.Key) []const Command { .esc, .text, .cursor_key, + .set_font_size, .scroll_page_fractional, .scroll_page_lines, .adjust_selection, From 0a1ade01580a28d66894e127543bab8ad63a4896 Mon Sep 17 00:00:00 2001 From: "Breno A." Date: Sat, 5 Jul 2025 18:10:06 -0300 Subject: [PATCH 099/119] pin GitHub Actions to specific SHAs --- .github/pinact.yml | 4 + .github/workflows/clean-artifacts.yml | 2 +- .github/workflows/milestone.yml | 4 +- .github/workflows/nix.yml | 8 +- .github/workflows/publish-tag.yml | 2 +- .github/workflows/release-pr.yml | 20 +-- .github/workflows/release-tag.yml | 36 ++-- .github/workflows/release-tip.yml | 50 +++--- .github/workflows/test.yml | 209 ++++++++++++---------- .github/workflows/update-colorschemes.yml | 10 +- nix/devShell.nix | 2 + 11 files changed, 191 insertions(+), 156 deletions(-) create mode 100644 .github/pinact.yml diff --git a/.github/pinact.yml b/.github/pinact.yml new file mode 100644 index 000000000..3a29d18ab --- /dev/null +++ b/.github/pinact.yml @@ -0,0 +1,4 @@ +version: 3 +ignore_actions: + - name: "DeterminateSystems/nix-installer-action" + ref: "main" diff --git a/.github/workflows/clean-artifacts.yml b/.github/workflows/clean-artifacts.yml index 5337c264a..69cb74ae5 100644 --- a/.github/workflows/clean-artifacts.yml +++ b/.github/workflows/clean-artifacts.yml @@ -10,7 +10,7 @@ jobs: timeout-minutes: 10 steps: - name: Remove old artifacts - uses: c-hive/gha-remove-artifacts@v1 + uses: c-hive/gha-remove-artifacts@44fc7acaf1b3d0987da0e8d4707a989d80e9554b # v1.4.0 with: age: "1 week" skip-tags: true diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml index 9ac5536ff..bc5c3d76c 100644 --- a/.github/workflows/milestone.yml +++ b/.github/workflows/milestone.yml @@ -15,7 +15,7 @@ jobs: name: Milestone Update steps: - name: Set Milestone for PR - uses: hustcer/milestone-action@v2 + uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8 if: github.event.pull_request.merged == true with: action: bind-pr # `bind-pr` is the default action @@ -24,7 +24,7 @@ jobs: # Bind milestone to closed issue that has a merged PR fix - name: Set Milestone for Issue - uses: hustcer/milestone-action@v2 + uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8 if: github.event.issue.state == 'closed' with: action: bind-issue diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a905531c2..bf8fd7208 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -34,18 +34,18 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/publish-tag.yml b/.github/workflows/publish-tag.yml index 458982140..710d04647 100644 --- a/.github/workflows/publish-tag.yml +++ b/.github/workflows/publish-tag.yml @@ -64,7 +64,7 @@ jobs: mkdir blob mv appcast.xml blob/appcast.xml - name: Upload Appcast to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1 with: r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 37d5ba79b..cf96ffb21 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -8,7 +8,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -29,7 +29,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -51,16 +51,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -189,7 +189,7 @@ jobs: cp ghostty-macos-universal.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal.zip cp ghostty-macos-universal-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }} @@ -203,16 +203,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -341,7 +341,7 @@ jobs: cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug.zip cp ghostty-macos-universal-debug-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }} diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 33cf9f3a8..98ecf2fa3 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -56,7 +56,7 @@ jobs: fi - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 @@ -80,20 +80,20 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -111,7 +111,7 @@ jobs: nix develop -c minisign -S -m "ghostty-source.tar.gz" -s minisign.key < minisign.password - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: source-tarball path: |- @@ -128,12 +128,12 @@ jobs: GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -260,7 +260,7 @@ jobs: zip -9 -r --symlinks ../../../ghostty-macos-universal-dsym.zip Ghostty.app.dSYM/ - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: macos path: |- @@ -277,7 +277,7 @@ jobs: curl -sL https://sentry.io/get-cli/ | bash - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos @@ -297,10 +297,10 @@ jobs: GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos @@ -331,7 +331,7 @@ jobs: mv appcast_new.xml appcast.xml - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sparkle path: |- @@ -348,17 +348,17 @@ jobs: GHOSTTY_VERSION: ${{ needs.setup.outputs.version }} steps: - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos - name: Download Sparkle Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: sparkle - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball @@ -378,7 +378,7 @@ jobs: mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }} diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 4d009ab7b..b0916e657 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -19,7 +19,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Tip Tag run: | git config user.name "github-actions[bot]" @@ -31,7 +31,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug-slow] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -52,7 +52,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug-fast] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -73,7 +73,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -105,17 +105,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -132,7 +132,7 @@ jobs: nix develop -c minisign -S -m ghostty-source.tar.gz -s minisign.key < minisign.password - name: Update Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -158,16 +158,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -299,7 +299,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -331,7 +331,7 @@ jobs: cp Ghostty.dmg blob/${GHOSTTY_COMMIT_LONG}/Ghostty.dmg - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -349,7 +349,7 @@ jobs: cp appcast_new.xml blob/appcast.xml - name: Upload Appcast to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -373,16 +373,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -507,7 +507,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -524,7 +524,7 @@ jobs: cp ghostty-macos-universal-debug-slow.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow.zip cp ghostty-macos-universal-debug-slow-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -548,16 +548,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -682,7 +682,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -699,7 +699,7 @@ jobs: cp ghostty-macos-universal-debug-fast.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast.zip cp ghostty-macos-universal-debug-fast-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b34327f7d..8af7140c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,7 @@ jobs: - test-gtk - test-sentry-linux - test-macos + - pinact - prettier - alejandra - typos @@ -64,20 +65,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -95,20 +96,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -131,20 +132,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -160,20 +161,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -193,20 +194,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -237,20 +238,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -262,7 +263,7 @@ jobs: cp zig-out/dist/*.tar.gz ghostty-source.tar.gz - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: source-tarball path: |- @@ -273,13 +274,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -313,13 +314,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # TODO(tahoe): https://github.com/NixOS/nix/issues/13342 - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -353,13 +354,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -400,7 +401,7 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball - name: Extract tarball @@ -408,7 +409,7 @@ jobs: mkdir dist tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix @@ -420,7 +421,7 @@ jobs: _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@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0 with: path: dist @@ -431,7 +432,7 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # This could be from a script if we wanted to but inlining here for now # in one place. @@ -500,20 +501,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -542,20 +543,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -581,20 +582,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -608,13 +609,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -637,17 +638,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -656,6 +657,34 @@ jobs: - name: zig fmt run: nix develop -c zig fmt --check . + pinact: + name: "GitHub Actions Pins" + if: github.repository == 'ghostty-org/ghostty' + runs-on: namespace-profile-ghostty-xsm + timeout-minutes: 60 + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 + with: + path: | + /nix + /zig + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + skipPush: true + useDaemon: false # sometimes fails on short jobs + - name: pinact check + run: nix develop -c pinact run --check + prettier: if: github.repository == 'ghostty-org/ghostty' runs-on: namespace-profile-ghostty-xsm @@ -664,17 +693,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -691,17 +720,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -718,17 +747,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -745,17 +774,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -772,17 +801,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -806,20 +835,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -834,13 +863,13 @@ jobs: needs: [test, build-dist] steps: - name: Install and configure Namespace CLI - uses: namespacelabs/nscloud-setup@v0 + uses: namespacelabs/nscloud-setup@d1c625762f7c926a54bd39252efff0705fd11c64 # v0.0.10 - name: Configure Namespace powered Buildx - uses: namespacelabs/nscloud-setup-buildx-action@v0 + uses: namespacelabs/nscloud-setup-buildx-action@01628ae51ea5d6b0c90109c7dccbf511953aff29 # v0.0.18 - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball @@ -850,7 +879,7 @@ jobs: tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: dist file: dist/src/build/docker/debian/Dockerfile @@ -865,18 +894,18 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -901,8 +930,8 @@ jobs: runs-on: ${{ matrix.variant.runner }} needs: [flatpak-check-zig-cache, test] steps: - - uses: actions/checkout@v4 - - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: flatpak/flatpak-github-actions/flatpak-builder@10a3c29f0162516f0f68006be14c92f34bd4fa6c # v6.5 with: bundle: com.mitchellh.ghostty manifest-path: flatpak/com.mitchellh.ghostty.yml diff --git a/.github/workflows/update-colorschemes.yml b/.github/workflows/update-colorschemes.yml index 2533285e6..b9ded559e 100644 --- a/.github/workflows/update-colorschemes.yml +++ b/.github/workflows/update-colorschemes.yml @@ -17,22 +17,22 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -60,7 +60,7 @@ jobs: run: nix build .#ghostty - name: Create pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: title: Update iTerm2 colorschemes base: main diff --git a/nix/devShell.nix b/nix/devShell.nix index f4ea62235..8a8ab441f 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -58,6 +58,7 @@ jq, minisign, pandoc, + pinact, hyperfine, typos, uv, @@ -98,6 +99,7 @@ in # Linting nodePackages.prettier alejandra + pinact typos # Testing From 7de4d569b0c9483c5945f277f616ef40a821e82f Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 10:49:00 -0600 Subject: [PATCH 100/119] Revert "rely on stdin/stdout instead of hardcoded paths" This reverts commit 2fca0477bc7f3c955daf40a0d4663d63ef3d76a1. The idea of using stdin and stdout was the integrate it in to the build script, but since we don't want to do that because it contains an eval, it just makes it more annoying to use. --- src/font/nerd_font_codegen.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index 52d70ac01..99915c9f2 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -10,9 +10,9 @@ be safe and not malicious or anything. import ast import math -import sys from collections import defaultdict from contextlib import suppress +from pathlib import Path from types import SimpleNamespace from typing import Literal, TypedDict, cast @@ -236,9 +236,16 @@ def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: if __name__ == "__main__": - source = sys.stdin.read() + project_root = Path(__file__).resolve().parents[2] + + patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py" + source = patcher_path.read_text(encoding="utf-8") patch_set = extract_patch_set_values(source) - print("""//! This is a generated file, produced by nerd_font_codegen.py + + out_path = project_root / "src" / "font" / "nerd_font_attributes.zig" + + with out_path.open("w", encoding="utf-8") as f: + f.write("""//! This is a generated file, produced by nerd_font_codegen.py //! DO NOT EDIT BY HAND! //! //! This file provides info extracted from the nerd fonts patcher script, @@ -248,6 +255,7 @@ const Constraint = @import("face.zig").RenderOptions.Constraint; /// Get the a constraints for the provided codepoint. pub fn getConstraint(cp: u21) Constraint { - return switch (cp) {""") - print(generate_zig_switch_arms(patch_set)) - print(" else => .none,\n };\n}") + return switch (cp) { +""") + f.write(generate_zig_switch_arms(patch_set)) + f.write("\n else => .none,\n };\n}\n") From 8f989f6bfd44b60cfe492031b3ba247be89dd041 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 11:02:00 -0600 Subject: [PATCH 101/119] font: fix nerd font codegen to handle ypadding properly Previously `ypadding` was effectively ignored, since it's mutually exclusive with `overlap`. This had a noticeable effect on the heavy bracket characters U+276C...U+2771, which were much taller than they should have been. I also fixed the vertical overlap limit, since negative `overlap` values are used in the nerd font attributes to create padding on all sides of the cell, so we don't want to limit the magnitude of the overlap for vertical padding, we only want to limit it if the value is positive. That change fixed the vertical padding for a handful of ranges, which should give more consistent results. --- src/font/nerd_font_attributes.zig | 22 ++++++++++++---------- src/font/nerd_font_codegen.py | 11 ++++++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index dfb11c5a5..a82699b0e 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -28,8 +28,8 @@ pub fn getConstraint(cp: u21) Constraint { .align_vertical = .center, .pad_left = 0.1, .pad_right = 0.1, - .pad_top = 0.01, - .pad_bottom = 0.01, + .pad_top = 0.1, + .pad_bottom = 0.1, }, 0x276c...0x2771, => .{ @@ -37,6 +37,8 @@ pub fn getConstraint(cp: u21) Constraint { .size_vertical = .fit, .align_horizontal = .center, .align_vertical = .center, + .pad_top = 0.3, + .pad_bottom = 0.3, }, 0xe0b0, => .{ @@ -204,8 +206,8 @@ pub fn getConstraint(cp: u21) Constraint { .align_vertical = .center, .pad_left = 0.03, .pad_right = 0.03, - .pad_top = 0.01, - .pad_bottom = 0.01, + .pad_top = 0.03, + .pad_bottom = 0.03, .max_xy_ratio = 0.86, }, 0xe0c5, @@ -216,8 +218,8 @@ pub fn getConstraint(cp: u21) Constraint { .align_vertical = .center, .pad_left = 0.03, .pad_right = 0.03, - .pad_top = 0.01, - .pad_bottom = 0.01, + .pad_top = 0.03, + .pad_bottom = 0.03, .max_xy_ratio = 0.86, }, 0xe0c6, @@ -228,8 +230,8 @@ pub fn getConstraint(cp: u21) Constraint { .align_vertical = .center, .pad_left = 0.03, .pad_right = 0.03, - .pad_top = 0.01, - .pad_bottom = 0.01, + .pad_top = 0.03, + .pad_bottom = 0.03, .max_xy_ratio = 0.78, }, 0xe0c7, @@ -240,8 +242,8 @@ pub fn getConstraint(cp: u21) Constraint { .align_vertical = .center, .pad_left = 0.03, .pad_right = 0.03, - .pad_top = 0.01, - .pad_bottom = 0.01, + .pad_top = 0.03, + .pad_bottom = 0.03, .max_xy_ratio = 0.78, }, 0xe0cc, diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index 99915c9f2..b62768cc5 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -6,6 +6,8 @@ attributes and scaling rules. This does include an `eval` call! This is spooky, but we trust the nerd fonts code to be safe and not malicious or anything. + +This script requires Python 3.12 or greater. """ import ast @@ -193,13 +195,20 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) if valign is not None: s += f" .align_vertical = {valign},\n" + # `overlap` and `ypadding` are mutually exclusive, + # this is asserted in the nerd fonts patcher itself. if overlap: pad = -overlap s += f" .pad_left = {pad},\n" s += f" .pad_right = {pad},\n" - v_pad = y_padding - math.copysign(min(0.01, abs(overlap)), overlap) + # In the nerd fonts patcher, overlap values + # are capped at 0.01 in the vertical direction. + v_pad = -min(0.01, overlap) s += f" .pad_top = {v_pad},\n" s += f" .pad_bottom = {v_pad},\n" + elif y_padding: + s += f" .pad_top = {y_padding},\n" + s += f" .pad_bottom = {y_padding},\n" if xy_ratio > 0: s += f" .max_xy_ratio = {xy_ratio},\n" From 3cf56b8af3d5766210bc25202d0f82e15ce0ce6d Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 12:12:27 -0500 Subject: [PATCH 102/119] keybind: add copy_title action Fixes #7829 This will copy the terminal title to the clipboard. If the terminal title is not set it has no effect. --- src/Surface.zig | 11 +++++++++++ src/input/Binding.zig | 5 +++++ src/input/command.zig | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/src/Surface.zig b/src/Surface.zig index d9d6a3012..faba60ced 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4443,6 +4443,17 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool return false; }, + .copy_title => { + const title = self.rt_surface.getTitle() orelse return false; + + self.rt_surface.setClipboardString(title, .standard, false) catch |err| { + log.err("error copying title to clipboard err={}", .{err}); + return true; + }; + + return true; + }, + .paste_from_clipboard => try self.startClipboardRequest( .standard, .{ .paste = {} }, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index c342c9cc2..6339674f8 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -281,6 +281,10 @@ pub const Action = union(enum) { /// If there is a URL under the cursor, copy it to the default clipboard. copy_url_to_clipboard, + /// Copy the terminal title to the clipboard. If the terminal title is not + /// set this has no effect. + copy_title, + /// Increase the font size by the specified amount in points (pt). /// /// For example, `increase_font_size:1.5` will increase the font size @@ -1005,6 +1009,7 @@ pub const Action = union(enum) { .reset, .copy_to_clipboard, .copy_url_to_clipboard, + .copy_title, .paste_from_clipboard, .paste_from_selection, .increase_font_size, diff --git a/src/input/command.zig b/src/input/command.zig index 8938835d0..434a4edf3 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -132,6 +132,12 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Copy the URL under the cursor to the clipboard.", }}, + .copy_title => comptime &.{.{ + .action = .copy_title, + .title = "Copy Terminal Title to Clipboard", + .description = "Copy the terminal title to the clipboard. If the terminal title is not set this has no effect.", + }}, + .paste_from_clipboard => comptime &.{.{ .action = .paste_from_clipboard, .title = "Paste from Clipboard", From c7e65b0c1c75c1512bab068a0c37a22f417bd0e4 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 11:23:49 -0600 Subject: [PATCH 103/119] font: respect cell width attributes in nerd font constraints This mostly applies to powerline glyphs, but is also relevant for heavy bracket characters, which need to always be 1 wide otherwise they look silly because they misalign depending on if there's a space after them or not. --- src/font/face.zig | 27 ++++++++++++++++++++------- src/font/face/coretext.zig | 3 ++- src/font/face/freetype.zig | 3 ++- src/font/nerd_font_attributes.zig | 19 +++++++++++++++++++ src/font/nerd_font_codegen.py | 6 ++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/font/face.zig b/src/font/face.zig index 363576ff0..245edcf4b 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -147,6 +147,9 @@ pub const RenderOptions = struct { /// Maximum ratio of width to height when resizing. max_xy_ratio: ?f64 = null, + /// Maximum number of cells horizontally to use. + max_constraint_width: u2 = 2, + pub const Size = enum { /// Don't change the size of this glyph. none, @@ -186,16 +189,26 @@ pub const RenderOptions = struct { pub fn constrain( self: Constraint, glyph: GlyphSize, - /// Available width + /// Width of one cell. cell_width: f64, - /// Available height + /// Height of one cell. cell_height: f64, + /// Number of cells horizontally available for this glyph. + constraint_width: u2, ) GlyphSize { var g = glyph; - const w = cell_width - - self.pad_left * cell_width - - self.pad_right * cell_width; + const available_width = + cell_width * @as(f64, @floatFromInt( + @min( + self.max_constraint_width, + constraint_width, + ), + )); + + const w = available_width - + self.pad_left * available_width - + self.pad_right * available_width; const h = cell_height - self.pad_top * cell_height - self.pad_bottom * cell_height; @@ -203,7 +216,7 @@ pub const RenderOptions = struct { // Subtract padding from the bearings so that our // alignment and sizing code works correctly. We // re-add before returning. - g.x -= self.pad_left * cell_width; + g.x -= self.pad_left * available_width; g.y -= self.pad_bottom * cell_height; switch (self.size_horizontal) { @@ -305,7 +318,7 @@ pub const RenderOptions = struct { } // Re-add our padding before returning. - g.x += self.pad_left * cell_width; + g.x += self.pad_left * available_width; g.y += self.pad_bottom * cell_height; return g; diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 89d771d95..5c9c259d2 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -337,7 +337,7 @@ pub const Face = struct { }; const metrics = opts.grid_metrics; - const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width); + const cell_width: f64 = @floatFromInt(metrics.cell_width); const cell_height: f64 = @floatFromInt(metrics.cell_height); const glyph_size = opts.constraint.constrain( @@ -349,6 +349,7 @@ pub const Face = struct { }, cell_width, cell_height, + opts.constraint_width, ); const width = glyph_size.width; diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 585d21c5b..b27b28ab8 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -390,7 +390,7 @@ pub const Face = struct { // Next we need to apply any constraints. const metrics = opts.grid_metrics; - const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width); + const cell_width: f64 = @floatFromInt(metrics.cell_width); const cell_height: f64 = @floatFromInt(metrics.cell_height); const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX); @@ -405,6 +405,7 @@ pub const Face = struct { }, cell_width, cell_height, + opts.constraint_width, ); const width = glyph_size.width; diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index a82699b0e..817d838f8 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -13,6 +13,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .center, .align_vertical = .center, .pad_left = -0.02, @@ -24,6 +25,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .cover, .size_vertical = .fit, + .max_constraint_width = 1, .align_horizontal = .center, .align_vertical = .center, .pad_left = 0.1, @@ -35,6 +37,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .cover, .size_vertical = .fit, + .max_constraint_width = 1, .align_horizontal = .center, .align_vertical = .center, .pad_top = 0.3, @@ -44,6 +47,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .pad_left = -0.06, @@ -56,6 +60,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .max_xy_ratio = 0.7, @@ -64,6 +69,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .pad_left = -0.06, @@ -76,6 +82,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .max_xy_ratio = 0.7, @@ -84,6 +91,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .pad_left = -0.06, @@ -96,6 +104,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .max_xy_ratio = 0.5, @@ -104,6 +113,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .pad_left = -0.06, @@ -116,6 +126,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .max_xy_ratio = 0.5, @@ -125,6 +136,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .pad_left = -0.05, @@ -137,6 +149,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, }, @@ -145,6 +158,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .pad_left = -0.05, @@ -157,6 +171,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, }, @@ -287,6 +302,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .pad_left = -0.02, @@ -299,6 +315,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .pad_left = -0.02, @@ -311,6 +328,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .start, .align_vertical = .center, .pad_left = -0.05, @@ -323,6 +341,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .stretch, .size_vertical = .stretch, + .max_constraint_width = 1, .align_horizontal = .end, .align_vertical = .center, .pad_left = -0.05, diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index b62768cc5..f8ff7caa6 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -190,6 +190,12 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) s += " .size_horizontal = .fit,\n" s += " .size_vertical = .fit,\n" + # There are two cases where we want to limit the constraint width to 1: + # - If there's a `1` in the stretch mode string. + # - If the stretch mode is `xy` and there's not an explicit `2`. + if "1" in stretch or ("xy" in stretch and "2" not in stretch): + s += " .max_constraint_width = 1,\n" + if align is not None: s += f" .align_horizontal = {align},\n" if valign is not None: From 871f90e4485b3b123b8f9bd4e3327efa7d1ecd5a Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 12:58:55 -0500 Subject: [PATCH 104/119] linux/kde: add KDE shortcut to desktop file Fixes #7673 This adds `Ctrl+Alt+T` as a KDE shortcut to the desktop file. If Konsole is installed (or any other prorgam that has the same shortcut) the user will need to go into the KDE system settings and manually reassign the `Ctrl+Alt+T` shortcut to Ghostty. If Ghostty is the only terminal installed that claims that shortcut KDE _should_ automatically enable the shortcut (but YMMV). Non-KDE systems will ignore this setting and if the user desires a global shortcut to open a Ghostty window it will need to be accomplished in other ways. --- dist/linux/app.desktop.in | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/linux/app.desktop.in b/dist/linux/app.desktop.in index c39164158..32ba00cfd 100644 --- a/dist/linux/app.desktop.in +++ b/dist/linux/app.desktop.in @@ -19,6 +19,7 @@ X-TerminalArgAppId=--class= X-TerminalArgDir=--working-directory= X-TerminalArgHold=--wait-after-command DBusActivatable=true +X-KDE-Shortcuts=Ctrl+Alt+T [Desktop Action new-window] Name=New Window From a23b5328a5e230fd29cb53a0f468f7eca5149c3d Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 13:12:00 -0500 Subject: [PATCH 105/119] keybind: rename copy_title to copy_title_to_clipboard Co-authored-by: Jon Parise --- src/Surface.zig | 2 +- src/input/Binding.zig | 4 ++-- src/input/command.zig | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index faba60ced..30235f26f 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4443,7 +4443,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool return false; }, - .copy_title => { + .copy_title_to_clipboard => { const title = self.rt_surface.getTitle() orelse return false; self.rt_surface.setClipboardString(title, .standard, false) catch |err| { diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 6339674f8..21edf2636 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -283,7 +283,7 @@ pub const Action = union(enum) { /// Copy the terminal title to the clipboard. If the terminal title is not /// set this has no effect. - copy_title, + copy_title_to_clipboard, /// Increase the font size by the specified amount in points (pt). /// @@ -1009,7 +1009,7 @@ pub const Action = union(enum) { .reset, .copy_to_clipboard, .copy_url_to_clipboard, - .copy_title, + .copy_title_to_clipboard, .paste_from_clipboard, .paste_from_selection, .increase_font_size, diff --git a/src/input/command.zig b/src/input/command.zig index 434a4edf3..84e9afc79 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -132,8 +132,8 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Copy the URL under the cursor to the clipboard.", }}, - .copy_title => comptime &.{.{ - .action = .copy_title, + .copy_title_to_clipboard => comptime &.{.{ + .action = .copy_title_to_clipboard, .title = "Copy Terminal Title to Clipboard", .description = "Copy the terminal title to the clipboard. If the terminal title is not set this has no effect.", }}, From 7884872d4ec15b8a42f599443a337a1894d232c3 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 6 Jul 2025 14:15:11 -0500 Subject: [PATCH 106/119] keybind: don't clobber the clipboard if the title is empty --- src/Surface.zig | 1 + src/input/Binding.zig | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index 30235f26f..372da325a 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4445,6 +4445,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .copy_title_to_clipboard => { const title = self.rt_surface.getTitle() orelse return false; + if (title.len == 0) return false; self.rt_surface.setClipboardString(title, .standard, false) catch |err| { log.err("error copying title to clipboard err={}", .{err}); diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 21edf2636..f76da360a 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -282,7 +282,7 @@ pub const Action = union(enum) { copy_url_to_clipboard, /// Copy the terminal title to the clipboard. If the terminal title is not - /// set this has no effect. + /// set or is empty this has no effect. copy_title_to_clipboard, /// Increase the font size by the specified amount in points (pt). From 52790fb92c0ba71bcd68c159e2f700c583097cc1 Mon Sep 17 00:00:00 2001 From: Robert Ian Hawdon Date: Wed, 23 Apr 2025 12:03:26 +0100 Subject: [PATCH 107/119] Added bold-color option --- src/config.zig | 1 + src/config/Config.zig | 113 ++++++++++++++++++++++++++++++++++++++- src/renderer/generic.zig | 29 ++++++---- src/terminal/style.zig | 69 +++++++++++++++++++----- 4 files changed, 187 insertions(+), 25 deletions(-) diff --git a/src/config.zig b/src/config.zig index b6fecde4e..efc9fd973 100644 --- a/src/config.zig +++ b/src/config.zig @@ -14,6 +14,7 @@ pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; // Field types +pub const BoldColor = Config.BoldColor; pub const ClipboardAccess = Config.ClipboardAccess; pub const Command = Config.Command; pub const ConfirmCloseSurface = Config.ConfirmCloseSurface; diff --git a/src/config/Config.zig b/src/config/Config.zig index f9454bf98..a53986bc9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -69,6 +69,10 @@ pub const compatibility = std.StaticStringMap( // this behavior. This applies to selection too. .{ "cursor-invert-fg-bg", compatCursorInvertFgBg }, .{ "selection-invert-fg-bg", compatSelectionInvertFgBg }, + + // Ghostty 1.2 merged `bold-is-bright` into the new `bold-color` + // by setting the value to "bright". + .{ "bold-is-bright", compatBoldIsBright }, }); /// The font families to use. @@ -2804,8 +2808,24 @@ else /// notifications using certain escape sequences such as OSC 9 or OSC 777. @"desktop-notifications": bool = true, -/// If `true`, the bold text will use the bright color palette. -@"bold-is-bright": bool = false, +/// Modifies the color used for bold text in the terminal. +/// +/// This can be set to a specific color, using the same format as +/// `background` or `foreground` (e.g. `#RRGGBB` but other formats +/// are also supported; see the aforementioned documentation). If a +/// specific color is set, this color will always be used for all +/// bold text regardless of the terminal's color scheme. +/// +/// This can also be set to `bright`, which uses the bright color palette +/// for bold text. For example, if the text is red, then the bold will +/// use the bright red color. The terminal palette is set with `palette` +/// but can also be overridden by the terminal application itself using +/// escape sequences such as OSC 4. (Since Ghostty 1.2.0, the previous +/// configuration `bold-is-bright` is deprecated and replaced by this +/// usage). +/// +/// Available since Ghostty 1.2.0. +@"bold-color": ?BoldColor = null, /// This will be used to set the `TERM` environment variable. /// HACK: We set this with an `xterm` prefix because vim uses that to enable key @@ -3910,6 +3930,23 @@ fn compatSelectionInvertFgBg( return true; } +fn compatBoldIsBright( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "bold-is-bright")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"bold-color" = .bright; + } + + return true; +} + /// Create a shallow copy of this config. This will share all the memory /// allocated with the previous config but will have a new arena for /// any changes or new allocations. The config should have `deinit` @@ -4537,6 +4574,58 @@ pub const TerminalColor = union(enum) { } }; +/// Represents color values that can be used for bold. See `bold-color`. +pub const BoldColor = union(enum) { + color: Color, + bright, + + pub fn parseCLI(input_: ?[]const u8) !BoldColor { + const input = input_ orelse return error.ValueRequired; + if (std.mem.eql(u8, input, "bright")) return .bright; + return .{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: BoldColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + .bright => try formatter.formatEntry( + [:0]const u8, + @tagName(self), + ), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try BoldColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try BoldColor.parseCLI("black"), + ); + try testing.expectEqual( + BoldColor.bright, + try BoldColor.parseCLI("bright"), + ); + + try testing.expectError(error.InvalidValue, BoldColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: BoldColor = .bright; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = bright\n", buf.items); + } +}; + pub const ColorList = struct { const Self = @This(); @@ -8236,3 +8325,23 @@ test "compatibility: removed selection-invert-fg-bg" { ); } } + +test "compatibility: removed bold-is-bright" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--bold-is-bright", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + BoldColor.bright, + cfg.@"bold-color", + ); + } +} diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 829563075..3965d302a 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -519,7 +519,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { foreground: terminal.color.RGB, selection_background: ?configpkg.Config.TerminalColor, selection_foreground: ?configpkg.Config.TerminalColor, - bold_is_bright: bool, + bold_color: ?configpkg.BoldColor, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, custom_shaders: configpkg.RepeatablePath, @@ -580,7 +580,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .background = config.background.toTerminalRGB(), .foreground = config.foreground.toTerminalRGB(), - .bold_is_bright = config.@"bold-is-bright", + .bold_color = config.@"bold-color", + .min_contrast = @floatCast(config.@"minimum-contrast"), .padding_color = config.@"window-padding-color", @@ -2540,10 +2541,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // the cell style (SGR), before applying any additional // configuration, inversions, selections, etc. const bg_style = style.bg(cell, color_palette); - const fg_style = style.fg( - color_palette, - self.config.bold_is_bright, - ) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = style.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); // The final background color for the cell. const bg = bg: { @@ -2801,10 +2803,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .@"cell-background", => |_, tag| { const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - const fg_style = sty.fg( - color_palette, - self.config.bold_is_bright, - ) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); const bg_style = sty.bg( screen.cursor.page_cell, color_palette, @@ -2852,7 +2855,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { } const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; break :blk switch (txt) { diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 865e15f64..78afcdf39 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const configpkg = @import("../config.zig"); const color = @import("color.zig"); const sgr = @import("sgr.zig"); const page = @import("page.zig"); @@ -115,24 +116,68 @@ pub const Style = struct { }; } - /// Returns the fg color for a cell with this style given the palette. + pub const Fg = struct { + /// The default color to use if the style doesn't specify a + /// foreground color and no configuration options override + /// it. + default: color.RGB, + + /// The current color palette. Required to map palette indices to + /// real color values. + palette: *const color.Palette, + + /// If specified, the color to use for bold text. + bold: ?configpkg.BoldColor = null, + }; + + /// Returns the fg color for a cell with this style given the palette + /// and various configuration options. pub fn fg( self: Style, - palette: *const color.Palette, - bold_is_bright: bool, - ) ?color.RGB { + opts: Fg, + ) color.RGB { + // Note we don't pull the bold check to the top-level here because + // we don't want to duplicate the conditional multiple times since + // certain colors require more checks (e.g. `bold_is_bright`). + return switch (self.fg_color) { - .none => null, - .palette => |idx| palette: { - if (bold_is_bright and self.flags.bold) { - const bright_offset = @intFromEnum(color.Name.bright_black); - if (idx < bright_offset) - break :palette palette[idx + bright_offset]; + .none => default: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .bright => {}, + .color => |v| break :default v.toTerminalRGB(), + }; } - break :palette palette[idx]; + break :default opts.default; + }, + + .palette => |idx| palette: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :palette v.toTerminalRGB(), + .bright => { + const bright_offset = @intFromEnum(color.Name.bright_black); + if (idx < bright_offset) { + break :palette opts.palette[idx + bright_offset]; + } + }, + }; + } + + break :palette opts.palette[idx]; + }, + + .rgb => |rgb| rgb: { + if (self.flags.bold and rgb.eql(opts.default)) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :rgb v.toTerminalRGB(), + .bright => {}, + }; + } + + break :rgb rgb; }, - .rgb => |rgb| rgb, }; } From 3ff11cdd86cd8a5a0d26631a8935edf5f1dfeede Mon Sep 17 00:00:00 2001 From: "Sl (Shahaf Levi)" Date: Sun, 6 Jul 2025 23:38:22 +0300 Subject: [PATCH 108/119] Add Hebrew Translations --- CODEOWNERS | 1 + po/he_IL.UTF-8.po | 298 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 3 files changed, 300 insertions(+) create mode 100644 po/he_IL.UTF-8.po diff --git a/CODEOWNERS b/CODEOWNERS index 7995650b7..3bb6a4123 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,6 +181,7 @@ /po/zh_CN.UTF-8.po @ghostty-org/zh_CN /po/ga_IE.UTF-8.po @ghostty-org/ga_IE /po/ko_KR.UTF-8.po @ghostty-org/ko_KR +/po/he_IL.UTF-8.po @ghostty-org/he_IL # Packaging - Snap /snap/ @ghostty-org/snap diff --git a/po/he_IL.UTF-8.po b/po/he_IL.UTF-8.po new file mode 100644 index 000000000..636bf46e3 --- /dev/null +++ b/po/he_IL.UTF-8.po @@ -0,0 +1,298 @@ +# Hebrew translations for com.mitchellh.ghostty. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Sl (Shahaf Levi), Sl's Repository Ltd , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-06-28 17:01+0200\n" +"PO-Revision-Date: 2025-03-13 00:00+0000\n" +"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd \n" +"Language-Team: Hebrew \n" +"Language: he\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "שינוי כותרת המסוף" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "השאר/י ריק כדי לשחזר את כותרת ברירת המחדל." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10 +#: src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "ביטול" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "אישור" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "שגיאות בהגדרות" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "נמצאו אחת או יותר שגיאות בהגדרות. אנא בדוק/י את השגיאות המופיעות מטה ולאחר מכן טען/י את ההגדרות מחדש או התעלם/י מהשגיאות." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "התעלמות" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +msgid "Reload Configuration" +msgstr "טעינה מחדש של ההגדרות" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "פיצול למעלה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "פיצול למטה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "פיצול שמאלה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "פיצול ימינה" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:16 +msgid "Execute a command…" +msgstr "הרץ/י פקודה…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "העתקה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11 +msgid "Paste" +msgstr "הדבקה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "ניקוי" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "איפוס" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "פיצול" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "שינוי כותרת…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "כרטיסייה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:263 +msgid "New Tab" +msgstr "כרטיסייה חדשה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "סגור/י כרטיסייה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "חלון" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "חלון חדש" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "סגור/י חלון" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "הגדרות" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Open Configuration" +msgstr "פתיחת ההגדרות" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Command Palette" +msgstr "לוח פקודות" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Terminal Inspector" +msgstr "בודק המסוף" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +#: src/apprt/gtk/Window.zig:1036 +msgid "About Ghostty" +msgstr "אודות Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112 +msgid "Quit" +msgstr "יציאה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "אשר/י גישה ללוח ההעתקה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "יש אפליקציה שמנסה לקרוא מלוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "דחייה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "אישור" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77 +msgid "Remember choice for this split" +msgstr "זכור/י את הבחירה עבור פיצול זה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78 +msgid "Reload configuration to show this prompt again" +msgstr "טען/י את ההגדרות מחדש כדי להציג את הבקשה הזו שוב" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "יש אפליקציה שמנסה לכתוב לתוך לוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "אזהרה: ההדבקה עלולה להיות מסוכנת" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "הדבקת טקסט זה במסוף עלולה להיות מסוכנת, מכיוון שככל הנראה היא תוביל להרצה של פקודות מסוימות." + +#: src/apprt/gtk/Window.zig:216 +msgid "Main Menu" +msgstr "תפריט ראשי" + +#: src/apprt/gtk/Window.zig:238 +msgid "View Open Tabs" +msgstr "הצג/י כרטיסיות פתוחות" + +#: src/apprt/gtk/Window.zig:264 +msgid "New Split" +msgstr "פיצול חדש" + +#: src/apprt/gtk/Window.zig:327 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ את/ה מריץ/ה גרסת ניפוי שגיאות של Ghostty! הביצועים יהיו ירודים." + +#: src/apprt/gtk/Window.zig:773 +msgid "Reloaded the configuration" +msgstr "ההגדרות הוטענו מחדש" + +#: src/apprt/gtk/Window.zig:1017 +msgid "Ghostty Developers" +msgstr "המפתחים של Ghostty" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: בודק המסוף" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "סגירה" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "לצאת מGhostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "לסגור את החלון?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "לסגור את הכרטיסייה?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "לסגור את הפיצול?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "כל הפעלות המסוף יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "כל הפעלות המסוף בחלון זה יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "כל הפעלות המסוף בכרטיסייה זו יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "התהליך שרץ כרגע בפיצול זה יסתיים." + +#: src/apprt/gtk/Surface.zig:1257 +msgid "Copied to clipboard" +msgstr "הועתק ללוח ההעתקה" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index a4d6c1577..2ecae27ac 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -49,6 +49,7 @@ pub const locales = [_][:0]const u8{ "ca_ES.UTF-8", "bg_BG.UTF-8", "ga_IE.UTF-8", + "he_IL.UTF-8", }; /// Set for faster membership lookup of locales. From b10b0f06c329b865b72ee40c3122f009c457f2d4 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 15:53:59 -0600 Subject: [PATCH 109/119] font: remove unused fields from Glyph We can reintroduce `advance` if we ever want to do proportional string drawing, but we don't use it anywhere right now. And we also don't need `sprite` anymore since that was just there to disable constraints for sprites back when we did them on the GPU. --- src/font/Glyph.zig | 6 ------ src/font/face/coretext.zig | 6 ------ src/font/face/freetype.zig | 2 -- src/font/face/web_canvas.zig | 1 - src/font/sprite/Face.zig | 3 --- 5 files changed, 18 deletions(-) diff --git a/src/font/Glyph.zig b/src/font/Glyph.zig index fa29e44fa..f99370271 100644 --- a/src/font/Glyph.zig +++ b/src/font/Glyph.zig @@ -17,9 +17,3 @@ offset_y: i32, /// be normalized to be between 0 and 1 prior to use in shaders. atlas_x: u32, atlas_y: u32, - -/// horizontal position to increase drawing position for strings -advance_x: f32, - -/// Whether we drew this glyph ourselves with the sprite font. -sprite: bool = false, diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 5c9c259d2..7d750b0d6 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -333,7 +333,6 @@ pub const Face = struct { .offset_y = 0, .atlas_x = 0, .atlas_y = 0, - .advance_x = 0, }; const metrics = opts.grid_metrics; @@ -498,10 +497,6 @@ pub const Face = struct { break :offset_x result; }; - // Get our advance - var advances: [glyphs.len]macos.graphics.Size = undefined; - _ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances); - return .{ .width = px_width, .height = px_height, @@ -509,7 +504,6 @@ pub const Face = struct { .offset_y = offset_y, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = @floatCast(advances[0].width), }; } diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index b27b28ab8..079cf5b2d 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -373,7 +373,6 @@ pub const Face = struct { .offset_y = 0, .atlas_x = 0, .atlas_y = 0, - .advance_x = 0, }; // For synthetic bold, we embolden the glyph. @@ -662,7 +661,6 @@ pub const Face = struct { .offset_y = offset_y, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = f26dot6ToFloat(glyph.*.advance.x), }; } diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 30540191d..7ea2f0426 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -235,7 +235,6 @@ pub const Face = struct { .offset_y = 0, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = 0, }; } diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 1463fb38b..dfff8fa75 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -195,7 +195,6 @@ pub fn renderGlyph( .offset_y = 0, .atlas_x = 0, .atlas_y = 0, - .advance_x = 0, }; const metrics = self.metrics; @@ -227,8 +226,6 @@ pub fn renderGlyph( .offset_y = @as(i32, @intCast(region.height +| canvas.clip_bottom)) - @as(i32, @intCast(padding_y)), .atlas_x = region.x, .atlas_y = region.y, - .advance_x = @floatFromInt(width), - .sprite = true, }; } From 9583ea1b7ae2d2ae40fa03599901fb9378154ab7 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 25 Feb 2025 11:59:46 -0600 Subject: [PATCH 110/119] core/gtk: open urls using an apprt action instead of doing it directly Partial implementation of #5256 This implements the core changes necessary to open urls using an apprt action rather than doing it directly from the core. Implements the open_url action in the GTK and GLFW apprts. Note that this should not be merged until a macOS-savvy developer can add an implementation of the open_url action for the macOS apprt. --- include/ghostty.h | 17 ++++- src/Surface.zig | 20 +++++- src/apprt/action.zig | 49 ++++++++++++- src/apprt/gtk/App.zig | 11 +++ src/os/open.zig | 156 +++++++++++++++++++++++++++++++----------- 5 files changed, 209 insertions(+), 44 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 73c708c6b..16ca21d89 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -662,6 +662,19 @@ typedef struct { bool soft; } ghostty_action_reload_config_s; +// apprt.action.OpenUrlKind +typedef enum { + GHOSTTY_ACTION_OPEN_URL_KIND_TEXT, + GHOSTTY_ACTION_OPEN_URL_KIND_UNKNOWN, +} ghostty_action_open_url_kind_e; + +// apprt.action.OpenUrl.C +typedef struct { + ghostty_action_open_url_kind_e kind; + const char* url; + uintptr_t len; +} ghostty_action_open_url_s; + // apprt.Action.Key typedef enum { GHOSTTY_ACTION_QUIT, @@ -711,7 +724,8 @@ typedef enum { GHOSTTY_ACTION_RING_BELL, GHOSTTY_ACTION_UNDO, GHOSTTY_ACTION_REDO, - GHOSTTY_ACTION_CHECK_FOR_UPDATES + GHOSTTY_ACTION_CHECK_FOR_UPDATES, + GHOSTTY_ACTION_OPEN_URL, } ghostty_action_tag_e; typedef union { @@ -739,6 +753,7 @@ typedef union { ghostty_action_color_change_s color_change; ghostty_action_reload_config_s reload_config; ghostty_action_config_change_s config_change; + ghostty_action_open_url_s open_url; } ghostty_action_u; typedef struct { diff --git a/src/Surface.zig b/src/Surface.zig index 33cf581af..db272dddc 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3724,7 +3724,11 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { .trim = false, }); defer self.alloc.free(str); - try internal_os.open(self.alloc, .unknown, str); + _ = try self.rt_app.performAction( + .{ .surface = self }, + .open_url, + .{ .kind = .unknown, .url = str }, + ); }, ._open_osc8 => { @@ -3732,7 +3736,11 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { log.warn("failed to get URI for OSC8 hyperlink", .{}); return false; }; - try internal_os.open(self.alloc, .unknown, uri); + _ = try self.rt_app.performAction( + .{ .surface = self }, + .open_url, + .{ .kind = .unknown, .url = uri }, + ); }, } @@ -4957,7 +4965,13 @@ fn writeScreenFile( defer self.alloc.free(pathZ); try self.rt_surface.setClipboardString(pathZ, .standard, false); }, - .open => try internal_os.open(self.alloc, .text, path), + .open => { + _ = try self.rt_app.performAction( + .{ .surface = self }, + .open_url, + .{ .kind = .text, .url = path }, + ); + }, .paste => self.io.queueMessage(try termio.Message.writeReq( self.alloc, path, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index b4c5164c2..6c33a296f 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); @@ -267,6 +268,11 @@ pub const Action = union(Key) { check_for_updates, + /// Open a URL using the native OS mechanisms. On macOS this might be `open` + /// or on Linux this might be `xdg-open`. The exact mechanism is up to the + /// apprt. + open_url: OpenUrl, + /// Sync with: ghostty_action_tag_e pub const Key = enum(c_int) { quit, @@ -317,6 +323,7 @@ pub const Action = union(Key) { undo, redo, check_for_updates, + open_url, }; /// Sync with: ghostty_action_u @@ -357,7 +364,13 @@ pub const Action = union(Key) { // For ABI compatibility, we expect that this is our union size. // At the time of writing, we don't promise ABI compatibility // so we can change this but I want to be aware of it. - assert(@sizeOf(CValue) == 16); + assert(@sizeOf(CValue) == switch (builtin.target.os.tag) { + .windows => switch (builtin.target.cpu.arch) { + .x86 => 16, + else => 24, + }, + else => 24, + }); } /// Returns the value type for the given key. @@ -614,3 +627,37 @@ pub const ConfigChange = struct { }; } }; + +/// The type of the data at the URL to open. This is used as a hint to +/// potentially open the URL in a different way. +/// Sync with: ghostty_action_open_url_kind_s +pub const OpenUrlKind = enum(c_int) { + text, + unknown, +}; + +/// Open a URL +pub const OpenUrl = struct { + /// The type of data that the URL refers to. + kind: OpenUrlKind, + /// The URL. + url: []const u8, + + // Sync with: ghostty_action_open_url_s + pub const C = extern struct { + /// The type of data that the URL refers to. + kind: OpenUrlKind, + /// The URL (not zero terminated). + url: [*]const u8, + /// The number of bytes in the URL. + len: usize, + }; + + pub fn cval(self: OpenUrl) C { + return .{ + .kind = self.kind, + .url = self.url.ptr, + .len = self.url.len, + }; + } +}; diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index c61254fbd..a046291ef 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -519,6 +519,7 @@ pub fn performAction( .secure_input => self.setSecureInput(target, value), .ring_bell => try self.ringBell(target), .toggle_command_palette => try self.toggleCommandPalette(target), + .open_url => self.openUrl(value), // Unimplemented .close_all_windows, @@ -1757,3 +1758,13 @@ fn initActions(self: *App) void { action_map.addAction(action.as(gio.Action)); } } + +// TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html +pub fn openUrl( + app: *App, + value: apprt.action.OpenUrl, +) void { + internal_os.open(app.core_app.alloc, value.kind, value.url) catch |err| { + log.warn("unable to open url: {}", .{err}); + }; +} diff --git a/src/os/open.zig b/src/os/open.zig index ce62a7e0b..6841c76ab 100644 --- a/src/os/open.zig +++ b/src/os/open.zig @@ -2,14 +2,10 @@ const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; -const log = std.log.scoped(.@"os-open"); +const apprt = @import("../apprt.zig"); +const CircBuf = @import("../datastruct/circ_buf.zig").CircBuf; -/// The type of the data at the URL to open. This is used as a hint -/// to potentially open the URL in a different way. -pub const Type = enum { - text, - unknown, -}; +const log = std.log.scoped(.@"os-open"); /// Open a URL in the default handling application. /// @@ -18,9 +14,39 @@ pub const Type = enum { /// log output and may allocate from another thread. pub fn open( alloc: Allocator, - typ: Type, + kind: apprt.action.OpenUrlKind, url: []const u8, ) !void { + // Make a copy of the URL so that we can use it in the thread without + // worrying about it getting freed by other threads. + const copy = try alloc.dupe(u8, url); + errdefer alloc.free(copy); + + // Run in a thread so that it never blocks the main thread, no matter how + // long it takes to execute. + const thread = try std.Thread.spawn(.{}, _openThread, .{ alloc, kind, copy }); + + // Don't worry about the thread any more. + thread.detach(); +} + +fn _openThread( + alloc: Allocator, + kind: apprt.action.OpenUrlKind, + url: []const u8, +) void { + _openThreadError(alloc, kind, url) catch |err| { + log.warn("error while opening url: {}", .{err}); + }; +} + +fn _openThreadError( + alloc: Allocator, + kind: apprt.action.OpenUrlKind, + url: []const u8, +) !void { + defer alloc.free(url); + var exe: std.process.Child = switch (builtin.os.tag) { .linux, .freebsd => .init( &.{ "xdg-open", url }, @@ -33,7 +59,7 @@ pub fn open( ), .macos => .init( - switch (typ) { + switch (kind) { .text => &.{ "open", "-t", url }, .unknown => &.{ "open", url }, }, @@ -44,43 +70,95 @@ pub fn open( else => @compileError("unsupported OS"), }; - // Pipe stdout/stderr so we can collect output from the command. + // Ignore stdin & stdout, collect the output from stderr. // This must be set before spawning the process. - exe.stdout_behavior = .Pipe; + exe.stdin_behavior = .Ignore; + exe.stdout_behavior = .Ignore; exe.stderr_behavior = .Pipe; - // Spawn the process on our same thread so we can detect failure - // quickly. - try exe.spawn(); + exe.spawn() catch |err| { + switch (err) { + error.FileNotFound => { + log.warn("Unable to find {s}. Please install {s} and ensure that it is available on the PATH.", .{ + exe.argv[0], + exe.argv[0], + }); + }, + else => |e| return e, + } + return; + }; - // Create a thread that handles collecting output and reaping - // the process. This is done in a separate thread because SOME - // open implementations block and some do not. It's easier to just - // spawn a thread to handle this so that we never block. - const thread = try std.Thread.spawn(.{}, openThread, .{ alloc, exe }); - thread.detach(); -} + const stderr = exe.stderr orelse { + log.warn("Unable to access the stderr of the spawned program!", .{}); + return; + }; -fn openThread(alloc: Allocator, exe_: std.process.Child) !void { - // 50 KiB is the default value used by std.process.Child.run and should - // be enough to get the output we care about. - const output_max_size = 50 * 1024; + var cb = try CircBuf(u8, 0).init(alloc, 50 * 1024); + defer cb.deinit(alloc); - var stdout: std.ArrayListUnmanaged(u8) = .{}; - var stderr: std.ArrayListUnmanaged(u8) = .{}; - defer { - stdout.deinit(alloc); - stderr.deinit(alloc); + // Read any error output and store it in a circular buffer so that we + // get that _last_ 50K of output. + while (true) { + var buf: [1024]u8 = undefined; + const len = try stderr.read(&buf); + if (len == 0) break; + try cb.appendSlice(buf[0..len]); } - // Copy the exe so it is non-const. This is necessary because wait() - // requires a mutable reference and we can't have one as a thread - // param. - var exe = exe_; - try exe.collectOutput(alloc, &stdout, &stderr, output_max_size); - _ = try exe.wait(); + // If we have any stderr output we log it. This makes it easier for users to + // debug why some open commands may not work as expected. + if (cb.len() > 0) log: { + { + var it = cb.iterator(.forward); + while (it.next()) |char| { + if (std.mem.indexOfScalar(u8, &std.ascii.whitespace, char.*)) |_| continue; + break; + } + // it's all whitespace, don't log + break :log; + } + var buf = std.ArrayList(u8).init(alloc); + defer buf.deinit(); + var it = cb.iterator(.forward); + while (it.next()) |char| { + if (char.* == '\n') { + log.err("{s} stderr: {s}", .{ exe.argv[0], buf.items }); + buf.clearRetainingCapacity(); + } + try buf.append(char.*); + } + if (buf.items.len > 0) + log.err("{s} stderr: {s}", .{buf.items}); + } - // If we have any stderr output we log it. This makes it easier for - // users to debug why some open commands may not work as expected. - if (stderr.items.len > 0) log.warn("wait stderr={s}", .{stderr.items}); + const rc = exe.wait() catch |err| { + switch (err) { + error.FileNotFound => { + log.warn("Unable to find {s}. Please install {s} and ensure that it is available on the PATH.", .{ + exe.argv[0], + exe.argv[0], + }); + }, + else => |e| return e, + } + return; + }; + + switch (rc) { + .Exited => |code| { + if (code != 0) { + log.warn("{s} exited with error code {d}", .{ exe.argv[0], code }); + } + }, + .Signal => |signal| { + log.warn("{s} was terminaled with signal {}", .{ exe.argv[0], signal }); + }, + .Stopped => |signal| { + log.warn("{s} was stopped with signal {}", .{ exe.argv[0], signal }); + }, + .Unknown => |code| { + log.warn("{s} had an unknown error {}", .{ exe.argv[0], code }); + }, + } } From 70a2a0afd5072dbe77d50f5616a44b2bcba72581 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 8 May 2025 19:28:28 -0500 Subject: [PATCH 111/119] better ABI check for apprt.Action.CValue --- src/apprt/action.zig | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 6c33a296f..79f8740d2 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const builtin = @import("builtin"); const assert = std.debug.assert; const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); @@ -364,12 +363,10 @@ pub const Action = union(Key) { // For ABI compatibility, we expect that this is our union size. // At the time of writing, we don't promise ABI compatibility // so we can change this but I want to be aware of it. - assert(@sizeOf(CValue) == switch (builtin.target.os.tag) { - .windows => switch (builtin.target.cpu.arch) { - .x86 => 16, - else => 24, - }, - else => 24, + assert(@sizeOf(CValue) == switch (@sizeOf(usize)) { + 4 => 16, + 8 => 24, + else => unreachable, }); } From cbcb0b795c43453d22d2138ae5edc8deb7fa3727 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Jul 2025 15:10:14 -0700 Subject: [PATCH 112/119] Fallback to cross-platform minimal open when apprt is not available --- include/ghostty.h | 2 +- src/Surface.zig | 42 ++++++----- src/apprt/action.zig | 33 +++++---- src/apprt/gtk/App.zig | 14 ++-- src/os/open.zig | 161 +++++++++++------------------------------- 5 files changed, 97 insertions(+), 155 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 16ca21d89..2a4a7fb6e 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -664,8 +664,8 @@ typedef struct { // apprt.action.OpenUrlKind typedef enum { - GHOSTTY_ACTION_OPEN_URL_KIND_TEXT, GHOSTTY_ACTION_OPEN_URL_KIND_UNKNOWN, + GHOSTTY_ACTION_OPEN_URL_KIND_TEXT, } ghostty_action_open_url_kind_e; // apprt.action.OpenUrl.C diff --git a/src/Surface.zig b/src/Surface.zig index db272dddc..a4a8d46df 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3724,11 +3724,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { .trim = false, }); defer self.alloc.free(str); - _ = try self.rt_app.performAction( - .{ .surface = self }, - .open_url, - .{ .kind = .unknown, .url = str }, - ); + try self.openUrl(.{ .kind = .unknown, .url = str }); }, ._open_osc8 => { @@ -3736,17 +3732,35 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { log.warn("failed to get URI for OSC8 hyperlink", .{}); return false; }; - _ = try self.rt_app.performAction( - .{ .surface = self }, - .open_url, - .{ .kind = .unknown, .url = uri }, - ); + try self.openUrl(.{ .kind = .unknown, .url = uri }); }, } return true; } +fn openUrl( + self: *Surface, + action: apprt.action.OpenUrl, +) !void { + // If the apprt handles it then we're done. + if (try self.rt_app.performAction( + .{ .surface = self }, + .open_url, + action, + )) return; + + // apprt didn't handle it, fallback to our simple cross-platform + // URL opener. We log a warning because we want well-behaved + // apprts to handle this themselves. + log.warn("apprt did not handle open URL action, falling back to default opener", .{}); + try internal_os.open( + self.alloc, + action.kind, + action.url, + ); +} + /// Return the URI for an OSC8 hyperlink at the given position or null /// if there is no hyperlink. fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 { @@ -4965,13 +4979,7 @@ fn writeScreenFile( defer self.alloc.free(pathZ); try self.rt_surface.setClipboardString(pathZ, .standard, false); }, - .open => { - _ = try self.rt_app.performAction( - .{ .surface = self }, - .open_url, - .{ .kind = .text, .url = path }, - ); - }, + .open => try self.openUrl(.{ .kind = .text, .url = path }), .paste => self.io.queueMessage(try termio.Message.writeReq( self.alloc, path, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 79f8740d2..1c3c7c72c 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -625,28 +625,35 @@ pub const ConfigChange = struct { } }; -/// The type of the data at the URL to open. This is used as a hint to -/// potentially open the URL in a different way. -/// Sync with: ghostty_action_open_url_kind_s -pub const OpenUrlKind = enum(c_int) { - text, - unknown, -}; - /// Open a URL pub const OpenUrl = struct { /// The type of data that the URL refers to. - kind: OpenUrlKind, + kind: Kind, + /// The URL. url: []const u8, + /// The type of the data at the URL to open. This is used as a hint to + /// potentially open the URL in a different way. + /// + /// Sync with: ghostty_action_open_url_kind_e + pub const Kind = enum(c_int) { + /// The type is unknown. This is the default and apprts should + /// open the URL in the most generic way possible. For example, + /// on macOS this would be the equivalent of `open` or on Linux + /// this would be `xdg-open`. + unknown, + + /// The URL is known to be a text file. In this case, the apprt + /// should try to open the URL in a text editor or viewer or + /// some equivalent, if possible. + text, + }; + // Sync with: ghostty_action_open_url_s pub const C = extern struct { - /// The type of data that the URL refers to. - kind: OpenUrlKind, - /// The URL (not zero terminated). + kind: Kind, url: [*]const u8, - /// The number of bytes in the URL. len: usize, }; diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index a046291ef..369090ee2 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -1759,12 +1759,18 @@ fn initActions(self: *App) void { } } -// TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html pub fn openUrl( app: *App, value: apprt.action.OpenUrl, ) void { - internal_os.open(app.core_app.alloc, value.kind, value.url) catch |err| { - log.warn("unable to open url: {}", .{err}); - }; + // TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html + + // Fallback to the minimal cross-platform way of opening a URL. + // This is always a safe fallback and enables for example Windows + // to open URLs (GTK on Windows via WSL is a thing). + internal_os.open( + app.core_app.alloc, + value.kind, + value.url, + ) catch |err| log.warn("unable to open url: {}", .{err}); } diff --git a/src/os/open.zig b/src/os/open.zig index 6841c76ab..9b069c80f 100644 --- a/src/os/open.zig +++ b/src/os/open.zig @@ -1,9 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; - const apprt = @import("../apprt.zig"); -const CircBuf = @import("../datastruct/circ_buf.zig").CircBuf; const log = std.log.scoped(.@"os-open"); @@ -12,41 +10,16 @@ const log = std.log.scoped(.@"os-open"); /// Any output on stderr is logged as a warning in the application logs. /// Output on stdout is ignored. The allocator is used to buffer the /// log output and may allocate from another thread. +/// +/// This function is purposely simple for the sake of providing +/// some portable way to open URLs. If you are implementing an +/// apprt for Ghostty, you should consider doing something special-cased +/// for your platform. pub fn open( alloc: Allocator, - kind: apprt.action.OpenUrlKind, + kind: apprt.action.OpenUrl.Kind, url: []const u8, ) !void { - // Make a copy of the URL so that we can use it in the thread without - // worrying about it getting freed by other threads. - const copy = try alloc.dupe(u8, url); - errdefer alloc.free(copy); - - // Run in a thread so that it never blocks the main thread, no matter how - // long it takes to execute. - const thread = try std.Thread.spawn(.{}, _openThread, .{ alloc, kind, copy }); - - // Don't worry about the thread any more. - thread.detach(); -} - -fn _openThread( - alloc: Allocator, - kind: apprt.action.OpenUrlKind, - url: []const u8, -) void { - _openThreadError(alloc, kind, url) catch |err| { - log.warn("error while opening url: {}", .{err}); - }; -} - -fn _openThreadError( - alloc: Allocator, - kind: apprt.action.OpenUrlKind, - url: []const u8, -) !void { - defer alloc.free(url); - var exe: std.process.Child = switch (builtin.os.tag) { .linux, .freebsd => .init( &.{ "xdg-open", url }, @@ -70,95 +43,43 @@ fn _openThreadError( else => @compileError("unsupported OS"), }; - // Ignore stdin & stdout, collect the output from stderr. + // Pipe stdout/stderr so we can collect output from the command. // This must be set before spawning the process. - exe.stdin_behavior = .Ignore; - exe.stdout_behavior = .Ignore; + exe.stdout_behavior = .Pipe; exe.stderr_behavior = .Pipe; - exe.spawn() catch |err| { - switch (err) { - error.FileNotFound => { - log.warn("Unable to find {s}. Please install {s} and ensure that it is available on the PATH.", .{ - exe.argv[0], - exe.argv[0], - }); - }, - else => |e| return e, - } - return; - }; + // Spawn the process on our same thread so we can detect failure + // quickly. + try exe.spawn(); - const stderr = exe.stderr orelse { - log.warn("Unable to access the stderr of the spawned program!", .{}); - return; - }; - - var cb = try CircBuf(u8, 0).init(alloc, 50 * 1024); - defer cb.deinit(alloc); - - // Read any error output and store it in a circular buffer so that we - // get that _last_ 50K of output. - while (true) { - var buf: [1024]u8 = undefined; - const len = try stderr.read(&buf); - if (len == 0) break; - try cb.appendSlice(buf[0..len]); - } - - // If we have any stderr output we log it. This makes it easier for users to - // debug why some open commands may not work as expected. - if (cb.len() > 0) log: { - { - var it = cb.iterator(.forward); - while (it.next()) |char| { - if (std.mem.indexOfScalar(u8, &std.ascii.whitespace, char.*)) |_| continue; - break; - } - // it's all whitespace, don't log - break :log; - } - var buf = std.ArrayList(u8).init(alloc); - defer buf.deinit(); - var it = cb.iterator(.forward); - while (it.next()) |char| { - if (char.* == '\n') { - log.err("{s} stderr: {s}", .{ exe.argv[0], buf.items }); - buf.clearRetainingCapacity(); - } - try buf.append(char.*); - } - if (buf.items.len > 0) - log.err("{s} stderr: {s}", .{buf.items}); - } - - const rc = exe.wait() catch |err| { - switch (err) { - error.FileNotFound => { - log.warn("Unable to find {s}. Please install {s} and ensure that it is available on the PATH.", .{ - exe.argv[0], - exe.argv[0], - }); - }, - else => |e| return e, - } - return; - }; - - switch (rc) { - .Exited => |code| { - if (code != 0) { - log.warn("{s} exited with error code {d}", .{ exe.argv[0], code }); - } - }, - .Signal => |signal| { - log.warn("{s} was terminaled with signal {}", .{ exe.argv[0], signal }); - }, - .Stopped => |signal| { - log.warn("{s} was stopped with signal {}", .{ exe.argv[0], signal }); - }, - .Unknown => |code| { - log.warn("{s} had an unknown error {}", .{ exe.argv[0], code }); - }, - } + // Create a thread that handles collecting output and reaping + // the process. This is done in a separate thread because SOME + // open implementations block and some do not. It's easier to just + // spawn a thread to handle this so that we never block. + const thread = try std.Thread.spawn(.{}, openThread, .{ alloc, exe }); + thread.detach(); +} + +fn openThread(alloc: Allocator, exe_: std.process.Child) !void { + // 50 KiB is the default value used by std.process.Child.run and should + // be enough to get the output we care about. + const output_max_size = 50 * 1024; + + var stdout: std.ArrayListUnmanaged(u8) = .{}; + var stderr: std.ArrayListUnmanaged(u8) = .{}; + defer { + stdout.deinit(alloc); + stderr.deinit(alloc); + } + + // Copy the exe so it is non-const. This is necessary because wait() + // requires a mutable reference and we can't have one as a thread + // param. + var exe = exe_; + try exe.collectOutput(alloc, &stdout, &stderr, output_max_size); + _ = try exe.wait(); + + // If we have any stderr output we log it. This makes it easier for + // users to debug why some open commands may not work as expected. + if (stderr.items.len > 0) log.warn("wait stderr={s}", .{stderr.items}); } From d3aece21d8eb5985681f0d57ed7f280c9a7065b5 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 16:32:22 -0600 Subject: [PATCH 113/119] font: more generic bearing adjustments This generally adjusts the bearings of any glyph whose original advance was narrower than the cell, which helps a lot with proportional fallback glyphs so they aren't just left-aligned. This only applies to situations where the glyph was originally narrower than the cell, so that we don't mess up ligatures, and this centers the old advance width in the new one rather than adjusting proportionally, because otherwise we can mess up glyphs that are meant to align with others when placed vertically. --- src/font/face/coretext.zig | 48 +++++++++++++++++++++++++++----------- src/font/face/freetype.zig | 46 +++++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 7d750b0d6..6aedd7696 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -481,20 +481,42 @@ pub const Face = struct { // This should be the distance from the left of // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = @intFromFloat(@round(x)); - - // If our cell was resized then we adjust our glyph's - // position relative to the new center. This keeps glyphs - // centered in the cell whether it was made wider or narrower. - if (metrics.original_cell_width) |original_width| { - const before: i32 = @intCast(original_width); - const after: i32 = @intCast(metrics.cell_width); - // Increase the offset by half of the difference - // between the widths to keep things centered. - result += @divTrunc(after - before, 2); + // If the glyph's advance is narrower than the cell width then we + // center the advance of the glyph within the cell width. At first + // I implemented this to proportionally scale the center position + // of the glyph but that messes up glyphs that are meant to align + // vertically with others, so this is a compromise. + // + // This makes it so that when the `adjust-cell-width` config is + // used, or when a fallback font with a different advance width + // is used, we don't get weirdly aligned glyphs. + // + // We don't do this if the constraint has a horizontal alignment, + // since in that case the position was already calculated with the + // new cell width in mind. + if (opts.constraint.align_horizontal == .none) { + var advances: [glyphs.len]macos.graphics.Size = undefined; + _ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances); + const advance = advances[0].width; + const new_advance = + cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1)); + // If the original advance is greater than the cell width then + // it's possible that this is a ligature or other glyph that is + // intended to overflow the cell to one side or the other, and + // adjusting the bearings could mess that up, so we just leave + // it alone if that's the case. + // + // We also don't want to do anything if the advance is zero or + // less, since this is used for stuff like combining characters. + if (advance > new_advance or advance <= 0.0) { + break :offset_x @intFromFloat(@round(x)); + } + break :offset_x @intFromFloat( + @round(x + (new_advance - advance) / 2), + ); + } else { + break :offset_x @intFromFloat(@round(x)); } - - break :offset_x result; }; return .{ diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 079cf5b2d..6aeb951af 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -638,20 +638,40 @@ pub const Face = struct { // This should be the distance from the left of // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = @intFromFloat(@floor(x)); - - // If our cell was resized then we adjust our glyph's - // position relative to the new center. This keeps glyphs - // centered in the cell whether it was made wider or narrower. - if (metrics.original_cell_width) |original_width| { - const before: i32 = @intCast(original_width); - const after: i32 = @intCast(metrics.cell_width); - // Increase the offset by half of the difference - // between the widths to keep things centered. - result += @divTrunc(after - before, 2); + // If the glyph's advance is narrower than the cell width then we + // center the advance of the glyph within the cell width. At first + // I implemented this to proportionally scale the center position + // of the glyph but that messes up glyphs that are meant to align + // vertically with others, so this is a compromise. + // + // This makes it so that when the `adjust-cell-width` config is + // used, or when a fallback font with a different advance width + // is used, we don't get weirdly aligned glyphs. + // + // We don't do this if the constraint has a horizontal alignment, + // since in that case the position was already calculated with the + // new cell width in mind. + if (opts.constraint.align_horizontal == .none) { + const advance = f26dot6ToFloat(glyph.*.advance.x); + const new_advance = + cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1)); + // If the original advance is greater than the cell width then + // it's possible that this is a ligature or other glyph that is + // intended to overflow the cell to one side or the other, and + // adjusting the bearings could mess that up, so we just leave + // it alone if that's the case. + // + // We also don't want to do anything if the advance is zero or + // less, since this is used for stuff like combining characters. + if (advance > new_advance or advance <= 0.0) { + break :offset_x @intFromFloat(@floor(x)); + } + break :offset_x @intFromFloat( + @floor(x + (new_advance - advance) / 2), + ); + } else { + break :offset_x @intFromFloat(@floor(x)); } - - break :offset_x result; }; return Glyph{ From 65a7c81c94432a94eb5ccb8ca8d835c3f083290b Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sun, 6 Jul 2025 18:39:32 -0400 Subject: [PATCH 114/119] bash: conditionally add cursor shape sequences In #7808, we stopped using PS0 to reset the cursor shape because restoring PS0 in __ghostty_preexec was causing issues (#7802). The alternate approach of printing the cursor reset escape sequence directly from __ghostty_preexec caused a new issue: the input cursor would persist longer than intended, such as when a suspended vim process was restored to the foreground. This change takes a different approach. We now conditionally add the cursor shape escape sequences to PS0 (and PS1, for consistency) when they don't already appear. The fixes the cursor shape reset problem. The main downside to this approach is that PS0 will continue to contain this escape sequence; it won't be cleared/reset in __ghostty_preexec for the reasons described in #7808. This feels like an acceptable outcome because there's no harm in the modified PS0 existing for the life of the bash session (rather than it being modified and then restored for each command cycle), and it's consistent with how some other terminals' bash integration works (e.g. kitty). --- src/shell-integration/bash/ghostty.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 21a6965ca..df4c7f9a7 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -122,8 +122,8 @@ function __ghostty_precmd() { # Cursor if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then - PS1=$PS1'\[\e[5 q\]' # blinking bar for input - builtin printf "\e[0 q" # reset to default cursor + [[ "$PS1" != *'\[\e[5 q\]'* ]] && PS1=$PS1'\[\e[5 q\]' # input + [[ "$PS0" != *'\[\e[0 q\]'* ]] && PS0=$PS0'\[\e[0 q\]' # reset fi # Title (working directory) From db08bf1655d10f5c459f41f2c01963bede98db55 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 16:37:15 -0600 Subject: [PATCH 115/119] font: adjust fallback font sizes to match primary metrics This better harmonizes fallback fonts with the primary font by matching the heights of lowercase letters. This should be a big improvement for users who use mixed scripts and so rely heavily on fallback fonts. --- src/font/Collection.zig | 169 ++++++++++++++++++++++++++++++++----- src/font/Metrics.zig | 12 +++ src/font/face/coretext.zig | 15 ++++ src/font/face/freetype.zig | 22 ++++- 4 files changed, 196 insertions(+), 22 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 8533331bc..cdbd3d84f 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -69,10 +69,14 @@ pub fn deinit(self: *Collection, alloc: Allocator) void { if (self.load_options) |*v| v.deinit(alloc); } -pub const AddError = Allocator.Error || error{ - CollectionFull, - DeferredLoadingUnavailable, -}; +pub const AddError = + Allocator.Error || + AdjustSizeError || + error{ + CollectionFull, + DeferredLoadingUnavailable, + SetSizeFailed, + }; /// Add a face to the collection for the given style. This face will be added /// next in priority if others exist already, i.e. it'll be the _last_ to be @@ -81,10 +85,9 @@ pub const AddError = Allocator.Error || error{ /// If no error is encountered then the collection takes ownership of the face, /// in which case face will be deallocated when the collection is deallocated. /// -/// If a loaded face is added to the collection, it should be the same -/// size as all the other faces in the collection. This function will not -/// verify or modify the size until the size of the entire collection is -/// changed. +/// If a loaded face is added to the collection, its size will be changed to +/// match the size specified in load_options, adjusted for harmonization with +/// the primary face. pub fn add( self: *Collection, alloc: Allocator, @@ -103,9 +106,106 @@ pub fn add( return error.DeferredLoadingUnavailable; try list.append(alloc, face); + + var owned: *Entry = list.at(idx); + + // If the face is already loaded, apply font size adjustment + // now, otherwise we'll apply it whenever we do load it. + if (owned.getLoaded()) |loaded| { + if (try self.adjustedSize(loaded)) |opts| { + loaded.setSize(opts.faceOptions()) catch return error.SetSizeFailed; + } + } + return .{ .style = style, .idx = @intCast(idx) }; } +pub const AdjustSizeError = font.Face.GetMetricsError; + +// Calculate a size for the provided face that will match it with the primary +// font, metrically, to improve consistency with fallback fonts. Right now we +// match the font based on the ex height, or the ideograph width if the font +// has ideographs in it. +// +// This returns null if load options is null or if self.load_options is null. +// +// +// This is very much like the `font-size-adjust` CSS property in how it works. +// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust +// +// TODO: In the future, provide config options that allow the user to select +// which metric should be matched for fallback fonts, instead of hard +// coding it as ex height. +pub fn adjustedSize( + self: *Collection, + face: *Face, +) AdjustSizeError!?LoadOptions { + const load_options = self.load_options orelse return null; + + // We silently do nothing if we can't get the primary + // face, because this might be the primary face itself. + const primary_face = self.getFace(.{ .idx = 0 }) catch return null; + + // We do nothing if the primary face and this face are the same. + if (@intFromPtr(primary_face) == @intFromPtr(face)) return null; + + const primary_metrics = try primary_face.getMetrics(); + const face_metrics = try face.getMetrics(); + + // We use the ex height to match our font sizes, so that the height of + // lower-case letters matches between all fonts in the fallback chain. + // + // We estimate ex height as 0.75 * cap height if it's not specifically + // provided, and we estimate cap height as 0.75 * ascent in the same case. + // + // If the fallback font has an ic_width we prefer that, for normalization + // of CJK font sizes when mixed with latin fonts. + // + // We estimate the ic_width as twice the cell width if it isn't provided. + var primary_cap = primary_metrics.cap_height orelse 0.0; + if (primary_cap <= 0) primary_cap = primary_metrics.ascent * 0.75; + + var primary_ex = primary_metrics.ex_height orelse 0.0; + if (primary_ex <= 0) primary_ex = primary_cap * 0.75; + + var primary_ic = primary_metrics.ic_width orelse 0.0; + if (primary_ic <= 0) primary_ic = primary_metrics.cell_width * 2; + + var face_cap = face_metrics.cap_height orelse 0.0; + if (face_cap <= 0) face_cap = face_metrics.ascent * 0.75; + + var face_ex = face_metrics.ex_height orelse 0.0; + if (face_ex <= 0) face_ex = face_cap * 0.75; + + var face_ic = face_metrics.ic_width orelse 0.0; + if (face_ic <= 0) face_ic = face_metrics.cell_width * 2; + + // If the line height of the scaled font would be larger than + // the line height of the primary font, we don't want that, so + // we take the minimum between matching the ic/ex and the line + // height. + // + // NOTE: We actually allow the line height to be up to 1.2 + // times the primary line height because empirically + // this is usually fine and is better for CJK. + // + // TODO: We should probably provide a config option that lets + // the user pick what metric to use for size adjustment. + const scale = @min( + 1.2 * primary_metrics.lineHeight() / face_metrics.lineHeight(), + if (face_metrics.ic_width != null) + primary_ic / face_ic + else + primary_ex / face_ex, + ); + + // Make a copy of our load options and multiply the size by our scale. + var opts = load_options; + opts.size.points *= @as(f32, @floatCast(scale)); + + return opts; +} + /// Return the Face represented by a given Index. The returned pointer /// is only valid as long as this collection is not modified. /// @@ -129,21 +229,38 @@ pub fn getFace(self: *Collection, index: Index) !*Face { break :item item; }; - return try self.getFaceFromEntry(item); + const face = try self.getFaceFromEntry( + item, + // We only want to adjust the size if this isn't the primary face. + index.style != .regular or index.idx > 0, + ); + + return face; } /// Get the face from an entry. /// /// This entry must not be an alias. -fn getFaceFromEntry(self: *Collection, entry: *Entry) !*Face { +fn getFaceFromEntry( + self: *Collection, + entry: *Entry, + /// Whether to adjust the font size to match the primary face after loading. + adjust: bool, +) !*Face { assert(entry.* != .alias); return switch (entry.*) { inline .deferred, .fallback_deferred => |*d, tag| deferred: { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; - const face = try d.load(opts.library, opts.faceOptions()); + var face = try d.load(opts.library, opts.faceOptions()); d.deinit(); + + // If we need to adjust the size, do so. + if (adjust) if (try self.adjustedSize(&face)) |new_opts| { + try face.setSize(new_opts.faceOptions()); + }; + entry.* = switch (tag) { .deferred => .{ .loaded = face }, .fallback_deferred => .{ .fallback_loaded = face }, @@ -247,7 +364,7 @@ pub fn completeStyles( while (it.next()) |entry| { // Load our face. If we fail to load it, we just skip it and // continue on to try the next one. - const face = self.getFaceFromEntry(entry) catch |err| { + const face = self.getFaceFromEntry(entry, false) catch |err| { log.warn("error loading regular entry={d} err={}", .{ it.index - 1, err, @@ -371,7 +488,7 @@ fn syntheticBold(self: *Collection, entry: *Entry) !Face { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; // Try to bold it. - const regular = try self.getFaceFromEntry(entry); + const regular = try self.getFaceFromEntry(entry, false); const face = try regular.syntheticBold(opts.faceOptions()); var buf: [256]u8 = undefined; @@ -391,7 +508,7 @@ fn syntheticItalic(self: *Collection, entry: *Entry) !Face { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; // Try to italicize it. - const regular = try self.getFaceFromEntry(entry); + const regular = try self.getFaceFromEntry(entry, false); const face = try regular.syntheticItalic(opts.faceOptions()); var buf: [256]u8 = undefined; @@ -420,9 +537,12 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void { while (it.next()) |array| { var entry_it = array.value.iterator(0); while (entry_it.next()) |entry| switch (entry.*) { - .loaded, .fallback_loaded => |*f| try f.setSize( - opts.faceOptions(), - ), + .loaded, + .fallback_loaded, + => |*f| { + const new_opts = try self.adjustedSize(f) orelse opts.*; + try f.setSize(new_opts.faceOptions()); + }, // Deferred aren't loaded so we don't need to set their size. // The size for when they're loaded is set since `opts` changed. @@ -549,6 +669,16 @@ pub const Entry = union(enum) { } } + /// If this face is loaded, or is an alias to a loaded face, + /// then this returns the `Face`, otherwise returns null. + pub fn getLoaded(self: *Entry) ?*Face { + return switch (self.*) { + .deferred, .fallback_deferred => null, + .loaded, .fallback_loaded => |*face| face, + .alias => |v| v.getLoaded(), + }; + } + /// True if the entry is deferred. fn isDeferred(self: Entry) bool { return switch (self) { @@ -906,12 +1036,13 @@ test "metrics" { var c = init(); defer c.deinit(alloc); - c.load_options = .{ .library = lib }; + const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 }; + c.load_options = .{ .library = lib, .size = size }; _ = try c.add(alloc, .regular, .{ .loaded = try .init( lib, testFont, - .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + .{ .size = size }, ) }); try c.updateMetrics(); diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index bf527a021..069606c06 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -107,6 +107,18 @@ pub const FaceMetrics = struct { /// a provided ex height metric or measured from the height of the /// lowercase x glyph. ex_height: ?f64 = null, + + /// The width of the character "水" (CJK water ideograph, U+6C34), + /// if present. This is used for font size adjustment, to normalize + /// the width of CJK fonts mixed with latin fonts. + /// + /// NOTE: IC = Ideograph Character + ic_width: ?f64 = null, + + /// Convenience function for getting the line height (ascent - descent). + pub inline fn lineHeight(self: FaceMetrics) f64 { + return self.ascent - self.descent; + } }; /// Calculate our metrics based on values extracted from a font. diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 6aedd7696..c1f16e025 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -757,6 +757,20 @@ pub const Face = struct { break :cell_width max; }; + // Measure "水" (CJK water ideograph, U+6C34) for our ic width. + const ic_width: ?f64 = ic_width: { + const glyph = self.glyphIndex('水') orelse break :ic_width null; + + var advances: [1]macos.graphics.Size = undefined; + _ = ct_font.getAdvancesForGlyphs( + .horizontal, + &.{@intCast(glyph)}, + &advances, + ); + + break :ic_width advances[0].width; + }; + return .{ .cell_width = cell_width, .ascent = ascent, @@ -768,6 +782,7 @@ pub const Face = struct { .strikethrough_thickness = strikethrough_thickness, .cap_height = cap_height, .ex_height = ex_height, + .ic_width = ic_width, }; } diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 6aeb951af..db5a3622e 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -851,7 +851,7 @@ pub const Face = struct { while (c < 127) : (c += 1) { if (face.getCharIndex(c)) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { max = @max( @@ -889,7 +889,7 @@ pub const Face = struct { defer self.ft_mutex.unlock(); if (face.getCharIndex('H')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { break :cap f26dot6ToF64(face.handle.*.glyph.*.metrics.height); @@ -902,7 +902,7 @@ pub const Face = struct { defer self.ft_mutex.unlock(); if (face.getCharIndex('x')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { break :ex f26dot6ToF64(face.handle.*.glyph.*.metrics.height); @@ -913,6 +913,21 @@ pub const Face = struct { }; }; + // Measure "水" (CJK water ideograph, U+6C34) for our ic width. + const ic_width: ?f64 = ic_width: { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); + + const glyph = face.getCharIndex('水') orelse break :ic_width null; + + face.loadGlyph(glyph, .{ + .render = false, + .no_svg = true, + }) catch break :ic_width null; + + break :ic_width f26dot6ToF64(face.handle.*.glyph.*.advance.x); + }; + return .{ .cell_width = cell_width, @@ -928,6 +943,7 @@ pub const Face = struct { .cap_height = cap_height, .ex_height = ex_height, + .ic_width = ic_width, }; } From 327caf903c8e733480abb7da23c464e665ea3ee7 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 17:20:39 -0600 Subject: [PATCH 116/119] font: fix nerd font patcher ypadding twice what it should be The nerd font patcher uses `ypadding` as a single subtraction from the cell height, which means that half of it should go to the top padding and the other half to the bottom, this was making the heavy brackets way too small lol (0.4 of the cell height instead of 0.7) --- src/font/nerd_font_attributes.zig | 4 ++-- src/font/nerd_font_codegen.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index 817d838f8..70920bb0a 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -40,8 +40,8 @@ pub fn getConstraint(cp: u21) Constraint { .max_constraint_width = 1, .align_horizontal = .center, .align_vertical = .center, - .pad_top = 0.3, - .pad_bottom = 0.3, + .pad_top = 0.15, + .pad_bottom = 0.15, }, 0xe0b0, => .{ diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index f8ff7caa6..e74b2ead1 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -213,8 +213,8 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) s += f" .pad_top = {v_pad},\n" s += f" .pad_bottom = {v_pad},\n" elif y_padding: - s += f" .pad_top = {y_padding},\n" - s += f" .pad_bottom = {y_padding},\n" + s += f" .pad_top = {y_padding / 2},\n" + s += f" .pad_bottom = {y_padding / 2},\n" if xy_ratio > 0: s += f" .max_xy_ratio = {xy_ratio},\n" From b7ffbf933f1b2274a14f6471eb932fee0987096a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 6 Jul 2025 20:23:20 -0700 Subject: [PATCH 117/119] macos: open URLs with NSWorkspace APIs instead of `open` Fixes #5256 This updates the macOS apprt to implement the `OPEN_URL` apprt action to use the NSWorkspace APIs instead of the `open` command line utility. As part of this, we removed the `ghostty_config_open` libghostty API and instead introduced a new `ghostty_config_open_path` API that returns the path to open, and then we use the `NSWorkspace` APIs to open it (same function as the `OPEN_URL` action). --- include/ghostty.h | 8 ++- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ macos/Sources/App/macOS/AppDelegate.swift | 2 +- macos/Sources/Ghostty/Ghostty.Action.swift | 30 +++++++++++ macos/Sources/Ghostty/Ghostty.App.swift | 51 +++++++++++++++++-- macos/Sources/Ghostty/Package.swift | 20 ++++++++ .../Extensions/NSWorkspace+Extension.swift | 29 +++++++++++ src/apprt/gtk/App.zig | 19 ++++++- src/config/CAPI.zig | 31 ++++++----- src/config/edit.zig | 13 ++--- src/main_c.zig | 34 ++++++++++++- 11 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift diff --git a/include/ghostty.h b/include/ghostty.h index 2a4a7fb6e..312e6595a 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -350,6 +350,11 @@ typedef struct { const char* message; } ghostty_diagnostic_s; +typedef struct { + const char* ptr; + uintptr_t len; +} ghostty_string_s; + typedef struct { double tl_px_x; double tl_px_y; @@ -797,6 +802,7 @@ int ghostty_init(uintptr_t, char**); void ghostty_cli_try_action(void); ghostty_info_s ghostty_info(void); const char* ghostty_translate(const char*); +void ghostty_string_free(ghostty_string_s); ghostty_config_t ghostty_config_new(); void ghostty_config_free(ghostty_config_t); @@ -811,7 +817,7 @@ ghostty_input_trigger_s ghostty_config_trigger(ghostty_config_t, uintptr_t); uint32_t ghostty_config_diagnostics_count(ghostty_config_t); ghostty_diagnostic_s ghostty_config_get_diagnostic(ghostty_config_t, uint32_t); -void ghostty_config_open(); +ghostty_string_s ghostty_config_open_path(void); ghostty_app_t ghostty_app_new(const ghostty_runtime_config_s*, ghostty_config_t); diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 08c3ef3b3..f6eedd864 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; }; A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; }; A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */; }; + A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */; }; A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; }; A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; }; A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; }; @@ -160,6 +161,7 @@ 9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = ""; }; A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+Extension.swift"; sourceTree = ""; }; + A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWorkspace+Extension.swift"; sourceTree = ""; }; A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = ""; }; A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = ""; }; A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; @@ -531,6 +533,7 @@ AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */, + A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */, A5985CD62C320C4500C57AD3 /* String+Extension.swift */, A58636722DF4813000E04A10 /* UndoManager+Extension.swift */, A5CC36142C9CDA03004D6760 /* View+Extension.swift */, @@ -819,6 +822,7 @@ A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */, A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */, A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */, + A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */, A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */, A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */, A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */, diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 53b6dce88..38500b7d3 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -932,7 +932,7 @@ class AppDelegate: NSObject, //MARK: - IB Actions @IBAction func openConfig(_ sender: Any?) { - ghostty.openConfig() + Ghostty.App.openConfig() } @IBAction func reloadConfig(_ sender: Any?) { diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift index dfdb0bff5..a6559600d 100644 --- a/macos/Sources/Ghostty/Ghostty.Action.swift +++ b/macos/Sources/Ghostty/Ghostty.Action.swift @@ -40,4 +40,34 @@ extension Ghostty.Action { self.amount = c.amount } } + + struct OpenURL { + enum Kind { + case unknown + case text + + init(_ c: ghostty_action_open_url_kind_e) { + switch c { + case GHOSTTY_ACTION_OPEN_URL_KIND_TEXT: + self = .text + default: + self = .unknown + } + } + } + + let kind: Kind + let url: String + + init(c: ghostty_action_open_url_s) { + self.kind = Kind(c.kind) + + if let urlCString = c.url { + let data = Data(bytes: urlCString, count: Int(c.len)) + self.url = String(data: data, encoding: .utf8) ?? "" + } else { + self.url = "" + } + } + } } diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 17abe2b0e..0fdea1760 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -114,9 +114,21 @@ extension Ghostty { ghostty_app_tick(app) } - func openConfig() { - guard let app = self.app else { return } - ghostty_app_open_config(app) + static func openConfig() { + let str = Ghostty.AllocatedString(ghostty_config_open_path()).string + guard !str.isEmpty else { return } + #if os(macOS) + let fileURL = URL(fileURLWithPath: str).absoluteString + var action = ghostty_action_open_url_s() + action.kind = GHOSTTY_ACTION_OPEN_URL_KIND_TEXT + fileURL.withCString { cStr in + action.url = cStr + action.len = UInt(fileURL.count) + _ = openURL(action) + } + #else + fatalError("Unsupported platform for opening config file") + #endif } /// Reload the configuration. @@ -488,7 +500,7 @@ extension Ghostty { pwdChanged(app, target: target, v: action.action.pwd) case GHOSTTY_ACTION_OPEN_CONFIG: - ghostty_config_open() + openConfig() case GHOSTTY_ACTION_FLOAT_WINDOW: toggleFloatWindow(app, target: target, mode: action.action.float_window) @@ -546,6 +558,9 @@ extension Ghostty { case GHOSTTY_ACTION_CHECK_FOR_UPDATES: checkForUpdates(app) + + case GHOSTTY_ACTION_OPEN_URL: + return openURL(action.action.open_url) case GHOSTTY_ACTION_UNDO: return undo(app, target: target) @@ -598,6 +613,34 @@ extension Ghostty { appDelegate.checkForUpdates(nil) } } + + private static func openURL( + _ v: ghostty_action_open_url_s + ) -> Bool { + let action = Ghostty.Action.OpenURL(c: v) + + // Convert the URL string to a URL object + guard let url = URL(string: action.url) else { + Ghostty.logger.warning("invalid URL for open URL action: \(action.url)") + return false + } + + switch action.kind { + case .text: + // Open with the default text editor + if let textEditor = NSWorkspace.shared.defaultTextEditor { + NSWorkspace.shared.open([url], withApplicationAt: textEditor, configuration: NSWorkspace.OpenConfiguration()) + return true + } + + case .unknown: + break + } + + // Open with the default application for the URL + NSWorkspace.shared.open(url) + return true + } private static func undo(_ app: ghostty_app_t, target: ghostty_target_s) -> Bool { let undoManager: UndoManager? diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index f30f2f6f9..9b05934df 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -73,6 +73,26 @@ extension Ghostty { // MARK: Swift Types for C Types +extension Ghostty { + class AllocatedString { + private let cString: ghostty_string_s + + init(_ c: ghostty_string_s) { + self.cString = c + } + + var string: String { + guard let ptr = cString.ptr else { return "" } + let data = Data(bytes: ptr, count: Int(cString.len)) + return String(data: data, encoding: .utf8) ?? "" + } + + deinit { + ghostty_string_free(cString) + } + } +} + extension Ghostty { enum SetFloatWIndow { case on diff --git a/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift b/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift new file mode 100644 index 000000000..bc2d028b5 --- /dev/null +++ b/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift @@ -0,0 +1,29 @@ +import AppKit +import UniformTypeIdentifiers + +extension NSWorkspace { + /// Returns the URL of the default text editor application. + /// - Returns: The URL of the default text editor, or nil if no default text editor is found. + var defaultTextEditor: URL? { + defaultApplicationURL(forContentType: UTType.plainText.identifier) + } + + /// Returns the URL of the default application for opening files with the specified content type. + /// - Parameter contentType: The content type identifier (UTI) to find the default application for. + /// - Returns: The URL of the default application, or nil if no default application is found. + func defaultApplicationURL(forContentType contentType: String) -> URL? { + return LSCopyDefaultApplicationURLForContentType( + contentType as CFString, + .all, + nil + )?.takeRetainedValue() as? URL + } + + /// Returns the URL of the default application for opening files with the specified file extension. + /// - Parameter ext: The file extension to find the default application for. + /// - Returns: The URL of the default application, or nil if no default application is found. + func defaultApplicationURL(forExtension ext: String) -> URL? { + guard let uti = UTType(filenameExtension: ext) else { return nil} + return defaultApplicationURL(forContentType: uti.identifier) + } +} diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 369090ee2..907f3a36d 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -496,7 +496,7 @@ pub fn performAction( .resize_split => self.resizeSplit(target, value), .equalize_splits => self.equalizeSplits(target), .goto_split => return self.gotoSplit(target, value), - .open_config => try configpkg.edit.open(self.core_app.alloc), + .open_config => return self.openConfig(), .config_change => self.configChange(target, value.config), .reload_config => try self.reloadConfig(target, value), .inspector => self.controlInspector(target, value), @@ -1759,7 +1759,22 @@ fn initActions(self: *App) void { } } -pub fn openUrl( +fn openConfig(self: *App) !bool { + // Get the config file path + const alloc = self.core_app.alloc; + const path = configpkg.edit.openPath(alloc) catch |err| { + log.warn("error getting config file path: {}", .{err}); + return false; + }; + defer alloc.free(path); + + // Open it using openURL. "path" isn't actually a URL but + // at the time of writing that works just fine for GTK. + self.openUrl(.{ .kind = .text, .url = path }); + return true; +} + +fn openUrl( app: *App, value: apprt.action.OpenUrl, ) void { diff --git a/src/config/CAPI.zig b/src/config/CAPI.zig index 0b7108a59..bdc59797a 100644 --- a/src/config/CAPI.zig +++ b/src/config/CAPI.zig @@ -1,7 +1,9 @@ const std = @import("std"); +const assert = std.debug.assert; const cli = @import("../cli.zig"); const inputpkg = @import("../input.zig"); -const global = &@import("../global.zig").state; +const state = &@import("../global.zig").state; +const c = @import("../main_c.zig"); const Config = @import("Config.zig"); const c_get = @import("c_get.zig"); @@ -12,14 +14,14 @@ const log = std.log.scoped(.config); /// Create a new configuration filled with the initial default values. export fn ghostty_config_new() ?*Config { - const result = global.alloc.create(Config) catch |err| { + const result = state.alloc.create(Config) catch |err| { log.err("error allocating config err={}", .{err}); return null; }; - result.* = Config.default(global.alloc) catch |err| { + result.* = Config.default(state.alloc) catch |err| { log.err("error creating config err={}", .{err}); - global.alloc.destroy(result); + state.alloc.destroy(result); return null; }; @@ -29,20 +31,20 @@ export fn ghostty_config_new() ?*Config { export fn ghostty_config_free(ptr: ?*Config) void { if (ptr) |v| { v.deinit(); - global.alloc.destroy(v); + state.alloc.destroy(v); } } /// Deep clone the configuration. export fn ghostty_config_clone(self: *Config) ?*Config { - const result = global.alloc.create(Config) catch |err| { + const result = state.alloc.create(Config) catch |err| { log.err("error allocating config err={}", .{err}); return null; }; - result.* = self.clone(global.alloc) catch |err| { + result.* = self.clone(state.alloc) catch |err| { log.err("error cloning config err={}", .{err}); - global.alloc.destroy(result); + state.alloc.destroy(result); return null; }; @@ -51,7 +53,7 @@ export fn ghostty_config_clone(self: *Config) ?*Config { /// Load the configuration from the CLI args. export fn ghostty_config_load_cli_args(self: *Config) void { - self.loadCliArgs(global.alloc) catch |err| { + self.loadCliArgs(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -60,7 +62,7 @@ export fn ghostty_config_load_cli_args(self: *Config) void { /// is usually done first. The default file locations are locations /// such as the home directory. export fn ghostty_config_load_default_files(self: *Config) void { - self.loadDefaultFiles(global.alloc) catch |err| { + self.loadDefaultFiles(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -69,7 +71,7 @@ export fn ghostty_config_load_default_files(self: *Config) void { /// file locations in the previously loaded configuration. This will /// recursively continue to load up to a built-in limit. export fn ghostty_config_load_recursive_files(self: *Config) void { - self.loadRecursiveFiles(global.alloc) catch |err| { + self.loadRecursiveFiles(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -122,10 +124,13 @@ export fn ghostty_config_get_diagnostic(self: *Config, idx: u32) Diagnostic { return .{ .message = message.ptr }; } -export fn ghostty_config_open() void { - edit.open(global.alloc) catch |err| { +export fn ghostty_config_open_path() c.String { + const path = edit.openPath(state.alloc) catch |err| { log.err("error opening config in editor err={}", .{err}); + return .empty; }; + + return .fromSlice(path); } /// Sync with ghostty_diagnostic_s diff --git a/src/config/edit.zig b/src/config/edit.zig index ae4394942..38dc98169 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -5,18 +5,19 @@ const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const internal_os = @import("../os/main.zig"); -/// Open the configuration in the OS default editor according to the default -/// paths the main config file could be in. +/// The path to the configuration that should be opened for editing. /// -/// On Linux, this will open the file at the XDG config path. This is the +/// On Linux, this will use the file at the XDG config path. This is the /// only valid path for Linux so we don't need to check for other paths. /// /// On macOS, both XDG and AppSupport paths are valid. Because Ghostty -/// prioritizes AppSupport over XDG, we will open AppSupport if it exists, +/// prioritizes AppSupport over XDG, we will use AppSupport if it exists, /// followed by XDG if it exists, and finally AppSupport if neither exist. /// For the existence check, we also prefer non-empty files over empty /// files. -pub fn open(alloc_gpa: Allocator) !void { +/// +/// The returned value is allocated using the provided allocator. +pub fn openPath(alloc_gpa: Allocator) ![:0]const u8 { // Use an arena to make memory management easier in here. var arena = ArenaAllocator.init(alloc_gpa); defer arena.deinit(); @@ -41,7 +42,7 @@ pub fn open(alloc_gpa: Allocator) !void { } }; - try internal_os.open(alloc_gpa, .text, config_path); + return try alloc_gpa.dupeZ(u8, config_path); } /// Returns the config path to use for open for the current OS. diff --git a/src/main_c.zig b/src/main_c.zig index 0722900e7..2c266cfb5 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -19,7 +19,12 @@ const internal_os = @import("os/main.zig"); // Some comptime assertions that our C API depends on. comptime { - assert(apprt.runtime == apprt.embedded); + // We allow tests to reference this file because we unit test + // some of the C API. At runtime though we should never get these + // functions unless we are building libghostty. + if (!builtin.is_test) { + assert(apprt.runtime == apprt.embedded); + } } /// Global options so we can log. This is identical to main. @@ -29,7 +34,9 @@ comptime { // These structs need to be referenced so the `export` functions // are truly exported by the C API lib. _ = @import("config.zig").CAPI; - _ = apprt.runtime.CAPI; + if (@hasDecl(apprt.runtime, "CAPI")) { + _ = apprt.runtime.CAPI; + } } /// ghostty_info_s @@ -46,6 +53,24 @@ const Info = extern struct { }; }; +/// ghostty_string_s +pub const String = extern struct { + ptr: ?[*]const u8, + len: usize, + + pub const empty: String = .{ + .ptr = null, + .len = 0, + }; + + pub fn fromSlice(slice: []const u8) String { + return .{ + .ptr = slice.ptr, + .len = slice.len, + }; + } +}; + /// Initialize ghostty global state. export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int { assert(builtin.link_libc); @@ -95,3 +120,8 @@ export fn ghostty_info() Info { export fn ghostty_translate(msgid: [*:0]const u8) [*:0]const u8 { return internal_os.i18n._(msgid); } + +/// Free a string allocated by Ghostty. +export fn ghostty_string_free(str: String) void { + state.alloc.free(str.ptr.?[0..str.len]); +} From d33161ad66d46e5ca4d7f41a11abd85b9937a406 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 22:40:43 -0600 Subject: [PATCH 118/119] fix(font): include line gap in `lineHeight` helper --- src/font/Metrics.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index 069606c06..f96d753b3 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -115,9 +115,10 @@ pub const FaceMetrics = struct { /// NOTE: IC = Ideograph Character ic_width: ?f64 = null, - /// Convenience function for getting the line height (ascent - descent). + /// Convenience function for getting the line height + /// (ascent - descent + line_gap). pub inline fn lineHeight(self: FaceMetrics) f64 { - return self.ascent - self.descent; + return self.ascent - self.descent + self.line_gap; } }; From 08fd1688ff451c4cf2d1cc3e4864f05e71cefbdc Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Sun, 6 Jul 2025 22:45:13 -0600 Subject: [PATCH 119/119] font: add test for size adjustment, fix small bug in resize Previously produced very wrong values when calling Collection.setSize, since it was assuming that the provided face had the same point size as the primary face, which isn't true during resize-- so instead we just have faces keep track of their set size, this is generally useful. --- src/font/Collection.zig | 64 ++++++++++++++++++++++++++++++++++++-- src/font/face/coretext.zig | 4 +++ src/font/face/freetype.zig | 5 +++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index cdbd3d84f..1d85d8a28 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -129,7 +129,6 @@ pub const AdjustSizeError = font.Face.GetMetricsError; // // This returns null if load options is null or if self.load_options is null. // -// // This is very much like the `font-size-adjust` CSS property in how it works. // ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust // @@ -199,8 +198,10 @@ pub fn adjustedSize( primary_ex / face_ex, ); - // Make a copy of our load options and multiply the size by our scale. + // Make a copy of our load options, set the size to the size of + // the provided face, and then multiply that by our scaling factor. var opts = load_options; + opts.size = face.size; opts.size.points *= @as(f32, @floatCast(scale)); return opts; @@ -1089,3 +1090,62 @@ test "metrics" { .cursor_height = 34, }, c.metrics); } + +// TODO: Also test CJK fallback sizing, we don't currently have a CJK test font. +test "adjusted sizes" { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = font.embedded.inconsolata; + const fallback = font.embedded.monaspace_neon; + + var lib = try Library.init(alloc); + defer lib.deinit(); + + var c = init(); + defer c.deinit(alloc); + const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 }; + c.load_options = .{ .library = lib, .size = size }; + + // Add our primary face. + _ = try c.add(alloc, .regular, .{ .loaded = try .init( + lib, + testFont, + .{ .size = size }, + ) }); + + try c.updateMetrics(); + + // Add the fallback face. + const fallback_idx = try c.add(alloc, .regular, .{ .loaded = try .init( + lib, + fallback, + .{ .size = size }, + ) }); + + // The ex heights should match. + { + const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics(); + const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics(); + + try std.testing.expectApproxEqAbs( + primary_metrics.ex_height.?, + fallback_metrics.ex_height.?, + // We accept anything within half a pixel. + 0.5, + ); + } + + // Resize should keep that relationship. + try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 }); + { + const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics(); + const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics(); + + try std.testing.expectApproxEqAbs( + primary_metrics.ex_height.?, + fallback_metrics.ex_height.?, + // We accept anything within half a pixel. + 0.5, + ); + } +} diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index c1f16e025..00cc31b26 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -31,6 +31,9 @@ pub const Face = struct { /// tables). color: ?ColorState = null, + /// The current size this font is set to. + size: font.face.DesiredSize, + /// True if our build is using Harfbuzz. If we're not, we can avoid /// some Harfbuzz-specific code paths. const harfbuzz_shaper = font.options.backend.hasHarfbuzz(); @@ -106,6 +109,7 @@ pub const Face = struct { .font = ct_font, .hb_font = hb_font, .color = color, + .size = opts.size, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index db5a3622e..ae3bd0968 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -59,6 +59,9 @@ pub const Face = struct { bold: bool = false, } = .{}, + /// The current size this font is set to. + size: font.face.DesiredSize, + /// Initialize a new font face with the given source in-memory. pub fn initFile( lib: Library, @@ -107,6 +110,7 @@ pub const Face = struct { .hb_font = hb_font, .ft_mutex = ft_mutex, .load_flags = opts.freetype_load_flags, + .size = opts.size, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -203,6 +207,7 @@ pub const Face = struct { /// for clearing any glyph caches, font atlas data, etc. pub fn setSize(self: *Face, opts: font.face.Options) !void { try setSize_(self.face, opts.size); + self.size = opts.size; } fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {

V4M{9>iaK8qo&*ENvl+m@-bJfC)hF2ZCAPTAEhHCwSQyzL8st^Dc*5Hh@cNPkMg^^%p zGClpm-G3l+^>N)6Z5CZSY3YF=X5xt9XLEyhtE(Bk;CLp%M!1_tc#}FaaD$APT^S;_ zR!sJS@l&7HJ+#VzEp|k=3qYE8NEm_T4inf~AdASewplz_UY3KQ9u@(P8$#z~{G zLSfKTbqPwTdXDd?j?qojWm_Y8v@EIoVJEwNJnq$X7GapXk^OBt)hrh1lXahR(&I7G zD6C}IYv6+$#NSP^OZR9^scCC!;tRaP zB&`UlVJjEpy;Vs0SOw)?C`JYm8nPD@Xm)3M8OwfCKP^>}VDRF;zi>`EcOm7T3VurI z@pIQQPwUZ-PXofhV&*wk@Ssf6hZ6pWl7_5e`#H%HUk? zH|-X~9dpTxuQg^BH9vAPEm(UDBcG~+b3Z4r(|>?LyDkA#^4dY^1(j5dj=P*R-(JHB z6ud5Ob8h=J!+|r%pt!}APZ+d|0%O~LH)13Vn#+5)eBZK>B2e@wUJsUI721mM@ zVL6tc8+vP>!yV|!MI}X8Jn&@)$q#W@9a4jj5&OMDrB4q=vT5g|x=5|QmKiLIqBIH! zfD0C^^V>YPzQc(OSo;(G)>0Xump(T~osfvz3?@r0B%}rH74{dsi|GyZ1*{S2C~)k| z>19)mGI@&Bso2wCn|Tb$p&6DO_f_36RK$Gy19vzN(cwsPF%i$0D^eD1#-j5<_?fY( z%#MCl@mZRMxEk%~|A!wi&c2Pe?d>}2o7@1?tiQp%<}uyoV_oSeRg*?XAWvFk?T z(uCWq%s5IcLY^=alDp1cu^r5fKXap5Z^GGGVWq9Z@23EtklQ;%-aiS{W3#aOCRG?h zD)8`Yz`n0#PmbpTI;nUkJA;&?cGN2 zIhj~n?)*Ife`hEElN~spxuL9Nm}LO`rSQR2Y`iV_XAPD4_uWrI&6ZrBJhW7DyB2Oz z6|jcQUd?}lNB4fA3(;9WJR2Z`5&ZGU!E1l_STJ!b>R-~a+UNV>U(i_vlf1F z6ifPZ2Nk%&!9>@ zaY(E-1A_%<%@ywePfrUYQlOl5=GBOyuL~812=@@a!o@?V-3r%^$`f#o)3uB@nIcB? zd+cfT4PH2lqL-)wY%kn03!Qcm(Q%=YQXr>FqA{Syc=OSe*yOl-8hSZ1qbBO8(vs?I z8Yh-@Sp)}8Pv$xcJs76l`*4guOx$Pzo&EJPIY{y@I1ExSd3{}4c60RW*z9hYktx6Q zd|$ju`~=&uO3=R9)U;RC+Oo#Qgy3QTF^(jB)@*9abgaQM>?WDhd|rult_34*m$y}V z9+*bs_pv(#^jdDdmV-Tm7HgS|PM@lKf!^$Q-7LCs7uxr$NWdw<2{_dlc=r668NzR9 z^y=t6BQ|du@9tRvoMQ4*jQG|+a6U*fz{~u?B{hkF;-M=MWI2`8fK7raI$Hg?ADClb zlK`mJNIiF3={d0Q)IN-|E0J?**MwuLUO|+x{r$7|&d!2+d>KAww1%;3tZgha z9A!!$duP%Fk;?vBX_8$C*ZX_f88P|S3cG%LdjJ}DRV6e>!VLOjq4obw+L|T%mt^=a z3G@5UsCIbh2F(xLo4-E|sr?~sbpc8KFubl|P6-16+KsGdN`$8Hy$^n~)>PG1saCQH F`(L$nMXLY+ diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png index 9be9501ba18ef61d9715e67027bf5b9ef23e861e..3fa06a8fc969f3641e2f6d2a02871c11b4bf20ff 100644 GIT binary patch delta 2951 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 x1XfBWLjjc%A%qY@2qAZFy7x{ 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 P#UXOUy4Y1=0?7XdBHz?M delta 2048 zcmX|>dpOez7{|A{Z8o>%mS&cbl*@|9T|#b&bWCe%>Qoe(Tjp~5F~e~QOS-w$LF&w% zxn#+<}O=`A`H28u=A+%obNyH_j#Z9ectE!KA-2UP^wnS(Lr5xbv$%}Qt@!j++G1~JAk1fWd#e?qjNbF&o%$QO} zPG1f7z%^B+c$9NM?EvKh?QlF3V?Z_1W{tRSLA}hYTN6{yD^zK4%puBCKPMnJLaIiwUM$Wk-HC!1zP%@7C$!y zJuWihH!L$#p;*+WascfJzcN8Y<;=5tg}#8%C#y&0vGc!fOvQ;=O*13=M|$fkIQpqp zZ@m`A>o|awJ^;9P0S)>>mr-9(07+p=Yf^#8>WNl?S516HB6@Hk*(sZJ-vrn z+3(OVS8^||KC;XFGzDR+z?NzUMZv#|nBQVxnZnw@meazLP24TUZ$V`>z|5m3ly9uY zeG8fjS~R855RCxZsian#vAueL$*OrW7z6^5WaplxKFq6|N;gZ9y6lfBkl;WsN?!KI z00mrRKyAhYo%}ddwswD-6^&rRkniI;i7uAwnk~-I@;cYw9p>rQg>Zi{(dzSDnB(M? zN4`(fTfcG5plO?LPwuQt>xihqk5*X%#b%Q09p#`uk$`!`TN9UV5>m7?aU7xB=>S@^ z@-^^}Ant~2l;99u(JX^}(h{_#5*VSFS1maC}{?0Q^1Btj;~H z{bRy};nH@qL)%V%w9xS&+K@k(<&x_ptjL`L1B)7o%SoC8pTb*+Mz z<$ouq!zFJadA3FJ<<0h_1u!c;9z8wAZG0}_>uz2O`oq^cI8Ua6nuEL@8xWi)GxE4q z>45&(K7VH9R;DjLeNU%!Pv4AkRd!JEtR~P0g63bGUcDSH5X<}NMhXatJ}}LJM03Fy zC^tb$10XMKR&|&1hNFx{rHYg@qG(#T;26YKb5+lQByc3BC>~T{*+UVmm%^Q{H%gm2 zDz9G8npf2vw|Qb+oZ8Vw*K6(SJ4uK)F@;F<3mT=>e~4n(yjg-*Xma6qZDUxPN&Kga z3A(N~bX#zK;y+0`*^_J_vAnd&hFjBB%Q@IJs zrNM(PLnM;cON(xo&tsa9DT)7AKp+qln{^XDIT=w{ruydf!Q;}iW8V4{mR$dwdXd7& z&@(U55PMqP`OMhLW{gM<1QG+{nf#Nri#SB`i~EX@2Z|6~FUSK$gfNw~JJAcesh5FW zqr`Yy{u#0KawOFDU)^}z|umIb+8KMZ)OqvJ` z#XV~|bti}v6p8~-Pm&GY{Tif_2B~u@ZS$N)ME)|nr-gK_fy0J(EwFo=1UeMz8(B#8 z-#ttc9vSaPG+jlo{?R#_fU()L1Lec8UBj!g^xAXK#hDwjDcaIchulzY%Z*dX#a;c9 zS8j9;4gaZz;Nk!T7l#-R$G9b{FYm2=9NG?#f&Qw0rrS2s=U$16=%Md){`G$3G1zn0 zX`^64WnW_FA2xbMlW>`-5lDptu4fFhCCg;l$?H>7Po=ZVB<2M ZbpZE~je9oBQ?s`aa&_`_ta2bx{{;)y(zyTt diff --git a/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png b/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png index ee9a9d9053b66f4852f4c3d176f42e1cead1a9cb..89011df49d34f3da3b765d0df7097c5914331991 100644 GIT binary patch literal 2635 zcmZvdc{J2(AHaXcZ!~5GV@QZ*NQ~Xco@HiIwi_edvQ|cnvV{16FQ zu2I>uueVH_rLsj?hC~u!-otdxd*5^3@A;nRe9rTI{`l@sGS%Kn1SyLI06@gX+VU^} z2;cz#MnV7p03g1NX955cYhy`vK7WUMlM@uDgFdjr%lgjVW{`Au2%~XkR4!0Vzr4yE zTi_`L0I`P0eM?489Wzo%J(Z#`nPcp48d0vXtLCT_0_!Z{tm#lidb4o-g3U0Y!{83` z+lBCT3t~H$_eJB6(mCX)#E+9PY408q7Vq9zUgr#lBrPca#T7rb&fSnVNF)MQ>75L z02u}fYi$QN!vX;P&ef3fGj||$5l|STL}G1OH(_KCQ-eNzM@E=!@zy=y$onkBVm!@h zf$jOnnJ2o~j?zNeOxsG+37AW_lNPno^zZ^Pgq5+R8p;He}imL$M{SqY~8?QO8%!D1-ph)1Py zPh-YDUK6=NsN_@st(B$3PYVlnpL|v>i(5lAFp-pt^XOA@o&TJeUPPRYfoja_>cn^z zi`<0055PFx>Qk$GyqHGIh`(k5@D!;pWfXBTko&@VhutyyTInI|>I9 zjOa$+&^gO;zl;Ann*f=ah&Z(~y)1lfaIin`=eQ4UG1N7Ss5q4#noziB0_kFBJ~gKs z_9BMXaCgY>!|fypg4Sr2vQ}oF#P3Uh$sk#g%(BKPVfomrA+S}x*coEm^ApGI(n`}D z=Ual8N4Rl#&Ngz67}F95CYvL$(f8Cl1eqWLtEBcgHRPXt=tp0bvZJ}f{u_Q_lm?`{e&eA_nDRr;xG)GuATewh6Sf7C_^qKibFS+hZw93ot^7sh_dNTb;*zWXqwMGk zaiOJ~^@+x+Wn%g@8My{Eq7Ofhdw}9>H|mF|h2p<*yB+J&YGwzEO7Th1{8!qMx(#z4 zDlcU{`0z+bVL+%B6R}FBn)z=OZ|n}=Q$Ff{31TS_d=N+(Cgm5x0O0&0Y^!t`jKCH= z`an%%>Yvx76Rzpn9y}hqv&kjwkYIm5>D4Ypo}Ej0%Se_AZoB>u1|G5MSGUeG9wZuN z!AR9OKz9vbjc$uYTL3;k1n|u-s(Oh-eNH=~)()SldQcM4{&2i3ay|jI_2)TE=qq?ku zw$W3(&)3aotiCcU2sXS%^&v)A)sH82Xj|YC4pM$G6+xl|jArK;>Rozw z><yu&$j}xZ-+_GJfkUC9ve%&>K5rOnLm6^gyXmXtMv~v>#uWiQ27( z%ShAn!e+_$+EOZEt02FEWYK0F1jysfc)4o|6|#(O-M(-#!ZJ{0ypmbpuj}P-D)bxfICERKf-J}-Ao`-Fe|7}(?ZYLw^#N3+1Y7h`X|I}FK_*_iB1)B>9mK5d zSbd>J&-P`mwNx7(3*H19C=F|c7Uj?8Wuo8yot!8(N%N=#T`_E0p-2Teh+@JplErDz z@o*4cbXDrx15r?c;BlG_b@K<&B_vK1RG^nYR%h`DvJ)8il7R>Bs!r-}n)kv50zL}z zcWb#w6A1%%w9VDC&DDdcsGpd_ty=y_9Uu3ZrK`B(007WxGo77_`#3=~^12siJi2aK z8yyMz!$|ju*ITRLy~ps3>J20N@!uW=35Odi?7r z;!CUo|H(`8<#-bM5-0Z!CybrZ%D$!hvfGFAg@Gs;d-0i{-z|aOnRs>9xAJaUXJwSi zNw%xooc7qgF-DqMEUHm*hM8ce>q4m>f;ZxANmi7)BhOv?O6UHhiV##@M@>Law?!#C z3w8-tdKvr8UV!{4+5&m;CZ%$ck&x9dUKL2$Blm4O{xoXDK;;I2h$V==4=^`!vQcTd-^Z@Ck=s~B7)3ET% z(n8*eaXm@9al25Of=lh)J=|*2P36BBs{(t7p-OZKuEpd5UTA9C{<1DZEglf;G&F#AoRESGQ#DYU&@T5>7;cz`3@Hd z8VIRMKmN_eA?Ki;bgOE}hxp;3e%2Ric4rA~AI_|$#w$KGS!}z(K=}Jxq>q}C%Q>SD zD9t4_8cgH~^?gxc7IJ5VyrFV09R;uFu9Xz~d$zZMvH{u8`X+{N*a)t>Pv$F3{Wjjz z+NU1XSN=gy>29*efP$i4?}_ZpOZT<2?T+1VFp~ndxe?9=_C`e(u+l21+_8sCb2;(F zf%~=lBSaIcE}T#GTwz^kYM;^t+FWi5--<+icaY0b=J*O{b~)KoWsb|$D-!27L>{KWgxKl!UIzL_|$E)8*L8QTb59c GGyenQadeyj literal 2638 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 literal 4541 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 I>zopr097~6{{R30 delta 991 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 J74G(l|1bGGWx)Ud diff --git a/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png b/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png index 563f23d093b48bd455c244ca49e3b3448efbab10..4308c536144da75a46f915481571c3344ff02b81 100644 GIT binary patch delta 750 zcmbQrK9zmK=K2fwm>C%U|9^ACk?XL50P}(W|99yd9KHSM#3`O96An)jRbxNxJZo3n zIz54vX8B(}8ecr!%QSJ_y6LOASr`~LtOPNRgkCHEXPv%ix-{#fxXJ5ue_GczTD+Y8 z&hn#bLaRMH6GO#nhynZNKlyb)7_4xSPpp2pc*o@WdHI(_djBahocO>9aYrB*lOYew zh2QsYaYv+No6lYLzEM8tl-hz9J>j#;_QvdPwK(PT%wwOh?VN`4UF{ER@93QWaz)=} z`F$sr+xM4fO8wJnXCMvTN18HLooHiYeW&m+w4c&Zw*t z$HDL*>~Vd)YqnpV`chN%?&EUvr|2IEOfz!-H21|rmOZCKQvYu9TX@%L%I)~&nbNfr z86H&0Ge8`9kn50v0Lz8{|FhpWD#hknN#0}Tmvbub2r8=yE8QCts}NMX|Kw|Ke;Z-j zI~oG1uc~(La>)5!H+fmc7k4Ixh$~-RR;-7TTtx-9+h@1 z?`urRvGWvuUi2AaLA^agI>_N~ryFt|FyLYS|Nr$l*`Aex&kEivGz%&U#VMVCzrx-{ za%sMoq-xIYuxremQ~P`6>e+MiL_A)6v99BNyl(&3b9)__(l&*CV%Q+Sr=nCeon7hD Whezy;-Z2ae3=E#GelF{r5}E)%Z(eEu delta 750 zcmbQrK9zmK=6Zwc%nS_w|G&B6$kps1z;fXKe=Ge74^(#8=89H)Ft9lLDDn1T#nQbo zu?j(V_gp-F-bLS>%VpQDxOI903=9W+!7^bx_kDgpr!y{3q+_pIZ1kt{UrZmD#P523 zWO{?>KN${&2Wwwd?cC+C+JI^g!kUj5J&|J+0?(RnY=9H3qRw5Jb4C)Ljt)D z83?dk_*D*03=R3mz7{bm)fS^b7J}ZTzTRT zwS<;OtqctJ>a+f?U-9fodC)3LO}XmYOZx=eZ=RGnS-z-U_(R+>?bq8&xgLuxy8F3w z*0zh{oD3_zF+v=95aP)H|Fhq>2JOl+lDx;vFXvR=5meUq|H{{*(ngQ3HIu)K&#O?Y z+7YO53M>?nR}b>5I}^i>6|bMZVk(?+a##Jt8nr5m-FIKcy?gup?58Em{~a#=#WWG3 z(g>z<;;W}_^7UY&F0FlaI?Q&tzQD_UKVwzuI3*q)Z)9MYR|^k3WrDDwD?U~|IDb*#~-!q zXf#+kS>D=SVvB+t2Lr={(88TDcN_{z|4sVJUT%|oHzr%5#qz)EKlwTLCz}hXy;aYA z6qC1~^`ip=1H<}N`Yy9hSz2BQIg)#Q>c_@{spsy!t2wsI(rMD|TAl;9cK^Fl{fVt< z<6jluIj7%l2V1b?M>;q>-%j7iLl|4CyNOkCt~fCE`4}3 UHC?Nifq{X+)78&qol`;+03c&|iU0rr diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png b/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png index aa1569bfaba5e3e5faea55947ed3bdf3456e6736..addd1f49696bacc14482dfbb2ae7e0d4973d46ad 100644 GIT binary patch delta 627 zcmcb>agJkxiiW7Ci(^Q|oHqvy85tND4lVfVpUY*h!GH>ONH9z+(2_IcWnf@vSnzMH z?HWdDCR8OG7$#0pWQt*#xLvV+gCGL~!~g$pBH}-OQQ$ekDCFm)I;UlF0O!<(mdOn* zUMpBsLYRa;`S#1HOey+1HNS28-I;bNLQJ!D>py~A0RjcQZt+}~pZzbh))Qx*{;~g_ zbDH)V@twlQl>Dr#G?$(@tIU1=jBUvrSvZMj0t37_c_(Pl4Bl)ta&Tg7+z zknhqjGS>fQoff%NUpJA(J$T~#$>LGIPejypR&T7ndWKWp=(6asX{$wk)>)V7e0nvdirxKRy5?r@U%r#sm>dLV73r;v44Jjg!{*bONHH)l{Qv*vupuu414F}t ze`|Fga!NCyDl*`l=%Y1pmNe5H)`{B{>kGsodLm*seo^2#$|w}(q`Ia>d!djPBd0ba z=hOgBj}plbZW~KuvzI{_qR$#9M#v=N*5V^uXxf zrRozotzVtrwQTIT+*dax=*hE*t?AF?tj_guJqvbOTjJ(&tanSEtg8;|`^-sqmY$hn z{Ly)$zVsHUniZ#~U0z-@Q}Wn@?|sFaO|A%~#7NX7g=pOl7O+cM=G~(!UGijixs7{h zd*;JsK~;US^=DGKUf$pD(X@EwiSH-5*QynD2~WFvV{g{87XFzpxsp$ZcGcL+T8sUB zJb6E(uH?oW2h~5jHht(;UzU@n8xqCt{x4l~v-dC0$*fEc0yB#ARz`-*TIXT&Y2_4^ zKT4BBnN%6OCs#8`GQOWYl}TRpzJT_B?HQ9*Y&y5<=Gb%Zv$Yuyaw9fe z<+0)AUN))m`sA*y2@m#~tIFPr)B5l?(@uA0ylMSz<+{n=m{pCv6poS>5Pd7xS8Jk zDscIAov)xzkZaPk$pMq1yjN^I(CQ?(<;mt#O{^0q3SXYY9@4!c=hcIkGnF5$%aWVn za5XBZm%CM|VcM_YXJ3z$Kby#WUfiHD!)1A}a{~XG`T!G&jtGynp@Gh!8kf}lh3dH% zstY6~O`IPc(0In1*J9Tol{D|af(Oq(l zX|>ZtU*2g!XX?HFf4>o!@^G4+lJ&Lct6e#jH)!_1oXOLov*g;UO{+9SUiP{;E#&)h zRMacQ$y3$oYew;+H#a1VttU55XuT5k^|>C)gYMM=0)_Kgd&^VAn9e%!F>5;Uu^U*< zZtQBk5_RfKf{=@wkDud_TG21}CWdls30uDBqEjcQ%Z{5oAv#OyuW32&3(zo1nySiG zc>l%J3CgntGK58Pws4t>C|v1bJ0%vBz0hzqm#f>FZQ{On|ai!%`JTW zvD+28;oYq~YcIvEz5Q_3x7J_jXTQart*?9|{9f+&p4m_4_SFh}@9)d5l9dnrQ2YMD zhu?nR(sz_T_&$Bk{^iy(eC>xH7Fa;286Wt;$@0!w-ev`B6}ja`4~o ze{W1J1XwmpZfNkn{+2VXd4EjN*{od|@>}2Dvx7Mk4DM8ewZ6rgN9V9OcVy(GF!R0x Psr7XAb6Lmil+XkKul&Ko delta 1070 zcmaFD`IvKpis&j&7srr_Id2XaGBPkQ99r}l^kF~Q}SC7UC9P9Muv&o z-I#P(Co40$*2^pEm2aB<+H&Wr7kRqfoAIoa zSy!mSu_0Ez&*T3s#&d>Fj+csryn?bhO~rNuXf#c$35(pIU{sV8^{F9CaH_AYjcu{Z zozSz)!YiV@R~0S{V7U-fe`?RXALnd}9{gld6X<DqQeotz;OQamm?ORh2PcADtRJ87D>;PIBZZu1VX zS}9pSD)8)3HgH#N5@Sx}Ub*$ant+txX0@kY`kP+an90nHi&>z&$@WA=UWA5Wqkw&P zWZv?`CrbuOXtod_mtzmYPM=5vY0(Jw4K~Iq4i2sXqafL(%y+4 z8dnDimY^UTDpQhK*}!N{&qP|8GGD%{&RxcrNp>Rw{z^>ro&vH7p5zgm2Xs?Tg9GJ9-(t3FZfH=?4Iien$!zJUL0Zr)q{Q@_9V(cR?v<&*bZ-pK#x-^D`T^^X^G z?RzgPQ+NO5Kc4M;fBsGS{Jo_8@WTQN89pd=ys@4EoILNGg4 z#YRpOR~+&W&hg{;P%X4wreoe0& zU62-LdA5kvDwb7uU-s)${(sc#e<;gsY?)r-G~542fR9C%icFaBI%^^O`ouCuuv>2~ z&E_m&yvN-)R==i|4CJc^V7by%G{ WGIAza2X6%_^>p=fS?9!*&;$U!3A#D} diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png b/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png index faf502d01950cf3ca08beab990dde84bd641f639..787cbcb6c290adf9186a18cf8fcd89c4e8c60d2b 100644 GIT binary patch literal 2220 zcmai$dpO%?8^?b%a6DcicNdwXBcAI~43>wdo9`@XLGel8P_x*=d1 zFaQ7$1pE;n0Fa^sz^>B}0001Qlc#C`0Es0W!TBc81T3r+005nIk@x3NZ;TjtEAFZT z0I;$jP~U}~2coDvMVM1+7V zh|ZwcNB>%R2^?plk_Xs9|` zJPdrv>Q1id`EQUd{6UQ_l~WFRMm0Z;e`ZKn7HV{Oc;FvB_emzkp)F5F2ZguoXGG@TAQ(y>3lP+tK>7DXYiG z3!ZDLVazW+I=&q z^n@iDrW%At;qNLygPG(6sQSf7Ly;0iB?4dCnYW26k3Z3rwUFs`U8B+GLt#My7N_+f zKuTC-vEHl@XVIwZJO-j1ys?oJq2iE3;z<>&nwb&|CqA|&H~H6P^q6T+wvZYND`Yet zQ(_rb;mbx=r1`&QWsiBSL zIoctnF+dpQNlUr|a@@E&h+f?^9ORp3YSM+}ZKAQZDf$n_lVgmWi-iX-o`I;T&rVPE znoZ`a_}{b~6K;5=+hkR`?!S|=OAuE+(V7=(hnTho8B&TSKaTV;ZSa#vM^YUY*H(r( z_=b#vSxxdXq0;i0UQU8WI}{FwL%+&a(`Vy(nWgmRQX=%MK+|cN@1v_!kX6GUXM#ks zQNH?~_t~P8iSl!)k;bJL3VE*|*_O%b)b;f+#|6vd)8Q%;?Do>~Uo%M{yJ>}<)2N~I zC0`-E6Ni*=1MwiG-qaIZH}_cW;$q-pzcUvP8w#;6D*fdi-_*WdYD3+zJMk8F{?erL zhbOCAmB=~5km%mMD|qOa^_2=Lc`r%H58d|Uqhby(sEMr4KK#^Qe9zPnwMBMj=H#PZ z9L8vg9Ai3bI>dL*ZV@}7{fA%jQ7}7_jtHO9{#zj)Dl_Kf+SJNG1aY_ng|c-?x-G0% zR?+7fNtS+8lJa1nZpW!X>6gh{NxpJbtb4~IXBmEvD4xSdxbxw$D|;8Cv4{2!3{wWG z0+TjUNNXvN0;gh#o9`Wo;B8{^HWUCLtcI1 z@p1#pG#L{j{r7Z4A#3O=1`)z2VQJdEIl8y#c41n{9DQDH#R^6$h*5DB@NqbMpZ?m- z33r@*2={T8y_WKj{=+(SP&{iIc&$Q9lh3c_C-K;(vSFE&^xpIInusVK7e z)^($bHX1i?1nzt2VrvuLyg6n}`0mRoFG9&vqbSml){J>9=YMW)y5*9S{~z0774p8V t*sw$V`*&alD$eozuZ{S82C=g>S*nY>Zy4xAWygp+LvT5Iq|WJd+VA02W%&RA literal 2228 zcmai$c{tRGAIHBlM#eB`vYLZ+M9vB&xmq)haV*C0OG>eFW)Zn}D#krI#u)c8BXT^> zS{k7oP3_ROk}IUls&TGFt=W)$`mw)XO~2>Y=kM3^e%|lT=XpM#Pb$G44~L;)006+P zt;~r40Ad1wkOve10Kh?G8VLZoQ3k}z8T@n7uupG<0}Ht?GawGc+;3w3Uv zfzvS?sK7MN%3>*n+; ztsHxwU9Kd#yVu>_`;gpBg+}i{5fU?>2fhop2*2>~vibWJQ&)9AJcW4Mn4tNjFne7T zWziO0;-wdGuEyJ5TV`m{t`2UomUQj%&n)4y2keHiiyYhhTO#KzjN=UvZ^c-smqwDa zzWm`mp>Ox+=}Qk6-EnX)LPtj4&mghBTap~2@b?erz7-DSMg0y^ci;>ZxlIy=f+9-X z!g3<(!+x-e@k-^rH=VFT_Y{tzhgG**cbc1ZP7!J^;)1hziqN``Aok@_5k#^|2jZ4= za74Bp`qYIvO6-%qZw2m^*5#jA)1hO%?xnI1(beTKzc#=R&NhtkMH1w+>=4gyqEgBr z2z9UJ`Xx`bc+P{t>T{fD4DbRS<^-v8{4U=4) z6D%hXL1@!Nv4hH@kY`vN(pI4qx9de4b~B+&3tr=deBwU4wLaF8!ns=aR)h?K{1wZmr zHAA7FBwei5_gna055urH^4R@5PiNSEtEthLSCnMPX!-hxM5GS=hGM$CM0g@A7=O)x zVjWf7yvM#wIkue=+ZfV^k`S(|n#Qy$B^&+SD4kH4kD~+&k$>mO&R%;8E5hg%p8YU znB(8lz?R1qeFJ?M6jIJAl?#~~w{bC17T7@6RS)mcoij?1#M_S3KpsuOM_<{=c8q2WG|dt8NSV!0nIi5z#DQ7t>_&hLL&!GtN}zTeC>j$TGsmzkdC)E(X15~o@4 zSM-yv@Y9@d?lh+L*ibpT)<=&rcYw-h^pD%<*fgmXCVH4u>xG=C^~TEtDDiHxZm;EB zY(*C3xCGqu)vYu6Am6gy`RYJ$^usNcD_`AszT3p)4N*Wr%51>#*%ez&th8c!&GY!v z3i@4<|7@X;WojpzJ=x)TlXM|a?jKglu`5!VV6q4=9_RM zBh|KU^=IF`mV(jni~d#2U5#n4^yYQdW@9?jo(@`K0bt`$669jq81H0t;q<>U4bJ8k zim9*O?siNokc>$3{FStFlpg9cn8KhRo@m&3$sB9-Qx$nM;%=nkf3&|{-TLAN+b+FO z$j#^ZO&(PF?whZk$7Lx&B#CG`|wJK;=Y4 zPcy5L;dVZ2#=R%YU8U-8vwt~q_||gQQ*Yh++qWKHW)r%_dTK_~wQGsWW&XxzE^TmS zV_9habCt%6iJRwXXv`H@`b_hQ=yjhNr>L{UW4cjL$mSt_< z->;)$zAoa2L4&2_;s+d_zr@#^^qNv>G9gB4W#r{ePW|P(m#xx0f8p`xfa0)sQg%=0 z*>B96*TZ|?`T69k|9&eg%eU^(9$4%JV^C{d4zE{>f*UB4 z$*fKjmdvo#oRk`M2Kvl!GRYOM4hW3Fj>=;rw4O eJxL0bat;JtH8zodTxoF+WWT4YpUXO@geCxwBsJCm delta 679 zcmeyz_MdHnO1+z>i(^Q|oHqvy85tND4lVfVpUbVU!GH=XBpDbO{{Me-*pQcjfuUi+ zzqPs#Ii;CU6%}wo)HL!iFfbfYsDG^-vtSzws+t5=u$t-l8`l`{9DTh&XyPIf=_hBM z{?7^ewz1^ims-pHGp#r8%$+<(t@V(-6UW30qAdR#-^ovW-CMuz#In16RW%CimzQN& zFgoUW6>eG~t{SybSgb?2BJXdz35%mGAmHBmJ`fjojI4KbM$yT&YZUc9edz_KE7I9nx9)0YWJ{ z#||>hoDtnVx8dHC-AcXnZ<(teCBD6^^t5d8@x!;0gXd}8n(JlanEiT#;O^y;X_+^a zn42!lt_cLc-M8+FoD#ab?`w9R_}h0y?EUXjO~3C?+y3^$mWra{bvqZD z|CZy5wEwwn<43lKN|IV`?gOJ8+_vrT$G6W?*nX{FtaJW`!(*e`t2su@ zzDHY=ujVK*zjgoibgK9Yzq_)lYTADJ?cDV};Qo>T^V45_Z`0Y#DiK>=}-Kmitilc`e?bv0Rz z-I$HkA?xRd*_|Cl>>z=^zL?$LSHuny`16z5g9AnEAc5cCnLRpE#10bp?Ty*vV@2#B zfnQ#jJv~*#mXs{Hgi0tQ;Z}^52r)BC1^oQX?D@GOHm7XKWt2l18Mk7r1dN$cD&WUQ zW-l)lu~VX~{yQZZ(G<#mXhz+tFg6SfnW>RyHhq7*KHcA3oE+}W{odip#m)WG>&JIJ zfqIMrA&Qu}ND0?q9`}@w6 zX~8Z9dzgIi$Z~+$DOE~`R)Tho#}b@Fa7yVe0Ad+^xp=@$1MVRs1ctjL+%!iB3~vkY zE-^x2cprqf#SsF-8$Wyx7$Gox(}6EO!vHp!OeT}bWHN0>+iDkV?ef?YBm<6={eSfY z$$%qe|6e^pl5ARk5EV_g;jEa=iZchKrSKcC)3y{WR2fahMlo|ZHeWPba=+CIzv4S> zOThwgG!+}g%)!{S?vi<=L7-CO{5khPX*hd&hr2|&3 zQmt?ew_TPK&l;xSWS&Z1(i81Iq!aE^Z8x5YXO|*j@^Y&9rlmS*bi#9}_0i%vhe(*r zQpJy@?k+Jsy?Bfbc)ew*WWXD;>nuwp1KyBbXIU!s6xd`k^;EwA00960?VCXgz%U2| z|NrXrSlWYstx?0q%s>yah%AIul<)xnz%<7-j~I4@TY_On7{H$KEmX&tR~>cjDQ>^e zp40bJKQ=M_)c0e{d)M}36PJ@4QT=t#Gpu>=3Xinr)lztAsr)ULg+i@GU0d={ONq3H tTka(=oR*8>w7MZMoR%wU7yuxX3!bn%KckTSqyPX4002ovPDHLkV1fuyi!T5G delta 872 zcmV-u1DE{T2-*mcH#rM0C;$Ke000000000000000xEcTe0RR8&!!Zp2004lX{;e(H z2^#_c00000000000000005A!WOe&F@8v@(~k+&fNa2Ju3Vaj7`$|cmFD;ln**p)v0{3&A|MJ8e3rIRe|sBSe3W8 z3MBb)03j5)oS6{QAXeDSnJeJ@bf>TO&0T9dR%f$n38gLI8Au!w};ifr4 zV0c@AcZm@K!}}n-EshWv-uU5rzzBiin+|;O83wS)WHOmdCX;D9+E%+@YnR8KAQ^C^ z?EkALNCq4!`~T_*l4R3@f2e4>4QIt{R-8E?Ers8BowlW5p~`3~Hj0_UvH7CelKZVz z_!Zx2TM8C{qp8>^W)8-tb+0U~?Cg4X_j(p1+xb3o>c-AllC-YSDlAdVyA)RoSYP<1FJi8PLlb2J)H!amkqZ6J(t&bMZ zIYh!_mMVTMt?v@k(~HO0fV(YAB?I1&?XoPD40uDf%d%AJDX__8GIgt800030|LvR6 z3V<*OMgRY+*JIR!e`V^rbLT)0auK;OmMG%`0QjHnnnw&f!X?45BMe~A_$I1jd{-TH z?I~`*(4N!xQy(@l{nYzo%X8QEViT8>8&Umr_cN?{@CuK#=G9VoX{r1zriDVSMO|BR yQA>%mhfD4yFr1c);k3FTFr1bvY8U_@lLMZxJUXFs9e8>3C&2~hwSJ^ zLe`M$KLlxOCZMOp3L zlfUfQ*G;>>SKYnMVvpht7-+ceT<@Y^eMlwAz3#(#_=3Rk=;m|Jjf2BhPgU&O&NemmC6d zEx7ED&r;}lq;mGtrguGdk<&JQ@tmabTYpynhw3b@eVcH@ePz~6qnYvh%{#Qqo7a7gH`eeC z-p0Ff#~i-t!D{EblLG9;?e+b6f?fw+vR?D(#Al`RQ@ra79p9?0(avrAHRX-hy015Q zCds;&sZN(Z#d=ktY<*|UtGQnfMf==~{1lfx<>ry6KE)x;H&RrJRtEQn$h_Jpsj^(o zQc_Yq{NVmMOX{EA(Mf+DD6MjT&y(9XSMW}H|L)V@x+`oa*V}*MzPN&S(tZA?=5G6W ztC+#bWY)vnW&?p%TTe+v<&>753xQT#i%VGCK5~RCI@s|30iXK1J5PQu`S(J2&eQ3W zb00|7f8yOzDpoz%Cl-w$dauq zlLAUlhO{xQ3>X2)`? znkPFCs^3waYRqQ4ibZirUfh)QuTAUUf2z|o%~&7brzKt%@G>i?XWOGWvDSyqbxmIH z6()CSGh6GUzbgcC&s**O-7?o!+x)~S=c;l;)v3!>Ipy#DRNE5VZ!tG2E(-WL*k_i(SVT1rA)EXH17qLmCTv+F5&oKEKi%NY;QQ`!KW|lOz zR6p@*)AuHhN4?J089l$Oo^8b0KG}3-&zY06R8zM;SvT+Une(Uo4U5B-RW`bn^i0s?`qh<}a^AM9dwfa#er|W>n|)GwQu}uv_I+%jTJCqdYU#{X u^($_xIT&~}I5P1FC4elIU}2D8xNwM@jXf`T5wk%($cdh=elF{r5}E*Ph$>hB delta 1245 zcmeC>?&qGMA{yrD;uumf=gk2_Mg|6kLkqt8=W^>$v{b6seaI=z#DD?{xEL7z|9^A1 zk%xhS;ebN@Yi*eY+gMPPFeGq5)G%@~FfcSM_`iO!JQo+Lk^@X=N`4EXE7>5%$S`rc z8>7y|f6Dbb5cM;3Cp>ZxXuYl+uu;U0skQ%@dvl+=aBu&y<1Pyy8wOtdcu{2A`UiC$ zzf7mETxtEz<6ijIXK$D6e*X52&-oAS0!|!?Eoj7q{{n)Qx5M=c|E+x#vWWZo>rlOrs0gc#VoAe@K>C&7HF8_FJ1rIqx^u|9cz$y)t*>p?4CS>(jK)H8{@jw%YT{ z=hm(>n@u_we@KrOZV8;TBr@*W+-vHmji%ap3MKz@{jB`QahB8hKd*oPKeoO~Zu2BB ze#7iKw>r_~zhAq5n-?Ut^WPH3rrJ-l3xDr*jH;9< ziT-2Se90vAvcu8T(j9AzV)WbU9skBu?cHN=V$E@n_Xb|guUW58IsNZZ)6Ur|Vl%C` z_&;3tB&vVDc&%I#I08Tu!9_Aia5NMsbL{d>XrKRUWpjDUqOAfhK!I+c18_&+I zW#<&l-Tyv*-Ur1vzlNvbW*!Rr6HmOCsXw9sNsBKg-!qc9ySL-pwh*4IMJ7wOFHH&% zUaGiPSF~A5W4fYW;f^;`3Z=@ubiJn4`=4cr>WXRGUS#Gr`}B(9?&5o@_a=S%7m&rI zF6qVP1d=s55p5~G_-tw1b0K-og*!^ln5Hh>RKHQe)O~Rp(|OU%3Hw2sU++0L;f+LC z>Yn!BJGA64x0NoMyD4SQww3zlgNx_+YApJsr~U2ax;?d7D~s)>uaLR?USd+w-@N)I zu7&!gHK&W4^((xzRM~7-vnVdfi<=t$wQ2qPPjxFzU#y5frxjin@HQ`~ciZDB?_?h) z96wpEs?GoMX0y}R%8<^ow5jv#9zHm8ig(ke3HO41JfCKI9$N0dPx9@xGcS|YKbxzzB4XuD|7SJ7Qhe1O7Hm?RI?*z6^O7~w?p!UN^)vFPX4<=~ z7L{uprEV)GdzM_y{`TwF{Ttu?F4#0Z+hr9xYwh0;ES>w_+^aP2y&Hbm_py2Tj!c>5 zPtIjVuQv()%3pe$g@cXLz@x#DiAN}50z)$c14EQp1tS}KUU28;@Bctf^mO%eS?83{ F1OPonF{1zg diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png b/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png index feb4c4f8b0f4d013b581e94a49982f8ca577de80..e7415b7aef7801266881cf7c865815fcc67c41c3 100644 GIT binary patch literal 2473 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 aj)B3tI%j^tac3S-()D!pb6Mw<&;$Swkz~;T delta 826 zcmaFC_MB~kO1++^i(^Q|oHqvy85tND4lVfVpUbVU!GH=XBpDbO{{Me-*pQcjfuUi+ zzqPs#Ii;CU6%}wo)HL!iFfbfYsDG^-vtSzws+t5=h#E#th&k&g)=SpUFwA@9AkbFJ z9M53u_hF0arQHWtFRU=HnCd#c&q(&&rCnambERDt#cJxZc3Cj2%rHMBv1k8Bi{_Lw zQvdwB4sEdB{BQ4@H}5|53pjBowt$Eyeg$jGCfBaZFzM0y{8W15IrR+3`TIV4KNsGz zfdBrFwVyeoF6_?#bMYsO?u)ba=Ko&RtkJZW=5Cg{FHq~kE5RGcRrR&E-fF&%roFl( z&#WJ9FO<0sWpo5x=+6Ih{pXC*pgsAM*Zj5Yyt8uYFO~<&98DSTPDI*okJNj1`D4a} z=;n(RR`ELK$FHT^e-pMVPy4KJF73k=Ufp*$iqc#rUw!u^xQsD*`z#luQ<6-)3+nym zu|Hj45WB4IUBa$e#{;>V;wAt5dop#AjEGo>+S+wXo|>{$G8nbYIX$J>>D1<+f5y7$ zmM5-EIK1_D^SR}lYdO9J-AJw0;_$OB;Cr=F!J_|==ho#-4~{XEcV1u-bmCA zp>{=kQ&eS3+2X(p%G^pV0#0&`yKmcRYLx5}REeLu?!qTtQ2JWj&U5bEn+sD88s9N6 zN&UdX!Rsq&<6Pz(oF>qsIW;-sL9p%A&le6%_YTo}%I0iv)-hD_6?0M6Uq@RxlUEA^ k`6uR|*N|Xc%wW&JU|pRvU$K$pAt)Jpy85}Sb4q9e0K5@qEdT%j diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png b/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png index 3a60a59d5b294d62b39cf4f00c0a75aa1c9d8981..b216174e42ec037c12671a7cc87a0452fff178fd 100644 GIT binary patch delta 327 zcmaFM{GNG&O8quZ7srr_Id86AT-$8G)A}&yKt|64L5a>l3B|=JEb({Ed@qX&9BO0v z_x|mPLkXN6&I}FzR>TSaop)-ZDf4VwG3ME&b+-;|TA>&8=!g7q?NyqRrE%u3PuI-f z;b$^g=CTP-u-3-?uiAd>w0NfRUT)*X14qvhjh+dCMop`?WzLFo;E7ykHBtkdoKbyIeKxa5!Ux$Boc){cGNfBfU}(9@AcH#e44Fd9Vj|G6dhK2?I*WVV@(11&W!2w^W z8be;NntyB8K4g?;LREB!1!4|dP3}WhbTu(dXlisHa^h0c$iu+Ea6qB{wQ|gYZ7isE wNid?hS$a|vnschbO5P&8^;?h&6ptX#!z#vB!P|9P?mvj<>FVdQ&MBb@0NY@q&;S4c diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png b/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png index 8b3134e52adbba897a0f480d92636471d713cd7b..2243248b07668a08bcd7ba0e5d146238fb1b7b57 100644 GIT binary patch delta 402 zcmeyv@|R_TO1+Dxi(^Q|oHy4W=C&E|v^>l?#L;trqvt?|q~_uZmT*ylD?wMMW%N3} z|NJ>i=w(Rb{vZa13ae1xsTD5wTpa_a2L=Q@a&%eA3rg^)2?`&(f{rEjfgUcvjLSqw;NXh~QW>!W97&!3Oj$yJMqY~qq$$^Z@+H05? z82D~o delta 362 zcmey%@`q)DO1-nEi(^Q|oHy4W=C&B{9Q~Mch@j@fyQ{z1^f19@3 zM(tw0(btrllb(JwmOK1w%BnYK`*umo+dU5Yo&43~R?M?+%9T&jd%9mNRDT@zW0s6? zP}QchC%VsHylnaFyyf+@n141a{y#Md_A{Pu`}|T5_kYK>ocFCI?-%66Pb-PvShCx5 zo9&6+JCEgVpLn}$gY~~SZdXS(E+ep8L{b(gFu;Mwd+x~(7?qgTuuSG+RASVbtjnmZ zro+U*@c;jt!;L%)3=9Vp>R)TiEZD{Z*8>I?Oq0tPGZ;lC-)B^2@@E9;Hd@aB()ac- oBPRm`L&Jjq>!l|(F+sJ-u*g?FVdQ&MBb@0Ap^FIRF3v diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png b/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png index 64b1a80b8d1d89ee9a062a167cde4161afa90df2..f3a887d20c1f9b7646b2e588ac2110d5089c3005 100644 GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfq|vb)5S5QV$PeH4|5MI zh_vjT=$^phc1ok?5J%4>!MM<^y$shU?zpx`c;EZ*)%xjid?!86Y~xgH5pd#AY!O&8 zW9Hfvff?z_E3WKkd3KXS>#KfarCF2LU+;u@y$gc=3*nLKNaud}xkz(Oa#y6JX>l6! zjm@=_%V%cu9{RUM{Cw;)ujYXGwR`8Csh94^;oWI$nHPO#zhr8gF~>nGr)0VKdiy@N znGbnfUWt2ct$bVeuGi?XWXC1_l&zii|Bu{~QCe!|yX;Kd^9dU-pZRGeo~m$9|H?kU znfp@}X57D!CCPF3(TV)RGxDoerz*_YKW*K#g9-lp4~PMW895mk7#bG* zU;kT>3lv*OU=1tS4uq21f*Kk~@(c_NAH<->81jOR`L}lMLq=&PR7LuX5Od&aav!py zt67b1j_yNFf@;n_;73>EjqWY!NlmEEIS`Ak1k>lmP#41k2h-)$A!Sr|m zH(H|beaMdHUJNC?54q8lY=C;Hk%xhS;ebN@Yvq^)+gMPNUjhrrqPK{MLYNY~mZ9so SQSWLskOogzKbLh*2~7Yo)_@oQ delta 663 zcmdnRd5Cj@N`0rNi(^Q|oHsKc<~AFMwA}0vv|&+OnHt=$*2)!zHbD2wlZk>4X z{q-)>KU>dLopj@{LW_VChhmF>(}YLw?oDnwFsFCHr5{2wHVcMG#WNL~HF-VuPB`Zl zkXgfd<|c<$uYRMYwByo`ss`!GD@^vWJj-F3dThGG9Nn|?PoJ${DW6&xe@-iR6JyV_ z`!_V6MzJQ={5n-UrD$o>g!N(T)SvDb<@n6BT4Z)x;->o|m44L%lfsucRo3jS%Mq>g zt5$ecbzzS7>vz#>PZZx{-gu?iCVbg^^Wb$kI^OF<LwesG_ z4~x!gKDzhp9Q#TIE6sKHdFwAJ_x%w({%7f}+3Za}(=+qsc%Ipp?X6UJ#P9HKal#RK z#e41tJM3Hb_cJxc7JQ8BGK~K;@$tjy4}Wtxt+qVdai1;sg7>5Ciotda3=IGOzqx40 z%b>t;#Nq$C!Vkg=dCEOSzc{f;ehJ%uaKXy^2^X&98)UpPmoWJ%&BMe10-Ruyp+R3} zawU`EWNs$T$%~n!nbxpOKER~N{6T91QCrwoIPLA~HFig+q{(uXl@pOcq_s(>8o>FVdQ&MBb@ E0Cq|KJ^%m! diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png b/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png index a768ba7aab599f6660e0f8a6f6425e3a89942d42..82ce82468166189d95acac312068a368f06f667b 100644 GIT binary patch delta 260 zcmeBT?qr^zVjAG-;uumf=gqaVoktZ!*aECKw1hi(`EX84@SHYDNaoY=GpAg4__UOt zQ>)pj5!Wd7E-gU-|spZ>JpgFSVb&?tI+OW%^(K)p6W& zem_6r;PN{xxjq9 zoe}fH6~e?GOle^F+dnZ|5Ewc>(0mh^pAh>_piX7 z_IvgloBVfjzMFER`o+rc{}r1uJ)F-UXFz}tulF%9F#P}j<}f2C0|P_Dg8%D(3vzKG z6o9}7!HFMKR9PYF40*xo{;lnO$Slo-s_+lfWKl*Xy&5Kv(zghuz7N?!N)Kq7DDQhR SHF{16NSmjtpUXO@geCxNO=P10 From 05eeaddb0445fb3b2aedd74da00f3b284b329cac Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 11:21:50 -0600 Subject: [PATCH 020/119] update flatpak hash --- flatpak/zig-packages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 81024bb26..08fa9568b 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -151,9 +151,9 @@ }, { "type": "archive", - "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - "dest": "vendor/p/z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", - "sha256": "c2226cebf2d48b2f80a42e6ced53f2a5b06e92306be2f8f1deffe5f4ead3ef45" + "url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + "dest": "vendor/p/z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg", + "sha256": "c1f69d7a07a2c5c6e0c51cd1b5fe8cd87df8093baf0ccdb65d5221bae7c5046e" }, { "type": "archive", From b1f788a768579a857b0acf8b9c60a70e436846a2 Mon Sep 17 00:00:00 2001 From: Daniel Wennberg Date: Mon, 30 Jun 2025 10:07:21 -0700 Subject: [PATCH 021/119] macos: don't overwrite the .fullScreen styleMask option in reapplyHiddenStyle --- .../HiddenTitlebarTerminalWindow.swift | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift index 5f4d6b177..d7f8accb2 100644 --- a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift @@ -19,20 +19,27 @@ class HiddenTitlebarTerminalWindow: TerminalWindow { NotificationCenter.default.removeObserver(self) } + private let hiddenStyleMask: NSWindow.StyleMask = [ + // We need `titled` in the mask to get the normal window frame + .titled, + + // Full size content view so we can extend + // content in to the hidden titlebar's area + .fullSizeContentView, + + .resizable, + .closable, + .miniaturizable, + ] + /// Apply the hidden titlebar style. private func reapplyHiddenStyle() { - styleMask = [ - // We need `titled` in the mask to get the normal window frame - .titled, - - // Full size content view so we can extend - // content in to the hidden titlebar's area - .fullSizeContentView, - - .resizable, - .closable, - .miniaturizable, - ] + // Apply our style mask while preserving the .fullScreen option + if styleMask.contains(.fullScreen) { + styleMask = hiddenStyleMask.union([.fullScreen]) + } else { + styleMask = hiddenStyleMask + } // Hide the title titleVisibility = .hidden From 886e33d7b70febe2175e96c6506f5538b98b358d Mon Sep 17 00:00:00 2001 From: Daniel Wennberg Date: Mon, 30 Jun 2025 11:21:27 -0700 Subject: [PATCH 022/119] make hiddenStyleMask static --- .../Window Styles/HiddenTitlebarTerminalWindow.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift index d7f8accb2..996506f0b 100644 --- a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift @@ -19,7 +19,7 @@ class HiddenTitlebarTerminalWindow: TerminalWindow { NotificationCenter.default.removeObserver(self) } - private let hiddenStyleMask: NSWindow.StyleMask = [ + private static let hiddenStyleMask: NSWindow.StyleMask = [ // We need `titled` in the mask to get the normal window frame .titled, @@ -36,9 +36,9 @@ class HiddenTitlebarTerminalWindow: TerminalWindow { private func reapplyHiddenStyle() { // Apply our style mask while preserving the .fullScreen option if styleMask.contains(.fullScreen) { - styleMask = hiddenStyleMask.union([.fullScreen]) + styleMask = Self.hiddenStyleMask.union([.fullScreen]) } else { - styleMask = hiddenStyleMask + styleMask = Self.hiddenStyleMask } // Hide the title From 8c5122876fc62bc523b562d08f64872091672e01 Mon Sep 17 00:00:00 2001 From: RME Date: Mon, 30 Jun 2025 22:11:49 +0200 Subject: [PATCH 023/119] Fixed po/ko_KR.UTF-8.po Co-authored-by: Hojin You --- po/ko_KR.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po index be7fd2502..42cb2682f 100644 --- a/po/ko_KR.UTF-8.po +++ b/po/ko_KR.UTF-8.po @@ -248,7 +248,7 @@ msgstr "열린 탭 보기" #: src/apprt/gtk/Window.zig:295 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Ghostty는 디버그 빌드 실행 중! 성능이 저하됩니다." +msgstr "⚠️ Ghostty 디버그 빌드로 실행 중입니다! 성능이 저하됩니다." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From a00a727e779f674b1d79068803dd336d42e372f7 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 16:37:26 -0600 Subject: [PATCH 024/119] test(font/Atlas): add test case for `setFromLarger` --- src/font/Atlas.zig | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index aac2e7e8d..7b31e2794 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -587,6 +587,35 @@ test "writing data" { try testing.expectEqual(@as(u8, 4), atlas.data[66]); } +test "writing data from a larger source" { + const alloc = testing.allocator; + var atlas = try init(alloc, 32, .grayscale); + defer atlas.deinit(alloc); + + const reg = try atlas.reserve(alloc, 2, 2); + const old = atlas.modified.load(.monotonic); + // zig fmt: off + atlas.setFromLarger(reg, &[_]u8{ + 8, 8, 8, 8, 8, + 8, 8, 1, 2, 8, + 8, 8, 3, 4, 8, + 8, 8, 8, 8, 8, + }, 5, 2, 1); + // zig fmt: on + const new = atlas.modified.load(.monotonic); + try testing.expect(new > old); + + // 33 because of the 1px border and so on + try testing.expectEqual(@as(u8, 1), atlas.data[33]); + try testing.expectEqual(@as(u8, 2), atlas.data[34]); + try testing.expectEqual(@as(u8, 3), atlas.data[65]); + try testing.expectEqual(@as(u8, 4), atlas.data[66]); + + // None of the `8`s from the source data outside of the + // specified region should have made it on to the atlas. + try testing.expectEqual(null, std.mem.indexOfScalar(u8, atlas.data, 8)); +} + test "grow" { const alloc = testing.allocator; var atlas = try init(alloc, 4, .grayscale); // +2 for 1px border From 95fbeb5b821cfd3cfdbb3340a8638c0031eb7336 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 16:44:21 -0600 Subject: [PATCH 025/119] style(font/sprite): annotate type for value --- src/font/sprite/Face.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 8c39daef4..1463fb38b 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -54,7 +54,7 @@ const Range = struct { /// Automatically collect ranges for functions with names /// in the format `draw` or `draw_`. -const ranges = ranges: { +const ranges: []const Range = ranges: { @setEvalBranchQuota(1_000_000); // Structs containing drawing functions for codepoint ranges. @@ -137,7 +137,11 @@ const ranges = ranges: { i = n.max; } - break :ranges r; + // We need to copy in to a const rather than a var in order to take + // the reference at comptime so that we can break with a slice here. + const fixed = r; + + break :ranges &fixed; }; fn getDrawFn(cp: u32) ?*const DrawFn { From f773baa418350cd4109a8ad14c8683ff335689eb Mon Sep 17 00:00:00 2001 From: trag1c Date: Tue, 1 Jul 2025 09:36:07 +0200 Subject: [PATCH 026/119] remove blank line in CODEOWNERS --- CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index e8b632433..56768d5ae 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -181,6 +181,5 @@ /po/ga_IE.UTF-8.po @ghostty-org/ga_IE /po/ko_KR.UTF-8.po @ghostty-org/ko_KR - # Packaging - Snap /snap/ @ghostty-org/snap From 114c3f56656354ebee0cf434a1aabf56681522f1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 1 Jul 2025 12:06:37 -0700 Subject: [PATCH 027/119] Fix abnormal exit detection on macOS I made an oopsie with #7705 and omitted the check entirely on macOS when the original logic only omitted the exit code check. --- src/Surface.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 390adf91b..cef71f265 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1002,10 +1002,11 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { if (info.runtime_ms <= self.config.abnormal_command_exit_runtime_ms) runtime: { // On macOS, our exit code detection doesn't work, possibly // because of our `login` wrapper. More investigation required. - if (comptime builtin.target.os.tag.isDarwin()) break :runtime; + if (comptime !builtin.target.os.tag.isDarwin()) { + // If the exit code is 0 then we it was a good exit. + if (info.exit_code == 0) break :runtime; + } - // If the exit code is 0 then we it was a good exit. - if (info.exit_code == 0) break :runtime; log.warn("abnormal process exit detected, showing error message", .{}); // Update our terminal to note the abnormal exit. In the future we From fbdaea745698d20c994853524b9be5076954e8ba Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 1 Jul 2025 12:15:45 -0700 Subject: [PATCH 028/119] Update src/Surface.zig Co-authored-by: Gregory Anders --- src/Surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index cef71f265..dc7b0e3bf 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1003,7 +1003,7 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { // On macOS, our exit code detection doesn't work, possibly // because of our `login` wrapper. More investigation required. if (comptime !builtin.target.os.tag.isDarwin()) { - // If the exit code is 0 then we it was a good exit. + // If the exit code is 0 then it was a good exit. if (info.exit_code == 0) break :runtime; } From dd9ca556f953e257899811b513c76ad681b1b95c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 15:57:40 -0600 Subject: [PATCH 029/119] font/sprite: add sflc supplement circle pieces --- ...ymbols_for_legacy_computing_supplement.zig | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index 9f7e8815d..01258b041 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -192,6 +192,60 @@ pub fn draw1CC21_1CC2F( ); } +/// Twelfth and Quarter circle pieces. +/// 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿 +/// +/// 𜰰𜰱𜰲𜰳 +/// 𜰴𜰵𜰶𜰷 +/// 𜰸𜰹𜰺𜰻 +/// 𜰼𜰽𜰾𜰿 +/// +/// These are actually ellipses, sized to touch +/// the edge of their enclosing set of cells. +pub fn draw1CC30_1CC3F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + switch (cp) { + // 𜰰 UPPER LEFT TWELFTH CIRCLE + 0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2), + // 𜰱 UPPER CENTRE LEFT TWELFTH CIRCLE + 0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2), + // 𜰲 UPPER CENTRE RIGHT TWELFTH CIRCLE + 0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2), + // 𜰳 UPPER RIGHT TWELFTH CIRCLE + 0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2), + // 𜰴 UPPER MIDDLE LEFT TWELFTH CIRCLE + 0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2), + // 𜰵 UPPER LEFT QUARTER CIRCLE + 0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1), + // 𜰶 UPPER RIGHT QUARTER CIRCLE + 0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1), + // 𜰷 UPPER MIDDLE RIGHT TWELFTH CIRCLE + 0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2), + // 𜰸 LOWER MIDDLE LEFT TWELFTH CIRCLE + 0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2), + // 𜰹 LOWER LEFT QUARTER CIRCLE + 0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1), + // 𜰺 LOWER RIGHT QUARTER CIRCLE + 0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1), + // 𜰻 LOWER MIDDLE RIGHT TWELFTH CIRCLE + 0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2), + // 𜰼 LOWER LEFT TWELFTH CIRCLE + 0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2), + // 𜰽 LOWER CENTRE LEFT TWELFTH CIRCLE + 0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2), + // 𜰾 LOWER CENTRE RIGHT TWELFTH CIRCLE + 0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2), + // 𜰿 LOWER RIGHT TWELFTH CIRCLE + 0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2), + else => unreachable, + } +} + /// Separated Block Sextants pub fn draw1CE51_1CE8F( cp: u32, @@ -271,3 +325,93 @@ pub fn draw1CE51_1CE8F( .on, ); } + +fn circlePiece( + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, + x: f64, + y: f64, + w: f64, + h: f64, +) !void { + // Radius in pixels of the arc we need to draw. + const wdth: f64 = @as(f64, @floatFromInt(width)) * w; + const hght: f64 = @as(f64, @floatFromInt(height)) * h; + + // Position in pixels (rather than cells) for x/y + const xp: f64 = @as(f64, @floatFromInt(width)) * x; + const yp: f64 = @as(f64, @floatFromInt(height)) * y; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + const cw = c * wdth; + const ch = c * hght; + + const thick: f64 = @floatFromInt(metrics.box_thickness); + const ht = thick * 0.5; + + var path = canvas.staticPath(2); + + if (xp < wdth) { + if (yp < hght) { + // Upper left arc. + path.moveTo(wdth - xp, ht - yp); + path.curveTo( + wdth - cw - xp, + ht - yp, + ht - xp, + hght - ch - yp, + ht - xp, + hght - yp, + ); + } else { + // Lower left arc. + path.moveTo(ht - xp, hght - yp); + path.curveTo( + ht - xp, + hght + ch - yp, + wdth - cw - xp, + hght * 2 - ht - yp, + wdth - xp, + hght * 2 - ht - yp, + ); + } + } else { + if (yp < hght) { + // Upper right arc. + path.moveTo(wdth - xp, ht - yp); + path.curveTo( + wdth + cw - xp, + ht - yp, + wdth * 2 - ht - xp, + hght - ch - yp, + wdth * 2 - ht - xp, + hght - yp, + ); + } else { + // Lower right arc. + path.moveTo(wdth * 2 - ht - xp, hght - yp); + path.curveTo( + wdth * 2 - ht - xp, + hght + ch - yp, + wdth + cw - xp, + hght * 2 - ht - yp, + wdth - xp, + hght * 2 - ht - yp, + ); + } + } + + try canvas.strokePath(path.wrapped_path, .{ + .line_cap_mode = .butt, + .line_width = @floatFromInt(metrics.box_thickness), + }, .on); +} From 0414e9e2819dfa08bf47a9aca8c3cd8ea5d7eafe Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 16:19:15 -0600 Subject: [PATCH 030/119] font/sprite: add (some) sflc supplement box drawing chars --- ...ymbols_for_legacy_computing_supplement.zig | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index 01258b041..9ae92cc72 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -61,6 +61,8 @@ const xHalfs = common.xHalfs; const yQuads = common.yQuads; const rect = common.rect; +const box = @import("box.zig"); + const font = @import("../../main.zig"); const octant_min = 0x1cd00; @@ -246,6 +248,81 @@ pub fn draw1CC30_1CC3F( } } +/// TODO: These two characters should be easy, but it's not clear how they're +/// meant to align with adjacent cells, what characters they're meant to +/// be used with: +/// - 1CC1F 𜰟 BOX DRAWINGS DOUBLE DIAGONAL UPPER RIGHT TO LOWER LEFT +/// - 1CC20 𜰠 BOX DRAWINGS DOUBLE DIAGONAL UPPER LEFT TO LOWER RIGHT +pub fn draw1CC1B_1CC1E( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + const w: i32 = @intCast(width); + const h: i32 = @intCast(height); + const t: i32 = @intCast(metrics.box_thickness); + switch (cp) { + // 𜰛 BOX DRAWINGS LIGHT HORIZONTAL AND UPPER RIGHT + 0x1CC1B => { + box.linesChar(metrics, canvas, .{ .left = .light, .right = .light }); + canvas.box(w - t, 0, w, @divFloor(h, 2), .on); + }, + // 𜰜 BOX DRAWINGS LIGHT HORIZONTAL AND LOWER RIGHT + 0x1CC1C => { + box.linesChar(metrics, canvas, .{ .left = .light, .right = .light }); + canvas.box(w - t, @divFloor(h, 2), w, h, .on); + }, + // 𜰝 BOX DRAWINGS LIGHT TOP AND UPPER LEFT + 0x1CC1D => { + canvas.box(0, 0, w, t, .on); + canvas.box(0, 0, t, @divFloor(h, 2), .on); + }, + // 𜰞 BOX DRAWINGS LIGHT BOTTOM AND LOWER LEFT + 0x1CC1E => { + canvas.box(0, h - t, w, h, .on); + canvas.box(0, @divFloor(h, 2), t, h, .on); + }, + else => unreachable, + } +} + +pub fn draw1CE16_1CE19( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + const w: i32 = @intCast(width); + const h: i32 = @intCast(height); + const t: i32 = @intCast(metrics.box_thickness); + switch (cp) { + // 𜸖 BOX DRAWINGS LIGHT VERTICAL AND TOP RIGHT + 0x1CE16 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(@divFloor(w, 2), 0, w, t, .on); + }, + // 𜸗 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM RIGHT + 0x1CE17 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(@divFloor(w, 2), h - t, w, h, .on); + }, + // 𜸘 BOX DRAWINGS LIGHT VERTICAL AND TOP LEFT + 0x1CE18 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(0, 0, @divFloor(w, 2), t, .on); + }, + // 𜸙 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM LEFT + 0x1CE19 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(0, h - t, @divFloor(w, 2), h, .on); + }, + else => unreachable, + } +} + /// Separated Block Sextants pub fn draw1CE51_1CE8F( cp: u32, From b4d83e6349e34022f73ef550cfccaf8f361a3533 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 30 Jun 2025 18:46:49 -0600 Subject: [PATCH 031/119] font/sprite: align quadrants better with other glyphs Use `xHalfs` and `yHalfs` so that the dimensions of each quadrant are appropriately aligned with block elements like the one half block, which could be 1px taller than the bottom quadrants before this change. This is in line with what we do for sextants, the fact that on odd-sized cells there's a 1px overlap is considered acceptable there so I assume it's acceptable here too. --- src/font/sprite/draw/block.zig | 14 ++++++++------ src/font/sprite/draw/common.zig | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/font/sprite/draw/block.zig b/src/font/sprite/draw/block.zig index 27c6ae516..f7faacea7 100644 --- a/src/font/sprite/draw/block.zig +++ b/src/font/sprite/draw/block.zig @@ -15,6 +15,8 @@ const common = @import("common.zig"); const Shade = common.Shade; const Quads = common.Quads; const Alignment = common.Alignment; +const xHalfs = common.xHalfs; +const yHalfs = common.yHalfs; const rect = common.rect; const font = @import("../../main.zig"); @@ -174,11 +176,11 @@ fn quadrant( canvas: *font.sprite.Canvas, comptime quads: Quads, ) void { - const center_x = metrics.cell_width / 2 + metrics.cell_width % 2; - const center_y = metrics.cell_height / 2 + metrics.cell_height % 2; + const x_halfs = xHalfs(metrics); + const y_halfs = yHalfs(metrics); - if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y); - if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y); - if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height); - if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height); + if (quads.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_halfs[0]); + if (quads.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_halfs[0]); + if (quads.bl) rect(metrics, canvas, 0, y_halfs[1], x_halfs[0], metrics.cell_height); + if (quads.br) rect(metrics, canvas, x_halfs[1], y_halfs[1], metrics.cell_width, metrics.cell_height); } diff --git a/src/font/sprite/draw/common.zig b/src/font/sprite/draw/common.zig index 2f608180e..d10128cdf 100644 --- a/src/font/sprite/draw/common.zig +++ b/src/font/sprite/draw/common.zig @@ -204,6 +204,14 @@ pub fn xHalfs(metrics: font.Metrics) [2]u32 { return .{ half_width, metrics.cell_width - half_width }; } +/// yHalfs[0] should be used as the bottom edge of a top-aligned half. +/// yHalfs[1] should be used as the top edge of a bottom-aligned half. +pub fn yHalfs(metrics: font.Metrics) [2]u32 { + const float_height: f64 = @floatFromInt(metrics.cell_height); + const half_height: u32 = @intFromFloat(@round(0.5 * float_height)); + return .{ half_height, metrics.cell_height - half_height }; +} + /// Use these values as such: /// yThirds[0] bottom edge of the first third. /// yThirds[1] top edge of the second third. From adace942d014072470ba170d036be53f38f153a7 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 1 Jul 2025 13:20:10 -0600 Subject: [PATCH 032/119] font/sprite: update reference images --- .../testdata/U+1CC00...U+1CCFF-11x21+2.png | Bin 402 -> 1032 bytes .../testdata/U+1CC00...U+1CCFF-12x24+3.png | Bin 534 -> 1295 bytes .../testdata/U+1CC00...U+1CCFF-18x36+4.png | Bin 1025 -> 2193 bytes .../testdata/U+1CC00...U+1CCFF-9x17+1.png | Bin 316 -> 794 bytes .../testdata/U+1CE00...U+1CEFF-11x21+2.png | Bin 562 -> 632 bytes .../testdata/U+1CE00...U+1CEFF-12x24+3.png | Bin 741 -> 819 bytes .../testdata/U+1CE00...U+1CEFF-18x36+4.png | Bin 1388 -> 1492 bytes .../testdata/U+1CE00...U+1CEFF-9x17+1.png | Bin 399 -> 443 bytes .../testdata/U+2500...U+25FF-11x21+2.png | Bin 2220 -> 2225 bytes .../testdata/U+2500...U+25FF-9x17+1.png | Bin 1844 -> 1853 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png index 581b0bbf0547c2526878bb36c8ecbb8277689956..e04e7726b965fab41da5ed0c4f7e3501709db29f 100644 GIT binary patch literal 1032 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=GWYJzX3_D(1Yoq0M*L zK%&L*{{Ksw+ADUu)~#4AIW6?S|Aiis%pm0;AXYtZ{o~uMlfMbc92aVO9N|K#OMd;^-$%bI|M2D0PiB8XWa(s1dk42l1BL^3`V0&V|Np<4oxOIcfxz*?1{M_s zMote0CLyhdV3Lv3V@1nNo%P=7%hEH|e>dLwufOwT<2Hl$ex7>&-mH*)QCj?e;;W$h zJHAbIa(>veb-jobSNfXTr56G$lHY2#nrZSJ$Zu11ySDTxr;hGsJzs}JjXC?a*f4TA zCu;DtGJ?JELw?KR`0dvwhuLq7SK~A3u0ET7^Sa)AH=c7QZ|ozoXTK^DzAVM{|B|BG zgegxKeg3#V_=le5*Gd1`%i~}Fc#*0&`_NUV+9*a(fA69-^EwY*k*~}BttoJ}G-;E` z9TyW>wpaiDr5&E0-NV)Y=w$rN3?JUDjZNKOR>v+_aC~czj{C#3Uv?bw^VO1ORRkSg zp)4O8zgmh*Q(fcA%MLlFO2cavTuLR{x~kv!Es(>pX80xDPG0~yJZiB z&gQ%tFthCPr#(Ll*sFehkbD#RZpJ~AkS<46-Kxv=KPF9Wa;>h~EvY|M>hdy!{+xHy z<5;3(BT6k()4b=d$v^dW_u*zaVX3)cmtWV)drfP~ZMYU8KTm4&8eZk@U&dN958e8w zCR-nVV`bv1N2b~5{GC!dkFJ{eR7xq(Gi%<_#QBG7u3uWUw_vHpvUlCDj_bYK!gM|O z$0pHooiU+a{f`kS{s{)=XcYl zZ55lze`-zY$4RPId|iu*mK(_HRA!v~KY7xwVy?iDk3Lhc1w8ccDx3EBscZGDOnuWg z55-mYI;Xyf^0_-{q4)2F3#`}h)UI49Z}#P3%%U~_?iYPBm|MZ4uIqP=^L){^EpszZ zpTE?!#(2-e*NwgpKdo2gl8n#fx!<0{HNBocxblxIA0#z1Fg)jnq{zdDybKHs4GaFQ z^?k@L%>JZH3yXwB zB({RAgn))KtY8}sGjcL8Ff=Uqzy7x%7s%mApg<7f3WS>5f*NQ}J>UveW5^3O=il13 z4;iJIkW67v}eGzaNYY4FCVXx#8HxoG9UX@$dUx?6#b$m4CWoRu*SO@yasKCHbvC5tS?5vs56So)$v|eWva$^#j(7>Y7!=j>~2xhrBsm$3wqp{%S zl9JPd`|`aNpYt4DYR?~tA@UjAZcgO0Ss>keEdvPxHGK1--v^V?Y7yE;O&{iut|8nq+qo)o$tTf6>Oji?Zt z;k&bGipTYtOx0(<63SHW_piEfxla4`Rl)-eIjv zwq;5w6!2Fc517ScTFbM&=#=n#KGt1mC4<{JAc=xNCbboc;sk0 zQ*XCYm-*wTyJY|B7Hz**>-g$S&G*w9a~9fifB9wWqrN+&PT0fZz{IbL57wu7Y${yv zv)%RcUgND@F59Mke08h()HBgGWx)rB1(whCEqr}gp(s$7Icavs#5k+Rd38q?24!xa z^!~}M-9N9kh%qlp%&sh$TiO=($G~$Z^XV6V{>fE#I4`{<@ze8RiH_R-;`#t8=_7C<;H#J1fbXu9? zR1tRUqL$7}(Oo56`90~a4ofdBiCY=6yu`aQ#4AiI?=|O=;6MH!9wfJ1dM;?&SYjuy z`txV;v_y$EY-nOVBq1L*_-*{+?w(R{|*y4l{Cs-TQyN^rR+K=e}Wq7y?)ETaXJ>fFZyKVX%kZ t9&Y4eU|=|)Q2$z6X2CWVgaQVJc?|DM9vx1tS`V^@!PC{xWt~$(699W|8YKV# literal 534 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|Vn-PZ!6KiaBo%7&0<2 zFdSO&)jyZhUjw873H*>`U|{(F|IOtzkAOAf4*iz?+{q2}3&Q#wtqbE6i(-U1Y zRKwODa1|AN%YD+oWOD4GM3==ppAs%Cl%b+#jZN|BxmxprCb$}N!kev;Wln%*-DLZ# zYFAUEGWM$1;c><*N!#o1W5a_c z%WQ9B(>%-i0~;Nud)k>W|K_@KyJ^r&E)T-f(MB9>J$VHW%s&#wWD~?#X#CQDmEYB^`dfyZlBg zx7|nmZf4n?*k|G(E4Vs~lMORYXROZfwCb=wd*O=AutIdeojyD1IN-HyED-+#KGnea zxYBOV;h3klHN{DA!)A`@&a*$_oQLFRV`&?edFbR?DtW6jnXzHqHggn^oNWyXaP@z8 z12tf}t2)o(`n!D_+B0(sf3W%1=HMx|-6li-aq$WTW$!;(@x3)Dcl2awg&&ENep_!4 ztcv#bXI)8yoy$UC`z*WRaX3|z!~VLzC8H$tqu~Y>M$A_(<8I##N-9UAS896pNbtQO zcW>K@mOn61n!Dsa6J+CQp|?zsCkcZuwJegNpGDr2ACEw`PZd<;6}BkY0bfUtRO0wh~5`M!sLeN{{)YrYh`w^(}vfH%HmSruxuh2=}qRX$h<}Eft%j zbbLPX^H(EcIorr)JtwJcy?ILODIW-?)+?f6;0d_lsmNzX#F~H;k~pT_Lq4R6703W{ zQr6e5{mmkEt8?-jb8h?2x;(;L3VL~0l6cclGW~Zk#nPSlG(Ny6RUyo|3rpZRn&Nijk<(hWYYA)k>#n_k|E?B1Zu17fdc3LH@ z+AX*_(W9LSFU>p56gnLv2iZ9u3F=dXFKM#SSxcM0IQ?iAkCuC=xJA|Bw#B znH(xUKGE=)BZqR@bu(d|hEW1WBR$v&awSVA@sYLaC(*^1=yNX<61TJ{UaV;5#8_)% zdQV}4zwgN0d9n5H0fWi75@KVecMT29h=MV4#&-I`U{TD0u$2BOv;DWGv{(3gK@iIy z=d!qtcYF-Jv|#}ot0IUwCT$~K!o=N-F?`cuWoKZ}Nge4gw#qa9LQ4{-6L#zcYfvR0 zx*J;CT<(_NN)WEfaD1E!rIMzkQQstLl0HCzw(daK|S6p{?!@R?s`bB>(B)!cHG6y63G6b<}{y{gNA&mhKpgj zUII~Kl73DB(NDk54)AotxnDC|xav=-=Et4PS{DF-aM4Ugh8$uH{WzZIVmoAR+$ka=x332vLss4}_}&0DyE|SpRxokr-T3h`Epjf|%EmrHqMKDy4BLF`H$4Z7Csj zQ70z#ksp3%i=nk(XQ9e$R*ODi>F>(|R5ScVPo&Q3hN*I`rG}*@>d4y;T{T310r}Ju z?bRtudlJ4(-H*ECL5w<~?pVrDC%$k@Q{Un*9DgeF>7WG4HJ92MgsZjfi$jn-N4#BZ c{by2FgVP)9c-}#b>mRnto#H{xbYVsO3$o~Ewg3PC literal 1025 zcmeAS@N?(olHy`uVBq!ia0y~yU|hh!z?{Irz`(%Zuk~1hfr0sir;B4q#hf<>3>g_1 z7!ED?>YvN$uK`kk1a?R=Ffjc8|K@Nb4+8_k0fqY4+A<5au^_2nU|@K_4N+sr%fP_U zu;Aa?T3$wJCR8;Bj9@ia9JvlTFdV(`dw!I7)9$86KQ4Y?x?2);NPUYQ!~MtK9;|hr zXC7BE^SQBqUclMO(jlLpuwAv;>1awkm!YoXb^iA^C%r2E23u7dIV&_Sb82L{r8L1I zc%cJRuAs+)l>rKk+c;D%glJ4~h-MMW&=T=bc(8vbGnyxKA9A95Vh$5pU`Wrw2n-8| zM-MY{f&=6Kdg)0`Xkn3$uH?5M7bvQbz#3MFO>k3g3u>Un%?B}P)WCy%?L&-U&xTqB zSCjjY72Q|s(api)^VR5T&OYErH^&=Y&EB^R=xT(aYT*9D^m;LR=wN#N3^a7$zQXkR zff#hx^gd)pbIk!AbTybh$5eyq^8+wn!QJ!sEV|DRu%JaKMzSb?nSwiP4)}o`EPZrw jf=EwOpaGZL!3PWs8PT7PEVpxC07^cdu6{1-oD!M<4z3?y diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png index ecdb2ce10121405cfa22222a848c0ce9c99f775b..459822e6359f512fa3654227681071c1847c59e0 100644 GIT binary patch delta 684 zcmdnPG>dJ5ay`>SPZ!6KiaBqtUu0u86ll9x`+s_#=FSe^&P>(%H9M^0alJL=LwX2o3G4Zapmuq zZRYdOKTP$T*BR#*@T;il(sXTB$7Sz+Tm1}>d{ZYgnP0W3tG>Bu6^khQbXLX;E?%jeBTUvj1@E^uC~&*^JTmHlA%ZefIx}%=GpDn+oPFPAfOiD?}=&+C6j|D@VK{qpH?*W0BeaZNHqjBLwTe<)K<4-hK2?I*Go@oVuBe30X(c| zN`4D+ft-s3J_s|Qsktqvp@F1;fq~(GKU9q&FW9PoYu7$xlt#1b4-3egw+QDz&GBb^ W-CZ|9lza7RkWrqlelF{r5}E+AP+o-q diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png index ed0e8381614585f5bbb2eac395badc97154b2740..03305c81c24482574aa134051787ecb75293f0ab 100644 GIT binary patch literal 632 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=B;6o-U3d6?5L)Fyv}5 z5OFzp=l`Xs2#$?Wj)^~w?@kh_%)Q10Qq91?kg(@*(fPM2yZ^j@ywAYk$KN$77Bi-@ zuy3C9AR$ah=8RWUbEId1fi@>!n(D!WYrc4J**R$SaC3g?99W@YOz3~74>+KaGuFmThR(+N6QujHdx?ATFGg$3|eW#zR z_8t9y{vBu6v3sW$vhr1Fw~JZ4Xx;pDj?2@O_NP# z+eN;F@#z*^h_?KqVQ~K&GuXbH8@X5&d0a03|Nr{BUXxQ#$6;&nZ8MY-^&(Q+-@mWh zDxhZ5KbN;9pHsQ<+-9Gi^C*m+@1-nenXOA`%U;IC!Z%1K6FHHgYl;@Gu|zyZs!SqDyb9t6mUDgWLUZSqv0`-ou00KF6*2UngF19_2~cr literal 562 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=E7vJzX3_D(1X7V93b8 zz;I~6SN~itdkv5RB(Ot*fq~)w|2Kylc^DWN4k*;W)|OeYjRi>s0|P?>7g)_rN3IqF z9@Y!L=UZ)_;j5|Dvf#%@TY;V#llbo*Wd`f`kQ!S0@#FuZAO0EnPcAT-S1GrP?YPLj z`t7_er%rO-*3F%&%*0(2=jSKzW90#mjy-{ob{)xEc~RPO%Yoi|ClI?nj8 z&|>Lv+eDDL?l%*hEbUB3Mj-O*Yw7q4b z^YcQrW16-c!geCdzwA&41;T=p|2}Qm&#!vUZJJulj9wkz9R}PsFXN}~%dOaZDJaJg zZ1JD|5By+9URub@z`((L@b7lFM+di@oO}4A=jGR2#&-~P$aImTWAUI@TU;`T@ zxEVPa7#JEB{9pfDkP8%xNFav=qQsCFtmNO?wGSDknUEAPFfc?jg4Dc47$ZHY38sXZ XQTxAXnkq|03P`1=tDnm{r-UW|F|^Ys diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png index bfc7882152eff8b889fff1fbf733c0dd89746c07..c17e08c39e013a569c43f4e933fd0280ecbf106a 100644 GIT binary patch literal 819 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|V19PZ!6KiaBpCY~*TC zU~mom|9^RQCTF{}2xrl`A1)WH*+6QcV8gY-ow}cY*1TTNzD_MbK*Q$e9tU}y+d>)~ zA}m}?tc^_$jtdk5DnwtfFfjc8|K`RBM1I&=X=TAH07IY1;gq;yy^i02=xzt z{yENJJi2nL!#Fw2X~k{`>#`%aiTJuei28y&{@*dZqT2X~E$ubeFFS zi00e#s7Cj_UUcU3<3;6>$qNoRG&V9Zv$8RPHM`nFEV*@%lgUxQ<>H^sW(-H;wj|me zu70qT-!*W<@{9evTldetQdHEMWx3O7>ztT{QGV+Jbk)OGOk;Nc@%!`v2WDno9utEL z2OK_JytcgXwQBf^vXi2#VvAa{rpGM21>!O?FfizSc%u32(M_4G9df!+xom#q zKmY3g5p}!Q7+!#xDNwWVYvY`~dmwJSiR8vsxh+TEf4H}i&-LP)dC#gn^8$2F@~%?d z>9|$Je8sdUY*&RC7#JR`pKEh;j!plaTa%?jUZ3P$_4Ntc)vBWAEZd!qTjhRq-esS5 z{`S23Y2PQA@|hGYXk_M<$#~%K;nKC`71@)dL%fTcvycpCVPIegvxUZ+Aul-I{;l2n zmO&aEln5XWRRfP(=}AqfN)Gs-E5VHZJFJit0=H@HLq;^44#a?sd5cif`;Zx-W;#Q_ WYUdN5JCfZ&Zu4~Yb6Mw<&;$SxGBgVS literal 741 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|V0`U|{(F|IJ}TUIqq+h6VrDo_)YC&4i?afq|ib1x-yYFC)4d z14gi#%NN;>IEXM``2YXaZvNjP$t{K^uVki`hPrI21DpB5z~hMrEN#72^g#4=c4gVlsoUyw(x+$hh)8G{1SBjt-~iTq;2J;Jl3R{k zOojq37k}Ts#jH^_gY${-J!bR08eL71uKSI%>djX^eZqCMsHio|a;MYQIWY^P{MH5N zviE)XKE1J#jZI1-BVj>flcK9)i(0clTvi4Kh8+h^1)n+f z`NUde6V$_3Oq;A7a=KCZo89I;{=ff4%!}P-c;SEpGczxbiGjeMjjx$^ex0PfrR*mp zP;VaOJZvD~a`E48KSs_q9jyO^-pf8W;OVZN#GE?m&Vr~((jneO%~_nFAl@={`HZR0 zCq`EnHD}rGblf`c&Vnfaynwap<}0o-ufOs8z^6TLf2aS{zj>^YjnAZDK_fG-3`gCq zubTH}Jz=|wWSVDQfG#M){XRir1QD~o582UUZ9NgO?~kr#?^_0RbHt%)7(sq%Snz+n m^rR-#M0CIhtmG|LC39HLvA<^sljH3>g_1 z7!ED?>YvN$uK`kk1a?R=Ffjc8|K_40*8v3qhJ*k2TWMZa5fDu|D7oR&x}9I)N*e;N zKYsfB`-ks**(_bc8h#6Q%uMFqGSQ)s=_sd&dcYhQ<-!G{fb85kHE7W`Y= z`;b|h3GPlXn8Spo#`hsRK{dP&xzW|gFrt}L%gcza#s}itD~?<(1|lv8f6uQYoh%*l`U%@r ztDTNp{qkVkxvy=YemTh5U?AXf@!#%k44v$}k8>*CTeb7nYYvAtB{5_D$Vgj@9XQ0d;X;U|IgCx&^*0KJ%UAQkBH4dr3(+1KHwB< zWYcH$-(bTAinxZ|kSM)n$aUC2fc4_<`&O~D5*LE`!XGulskG8>#Mz^Qu-)*6?s#?8K^> zA;zki5wb#o_3R=A)?O~rB?=7EGjBPnb?^TD-`ybaLa#cz%)uw8o8+Jg*^sLxK)~hT z@B6pTxCod|nCTw6WA6pC$#tE~o-!HPMh}El__kO`e&C#Rn;DWw85sUNI{3Twbo@E_ zIGef)pVz9Jueb&Z^OL-*zCK~QT2<7XWxLaH>%2P)qWtp$z|w~yY3<5E&K3s&my3Tk zn>Br0;2YWd{@}b<2E6mH_A5RA|L)DCstJ52x40Q@z z$!$Rm)UxV;Ke}tzK19!qZ=e>z%LdGR{|CKj!1Qwt)I*Itpz=nc{*8nL%0y`ub7#RNle{;Bzhk=3NfI|IiZJ7nzSddgOFfcsehNv;*Wnf@v zSnzLcEia=q6RH{mMu-|lPOzH)>!l|(p(&Zef~MrQpaz+!}56pFYa%Fg#_z^9tm+24y?@?Q%cw@A$B3 zPP*kzr>%2h7DoB43(!>$Uoma6cF5_IqN|=h;ksH>)S3m7mU-ZM?f$!alUE-2>Yw@k zqy)2MhRFp7wppnP%)M+<9j*P-_A=`f{VPkKex>>O{7Y|ylqWb`WM>u9@K$JC#LuM?FwKFyLl>H2v>?PO-+W>8$DzELw45a}oqsR74guxh{B=dXRHkwgT9R@8lryam$d4 z$x(#m;_v&n&bWw`{H#gu4o^tpw)K7J@|c-{fx+kVQRlY)`Fr;NcqrO3uiAE}4UW`b%%7@HTNo>8f-r zh~-eq*g8R>sZ6nB!BwXw4km*W*JM?8R+j(&&mq(#H%F#`h0~GWkI$khCGr9!S>6K0 zF@wv+-}iUzR`PIAZTV4~e^ax^`9i1)-wWrHZ2by_4*Gtu5Wlvgp#I^P>Hqo7_3U3% zVsja~!x_zt>dK$1t>!u|zjWuxi_kc5)&-i%Bl-< W8vT0T@TA@bxysYk&t;ucLK6VA$lsX& diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png index cf8d5afc7e5294fbbe63568eb4fbcfdba549ca52..a822c4f58a76d4becfb6991ca4974fe3c528e2d2 100644 GIT binary patch literal 443 zcmeAS@N?(olHy`uVBq!ia0y~yV7S1*z&L?}fq{YH*Mp4(3=E7-o-U3d6?5KPH{@zC z;BmNk@BgK)ZUv1UB6bh=u01=+K1~;-m?2@_<0AXGdilwF-fv|!i|76|4iWqVVnHU)U|9^AGkc&ZqgZ0Ai`&JxMYuE}k+P^Cta(LmqHVUE+1U6W^ z&tEfnXT192l6RT4mW>P6e~6pxy40|yFC|bZVB^O~1qOx(yBWde-EibOsvy98;rIPt z@(2BO%w+F3>&5W+EZEiYKJ)C%^_iEK{`Gw^A{ z&}Fgq&;Q!$inK2eMVfYAs@3J2n@}ZlbfN3Qv=32=3=9t}Q3Fj;p@NJGX(jH7JteLKzPbz&S032S#wx^#N0zBp@>A6Wmv zxxxfaXtOieJYoX-=dd9!0|P_Df`4m$AF@j`Av^&BZ_w57KIBGMQ^N={=j~xePOv%u e*Dsdm0tGiWe_Qolf1!XtkU^fVelF{r5}E+BhllY1 diff --git a/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png b/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png index 00b508dcb221d03ea7ebb416569824a41637229b..9ae5b45ff498b398202e44aa19158721e7bd2b29 100644 GIT binary patch delta 184 zcmZ1@xKVI|3S-4a)kN0%)4WWEJPwXti%q;1n`}{cRuK4Hs8n|EY)7ey`FrU<%LFQ7 zKa{r|ec;dZ&-#OZ%Yg%}54aQyG%K0}Br0YecAli-sox#_RwDPY=B;DJB|Gf-*MCc$ zCnHy#o>?bv@FBS%o|y*(_DJlgee!L)h$gGtkIy^>zmGO3ZhiGKEkQ5=3?5i*Q1P6k j;^|ewxLJ^`ok=9@hsdp;hADjv3=9mOu6{1-oD!MiGO`-N_|qZhyV=z*F(V zz6b9)CLf&7@~8a4e2&IO;e(P+4?;dLDH?n*Yj0QaoODlY`z=$eW6>o&-(P&HkrVsv zHt&sGbb98;vllpcVw!JDC4j+$tq*?n$JyRd3YGU?^Xdi<49sDirs6qC#go~VVY3)p cJCn!)p>0}mN-Y~07#J8lUHx3vIVCg!0QNpiM*si- diff --git a/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png b/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png index ea100d7f117b4c4c506f6b174c9cdc11e9a108ee..cf96b7d15bd266af18ffb33076d8e614b44d4018 100644 GIT binary patch delta 688 zcmV;h0#E(44!sVLBLW8mu_b#1f6WPmFc5&zl(wa9X_h8AOz(SxAA2YX0Z#(byo z`-rx{exV-zp&q7Cwo9@NNq!@7=q}?ELj?6-EJ%!1Dnjk@f1POXp5C8xG0000000017Hv<3w|NrdVK@Gz&4259`g-|Gie=rDzFbIV( z2!SvNg%SvbuxWD~+wEzGX|eQw(keOXp>I~xoXigZ0000000000000000001f@6s7N|;~>Xur9S*kf#phX9JyB0}+qGd@H zsA?eBqR^n;qW?VKw{A9ie|_zXpXa~ZO>3S&({6$GNmrktYIo%bwEE%ry+xr2w~fyJ z`p}d^gIps=XD>&1ZH^7HS8Z@^`wAOd*z!b85vUvkwNFp@D2XLQAb$i1hl6v2^guDT zaty@&=?IKaME=^T z1IUD@BKarr#gvwZN&NbR1br|Y%?!4e4i}53!b^g}+n%Ju#iC&thG7^Vp&w2To5lhj}Kf`HF&E4kukLN>*0`*dB+g{a$l88E@f_8Ri>+Z(zdJTn#5(Deitq zi~c=cP-#$ZQ4}-{e`(P+;RVe!oSciGpVd8h;fq|>XMYntn7u5VNa$7)^v6Rn4foT* z1uZa9>)X|(p{__6!d{m4rDp%rK=w=+!n8?~ zqnS_|$W(*T4=1^Y&_I0$jD9%DU7iN&3}N)cN#?^fFz*hdA|Fn&J0%U@_tOuW-S%i_ce-*z) z!~)y9HXL0W&ThLe$vC8VM`1{par|8zmJZ5aEJ%= z2%9>`vE80_m=;U_pR`JedgwQ+sZZty00000000000000000000-}l!qN{0<^$6`x2 zdU)#>r4b}*wm|LvQk^L^@Oqu1BI`F{Spf8Dg^2{i2%XrFZV8CLDC9D!Cp9KV++EW&N0v%fwRl50HHa!7DyY4v6X8eY2U5| z(w1*%M;rYD00960?VZsL!Y~X(*?e*&=ER9N9hQRe^5k|IC0_tGT~Gt zzt2!w9VYSlghVOG8|+28TwFX9Y|vpN&>&qdE*gem7>4l^+Tmokj*M;X!RoC(oLsZT zZk`EizPun8!%3GGC9A6eY>)S+o$oQ6^do$oVSWL4fZ3nsQaI^oaa+2S=(^wqQGsHK zyr7{=iPixxXw1XOe~|>8X7}QaA99=D{i||kHrhCzy;?!gg@Z&OZ&=nF&MaHlcGA9ZEyi)nK&4 zN$w#u Date: Tue, 1 Jul 2025 15:04:21 -0600 Subject: [PATCH 033/119] font/sprite: introduce `Fraction` enum for cell fractions I've included a compatibility test here to make sure that the numbers from this are in line with the numbers produced by xHalfs, yThirds, etc. After this commit I'll introduce a helper function that fills based on a span specified with this enum to replace any uses of xHalfs and friends. Once I do that I'll remove them and the compatibility test, this should be a much cleaner interface for this and make it easier to consistently align block elements with each other. --- src/font/sprite/draw/common.zig | 156 ++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/src/font/sprite/draw/common.zig b/src/font/sprite/draw/common.zig index d10128cdf..ad9788b94 100644 --- a/src/font/sprite/draw/common.zig +++ b/src/font/sprite/draw/common.zig @@ -122,6 +122,162 @@ pub const Alignment = struct { pub const bottom_right = lower_right; }; +/// A value that indicates some fraction across +/// the cell either horizontally or vertically. +/// +/// This has some redundant names in it so that you can +/// use whichever one feels most semantically appropriate. +pub const Fraction = enum { + // Names for the min edge + start, + left, + top, + zero, + + // Names based on eighths + one_eighth, + two_eighths, + three_eighths, + four_eighths, + five_eighths, + six_eighths, + seven_eighths, + + // Names based on quarters + one_quarter, + two_quarters, + three_quarters, + + // Names based on thirds + one_third, + two_thirds, + + // Names based on halves + one_half, + half, + + // Alternative names for 1/2 + center, + middle, + + // Names for the max edge + end, + right, + bottom, + one, + full, + + /// Get the x position for this fraction across a particular + /// size (width or height), assuming it will be used as the + /// min (left/top) coordinate for a block. + /// + /// `size` can be any integer type, since it will be coerced + pub inline fn min(self: Fraction, size: anytype) i32 { + const s: f64 = @as(f64, @floatFromInt(size)); + // For min coordinates, we want to align with the complementary + // fraction taken from the end, this ensures that rounding evens + // out, so that for example, if `size` is `7`, and we're looking + // at the `half` line, `size - round((1 - 0.5) * size)` => `3`; + // whereas the max coordinate directly rounds, which means that + // both `start` -> `half` and `half` -> `end` will be 4px, from + // `0` -> `4` and `3` -> `7`. + return @intFromFloat(s - @round((1.0 - self.fraction()) * s)); + } + + /// Get the x position for this fraction across a particular + /// size (width or height), assuming it will be used as the + /// max (right/bottom) coordinate for a block. + /// + /// `size` can be any integer type, since it will be coerced + /// with `@floatFromInt`. + pub inline fn max(self: Fraction, size: anytype) i32 { + const s: f64 = @as(f64, @floatFromInt(size)); + // See explanation of why these are different in `min`. + return @intFromFloat(@round(self.fraction() * s)); + } + + pub inline fn fraction(self: Fraction) f64 { + return switch (self) { + .start, + .left, + .top, + .zero, + => 0.0, + + .one_eighth, + => 0.125, + + .one_quarter, + .two_eighths, + => 0.25, + + .one_third, + => 1.0 / 3.0, + + .three_eighths, + => 0.375, + + .one_half, + .two_quarters, + .four_eighths, + .half, + .center, + .middle, + => 0.5, + + .five_eighths, + => 0.625, + + .two_thirds, + => 2.0 / 3.0, + + .three_quarters, + .six_eighths, + => 0.75, + + .seven_eighths, + => 0.875, + + .end, + .right, + .bottom, + .one, + .full, + => 1.0, + }; + } +}; + +test "sprite font fraction" { + const testing = std.testing; + + for (4..64) |s| { + const metrics: font.Metrics = .calc(.{ + .cell_width = @floatFromInt(s), + .ascent = @floatFromInt(s), + .descent = 0.0, + .line_gap = 0.0, + .underline_thickness = 2.0, + .strikethrough_thickness = 2.0, + }); + + try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[0])), Fraction.half.max(s)); + try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[1])), Fraction.half.min(s)); + + try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[0])), Fraction.one_third.max(s)); + try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[1])), Fraction.one_third.min(s)); + try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[2])), Fraction.two_thirds.max(s)); + try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[3])), Fraction.two_thirds.min(s)); + + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[0])), Fraction.one_quarter.max(s)); + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[1])), Fraction.one_quarter.min(s)); + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[2])), Fraction.two_quarters.max(s)); + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[3])), Fraction.two_quarters.min(s)); + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[4])), Fraction.three_quarters.max(s)); + try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[5])), Fraction.three_quarters.min(s)); + } +} + /// Fill a rect, clamped to within the cell boundaries. /// /// TODO: Eliminate usages of this, prefer `canvas.box`. From 190c744a6fb547f9ca3b0424f549b258ac0d2617 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 1 Jul 2025 15:20:19 -0500 Subject: [PATCH 034/119] linux: add install target to systemd user service This will allow users to enable Ghostty startup on login. Users will need to explicitly enable startup on login via this command: ```sh systemctl enable --user com.mitchellh.ghostty.service ``` --- dist/linux/systemd.service.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dist/linux/systemd.service.in b/dist/linux/systemd.service.in index b0ef3d59a..3ff848ddd 100644 --- a/dist/linux/systemd.service.in +++ b/dist/linux/systemd.service.in @@ -1,7 +1,11 @@ [Unit] Description=@NAME@ +After=graphical-session.target [Service] Type=dbus BusName=@APPID@ ExecStart=@GHOSTTY@ --launched-from=systemd + +[Install] +WantedBy=graphical-session.target From c838d3d7d251f1420d7bdaa1c6428caf9f6416d6 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 1 Jul 2025 16:00:35 -0600 Subject: [PATCH 035/119] font/sprite: remove `yHalfs` and friends, use `Fraction` Introduces `fill`, which fills between two `Fraction`s, use this instead of `yHalfs` and friends wherever they're used, which also means we can remove `rect`. This commit does change alignment of the vertical/horizontal eighths in certain cell sizes, but the change is for the better IMO. Also changes the center-point alignment of smooth mosaics for odd cell widths, but the change is no more than half a pixel at worst and is probably an improvement ultimately. --- src/font/sprite/draw/block.zig | 15 +- src/font/sprite/draw/box.zig | 15 -- src/font/sprite/draw/common.zig | 168 +++++++----------- .../draw/symbols_for_legacy_computing.zig | 66 +++---- ...ymbols_for_legacy_computing_supplement.zig | 22 +-- typos.toml | 2 - 6 files changed, 108 insertions(+), 180 deletions(-) diff --git a/src/font/sprite/draw/block.zig b/src/font/sprite/draw/block.zig index f7faacea7..571f25a79 100644 --- a/src/font/sprite/draw/block.zig +++ b/src/font/sprite/draw/block.zig @@ -15,9 +15,7 @@ const common = @import("common.zig"); const Shade = common.Shade; const Quads = common.Quads; const Alignment = common.Alignment; -const xHalfs = common.xHalfs; -const yHalfs = common.yHalfs; -const rect = common.rect; +const fill = common.fill; const font = @import("../../main.zig"); const Sprite = @import("../../sprite.zig").Sprite; @@ -176,11 +174,8 @@ fn quadrant( canvas: *font.sprite.Canvas, comptime quads: Quads, ) void { - const x_halfs = xHalfs(metrics); - const y_halfs = yHalfs(metrics); - - if (quads.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_halfs[0]); - if (quads.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_halfs[0]); - if (quads.bl) rect(metrics, canvas, 0, y_halfs[1], x_halfs[0], metrics.cell_height); - if (quads.br) rect(metrics, canvas, x_halfs[1], y_halfs[1], metrics.cell_width, metrics.cell_height); + if (quads.tl) fill(metrics, canvas, .zero, .half, .zero, .half); + if (quads.tr) fill(metrics, canvas, .half, .full, .zero, .half); + if (quads.bl) fill(metrics, canvas, .zero, .half, .half, .full); + if (quads.br) fill(metrics, canvas, .half, .full, .half, .full); } diff --git a/src/font/sprite/draw/box.zig b/src/font/sprite/draw/box.zig index 91d78d2b2..f14e5a3f9 100644 --- a/src/font/sprite/draw/box.zig +++ b/src/font/sprite/draw/box.zig @@ -24,7 +24,6 @@ const Quads = common.Quads; const Corner = common.Corner; const Edge = common.Edge; const Alignment = common.Alignment; -const rect = common.rect; const hline = common.hline; const vline = common.vline; const hlineMiddle = common.hlineMiddle; @@ -695,20 +694,6 @@ pub fn lightDiagonalCross( lightDiagonalUpperLeftToLowerRight(metrics, canvas); } -fn quadrant( - metrics: font.Metrics, - canvas: *font.sprite.Canvas, - comptime quads: Quads, -) void { - const center_x = metrics.cell_width / 2 + metrics.cell_width % 2; - const center_y = metrics.cell_height / 2 + metrics.cell_height % 2; - - if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y); - if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y); - if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height); - if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height); -} - pub fn arc( metrics: font.Metrics, canvas: *font.sprite.Canvas, diff --git a/src/font/sprite/draw/common.zig b/src/font/sprite/draw/common.zig index ad9788b94..67b9dc778 100644 --- a/src/font/sprite/draw/common.zig +++ b/src/font/sprite/draw/common.zig @@ -135,6 +135,7 @@ pub const Fraction = enum { zero, // Names based on eighths + eighth, one_eighth, two_eighths, three_eighths, @@ -144,17 +145,19 @@ pub const Fraction = enum { seven_eighths, // Names based on quarters + quarter, one_quarter, two_quarters, three_quarters, // Names based on thirds + third, one_third, two_thirds, // Names based on halves - one_half, half, + one_half, // Alternative names for 1/2 center, @@ -167,6 +170,43 @@ pub const Fraction = enum { one, full, + /// This can be indexed to get the fraction for `i/8`. + pub const eighths: [9]Fraction = .{ + .zero, + .one_eighth, + .two_eighths, + .three_eighths, + .four_eighths, + .five_eighths, + .six_eighths, + .seven_eighths, + .one, + }; + + /// This can be indexed to get the fraction for `i/4`. + pub const quarters: [5]Fraction = .{ + .zero, + .one_quarter, + .two_quarters, + .three_quarters, + .one, + }; + + /// This can be indexed to get the fraction for `i/3`. + pub const thirds: [4]Fraction = .{ + .zero, + .one_third, + .two_thirds, + .one, + }; + + /// This can be indexed to get the fraction for `i/2`. + pub const halves: [3]Fraction = .{ + .zero, + .one_half, + .one, + }; + /// Get the x position for this fraction across a particular /// size (width or height), assuming it will be used as the /// min (left/top) coordinate for a block. @@ -196,6 +236,19 @@ pub const Fraction = enum { return @intFromFloat(@round(self.fraction() * s)); } + /// Get this fraction across a particular size (width/height). + /// If you need an integer, use `min` or `max` instead, since + /// they contain special logic for consistent alignment. This + /// is for when you're drawing with paths and don't care about + /// pixel alignment. + /// + /// `size` can be any integer type, since it will be coerced + /// with `@floatFromInt`. + pub inline fn float(self: Fraction, size: anytype) f64 { + return self.fraction() * @as(f64, @floatFromInt(size)); + } + + /// Get a float for the fraction this represents. pub inline fn fraction(self: Fraction) f64 { return switch (self) { .start, @@ -204,23 +257,26 @@ pub const Fraction = enum { .zero, => 0.0, + .eighth, .one_eighth, => 0.125, + .quarter, .one_quarter, .two_eighths, => 0.25, + .third, .one_third, => 1.0 / 3.0, .three_eighths, => 0.375, + .half, .one_half, .two_quarters, .four_eighths, - .half, .center, .middle, => 0.5, @@ -248,52 +304,21 @@ pub const Fraction = enum { } }; -test "sprite font fraction" { - const testing = std.testing; - - for (4..64) |s| { - const metrics: font.Metrics = .calc(.{ - .cell_width = @floatFromInt(s), - .ascent = @floatFromInt(s), - .descent = 0.0, - .line_gap = 0.0, - .underline_thickness = 2.0, - .strikethrough_thickness = 2.0, - }); - - try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[0])), Fraction.half.max(s)); - try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[1])), Fraction.half.min(s)); - - try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[0])), Fraction.one_third.max(s)); - try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[1])), Fraction.one_third.min(s)); - try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[2])), Fraction.two_thirds.max(s)); - try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[3])), Fraction.two_thirds.min(s)); - - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[0])), Fraction.one_quarter.max(s)); - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[1])), Fraction.one_quarter.min(s)); - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[2])), Fraction.two_quarters.max(s)); - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[3])), Fraction.two_quarters.min(s)); - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[4])), Fraction.three_quarters.max(s)); - try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[5])), Fraction.three_quarters.min(s)); - } -} - -/// Fill a rect, clamped to within the cell boundaries. -/// -/// TODO: Eliminate usages of this, prefer `canvas.box`. -pub fn rect( +/// Fill a section of the cell, specified by a +/// horizontal and vertical pair of fraction lines. +pub fn fill( metrics: font.Metrics, canvas: *font.sprite.Canvas, - x1: u32, - y1: u32, - x2: u32, - y2: u32, + x0: Fraction, + x1: Fraction, + y0: Fraction, + y1: Fraction, ) void { canvas.box( - @intCast(@min(@max(x1, 0), metrics.cell_width)), - @intCast(@min(@max(y1, 0), metrics.cell_height)), - @intCast(@min(@max(x2, 0), metrics.cell_width)), - @intCast(@min(@max(y2, 0), metrics.cell_height)), + x0.min(metrics.cell_width), + y0.min(metrics.cell_height), + x1.max(metrics.cell_width), + y1.max(metrics.cell_height), .on, ); } @@ -351,58 +376,3 @@ pub fn hline( ) void { canvas.box(x1, y, x2, y + @as(i32, @intCast(thickness_px)), .on); } - -/// xHalfs[0] should be used as the right edge of a left-aligned half. -/// xHalfs[1] should be used as the left edge of a right-aligned half. -pub fn xHalfs(metrics: font.Metrics) [2]u32 { - const float_width: f64 = @floatFromInt(metrics.cell_width); - const half_width: u32 = @intFromFloat(@round(0.5 * float_width)); - return .{ half_width, metrics.cell_width - half_width }; -} - -/// yHalfs[0] should be used as the bottom edge of a top-aligned half. -/// yHalfs[1] should be used as the top edge of a bottom-aligned half. -pub fn yHalfs(metrics: font.Metrics) [2]u32 { - const float_height: f64 = @floatFromInt(metrics.cell_height); - const half_height: u32 = @intFromFloat(@round(0.5 * float_height)); - return .{ half_height, metrics.cell_height - half_height }; -} - -/// Use these values as such: -/// yThirds[0] bottom edge of the first third. -/// yThirds[1] top edge of the second third. -/// yThirds[2] bottom edge of the second third. -/// yThirds[3] top edge of the final third. -pub fn yThirds(metrics: font.Metrics) [4]u32 { - const float_height: f64 = @floatFromInt(metrics.cell_height); - const one_third_height: u32 = @intFromFloat(@round(one_third * float_height)); - const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height)); - return .{ - one_third_height, - metrics.cell_height - two_thirds_height, - two_thirds_height, - metrics.cell_height - one_third_height, - }; -} - -/// Use these values as such: -/// yQuads[0] bottom edge of first quarter. -/// yQuads[1] top edge of second quarter. -/// yQuads[2] bottom edge of second quarter. -/// yQuads[3] top edge of third quarter. -/// yQuads[4] bottom edge of third quarter -/// yQuads[5] top edge of fourth quarter. -pub fn yQuads(metrics: font.Metrics) [6]u32 { - const float_height: f64 = @floatFromInt(metrics.cell_height); - const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height)); - const half_height: u32 = @intFromFloat(@round(0.50 * float_height)); - const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height)); - return .{ - quarter_height, - metrics.cell_height - three_quarters_height, - half_height, - metrics.cell_height - half_height, - three_quarters_height, - metrics.cell_height - quarter_height, - }; -} diff --git a/src/font/sprite/draw/symbols_for_legacy_computing.zig b/src/font/sprite/draw/symbols_for_legacy_computing.zig index a17ddb494..19e62cf4b 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing.zig @@ -28,13 +28,12 @@ const z2d = @import("z2d"); const common = @import("common.zig"); const Thickness = common.Thickness; const Alignment = common.Alignment; +const Fraction = common.Fraction; const Corner = common.Corner; const Quads = common.Quads; const Edge = common.Edge; const Shade = common.Shade; -const xHalfs = common.xHalfs; -const yThirds = common.yThirds; -const rect = common.rect; +const fill = common.fill; const box = @import("box.zig"); const block = @import("block.zig"); @@ -121,16 +120,12 @@ pub fn draw1FB00_1FB3B( const sex: Sextants = @bitCast(@as(u6, @intCast( idx + (idx / 0x14) + 1, ))); - - const x_halfs = xHalfs(metrics); - const y_thirds = yThirds(metrics); - - if (sex.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_thirds[0]); - if (sex.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_thirds[0]); - if (sex.ml) rect(metrics, canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]); - if (sex.mr) rect(metrics, canvas, x_halfs[1], y_thirds[1], metrics.cell_width, y_thirds[2]); - if (sex.bl) rect(metrics, canvas, 0, y_thirds[3], x_halfs[0], metrics.cell_height); - if (sex.br) rect(metrics, canvas, x_halfs[1], y_thirds[3], metrics.cell_width, metrics.cell_height); + if (sex.tl) fill(metrics, canvas, .zero, .half, .zero, .one_third); + if (sex.tr) fill(metrics, canvas, .half, .full, .zero, .one_third); + if (sex.ml) fill(metrics, canvas, .zero, .half, .one_third, .two_thirds); + if (sex.mr) fill(metrics, canvas, .half, .full, .one_third, .two_thirds); + if (sex.bl) fill(metrics, canvas, .zero, .half, .two_thirds, .end); + if (sex.br) fill(metrics, canvas, .half, .full, .two_thirds, .end); } /// Smooth Mosaics @@ -465,17 +460,12 @@ pub fn draw1FB3C_1FB67( else => unreachable, }; - const y_thirds = yThirds(metrics); const top: f64 = 0.0; - // We average the edge positions for the y_thirds boundaries here - // rather than having to deal with varying alignments depending on - // the surrounding pieces. The most this will be off by is half of - // a pixel, so hopefully it's not noticeable. - const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1]))); - const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3]))); + const upper: f64 = Fraction.one_third.float(metrics.cell_height); + const lower: f64 = Fraction.two_thirds.float(metrics.cell_height); const bottom: f64 = @floatFromInt(metrics.cell_height); const left: f64 = 0.0; - const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2); + const center: f64 = Fraction.half.float(metrics.cell_width); const right: f64 = @floatFromInt(metrics.cell_width); var path = canvas.staticPath(12); // nodes.len = 0 @@ -571,13 +561,14 @@ pub fn draw1FB70_1FB75( const n = cp + 1 - 0x1fb70; - const x: u32 = @intFromFloat( - @round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(metrics.cell_width)) / 8), + fill( + metrics, + canvas, + Fraction.eighths[n], + Fraction.eighths[n + 1], + .top, + .bottom, ); - const w: u32 = @intFromFloat( - @round(@as(f64, @floatFromInt(metrics.cell_width)) / 8), - ); - rect(metrics, canvas, x, 0, x + w, metrics.cell_height); } /// Horizontal one eighth blocks @@ -593,21 +584,14 @@ pub fn draw1FB76_1FB7B( const n = cp + 1 - 0x1fb76; - const h = @as( - u32, - @intFromFloat(@round(@as(f64, @floatFromInt(metrics.cell_height)) / 8)), + fill( + metrics, + canvas, + .left, + .right, + Fraction.eighths[n], + Fraction.eighths[n + 1], ); - const y = @min( - metrics.cell_height -| h, - @as( - u32, - @intFromFloat( - @round(@as(f64, @floatFromInt(n)) * - @as(f64, @floatFromInt(metrics.cell_height)) / 8), - ), - ), - ); - rect(metrics, canvas, 0, y, metrics.cell_width, y + h); } pub fn draw1FB7C_1FB97( diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index 9ae92cc72..fd193a0d5 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -57,9 +57,7 @@ const common = @import("common.zig"); const Thickness = common.Thickness; const Corner = common.Corner; const Shade = common.Shade; -const xHalfs = common.xHalfs; -const yQuads = common.yQuads; -const rect = common.rect; +const fill = common.fill; const box = @import("box.zig"); @@ -122,17 +120,15 @@ pub fn draw1CD00_1CDE5( break :octants result; }; - const x_halfs = xHalfs(metrics); - const y_quads = yQuads(metrics); const oct = octants[cp - octant_min]; - if (oct.@"1") rect(metrics, canvas, 0, 0, x_halfs[0], y_quads[0]); - if (oct.@"2") rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_quads[0]); - if (oct.@"3") rect(metrics, canvas, 0, y_quads[1], x_halfs[0], y_quads[2]); - if (oct.@"4") rect(metrics, canvas, x_halfs[1], y_quads[1], metrics.cell_width, y_quads[2]); - if (oct.@"5") rect(metrics, canvas, 0, y_quads[3], x_halfs[0], y_quads[4]); - if (oct.@"6") rect(metrics, canvas, x_halfs[1], y_quads[3], metrics.cell_width, y_quads[4]); - if (oct.@"7") rect(metrics, canvas, 0, y_quads[5], x_halfs[0], metrics.cell_height); - if (oct.@"8") rect(metrics, canvas, x_halfs[1], y_quads[5], metrics.cell_width, metrics.cell_height); + if (oct.@"1") fill(metrics, canvas, .zero, .half, .zero, .one_quarter); + if (oct.@"2") fill(metrics, canvas, .half, .full, .zero, .one_quarter); + if (oct.@"3") fill(metrics, canvas, .zero, .half, .one_quarter, .two_quarters); + if (oct.@"4") fill(metrics, canvas, .half, .full, .one_quarter, .two_quarters); + if (oct.@"5") fill(metrics, canvas, .zero, .half, .two_quarters, .three_quarters); + if (oct.@"6") fill(metrics, canvas, .half, .full, .two_quarters, .three_quarters); + if (oct.@"7") fill(metrics, canvas, .zero, .half, .three_quarters, .end); + if (oct.@"8") fill(metrics, canvas, .half, .full, .three_quarters, .end); } // Separated Block Quadrants diff --git a/typos.toml b/typos.toml index a8b296755..1fb54ecc6 100644 --- a/typos.toml +++ b/typos.toml @@ -39,8 +39,6 @@ extend-ignore-re = [ [default.extend-words] Pn = "Pn" thr = "thr" -# Should be "halves", but for now skip it as it would make diff huge -halfs = "halfs" # Swift oddities Requestor = "Requestor" iterm = "iterm" From ffe06f1ccdc82a4f0effa13729ab066640537615 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 1 Jul 2025 16:26:57 -0600 Subject: [PATCH 036/119] font/sprite: add sixteenth blocks from slfc supplement --- ...ymbols_for_legacy_computing_supplement.zig | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index fd193a0d5..40c330d2c 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -55,6 +55,7 @@ const z2d = @import("z2d"); const common = @import("common.zig"); const Thickness = common.Thickness; +const Fraction = common.Fraction; const Corner = common.Corner; const Shade = common.Shade; const fill = common.fill; @@ -399,6 +400,88 @@ pub fn draw1CE51_1CE8F( ); } +/// Sixteenth Blocks +pub fn draw1CE90_1CEAF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + const q = Fraction.quarters; + switch (cp) { + // 𜺐 UPPER LEFT ONE SIXTEENTH BLOCK + 0x1CE90 => fill(metrics, canvas, q[0], q[1], q[0], q[1]), + // 𜺑 UPPER CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE91 => fill(metrics, canvas, q[1], q[2], q[0], q[1]), + // 𜺒 UPPER CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE92 => fill(metrics, canvas, q[2], q[3], q[0], q[1]), + // 𜺓 UPPER RIGHT ONE SIXTEENTH BLOCK + 0x1CE93 => fill(metrics, canvas, q[3], q[4], q[0], q[1]), + // 𜺔 UPPER MIDDLE LEFT ONE SIXTEENTH BLOCK + 0x1CE94 => fill(metrics, canvas, q[0], q[1], q[1], q[2]), + // 𜺕 UPPER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE95 => fill(metrics, canvas, q[1], q[2], q[1], q[2]), + // 𜺖 UPPER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE96 => fill(metrics, canvas, q[2], q[3], q[1], q[2]), + // 𜺗 UPPER MIDDLE RIGHT ONE SIXTEENTH BLOCK + 0x1CE97 => fill(metrics, canvas, q[3], q[4], q[1], q[2]), + // 𜺘 LOWER MIDDLE LEFT ONE SIXTEENTH BLOCK + 0x1CE98 => fill(metrics, canvas, q[0], q[1], q[2], q[3]), + // 𜺙 LOWER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE99 => fill(metrics, canvas, q[1], q[2], q[2], q[3]), + // 𜺚 LOWER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9A => fill(metrics, canvas, q[2], q[3], q[2], q[3]), + // 𜺛 LOWER MIDDLE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9B => fill(metrics, canvas, q[3], q[4], q[2], q[3]), + // 𜺜 LOWER LEFT ONE SIXTEENTH BLOCK + 0x1CE9C => fill(metrics, canvas, q[0], q[1], q[3], q[4]), + // 𜺝 LOWER CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE9D => fill(metrics, canvas, q[1], q[2], q[3], q[4]), + // 𜺞 LOWER CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9E => fill(metrics, canvas, q[2], q[3], q[3], q[4]), + // 𜺟 LOWER RIGHT ONE SIXTEENTH BLOCK + 0x1CE9F => fill(metrics, canvas, q[3], q[4], q[3], q[4]), + + // 𜺠 RIGHT HALF LOWER ONE QUARTER BLOCK + 0x1CEA0 => fill(metrics, canvas, q[2], q[4], q[3], q[4]), + // 𜺡 RIGHT THREE QUARTERS LOWER ONE QUARTER BLOCK + 0x1CEA1 => fill(metrics, canvas, q[1], q[4], q[3], q[4]), + // 𜺢 LEFT THREE QUARTERS LOWER ONE QUARTER BLOCK + 0x1CEA2 => fill(metrics, canvas, q[0], q[3], q[3], q[4]), + // 𜺣 LEFT HALF LOWER ONE QUARTER BLOCK + 0x1CEA3 => fill(metrics, canvas, q[0], q[2], q[3], q[4]), + // 𜺤 LOWER HALF LEFT ONE QUARTER BLOCK + 0x1CEA4 => fill(metrics, canvas, q[0], q[1], q[2], q[4]), + // 𜺥 LOWER THREE QUARTERS LEFT ONE QUARTER BLOCK + 0x1CEA5 => fill(metrics, canvas, q[0], q[1], q[1], q[4]), + // 𜺦 UPPER THREE QUARTERS LEFT ONE QUARTER BLOCK + 0x1CEA6 => fill(metrics, canvas, q[0], q[1], q[0], q[3]), + // 𜺧 UPPER HALF LEFT ONE QUARTER BLOCK + 0x1CEA7 => fill(metrics, canvas, q[0], q[1], q[0], q[2]), + // 𜺨 LEFT HALF UPPER ONE QUARTER BLOCK + 0x1CEA8 => fill(metrics, canvas, q[0], q[2], q[0], q[1]), + // 𜺩 LEFT THREE QUARTERS UPPER ONE QUARTER BLOCK + 0x1CEA9 => fill(metrics, canvas, q[0], q[3], q[0], q[1]), + // 𜺪 RIGHT THREE QUARTERS UPPER ONE QUARTER BLOCK + 0x1CEAA => fill(metrics, canvas, q[1], q[4], q[0], q[1]), + // 𜺫 RIGHT HALF UPPER ONE QUARTER BLOCK + 0x1CEAB => fill(metrics, canvas, q[2], q[4], q[0], q[1]), + // 𜺬 UPPER HALF RIGHT ONE QUARTER BLOCK + 0x1CEAC => fill(metrics, canvas, q[3], q[4], q[0], q[2]), + // 𜺭 UPPER THREE QUARTERS RIGHT ONE QUARTER BLOCK + 0x1CEAD => fill(metrics, canvas, q[3], q[4], q[0], q[3]), + // 𜺮 LOWER THREE QUARTERS RIGHT ONE QUARTER BLOCK + 0x1CEAE => fill(metrics, canvas, q[3], q[4], q[1], q[4]), + // 𜺯 LOWER HALF RIGHT ONE QUARTER BLOCK + 0x1CEAF => fill(metrics, canvas, q[3], q[4], q[2], q[4]), + + else => unreachable, + } +} + fn circlePiece( canvas: *font.sprite.Canvas, width: u32, From 2fa4fc89027be57890e50973fff01a50f956b8b9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 1 Jul 2025 14:58:39 -0700 Subject: [PATCH 037/119] reload configuration on SIGUSR2 This is done at the apprt-level for a couple reasons. (1) For libghostty, we don't have a way to know what the embedding application is doing, so its risky to create signal handlers that might overwrite the application's signal handlers. (2) It's extremely messy to deal with signals and multi-threading. Apprts have framework access that handles this for us. For GTK, we use g_unix_signal_add. For macOS, we use `DispatchSource.makeSignalSource`. This is an awkward API but made for this purpose. --- macos/Sources/App/macOS/AppDelegate.swift | 34 +++++++++++++++++++++++ src/apprt/gtk/App.zig | 23 +++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 734fcbc20..418005927 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -112,6 +112,9 @@ class AppDelegate: NSObject, /// The observer for the app appearance. private var appearanceObserver: NSKeyValueObservation? = nil + /// Signals + private var signals: [DispatchSourceSignal] = [] + /// The custom app icon image that is currently in use. @Published private(set) var appIcon: NSImage? = nil { didSet { @@ -249,6 +252,9 @@ class AppDelegate: NSObject, // Setup our menu setupMenuImages() + + // Setup signal handlers + setupSignals() } func applicationDidBecomeActive(_ notification: Notification) { @@ -406,6 +412,34 @@ class AppDelegate: NSObject, return dockMenu } + /// Setup signal handlers + private func setupSignals() { + // Register a signal handler for config reloading. It appears that all + // of this is required. I've commented each line because its a bit unclear. + // Warning: signal handlers don't work when run via Xcode. They have to be + // run on a real app bundle. + + // We need to ignore signals we register with makeSignalSource or they + // don't seem to handle. + signal(SIGUSR2, SIG_IGN) + + // Make the signal source and register our event handle. We keep a weak + // ref to ourself so we don't create a retain cycle. + let sigusr2 = DispatchSource.makeSignalSource(signal: SIGUSR2, queue: .main) + sigusr2.setEventHandler { [weak self] in + guard let self else { return } + Ghostty.logger.info("reloading configuration in response to SIGUSR2") + self.ghostty.reloadConfig() + } + + // The signal source starts unactivated, so we have to resume it once + // we setup the event handler. + sigusr2.resume() + + // We need to keep a strong reference to it so it isn't disabled. + signals.append(sigusr2) + } + /// Setup all the images for our menu items. private func setupMenuImages() { // Note: This COULD Be done all in the xib file, but I find it easier to diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 7786f976a..c61254fbd 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -373,6 +373,13 @@ pub fn init(self: *App, core_app: *CoreApp, opts: Options) !void { .{}, ); + // Setup a listener for SIGUSR2 to reload the configuration. + _ = glib.unixSignalAdd( + std.posix.SIG.USR2, + sigusr2, + self, + ); + // We don't use g_application_run, we want to manually control the // loop so we have to do the same things the run function does: // https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533 @@ -1508,6 +1515,22 @@ pub fn quitNow(self: *App) void { self.running = false; } +// SIGUSR2 signal handler via g_unix_signal_add +fn sigusr2(ud: ?*anyopaque) callconv(.c) c_int { + const self: *App = @ptrCast(@alignCast(ud orelse + return @intFromBool(glib.SOURCE_CONTINUE))); + + log.info("received SIGUSR2, reloading configuration", .{}); + self.reloadConfig(.app, .{ .soft = false }) catch |err| { + log.err( + "error reloading configuration for SIGUSR2: {}", + .{err}, + ); + }; + + return @intFromBool(glib.SOURCE_CONTINUE); +} + /// This is called by the `activate` signal. This is sent on program startup and /// also when a secondary instance launches and requests a new window. fn gtkActivate(_: *adw.Application, core_app: *CoreApp) callconv(.c) void { From 0cd95a791f15dfc17d257c5d40b92a1fe1a26ec9 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 1 Jul 2025 16:52:17 -0600 Subject: [PATCH 038/119] font/sprite: add sflc supplement circle/ellipse glyphs --- .../draw/symbols_for_legacy_computing.zig | 2 +- ...ymbols_for_legacy_computing_supplement.zig | 129 +++++++++++++----- 2 files changed, 93 insertions(+), 38 deletions(-) diff --git a/src/font/sprite/draw/symbols_for_legacy_computing.zig b/src/font/sprite/draw/symbols_for_legacy_computing.zig index 19e62cf4b..164aa1ac3 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing.zig @@ -1367,7 +1367,7 @@ fn checkerboardFill( } } -fn circle( +pub fn circle( metrics: font.Metrics, canvas: *font.sprite.Canvas, comptime position: Alignment, diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig index 40c330d2c..f43949eb9 100644 --- a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -61,6 +61,7 @@ const Shade = common.Shade; const fill = common.fill; const box = @import("box.zig"); +const sflc = @import("symbols_for_legacy_computing.zig"); const font = @import("../../main.zig"); @@ -210,37 +211,37 @@ pub fn draw1CC30_1CC3F( ) !void { switch (cp) { // 𜰰 UPPER LEFT TWELFTH CIRCLE - 0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2), + 0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2, .tl), // 𜰱 UPPER CENTRE LEFT TWELFTH CIRCLE - 0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2), + 0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2, .tl), // 𜰲 UPPER CENTRE RIGHT TWELFTH CIRCLE - 0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2), + 0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2, .tr), // 𜰳 UPPER RIGHT TWELFTH CIRCLE - 0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2), + 0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2, .tr), // 𜰴 UPPER MIDDLE LEFT TWELFTH CIRCLE - 0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2), + 0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2, .tl), // 𜰵 UPPER LEFT QUARTER CIRCLE - 0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1), + 0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1, .tl), // 𜰶 UPPER RIGHT QUARTER CIRCLE - 0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1), + 0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1, .tr), // 𜰷 UPPER MIDDLE RIGHT TWELFTH CIRCLE - 0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2), + 0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2, .tr), // 𜰸 LOWER MIDDLE LEFT TWELFTH CIRCLE - 0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2), + 0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2, .bl), // 𜰹 LOWER LEFT QUARTER CIRCLE - 0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1), + 0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1, .bl), // 𜰺 LOWER RIGHT QUARTER CIRCLE - 0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1), + 0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1, .br), // 𜰻 LOWER MIDDLE RIGHT TWELFTH CIRCLE - 0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2), + 0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2, .br), // 𜰼 LOWER LEFT TWELFTH CIRCLE - 0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2), + 0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2, .bl), // 𜰽 LOWER CENTRE LEFT TWELFTH CIRCLE - 0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2), + 0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2, .bl), // 𜰾 LOWER CENTRE RIGHT TWELFTH CIRCLE - 0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2), + 0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2, .br), // 𜰿 LOWER RIGHT TWELFTH CIRCLE - 0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2), + 0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2, .br), else => unreachable, } } @@ -285,6 +286,62 @@ pub fn draw1CC1B_1CC1E( } } +/// 𜸀 RIGHT HALF AND LEFT HALF WHITE CIRCLE +pub fn draw1CE00( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + sflc.circle(metrics, canvas, .left, false); + sflc.circle(metrics, canvas, .right, false); +} + +/// 𜸁 LOWER HALF AND UPPER HALF WHITE CIRCLE +pub fn draw1CE01( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + sflc.circle(metrics, canvas, .top, false); + sflc.circle(metrics, canvas, .bottom, false); +} + +/// 𜸋 LEFT HALF WHITE ELLIPSE +pub fn draw1CE0B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .tl); + try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .bl); +} + +/// 𜸌 RIGHT HALF WHITE ELLIPSE +pub fn draw1CE0C( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .tr); + try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .br); +} + pub fn draw1CE16_1CE19( cp: u32, canvas: *font.sprite.Canvas, @@ -491,6 +548,7 @@ fn circlePiece( y: f64, w: f64, h: f64, + corner: Corner, ) !void { // Radius in pixels of the arc we need to draw. const wdth: f64 = @as(f64, @floatFromInt(width)) * w; @@ -516,9 +574,8 @@ fn circlePiece( var path = canvas.staticPath(2); - if (xp < wdth) { - if (yp < hght) { - // Upper left arc. + switch (corner) { + .tl => { path.moveTo(wdth - xp, ht - yp); path.curveTo( wdth - cw - xp, @@ -528,8 +585,19 @@ fn circlePiece( ht - xp, hght - yp, ); - } else { - // Lower left arc. + }, + .tr => { + path.moveTo(wdth - xp, ht - yp); + path.curveTo( + wdth + cw - xp, + ht - yp, + wdth * 2 - ht - xp, + hght - ch - yp, + wdth * 2 - ht - xp, + hght - yp, + ); + }, + .bl => { path.moveTo(ht - xp, hght - yp); path.curveTo( ht - xp, @@ -539,21 +607,8 @@ fn circlePiece( wdth - xp, hght * 2 - ht - yp, ); - } - } else { - if (yp < hght) { - // Upper right arc. - path.moveTo(wdth - xp, ht - yp); - path.curveTo( - wdth + cw - xp, - ht - yp, - wdth * 2 - ht - xp, - hght - ch - yp, - wdth * 2 - ht - xp, - hght - yp, - ); - } else { - // Lower right arc. + }, + .br => { path.moveTo(wdth * 2 - ht - xp, hght - yp); path.curveTo( wdth * 2 - ht - xp, @@ -563,7 +618,7 @@ fn circlePiece( wdth - xp, hght * 2 - ht - yp, ); - } + }, } try canvas.strokePath(path.wrapped_path, .{ From cff6860fd936b3265adf01db068f8b86a7458aa6 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 1 Jul 2025 17:03:10 -0600 Subject: [PATCH 039/119] font/sprite: update reference images --- .../testdata/U+1CE00...U+1CEFF-11x21+2.png | Bin 632 -> 1006 bytes .../testdata/U+1CE00...U+1CEFF-12x24+3.png | Bin 819 -> 1255 bytes .../testdata/U+1CE00...U+1CEFF-18x36+4.png | Bin 1492 -> 2247 bytes .../testdata/U+1CE00...U+1CEFF-9x17+1.png | Bin 443 -> 751 bytes .../testdata/U+1FB00...U+1FBFF-11x21+2.png | Bin 5448 -> 5427 bytes .../testdata/U+1FB00...U+1FBFF-12x24+3.png | Bin 5724 -> 5718 bytes .../testdata/U+1FB00...U+1FBFF-18x36+4.png | Bin 9973 -> 9974 bytes .../testdata/U+1FB00...U+1FBFF-9x17+1.png | Bin 4295 -> 4334 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png index 03305c81c24482574aa134051787ecb75293f0ab..b3cda82d1391bf8a59367dae98436e1ed243ef7f 100644 GIT binary patch delta 943 zcmeyt@{WCiay|1_PZ!6KiaBp?T+BVJAi$PjmcZh6f}!>VN6!R7BPQhvmi&PJ0~~UV zf)Wqczgiaf_Li4}$)l?O-yg~J`R!z2OXd-rm~r5nv%R0yKUu_e+}E@!s^Z1ui7rXy7us^vZ(0)b^3dkzRmT#`+LJKVgAzeC>IwO z7ZsL*`wX+c)|J*KSBLAgvR-dj+W4R;Kt(J@pzBBzx6{Ok0!IxYZaogyq=T#rJ#=;` zh;*`wwM`T z#eVbDLQ%d=1>5IL&ak|hXu0%akxOFh_tPi&xavbCKvvmj?a?dTE|Q|%9%gYOjL-Ij z90S-THx0R13^`aY{Jvjbb^B&3ON{denYn>_>oQYJYVjBSXWozfUvk<-OkY#zZ}E%CCQNiAlWb z;mZ0@Ex#WS$6fR0Yc}9%JNSG4ti07bLgn2W^WV|z|8!gbDE0hq;o6p8J<2T-p|6y!!w8Xoyofm4ULV@n`Ffd3<^RX zADDX#%rc9Zq>@>6I(D7j{QB~~G5>+ Kb6Mw<&;$VCm8w<% literal 632 zcmeAS@N?(olHy`uVBq!ia0y~yVEDkm!1#fKfq{Wxujk%h3=B;6o-U3d6?5L)Fyv}5 z5OFzp=l`Xs2#$?Wj)^~w?@kh_%)Q10Qq91?kg(@*(fPM2yZ^j@ywAYk$KN$77Bi-@ zuy3C9AR$ah=8RWUbEId1fi@>!n(D!WYrc4J**R$SaC3g?99W@YOz3~74>+KaGuFmThR(+N6QujHdx?ATFGg$3|eW#zR z_8t9y{vBu6v3sW$vhr1Fw~JZ4Xx;pDj?2@O_NP# z+eN;F@#z*^h_?KqVQ~K&GuXbH8@X5&d0a03|Nr{BUXxQ#$6;&nZ8MY-^&(Q+-@mWh zDxhZ5KbN;9pHsQ<+-9Gi^C*m+@1-nenXOA`%U;IC!Z%1K6FHHgYl;@Gu|zyZs!SqDyb9t6mUDgWLUZSqv0`-ou00KF6*2UngF19_2~cr diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png index c17e08c39e013a569c43f4e933fd0280ecbf106a..e076da7c52f1c09e553d278e5025d464d17985db 100644 GIT binary patch literal 1255 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|U!CPZ!6KiaBp?<`=dY z2(T8&C@2a|XkbxMc&OsSG)ak(bK(Y}ICWMl-KRJFrq4Z7`2PFSxsTH<<$k(nNGT~T zs%G5q`*|u)@T9lLy=Sq_-+bmk#J|1+Z;ILEb44EfzRRkzjZeL+p+~WY$)){M+##Mf z%uLS;{?5F$N6h`(iTj=!XVs7Y)1GkQc#-0x2?5*HrtE&5y<79`^Ns&M1m2o(I5sa? z$W!m{b#In}0(DO7cVz1=0|lM#D#IJO^sxLWAJ$+x`E95bSWBUgu% ze)_Gy!-TD*FZPWHdnW7hN>=eMF1xHh%<^I93wJ-3F+U@6rzo`Ga@@h|b}nG>C9UCa z_Kx}IkL>4@uX%n=VnP$6=D(tkJTIF3^ST)|!F*)mkjMpg1_p-z|KHp&GCcbbEv0w|WCpdpYQtp6}0Cj$dR z!-D_oZwqQ@z+@r7Kp0}j<%?`b97LEe{Qv)IH~(*oTLx{*O8HLj(o%}D2P z`~Ux|)%VX04qu_Wd|g1aU+ltdb9Oo9T9&rnDtaLJI(uii<7~qpOW{YuPxG$+AJk|qF$u()HU{^tVz-#-bKw>o_PVf zCwW(??sVMB!oa{#vDMn&uza4o-`kVCtG+&AyINJ$oMpSyaqGN03!?aKlD;=S{lD#f z|I_U$z0ABa84nzo*`+K5Vs8D?oHy$U+to>T7DOQ#%*gQI5G3NR8*(u@3b0=MJwHmB zdFw*4){PUtCrtMH7JTyuLtS>w_PWYH+Ry}`a6!?D9h?*t0t8+NENpgg1hF(YN;)n; zSt2Y<3=9t@eHVXFQE7QkPEXGM$4fD|_Q|Tz)6YogL~cttmT+>Dsxh1QG~Jno5u0*O z_8e?VEr-P4^^Lp@20W}6|88H#-{G8Du;jbghwG(sCdp^|`2Wq+x^QB3G&o5fI(Oyi z+32`~zteyG(0F!rBO;xCsemRtP>K{6hZuXLdmO+{cp@M;dp@0!B4NFgI oLRImH1*GCF!jQEO8IcV+5ShrfSW)2KULlZqp00i_>zopr0E+Sig8%>k literal 819 zcmeAS@N?(olHy`uVBq!ia0y~yU{qjWU~=GKU|?Y2`0`{n0|V19PZ!6KiaBpCY~*TC zU~mom|9^RQCTF{}2xrl`A1)WH*+6QcV8gY-ow}cY*1TTNzD_MbK*Q$e9tU}y+d>)~ zA}m}?tc^_$jtdk5DnwtfFfjc8|K`RBM1I&=X=TAH07IY1;gq;yy^i02=xzt z{yENJJi2nL!#Fw2X~k{`>#`%aiTJuei28y&{@*dZqT2X~E$ubeFFS zi00e#s7Cj_UUcU3<3;6>$qNoRG&V9Zv$8RPHM`nFEV*@%lgUxQ<>H^sW(-H;wj|me zu70qT-!*W<@{9evTldetQdHEMWx3O7>ztT{QGV+Jbk)OGOk;Nc@%!`v2WDno9utEL z2OK_JytcgXwQBf^vXi2#VvAa{rpGM21>!O?FfizSc%u32(M_4G9df!+xom#q zKmY3g5p}!Q7+!#xDNwWVYvY`~dmwJSiR8vsxh+TEf4H}i&-LP)dC#gn^8$2F@~%?d z>9|$Je8sdUY*&RC7#JR`pKEh;j!plaTa%?jUZ3P$_4Ntc)vBWAEZd!qTjhRq-esS5 z{`S23Y2PQA@|hGYXk_M<$#~%K;nKC`71@)dL%fTcvycpCVPIegvxUZ+Aul-I{;l2n zmO&aEln5XWRRfP(=}AqfN)Gs-E5VHZJFJit0=H@HLq;^44#a?sd5cif`;Zx-W;#Q_ WYUdN5JCfZ&Zu4~Yb6Mw<&;$SxGBgVS diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png index 6f4a004d497fbb023e9bfb3d94b4268f823015e4..366be38671868fa0eeefedb0a19356ef4d3c2b59 100644 GIT binary patch literal 2247 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 literal 443 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 IHQSK?0qw~kI{*Lx diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png index 35504e45bc06f179958280e2cec69348399d1fef..2eff59c76114698bf12ffc228db36a935a03c16e 100644 GIT binary patch delta 1862 zcmV-M2f6s%EY>WLBmo_GP0%W<>-+#q-&&P7_)n}p1*l82 zJhO2!A+pvKaUinNiZEzZ2)#W(eVXNwt+&>NN}cLL30j@-h!q1DV(|8V0J${F6PvfK z3za(6g)+1{*%2!SF2vYv0b=LB*^hT@|F%4RO`Hx$k3_{0H_sz!i6|DG!-xHS2bo9n@~%~Ao1~3_NM+rkpQcC{_{UGMIo?B zd)kFz#d`1kNJ|H4@$pmwQ*)u#-JuylazK=Y=y=kedR@O_J@d|5rlq6U#CR%uQ;(E< ziFF_qCftYc&B6Y3mh6R~wc{X5 z3qPJhKxjMyLWp_-VFZABh~=BzzK7$LSF|l0D02(pGijDGbsKkPp%r$fq0#g0u_d2;;n&%@DM7sBx1WPvK*p0&%#N`G`vWCKMGs2?ceU~j zGU-5Ws^O<2a@Xi~2%6E)0U(^LDnNF!iWAyt2kIuPFrk&(K(<5BjJ^*5VY)*CGU*OX ziA+0Cm+pX+h&qw{)(|xMQ6VPGG@zC+!=&1R1DS*wCe`SFuF?BnRI4wKDc1p@nsUXo z?1BTClq;rX*}jL{L(t$yg_yJ-0Xo58Vt>hjYITy>XZs#*4?%+;72>kb5KzH^OG8Tz zRI3k{hC-oGC=?2XLZMJ7^qAP187aHA+Wo>W9Zq^lZGEKx6o6C-YyTji(G6X#k;!Yd z15g0cw>DD^`@hDZ`;Y~oPee`r3zM1(9DmTk&f7UP4d~23t>I(H0LAZ&tO4jcK&|65 zWPswSku?Ba8L0KaOa=&0KS0CPWPo0N0?=lFhTB6{PWMg$dU^+_JKZ}aNs=T<`a!Kf zb>DkZu2HX5Z&|NhU*qv${ja`n`Ft$`1n5IRdjb>!6ao}D_RIhZGl0U(jezC?6dnQ; zx)IREHHJ=|AE@^>kO2bp9?($%3IPfMT9!Wmvr`Lo2Yd%NizlaaZCS*{^u!R zsJ^N4Q2m_Wgj5T1-W0|`CCuZN{t@~76fnmo^V|p0#Cx7&&+wa&Zb8ybVdSc0e(lx2 zIuXg^mVf@yd*!EyIY>H=roYU8PI>qSpkt_%@=fRis(MIE-?Un^W*q2E%m!byDIddZyLoezhE}1Blwwo3NVO<)^ zmpQhCGtOWuAiUgVeWXnnilptP1wmNX&hllBZ+`+oBE~k8QThnmb`(h4TP(=D5*h51 zKpH_f!*nD{ADs9(OsL6%%uBV=K8YuyHD{dAhUp{!VkzqOHVZN?Plp?Ac9cOl!y}PE zebC{P!-U!_$hdwVZnWuP80Cx()k5_F$CnNhYP29#7o4e)<5b2O9LomlLqF~@p;il0 zXMdKEsZ|H^j59dc2Wn`Umj~6BoE2C=`?(+^6@?_^vJWo}@v=`wRnlRcL1_r7p=Dkk z)I~TPQ@6`SLkoL`#*2%%q*xaPIcHv6>{Vl6U|?WiU|?WiU|_(9^Z&c=?<%U3kgggi zf%sI8RMIcAfk;K4UOq=}{&+C%vTf`9cz?|6wk@4(LIj$NKt?g%-I;IF(giHBeADex{!E5KhT^1yUzV*p&1mo3}!UoBF*1seg7G zX6k~?|v0{{U3|LoX73Iah4MA7@7TDNMam3%9Z#eXXoL7IY@p486+1%XHG6bxwHBL-g5 zr(i(qUee&2ug5|Gt-I#IhhjMv3u$QEhay-tPRW4Qtr~COLOXZ4fQ9zUd~2bt&`M+~ zAXJH5=bM#C^$)aEn+gb(YS;N@saC+X>`*|gmc7k)YuN%W_J;yu#r|!+TkO|={qdu7 zrhGt;7yln-?k15WNs=T41@f~=Y delta 1912 zcmV-;2Z#99EZi)RBmpC_C2I$N-<8}f48_w)N(r5$w3Lt%mQGTNbF#}Hp?jqT#yAhF zd(QW;{=hT(MaUoqIsOZULZL4vT$^d^Mf9-uMJ^2pI!=|ZJVbD;#iDm-F^<3bF7-X0*8W_jZ9 zwsfJ=rnyjtUX>lO!f_$SZVM2r|0e%Dar|GpP;FB%B1BwZX@%FkOg}+XBRDP^t^JmmrZY)Y`m*NYJZ?Z)%0%LL3~0W8S)mRWa2U z?k-{@U8uErCy}97p8!yQD-0Ln+|X3Kwcgd3*=#~Bl|kb3sjN-?hav%1@%-oi%oK%z z&9tZA75}29`wc#6z86*cpSr`M)w5Q!y?^w^gvzBS86q_-h%G%T` zC4EIYkQFEh3@db-_8^XSWoEO9Xr-9nH$IqvXfGgy0MH1re6l+CaJ}-1wuOSCwZ-qxh8O_^Lkx$Zy#y74AvO-f zj}O!59Wv{sU~X_!x8kY<2%kj6khIpMUsx3H>NSI-NQjMM(J^!Lw`}UY}bpW-L zE2d=^97v>GF)ho^Jsb}~&W{Q)Y3~85U@)=2K zR+P%$=3qRCd^pe)jtpZR0GOp16&jIqj(A{!P zUTYnI0+988&E{JF&lq$LSpfP()a1Va00960?AJk(f-n$O2PY< z*n!U5IX4aH#6Yd{$B+Sv?-^MG&~<=XZ=4|m6igp{OGR6l_0Kj#-ARf3$I!Z;{}dEGKU zay~x=o@0@D?h9$;J62%Wr{2U_GBtd3VZ4@W* zM6~u9C$wSw$iG;Mdb~}7jOOVu&}LT|#AkRV637p_d~%3Tn*u?5TQm1QhC9d8o5qoK7(u7V1DSwJtEY9DnaVb5;C>wLZ0~yF7^Q%TIS_J6_T?8 z3m88aWTc{ygk1LFr6FGS$*4*?%x6#Am~&o;tmW*%vAB2lSoIZ;sae^JVCL;qp_S zy`P}z%TVh5zRGNVbGYubyw>%#^!pQ~taIA76tKUJ+5E9l)XfsMCH=_atvJMgMg3laRJjckbwBd)FFl1}!L0=82X<6gZ~@8w zU#0XbyOkh44awB5j!;owC)iDFX>ZbKFP18v+0T0B{z*S!#lu9g2Ja00006UunmM7NgfcnB|sC@Y}bzr+q?WV0q+I z+v_!AdUK|Hwd{?5z36eIMjzvaY6dB}hBkJE*UjJ97sxZ4mNQIYQD}H4-tdP(<+H7< zgAs#h8pF=X8<-cUFq~Y|Say+7gl)ksPli{o88mJ`_CLheu*vqH${P!AkNS1FOPE*K zGF(k(u#{y;&HbnHhCyQ?b4D`5s_Am9C5#LV|Np|eQldK^m^=M^gp{{`0$Rx)knJ9nDt>*cwx?`Jdp zRk={aq^o|RsJW2=3@U6H7#RL$_r0?H?y+`3gUh zNV$6HUxvyrHK{i%>gM06=dfPa&fs-pc5>?W$5EH#l|JgsiIic|5EST{dUvL)?@eX7 zb@nU)k7h3QpA%!^@Y#Kx{v%cq;kO-!CoyfZew5+MG)-he>|}@R%^R0Ayt$Qg%T=`A zo9o28MOqu&wlut1Gq0{Vko&|rowwVcA8mN^sO?*WH}46(4Z8(R!{X}B+g$u6tQ*vO z^0^X!S`NF*9se~RwHH$2=GolZA}vsCzfh?qaIHh7+`_XBb-#ZyfkW@MAy!-r==KYJm!&-9w`HeXr4{J|S@tmaMd6N}v z-C;vs1_p+P1^?FSKID{U0*CcxP*DFzk~;fzopr0BnRY5dZ)H delta 700 zcmez7`_*@X3ge!Qs;7nOZv}E4HsEP~`1}4Yb`8O48=4jOC-^g^-u@ntak_m%>qPx4 ztBMv~e!6JR{^cJtVsobSF};v!n8v=~DPu+@!=}0me?|V^>Emsf#K01eDX_qqA#2wi zKUM|F1}#;GG~qmkDJ&rN(^J-*+g~tnF<;1Gzp$TC!}@ssq2BO@s+SD)S^N&NvJU^* z7UZ1GKg8Rx$#g%bc^qR<`mYj(*fSy**cll9|9^8;k?(*3N882U_p8#@B{sJS9I5{1 zzv73>>^u&YfI8t9F)UrowYnd-{@K0Kb-@gWt%Y--IOhHPlDPl-)h?(uP7}_sY@8;| z!@>Xp2VOIRP2T)L*hR4Z%1q5a3}F7OK)%BYJkIayyJAb@YeLp$I>|JziMl1<>vG?v zdG5OV`rqXb^}SYX5c>8k;^x~O*GfE9?zI|sJ2R*r4rT5#YdsH4V%n4~l#y#TO=LpsY~S50lIlGfZ^nkOUh5E> zF!$=^e7!8jn=_aG{kBJ9LhPLjas?|HZ?cy0FFJPW^!u6Wx9S$Tt}^@?^Wi@)#RiYqw7SMT{* zK7zxDBXjcI$uf$k`h@rB#nkh0tmRgB@7VRBLH|xUa}xLWgMv#6>>sFW-)*>Y(yM@F z<{F+K7sJgJ%D#xoIJ(8#@Bi?1+x{EeFXo=#YWet}`Xm+4Nh+S{a*!ZAY{<*Nz|gSZ z-&);=oYG9-;NFfdb@l;2RLY-$Ve$qw1+_*VP_!u2zm}e}U>geqLj~j4u%!tK9;~q- O6Fgo0T-G@yGyworh&BTN diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png index 3fa06a8fc969f3641e2f6d2a02871c11b4bf20ff..062a7da810b835f0f27d0c9b540ce22f3207e157 100644 GIT binary patch delta 4142 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 delta 4081 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 From 2a836b0ab7677ededb9efef18301b9bb3e53ab21 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:02:33 -0600 Subject: [PATCH 040/119] font/coretext: fix small memory leak --- src/font/shaper/coretext.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 1fd9719bb..1aaa029dc 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -109,7 +109,8 @@ pub const Shaper = struct { /// settings the font features of a CoreText font. fn makeFeaturesDict(feats: []const Feature) !*macos.foundation.Dictionary { const list = try macos.foundation.MutableArray.create(); - errdefer list.release(); + // The list will be retained by the dict once we add it to it. + defer list.release(); for (feats) |feat| { const value_num: c_int = @intCast(feat.value); From a91f9ed0e29797e726d278f0a81998136e7167f3 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:38:26 -0600 Subject: [PATCH 041/119] font/coretext: fix small memory leak --- src/font/discovery.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/font/discovery.zig b/src/font/discovery.zig index 9284f9486..6f51379b4 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -831,6 +831,9 @@ pub const CoreText = struct { i: usize, pub fn deinit(self: *DiscoverIterator) void { + for (self.list) |desc| { + desc.release(); + } self.alloc.free(self.list); self.* = undefined; } From 1f733c9e7fbd681dd5557009819515ed614a7119 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:48:30 -0600 Subject: [PATCH 042/119] renderer/metal: properly release texture descriptors Fixes memory leak. We always need to release these descriptors; the textures themselves will retain or copy them if necessary. --- src/renderer/metal/Target.zig | 2 +- src/renderer/metal/Texture.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/metal/Target.zig b/src/renderer/metal/Target.zig index fa62d3014..15780189b 100644 --- a/src/renderer/metal/Target.zig +++ b/src/renderer/metal/Target.zig @@ -68,7 +68,7 @@ pub fn init(opts: Options) !Self { const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("width", @as(c_ulong, @intCast(opts.width))); diff --git a/src/renderer/metal/Texture.zig b/src/renderer/metal/Texture.zig index 32820f8fc..5e6ef78d0 100644 --- a/src/renderer/metal/Texture.zig +++ b/src/renderer/metal/Texture.zig @@ -50,7 +50,7 @@ pub fn init( const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format)); From 5dd1ebb5836c2155976cf6adb227e7327e6d78d2 Mon Sep 17 00:00:00 2001 From: trag1c Date: Wed, 2 Jul 2025 22:17:17 +0200 Subject: [PATCH 043/119] add newline to end of file --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index b371cb04d..18cadddf5 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -272,4 +272,4 @@ msgstr "Текущият процес в това разделяне ще бъд #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" -msgstr "Копирано в клипборда" \ No newline at end of file +msgstr "Копирано в клипборда" From 8ed08aaecf19c9539a4679309fef849bee8207ba Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 16:16:33 -0600 Subject: [PATCH 044/119] deps: update zig-objc This update also fixes a memory leak that was caused by blocks not being deallocated and just collecting every single frame, slowly accumulating memory until OOM. --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 6 +++--- src/renderer/metal/Frame.zig | 18 ++++++++---------- src/renderer/metal/IOSurfaceLayer.zig | 25 ++++++++++--------------- 7 files changed, 30 insertions(+), 37 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 68d65fbe9..237720f35 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ }, .zig_objc = .{ // mitchellh/zig-objc - .url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - .hash = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", + .url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + .hash = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", .lazy = true, }, .zig_js = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 3099ca823..420893ef7 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -144,10 +144,10 @@ "url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz", "hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0=" }, - "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt": { + "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk": { "name": "zig_objc", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "hash": "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw=" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "hash": "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw=" }, "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy": { "name": "zig_wayland", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 133284201..6e4b86606 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -314,11 +314,11 @@ in }; } { - name = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt"; + name = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk"; path = fetchZigArtifact { name = "zig_objc"; - url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz"; - hash = "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw="; + url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz"; + hash = "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index bb0a27105..f05a789dd 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -29,6 +29,6 @@ https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.ta https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz -https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz +https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 08fa9568b..daf7e5cea 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -175,9 +175,9 @@ }, { "type": "archive", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", - "sha256": "ce7d6d47ac614a60e56b8509dedf869e2e0d8b747c75e48aded11eec31b3357c" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "dest": "vendor/p/zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", + "sha256": "a37be5eea7e44a2d1b2976ba256b85f76a8c1063fc01ffec85c8a9e67468e4dc" }, { "type": "archive", diff --git a/src/renderer/metal/Frame.zig b/src/renderer/metal/Frame.zig index 81b38e7b6..c766fb8ed 100644 --- a/src/renderer/metal/Frame.zig +++ b/src/renderer/metal/Frame.zig @@ -28,7 +28,7 @@ pub const Options = struct { /// MTLCommandBuffer buffer: objc.Object, -block: CompletionBlock, +block: CompletionBlock.Context, /// Begin encoding a frame. pub fn begin( @@ -47,7 +47,7 @@ pub fn begin( // Create our block to register for completion updates. // The block is deallocated by the objC runtime on success. - const block = try CompletionBlock.init( + const block = CompletionBlock.init( .{ .renderer = renderer, .target = target, @@ -55,7 +55,6 @@ pub fn begin( }, &bufferCompleted, ); - errdefer block.deinit(); return .{ .buffer = buffer, .block = block }; } @@ -114,24 +113,23 @@ pub inline fn complete(self: *Self, sync: bool) void { // If we don't need to complete synchronously, // we add our block as a completion handler. // - // It will be deallocated by the objc runtime on success. + // It will be copied when we add the handler, and then the + // copy will be deallocated by the objc runtime on success. if (!sync) { self.buffer.msgSend( void, objc.sel("addCompletedHandler:"), - .{self.block.context}, + .{&self.block}, ); } self.buffer.msgSend(void, objc.sel("commit"), .{}); // If we need to complete synchronously, we wait until - // the buffer is completed and call the callback directly, - // deiniting the block after we're done. + // the buffer is completed and invoke the block directly. if (sync) { self.buffer.msgSend(void, "waitUntilCompleted", .{}); - self.block.context.sync = true; - bufferCompleted(self.block.context, self.buffer.value); - self.block.deinit(); + self.block.sync = true; + CompletionBlock.invoke(&self.block, .{self.buffer.value}); } } diff --git a/src/renderer/metal/IOSurfaceLayer.zig b/src/renderer/metal/IOSurfaceLayer.zig index 9212bd5e1..5a6bf7307 100644 --- a/src/renderer/metal/IOSurfaceLayer.zig +++ b/src/renderer/metal/IOSurfaceLayer.zig @@ -54,13 +54,11 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // // We release in the callback after setting the contents. surface.retain(); - // We also need to retain the layer itself to make sure it - // isn't destroyed before the callback completes, since if - // that happens it will try to interact with a deallocated - // object. - _ = self.layer.retain(); + // NOTE: Since `self.layer` is passed as an `objc.c.id`, it's + // automatically retained when the block is copied, so we + // don't need to retain it ourselves like with the surface. - var block = try SetSurfaceBlock.init(.{ + var block = SetSurfaceBlock.init(.{ .layer = self.layer.value, .surface = surface, }, &setSurfaceCallback); @@ -68,15 +66,15 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // We check if we're on the main thread and run the block directly if so. const NSThread = objc.getClass("NSThread").?; if (NSThread.msgSend(bool, "isMainThread", .{})) { - setSurfaceCallback(block.context); - block.deinit(); + setSurfaceCallback(&block); } else { - // NOTE: The block will automatically be deallocated by the objc - // runtime once it's executed, so there's no need to deinit it. + // NOTE: The block will be copied when we pass it to dispatch_async, + // and then automatically be deallocated by the objc runtime + // once it's executed. macos.dispatch.dispatch_async( @ptrCast(macos.dispatch.queue.getMain()), - @ptrCast(block.context), + @ptrCast(&block), ); } } @@ -100,10 +98,7 @@ fn setSurfaceCallback( const surface: *IOSurface = block.surface; // See explanation of why we retain and release in `setSurface`. - defer { - surface.release(); - layer.release(); - } + defer surface.release(); // We check to see if the surface is the appropriate size for // the layer, if it's not then we discard it. This is because From f1f9d5eb4b1f027aff4c7a4ed52911a0903a7e64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Jul 2025 16:21:31 -0700 Subject: [PATCH 045/119] Fix some config help that caused website errors when copied --- src/config/Config.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index ef8f48ee9..f36132ea9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2761,14 +2761,14 @@ else /// /// GTK CSS documentation can be found at the following links: /// -/// * - An overview of GTK CSS. -/// * - A comprehensive list +/// * 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 -/// for more +/// 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. From 1270e04480c7925063ce2f037ff085566d2a0b45 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 17:43:05 -0600 Subject: [PATCH 046/119] renderer/opengl: maybe fix issue with cell bg alignment By using integers for the fragcoords I may have stepped on an edge case which causes cell background positions to be shifted by 1 px under some circumstances. I couldn't reproduce that issue in a VM, so I'm making this commit for the user who was having the problem to test it. --- src/renderer/shaders/glsl/cell_bg.f.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/shaders/glsl/cell_bg.f.glsl b/src/renderer/shaders/glsl/cell_bg.f.glsl index 7ba6caaa6..fa48c6736 100644 --- a/src/renderer/shaders/glsl/cell_bg.f.glsl +++ b/src/renderer/shaders/glsl/cell_bg.f.glsl @@ -1,7 +1,7 @@ #include "common.glsl" // Position the origin to the upper left -layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord; +layout(origin_upper_left) in vec4 gl_FragCoord; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; From 182f8ddd1a00d9abcdcee5d7179ecabcdd126a0e Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Wed, 2 Jul 2025 17:37:30 -0700 Subject: [PATCH 047/119] Do not resolve the symbolic link for the initial working directory --- src/termio/Exec.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index b8f838cf9..598617a12 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -847,6 +847,15 @@ const Subprocess = struct { else null; + // Propagate the current working directory (CWD) to the shell, enabling + // the shell to display the current directory name rather than the + // resolved path for symbolic links. This is important and based + // on the same behavior in Konsole and Kitty (see the linked issues): + // https://bugs.kde.org/show_bug.cgi?id=242114 + // https://github.com/kovidgoyal/kitty/issues/1595 + // https://github.com/ghostty-org/ghostty/discussions/7769 + if (cwd) |pwd| try env.put("PWD", pwd); + // If we have a cgroup, then we copy that into our arena so the // memory remains valid when we start. const linux_cgroup: Command.LinuxCgroup = cgroup: { From 9e341a3d60212b361c45527a82e8c8f774e6cf47 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 18 Jan 2025 20:47:23 -0600 Subject: [PATCH 048/119] Created tagged union for selection colors, enabled parsing Implemented cell color for Metal Removed use of selection-invert-fg-bg Mirrored feature to OpenGL Added tests for SelectionColor Fixed selection on inverted cell behavior Implemented cell colors for cursor-text Implemented cell colors for cursor-color, removed uses of cursor-invert-fg-bg during rendering Updated docs for dynamically colored options Updated docstrings, cleaned up awkward formatting, and moved style computation to avoid unnecssary invocations Bump version in docstrings --- src/config/Config.zig | 73 +++++++++++++++++++++++++++++++++-- src/termio/Termio.zig | 11 +++--- src/termio/stream_handler.zig | 7 +++- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index f36132ea9..76f91f6a8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -591,8 +591,11 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// the selection color is just the inverted window background and foreground /// (note: not to be confused with the cell bg/fg). /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"selection-foreground": ?Color = null, -@"selection-background": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"selection-foreground": ?DynamicColor = null, +@"selection-background": ?DynamicColor = null, /// Swap the foreground and background colors of cells for selection. This /// option overrides the `selection-foreground` and `selection-background` @@ -600,6 +603,10 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// /// If you select across cells with differing foregrounds and backgrounds, the /// selection color will vary across the selection. +/// +/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// users should set `selection-foreground` and `selection-background` to +/// `cell-background` and `cell-foreground`, respectively. @"selection-invert-fg-bg": bool = false, /// Whether to clear selected text when typing. This defaults to `true`. @@ -645,10 +652,17 @@ palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-color": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"cursor-color": ?DynamicColor = null, /// Swap the foreground and background colors of the cell under the cursor. This /// option overrides the `cursor-color` and `cursor-text` options. +/// +/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// users should set `cursor-color` and `cursor-text` to `cell-foreground` and +/// `cell-background`, respectively. @"cursor-invert-fg-bg": bool = false, /// The opacity level (opposite of transparency) of the cursor. A value of 1 @@ -699,7 +713,10 @@ palette: Palette = .{}, /// The color of the text under the cursor. If this is not set, a default will /// be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-text": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"cursor-text": ?DynamicColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -4409,6 +4426,54 @@ pub const Color = struct { } }; +/// Represents the color values that can be set to a non-static value. +/// +/// Can either be a Color or one of the special values +/// "cell-foreground" or "cell-background". +pub const DynamicColor = union(enum) { + color: Color, + @"cell-foreground", + @"cell-background", + + pub fn parseCLI(input_: ?[]const u8) !DynamicColor { + const input = input_ orelse return error.ValueRequired; + + if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; + if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; + + return DynamicColor{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + .@"cell-foreground", .@"cell-background" => try formatter.formatEntry([:0]const u8, @tagName(self)), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual(DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, try DynamicColor.parseCLI("#4e2a84")); + try testing.expectEqual(DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, try DynamicColor.parseCLI("black")); + try testing.expectEqual(DynamicColor{.@"cell-foreground"}, try DynamicColor.parseCLI("cell-foreground")); + try testing.expectEqual(DynamicColor{.@"cell-background"}, try DynamicColor.parseCLI("cell-background")); + + try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: DynamicColor = .{.@"cell-foreground"}; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); + } +}; + pub const ColorList = struct { const Self = @This(); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 8aaa87011..fda52c375 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,8 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.Color, - cursor_invert: bool, + cursor_color: ?configpkg.Config.DynamicColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -185,7 +184,6 @@ pub const DerivedConfig = struct { .cursor_style = config.@"cursor-style", .cursor_blink = config.@"cursor-style-blink", .cursor_color = config.@"cursor-color", - .cursor_invert = config.@"cursor-invert-fg-bg", .foreground = config.foreground, .background = config.background, .osc_color_report_format = config.@"osc-color-report-format", @@ -265,8 +263,11 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (!opts.config.cursor_invert and opts.config.cursor_color != null) - opts.config.cursor_color.?.toTerminalRGB() + const default_cursor_color = if (opts.config.cursor_color) |color| + switch (color) { + .color => color.color.toTerminalRGB(), + else => null, + } else null; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 1b4fdd3aa..9946b0b8a 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,8 +121,11 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (!config.cursor_invert and config.cursor_color != null) - config.cursor_color.?.toTerminalRGB() + self.default_cursor_color = if (config.cursor_color) |color| + switch (color) { + .color => color.color.toTerminalRGB(), + else => null, + } else null; From 95de1987615bd40f3c8afa0180bc1c0f1c184d7d Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Jul 2025 23:04:58 -0400 Subject: [PATCH 049/119] Squash and rebase, updated refactored version with selection and cursor cell color changes --- src/config/Config.zig | 10 +-- src/renderer/generic.zig | 159 +++++++++++++++++---------------------- 2 files changed, 76 insertions(+), 93 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 76f91f6a8..1fc2f0f71 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -591,7 +591,7 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// the selection color is just the inverted window background and foreground /// (note: not to be confused with the cell bg/fg). /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"selection-foreground": ?DynamicColor = null, @@ -604,7 +604,7 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// If you select across cells with differing foregrounds and backgrounds, the /// selection color will vary across the selection. /// -/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// Warning: This option has been deprecated as of version 1.2.0. Instead, /// users should set `selection-foreground` and `selection-background` to /// `cell-background` and `cell-foreground`, respectively. @"selection-invert-fg-bg": bool = false, @@ -652,7 +652,7 @@ palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"cursor-color": ?DynamicColor = null, @@ -660,7 +660,7 @@ palette: Palette = .{}, /// Swap the foreground and background colors of the cell under the cursor. This /// option overrides the `cursor-color` and `cursor-text` options. /// -/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// Warning: This option has been deprecated as of version 1.2.0. Instead, /// users should set `cursor-color` and `cursor-text` to `cell-foreground` and /// `cell-background`, respectively. @"cursor-invert-fg-bg": bool = false, @@ -713,7 +713,7 @@ palette: Palette = .{}, /// The color of the text under the cursor. If this is not set, a default will /// be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"cursor-text": ?DynamicColor = null, diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 810e17686..617862e1c 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -133,12 +133,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// This is cursor color as set in the user's config, if any. If no cursor color /// is set in the user's config, then the cursor color is determined by the /// current foreground color. - default_cursor_color: ?terminal.color.RGB, - - /// When `cursor_color` is null, swap the foreground and background colors of - /// the cell under the cursor for the cursor color. Otherwise, use the default - /// foreground color as the cursor color. - cursor_invert: bool, + default_cursor_color: ?configpkg.Config.DynamicColor, /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of @@ -514,16 +509,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?terminal.color.RGB, - cursor_invert: bool, + cursor_color: ?configpkg.Config.DynamicColor, cursor_opacity: f64, - cursor_text: ?terminal.color.RGB, + cursor_text: ?configpkg.Config.DynamicColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?terminal.color.RGB, - selection_foreground: ?terminal.color.RGB, - invert_selection_fg_bg: bool, + selection_background: ?configpkg.Config.DynamicColor, + selection_foreground: ?configpkg.Config.DynamicColor, bold_is_bright: bool, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, @@ -571,8 +564,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { config.link.links.items, ); - const cursor_invert = config.@"cursor-invert-fg-bg"; - return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", @@ -581,36 +572,18 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .font_styles = font_styles, .font_shaping_break = config.@"font-shaping-break", - .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) - config.@"cursor-color".?.toTerminalRGB() - else - null, - - .cursor_invert = cursor_invert, - - .cursor_text = if (config.@"cursor-text") |txt| - txt.toTerminalRGB() - else - null, - + .cursor_color = config.@"cursor-color", + .cursor_text = config.@"cursor-text", .cursor_opacity = @max(0, @min(1, config.@"cursor-opacity")), .background = config.background.toTerminalRGB(), .foreground = config.foreground.toTerminalRGB(), - .invert_selection_fg_bg = config.@"selection-invert-fg-bg", .bold_is_bright = config.@"bold-is-bright", .min_contrast = @floatCast(config.@"minimum-contrast"), .padding_color = config.@"window-padding-color", - .selection_background = if (config.@"selection-background") |bg| - bg.toTerminalRGB() - else - null, - - .selection_foreground = if (config.@"selection-foreground") |bg| - bg.toTerminalRGB() - else - null, + .selection_background = config.@"selection-background", + .selection_foreground = config.@"selection-foreground", .custom_shaders = custom_shaders, .bg_image = bg_image, @@ -703,7 +676,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .default_background_color = options.config.background, .cursor_color = null, .default_cursor_color = options.config.cursor_color, - .cursor_invert = options.config.cursor_invert, // Render state .cells = .{}, @@ -2079,8 +2051,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Set our new colors self.default_background_color = config.background; self.default_foreground_color = config.foreground; - self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null; - self.cursor_invert = config.cursor_invert; + self.default_cursor_color = config.cursor_color; const bg_image_config_changed = self.config.bg_image_fit != config.bg_image_fit or @@ -2583,22 +2554,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.invert_selection_fg_bg) - if (style.flags.inverse) - // Cell is selected with invert selection fg/bg - // enabled, and the cell has the inverse style - // flag, so they cancel out and we get the normal - // bg color. - bg_style - else - // If it doesn't have the inverse style - // flag then we use the fg color instead. - fg_style + break :bg if (self.config.selection_background) |selection_color| + // Use the selection background if set, otherwise the default fg color. + switch (selection_color) { + .color => selection_color.color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, + } else - // If we don't have invert selection fg/bg set then we - // just use the selection background if set, otherwise - // the default fg color. - break :bg self.config.selection_background orelse self.foreground_color orelse self.default_foreground_color; + self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2618,20 +2582,26 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - if (selected and !self.config.invert_selection_fg_bg) { - // If we don't have invert selection fg/bg set - // then we just use the selection foreground if - // set, otherwise the default bg color. - break :fg self.config.selection_foreground orelse self.background_color orelse self.default_background_color; - } + const final_bg = bg_style orelse self.background_color orelse self.default_background_color; // Whether we need to use the bg color as our fg color: + // - Cell is selected, inverted, and set to cell-foreground + // - Cell is selected, not inverted, and set to cell-background // - Cell is inverted and not selected - // - Cell is selected and not inverted - // Note: if selected then invert sel fg / bg must be - // false since we separately handle it if true above. - break :fg if (style.flags.inverse != selected) - bg_style orelse self.background_color orelse self.default_background_color + if (selected) { + // Use the selection foreground if set, otherwise the default bg color. + break :fg if (self.config.selection_foreground) |selection_color| + switch (selection_color) { + .color => selection_color.color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, + } + else + self.background_color orelse self.default_background_color; + } + + break :fg if (style.flags.inverse) + final_bg else fg_style; }; @@ -2817,19 +2787,25 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse self.default_cursor_color orelse color: { - if (self.cursor_invert) { - // Use the foreground color from the cell under the cursor, if any. - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :color if (sty.flags.inverse) - // If the cell is reversed, use background color instead. - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color) - else - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color); - } else { - break :color self.foreground_color orelse self.default_foreground_color; + const cursor_color = self.cursor_color orelse if (self.default_cursor_color) |color| color: { + // If cursor-color is set, then compute the correct color. + // Otherwise, use the foreground color + if (color == .color) { + // Use the color set by cursor-color, if any. + break :color color.color.toTerminalRGB(); } - }; + + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + + break :color switch (color) { + // If the cell is reversed, use the opposite cell color instead. + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + else => unreachable, + }; + } else self.foreground_color orelse self.default_foreground_color; self.addCursor(screen, style, cursor_color); @@ -2853,18 +2829,25 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .wide, .spacer_tail => true, }; - const uniform_color = if (self.cursor_invert) blk: { - // Use the background color from the cell under the cursor, if any. + const uniform_color = if (self.config.cursor_text) |txt| blk: { + // If cursor-text is set, then compute the correct color. + // Otherwise, use the background color. + if (txt == .color) { + // Use the color set by cursor-text, if any. + break :blk txt.color.toTerminalRGB(); + } + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :blk if (sty.flags.inverse) - // If the cell is reversed, use foreground color instead. - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color) - else - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color); - } else if (self.config.cursor_text) |txt| - txt - else - self.background_color orelse self.default_background_color; + const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + + break :blk switch (txt) { + // If the cell is reversed, use the opposite cell color instead. + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + else => unreachable, + }; + } else self.background_color orelse self.default_background_color; self.uniforms.cursor_color = .{ uniform_color.r, From e87e5e73614aa7ef68bcaaf5814ce0d26b2bb1ea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 07:30:50 -0700 Subject: [PATCH 050/119] backwards compatibility handlers for removed fields --- src/config/Config.zig | 170 +++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 36 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 1fc2f0f71..3cb808179 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -62,6 +62,13 @@ pub const compatibility = std.StaticStringMap( // Ghostty 1.2 removed the `hidden` value from `gtk-tabs-location` and // moved it to `window-show-tab-bar`. .{ "gtk-tabs-location", compatGtkTabsLocation }, + + // Ghostty 1.2 lets you set `cell-foreground` and `cell-background` + // to match the cell foreground and background colors, respectively. + // This can be used with `cursor-color` and `cursor-text` to recreate + // this behavior. This applies to selection too. + .{ "cursor-invert-fg-bg", compatCursorInvertFgBg }, + .{ "selection-invert-fg-bg", compatSelectionInvertFgBg }, }); /// The font families to use. @@ -597,18 +604,6 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, @"selection-foreground": ?DynamicColor = null, @"selection-background": ?DynamicColor = null, -/// Swap the foreground and background colors of cells for selection. This -/// option overrides the `selection-foreground` and `selection-background` -/// options. -/// -/// If you select across cells with differing foregrounds and backgrounds, the -/// selection color will vary across the selection. -/// -/// Warning: This option has been deprecated as of version 1.2.0. Instead, -/// users should set `selection-foreground` and `selection-background` to -/// `cell-background` and `cell-foreground`, respectively. -@"selection-invert-fg-bg": bool = false, - /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as /// text input fields. If you set this to `false`, then the selected text @@ -651,19 +646,20 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. -/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.2.0, this can also be set to `cell-foreground` to match -/// the cell foreground color, or `cell-background` to match the cell -/// background color. -@"cursor-color": ?DynamicColor = null, - -/// Swap the foreground and background colors of the cell under the cursor. This -/// option overrides the `cursor-color` and `cursor-text` options. /// -/// Warning: This option has been deprecated as of version 1.2.0. Instead, -/// users should set `cursor-color` and `cursor-text` to `cell-foreground` and -/// `cell-background`, respectively. -@"cursor-invert-fg-bg": bool = false, +/// Direct colors can be specified as either hex (`#RRGGBB` or `RRGGBB`) +/// or a named X11 color. +/// +/// Additionally, special values can be used to set the color to match +/// other colors at runtime: +/// +/// * `cell-foreground` - Match the cell foreground color. +/// (Available since version 1.2.0) +/// +/// * `cell-background` - Match the cell background color. +/// (Available since version 1.2.0) +/// +@"cursor-color": ?DynamicColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -3843,10 +3839,6 @@ pub fn parseManuallyHook( return true; } -/// parseFieldManuallyFallback is a fallback called only when -/// parsing the field directly failed. It can be used to implement -/// backward compatibility. Since this is only called when parsing -/// fails, it doesn't impact happy-path performance. fn compatGtkTabsLocation( self: *Config, alloc: Allocator, @@ -3864,6 +3856,51 @@ fn compatGtkTabsLocation( return false; } +fn compatCursorInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "cursor-invert-fg-bg")); + + // We don't do anything if the value is unset, which is technically + // not EXACTLY the same as prior behavior since it would fallback + // to doing whatever cursor-color/cursor-text were set to, but + // I don't want to store what that is separately so this is close + // enough. + // + // Realistically, these fields were mutually exclusive so anyone + // relying on that behavior should just upgrade to the new + // cursor-color/cursor-text fields. + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"cursor-color" = .@"cell-foreground"; + self.@"cursor-text" = .@"cell-background"; + } + + return true; +} + +fn compatSelectionInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "selection-invert-fg-bg")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"selection-foreground" = .@"cell-background"; + self.@"selection-background" = .@"cell-foreground"; + } + + return true; +} + /// Create a shallow copy of this config. This will share all the memory /// allocated with the previous config but will have a new arena for /// any changes or new allocations. The config should have `deinit` @@ -4437,28 +4474,41 @@ pub const DynamicColor = union(enum) { pub fn parseCLI(input_: ?[]const u8) !DynamicColor { const input = input_ orelse return error.ValueRequired; - if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; - - return DynamicColor{ .color = try Color.parseCLI(input) }; + return .{ .color = try Color.parseCLI(input) }; } /// Used by Formatter pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { switch (self) { .color => try self.color.formatEntry(formatter), - .@"cell-foreground", .@"cell-background" => try formatter.formatEntry([:0]const u8, @tagName(self)), + + .@"cell-foreground", + .@"cell-background", + => try formatter.formatEntry([:0]const u8, @tagName(self)), } } test "parseCLI" { const testing = std.testing; - try testing.expectEqual(DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, try DynamicColor.parseCLI("#4e2a84")); - try testing.expectEqual(DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, try DynamicColor.parseCLI("black")); - try testing.expectEqual(DynamicColor{.@"cell-foreground"}, try DynamicColor.parseCLI("cell-foreground")); - try testing.expectEqual(DynamicColor{.@"cell-background"}, try DynamicColor.parseCLI("cell-background")); + try testing.expectEqual( + DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try DynamicColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try DynamicColor.parseCLI("black"), + ); + try testing.expectEqual( + DynamicColor{.@"cell-foreground"}, + try DynamicColor.parseCLI("cell-foreground"), + ); + try testing.expectEqual( + DynamicColor{.@"cell-background"}, + try DynamicColor.parseCLI("cell-background"), + ); try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); } @@ -8107,3 +8157,51 @@ test "theme specifying light/dark sets theme usage in conditional state" { try testing.expect(cfg._conditional_set.contains(.theme)); } } + +test "compatibility: removed cursor-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--cursor-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + DynamicColor.@"cell-foreground", + cfg.@"cursor-color", + ); + try testing.expectEqual( + DynamicColor.@"cell-background", + cfg.@"cursor-text", + ); + } +} + +test "compatibility: removed selection-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--selection-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + DynamicColor.@"cell-background", + cfg.@"selection-foreground", + ); + try testing.expectEqual( + DynamicColor.@"cell-foreground", + cfg.@"selection-background", + ); + } +} From 465ac5b1b7ad895951f459cfd4de578b14e0e741 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 09:25:55 -0700 Subject: [PATCH 051/119] clean up some of the color usage, use exhaustive switches --- src/config/Config.zig | 48 ++++++++-------- src/renderer/generic.zig | 100 +++++++++++++++++++++------------- src/termio/Termio.zig | 19 ++++--- src/termio/stream_handler.zig | 17 +++--- 4 files changed, 107 insertions(+), 77 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 3cb808179..5d9093bba 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -601,8 +601,8 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"selection-foreground": ?DynamicColor = null, -@"selection-background": ?DynamicColor = null, +@"selection-foreground": ?TerminalColor = null, +@"selection-background": ?TerminalColor = null, /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as @@ -659,7 +659,7 @@ palette: Palette = .{}, /// * `cell-background` - Match the cell background color. /// (Available since version 1.2.0) /// -@"cursor-color": ?DynamicColor = null, +@"cursor-color": ?TerminalColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -712,7 +712,7 @@ palette: Palette = .{}, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"cursor-text": ?DynamicColor = null, +@"cursor-text": ?TerminalColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -4463,16 +4463,14 @@ pub const Color = struct { } }; -/// Represents the color values that can be set to a non-static value. -/// -/// Can either be a Color or one of the special values -/// "cell-foreground" or "cell-background". -pub const DynamicColor = union(enum) { +/// Represents color values that can also reference special color +/// values such as "cell-foreground" or "cell-background". +pub const TerminalColor = union(enum) { color: Color, @"cell-foreground", @"cell-background", - pub fn parseCLI(input_: ?[]const u8) !DynamicColor { + pub fn parseCLI(input_: ?[]const u8) !TerminalColor { const input = input_ orelse return error.ValueRequired; if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; @@ -4480,7 +4478,7 @@ pub const DynamicColor = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { + pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { switch (self) { .color => try self.color.formatEntry(formatter), @@ -4494,23 +4492,23 @@ pub const DynamicColor = union(enum) { const testing = std.testing; try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, - try DynamicColor.parseCLI("#4e2a84"), + TerminalColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try TerminalColor.parseCLI("#4e2a84"), ); try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, - try DynamicColor.parseCLI("black"), + TerminalColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try TerminalColor.parseCLI("black"), ); try testing.expectEqual( - DynamicColor{.@"cell-foreground"}, - try DynamicColor.parseCLI("cell-foreground"), + TerminalColor{.@"cell-foreground"}, + try TerminalColor.parseCLI("cell-foreground"), ); try testing.expectEqual( - DynamicColor{.@"cell-background"}, - try DynamicColor.parseCLI("cell-background"), + TerminalColor{.@"cell-background"}, + try TerminalColor.parseCLI("cell-background"), ); - try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); + try testing.expectError(error.InvalidValue, TerminalColor.parseCLI("a")); } test "formatConfig" { @@ -4518,7 +4516,7 @@ pub const DynamicColor = union(enum) { var buf = std.ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var sc: DynamicColor = .{.@"cell-foreground"}; + var sc: TerminalColor = .{.@"cell-foreground"}; try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); } @@ -8172,11 +8170,11 @@ test "compatibility: removed cursor-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"cursor-color", ); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"cursor-text", ); } @@ -8196,11 +8194,11 @@ test "compatibility: removed selection-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"selection-foreground", ); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"selection-background", ); } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 617862e1c..e7faf633f 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -133,7 +133,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// This is cursor color as set in the user's config, if any. If no cursor color /// is set in the user's config, then the cursor color is determined by the /// current foreground color. - default_cursor_color: ?configpkg.Config.DynamicColor, + default_cursor_color: ?configpkg.Config.TerminalColor, /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of @@ -509,14 +509,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, cursor_opacity: f64, - cursor_text: ?configpkg.Config.DynamicColor, + cursor_text: ?configpkg.Config.TerminalColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?configpkg.Config.DynamicColor, - selection_foreground: ?configpkg.Config.DynamicColor, + selection_background: ?configpkg.Config.TerminalColor, + selection_foreground: ?configpkg.Config.TerminalColor, bold_is_bright: bool, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, @@ -2548,21 +2548,31 @@ pub fn Renderer(comptime GraphicsAPI: type) type { else false; + // The `_style` suffixed values are the colors based on + // the cell style (SGR), before applying any additional + // configuration, inversions, selections, etc. const bg_style = style.bg(cell, color_palette); - const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = style.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.selection_background) |selection_color| - // Use the selection background if set, otherwise the default fg color. - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // If we have an explicit selection background color + // specified int he config, use that + if (self.config.selection_background) |v| { + break :bg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, - } - else - self.foreground_color orelse self.default_foreground_color; + }; + } + + // If no configuration, then our selection background + // is our foreground color. + break :bg self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2582,22 +2592,27 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - const final_bg = bg_style orelse self.background_color orelse self.default_background_color; + // Our happy-path non-selection background color + // is our style or our configured defaults. + const final_bg = bg_style orelse + self.background_color orelse + self.default_background_color; // Whether we need to use the bg color as our fg color: // - Cell is selected, inverted, and set to cell-foreground // - Cell is selected, not inverted, and set to cell-background // - Cell is inverted and not selected if (selected) { - // Use the selection foreground if set, otherwise the default bg color. - break :fg if (self.config.selection_foreground) |selection_color| - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // Use the selection foreground if set + if (self.config.selection_foreground) |v| { + break :fg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, - } - else - self.background_color orelse self.default_background_color; + }; + } + + break :fg self.background_color orelse self.default_background_color; } break :fg if (style.flags.inverse) @@ -2787,25 +2802,36 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse if (self.default_cursor_color) |color| color: { - // If cursor-color is set, then compute the correct color. - // Otherwise, use the foreground color - if (color == .color) { - // Use the color set by cursor-color, if any. - break :color color.color.toTerminalRGB(); - } + const cursor_color = cursor_color: { + // If an explicit cursor color was set by OSC 12, use that. + if (self.cursor_color) |v| break :cursor_color v; - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; - const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + // Use our configured color if specified + if (self.default_cursor_color) |v| switch (v) { + .color => |color| break :cursor_color color.toTerminalRGB(), + inline .@"cell-foreground", + .@"cell-background", + => |_, tag| { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg( + screen.cursor.page_cell, + color_palette, + ) orelse self.background_color orelse self.default_background_color; - break :color switch (color) { - // If the cell is reversed, use the opposite cell color instead. - .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, - .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, - else => unreachable, + break :cursor_color switch (tag) { + .color => unreachable, + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + }; + }, }; - } else self.foreground_color orelse self.default_foreground_color; + + break :cursor_color self.foreground_color orelse self.default_foreground_color; + }; self.addCursor(screen, style, cursor_color); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index fda52c375..4b5b93641 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,7 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -263,13 +263,16 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (opts.config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + const default_cursor_color: ?terminalpkg.color.RGB = color: { + if (opts.config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; break :handler .{ .alloc = alloc, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 9946b0b8a..040132f03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,13 +121,16 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + self.default_cursor_color = color: { + if (config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; // If our cursor is the default, then we update it immediately. if (self.default_cursor) self.setCursorStyle(.default) catch |err| { From 32764f3a1d8aab3043c11170e2b4c691c3316ca4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 09:29:36 -0700 Subject: [PATCH 052/119] fix test syntax --- src/config/Config.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 5d9093bba..e140785bb 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4500,11 +4500,11 @@ pub const TerminalColor = union(enum) { try TerminalColor.parseCLI("black"), ); try testing.expectEqual( - TerminalColor{.@"cell-foreground"}, + TerminalColor.@"cell-foreground", try TerminalColor.parseCLI("cell-foreground"), ); try testing.expectEqual( - TerminalColor{.@"cell-background"}, + TerminalColor.@"cell-background", try TerminalColor.parseCLI("cell-background"), ); @@ -4516,7 +4516,7 @@ pub const TerminalColor = union(enum) { var buf = std.ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var sc: TerminalColor = .{.@"cell-foreground"}; + var sc: TerminalColor = .@"cell-foreground"; try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); } From e1be836283e0824f7f37dc9d94404ec4723c9050 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 13:45:38 -0700 Subject: [PATCH 053/119] config: for now, make goto_tab NOT performable on macOS Fixes #7786 Fixes regression from #7683 This is a band-aid fix. The issue is that performable keybinds don't show up in the reverse mapping that GUI toolkits use to find their key equivalents. The full explanation of why is already in Binding.zig. For macOS, we have a way to validate menu items before they're triggered so we ideally do want a way to get reverse mappings even with performable keybinds. But I think this wants to be optional and that's all a bigger change. For now, this is a simple fix that will work. --- src/config/Config.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index e140785bb..fec5b41fc 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -5455,7 +5455,14 @@ pub const Keybinds = struct { .mods = mods, }, .{ .goto_tab = (i - start) + 1 }, - .{ .performable = true }, + .{ + // On macOS we keep this not performable so that the + // keyboard shortcuts in tabs work. In the future the + // correct fix is to fix the reverse mapping lookup + // to allow us to lookup performable keybinds + // conditionally. + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } try self.set.putFlags( @@ -5465,7 +5472,10 @@ pub const Keybinds = struct { .mods = mods, }, .{ .last_tab = {} }, - .{ .performable = true }, + .{ + // See comment above with the numeric goto_tab + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } From c2484f48eff926aafddd9f8e1c7b4531b3bc6d65 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 1 Jan 2025 23:18:51 -0500 Subject: [PATCH 054/119] font: add jb mono and symbols-only nerd font as dependencies Rather than using binaries statically in our source tree; this makes them easier to update. This also makes it so that they are separated from each other rather than using a patched JB mono as our fallback. --- build.zig.zon | 10 ++++++++++ build.zig.zon.json | 10 ++++++++++ build.zig.zon.nix | 16 ++++++++++++++++ build.zig.zon.txt | 2 ++ flatpak/zig-packages.json | 12 ++++++++++++ src/build/SharedDeps.zig | 29 +++++++++++++++++++++++++++++ src/font/SharedGridSet.zig | 11 +++++++++++ src/font/embedded.zig | 19 +++++++++++++------ src/font/shaper/coretext.zig | 2 +- 9 files changed, 104 insertions(+), 7 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 237720f35..38beb70fa 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -100,6 +100,16 @@ .lazy = true, }, + // Fonts + .jetbrains_mono = .{ + .url = "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", + .hash = "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6", + }, + .nerd_fonts_symbols_only = .{ + .url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", + .hash = "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI", + }, + // Other .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .iterm2_themes = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 420893ef7..4d60be2c7 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -59,6 +59,11 @@ "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz", "hash": "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4=" }, + "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6": { + "name": "jetbrains_mono", + "url": "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", + "hash": "sha256-b2N2xu0pYOqKljzXOH7J124/YpElvDPR/c1+twEve78=" + }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", @@ -74,6 +79,11 @@ "url": "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz", "hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU=" }, + "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI": { + "name": "nerd_fonts_symbols_only", + "url": "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", + "hash": "sha256-f4wJDaOw6qcQhka/NMu7btE9U1inJGBSIQiwbH7NcWo=" + }, "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c": { "name": "oniguruma", "url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 6e4b86606..eaf15bbb0 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -177,6 +177,14 @@ in hash = "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4="; }; } + { + name = "N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6"; + path = fetchZigArtifact { + name = "jetbrains_mono"; + url = "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip"; + hash = "sha256-b2N2xu0pYOqKljzXOH7J124/YpElvDPR/c1+twEve78="; + }; + } { name = "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD"; path = fetchZigArtifact { @@ -201,6 +209,14 @@ in hash = "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU="; }; } + { + name = "N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI"; + path = fetchZigArtifact { + name = "nerd_fonts_symbols_only"; + url = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz"; + hash = "sha256-f4wJDaOw6qcQhka/NMu7btE9U1inJGBSIQiwbH7NcWo="; + }; + } { name = "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c"; path = fetchZigArtifact { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index f05a789dd..738ab4ccc 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -25,10 +25,12 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6 https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz +https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz +https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index daf7e5cea..dcc75776a 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -71,6 +71,12 @@ "dest": "vendor/p/N-V-__8AAGHcWgTaKLjwmFkxToNT4jgz5VXUHR7hz8TQ2_AS", "sha256": "83da3608873f4df598a144bf97f1cfe4a644083eea36c75052516bb9a2a4573e" }, + { + "type": "archive", + "url": "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip", + "dest": "vendor/p/N-V-__8AADWVlwASf7XCdCqpjVW5Jv_7oogANJ_H4_dIoEp6", + "sha256": "6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf" + }, { "type": "archive", "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", @@ -89,6 +95,12 @@ "dest": "vendor/p/N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK", "sha256": "6c28059e2e3eeb42b5b4b16489e3916a6346c1095a74fee3bc65cdc5d89a6215" }, + { + "type": "archive", + "url": "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/NerdFontsSymbolsOnly.tar.xz", + "dest": "vendor/p/N-V-__8AAI9HTABr_zPUAKuMp_GR5p_z-kjPIX_e3EtLjOqI", + "sha256": "7f8c090da3b0eaa7108646bf34cbbb6ed13d5358a72460522108b06c7ecd716a" + }, { "type": "archive", "url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz", diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index f173e4856..1864d4fb5 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -500,6 +500,35 @@ pub fn add( try static_libs.append(utfcpp_dep.artifact("utfcpp").getEmittedBin()); } + // Fonts + { + // JetBrains Mono + const jb_mono = b.dependency("jetbrains_mono", .{}); + step.root_module.addAnonymousImport( + "jetbrains_mono_regular", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Regular.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_bold", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Bold.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_italic", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Italic.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_bold_italic", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-BoldItalic.ttf") }, + ); + + // Symbols-only nerd font + const nf_symbols = b.dependency("nerd_fonts_symbols_only", .{}); + step.root_module.addAnonymousImport( + "nerd_fonts_symbols_only", + .{ .root_source_file = nf_symbols.path("SymbolsNerdFontMono-Regular.ttf") }, + ); + } + // If we're building an exe then we have additional dependencies. if (step.kind != .lib) { // We always statically compile glad diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index e3e61907b..c3e1ef964 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -292,6 +292,17 @@ fn collection( ) }, ); + // Nerd-font symbols fallback. + _ = try c.add( + self.alloc, + .regular, + .{ .fallback_loaded = try Face.init( + self.font_lib, + font.embedded.symbols_nerd_font, + load_options.faceOptions(), + ) }, + ); + // On macOS, always search for and add the Apple Emoji font // as our preferred emoji font for fallback. We do this in case // people add other emoji fonts to their system, we always want to diff --git a/src/font/embedded.zig b/src/font/embedded.zig index 31b07ff31..6116c6921 100644 --- a/src/font/embedded.zig +++ b/src/font/embedded.zig @@ -6,19 +6,26 @@ //! redistribution and include their license as necessary. /// Default fonts that we prefer for Ghostty. -pub const regular = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); -pub const bold = @embedFile("res/JetBrainsMonoNerdFont-Bold.ttf"); -pub const italic = @embedFile("res/JetBrainsMonoNerdFont-Italic.ttf"); -pub const bold_italic = @embedFile("res/JetBrainsMonoNerdFont-BoldItalic.ttf"); +pub const regular = @embedFile("jetbrains_mono_regular"); +pub const bold = @embedFile("jetbrains_mono_bold"); +pub const italic = @embedFile("jetbrains_mono_italic"); +pub const bold_italic = @embedFile("jetbrains_mono_bold_italic"); + +/// Symbols-only nerd font. +pub const symbols_nerd_font = @embedFile("nerd_fonts_symbols_only"); + +/// Emoji fonts pub const emoji = @embedFile("res/NotoColorEmoji.ttf"); pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf"); +// Fonts below are ONLY used for testing. + /// Fonts with general properties pub const arabic = @embedFile("res/KawkabMono-Regular.ttf"); pub const variable = @embedFile("res/Lilex-VF.ttf"); -/// Font with nerd fonts embedded. -pub const nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); +/// A font for testing which is patched with nerd font symbols. +pub const test_nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); /// Specific font families below: pub const code_new_roman = @embedFile("res/CodeNewRoman-Regular.otf"); diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 1aaa029dc..f4f01d105 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -1769,7 +1769,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper { .geist_mono => font.embedded.geist_mono, .jetbrains_mono => font.embedded.jetbrains_mono, .monaspace_neon => font.embedded.monaspace_neon, - .nerd_font => font.embedded.nerd_font, + .nerd_font => font.embedded.test_nerd_font, }; var lib = try Library.init(alloc); From d751a93ecf5b35ca18959419408f6ca65cea35c3 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 3 Jul 2025 16:27:39 -0600 Subject: [PATCH 055/119] font: use variable JetBrains Mono for embedded font This cuts down our file size significantly. --- src/build/SharedDeps.zig | 8 ++++++++ src/font/SharedGridSet.zig | 18 ++++++++++++------ src/font/embedded.zig | 11 +++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 1864d4fb5..4aea66366 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -520,6 +520,14 @@ pub fn add( "jetbrains_mono_bold_italic", .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-BoldItalic.ttf") }, ); + step.root_module.addAnonymousImport( + "jetbrains_mono_variable", + .{ .root_source_file = jb_mono.path("fonts/variable/JetBrainsMono[wght].ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_variable_italic", + .{ .root_source_file = jb_mono.path("fonts/variable/JetBrainsMono-Italic[wght].ttf") }, + ); // Symbols-only nerd font const nf_symbols = b.dependency("nerd_fonts_symbols_only", .{}); diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index c3e1ef964..b77b44f23 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -260,36 +260,42 @@ fn collection( .regular, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.regular, + font.embedded.variable, load_options.faceOptions(), ) }, ); - _ = try c.add( + try (try c.getFace(try c.add( self.alloc, .bold, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.bold, + font.embedded.variable, load_options.faceOptions(), ) }, + ))).setVariations( + &.{.{ .id = .init("wght"), .value = 700 }}, + load_options.faceOptions(), ); _ = try c.add( self.alloc, .italic, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.italic, + font.embedded.variable_italic, load_options.faceOptions(), ) }, ); - _ = try c.add( + try (try c.getFace(try c.add( self.alloc, .bold_italic, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.bold_italic, + font.embedded.variable_italic, load_options.faceOptions(), ) }, + ))).setVariations( + &.{.{ .id = .init("wght"), .value = 700 }}, + load_options.faceOptions(), ); // Nerd-font symbols fallback. diff --git a/src/font/embedded.zig b/src/font/embedded.zig index 6116c6921..1e496075d 100644 --- a/src/font/embedded.zig +++ b/src/font/embedded.zig @@ -6,14 +6,18 @@ //! redistribution and include their license as necessary. /// Default fonts that we prefer for Ghostty. +pub const variable = @embedFile("jetbrains_mono_variable"); +pub const variable_italic = @embedFile("jetbrains_mono_variable_italic"); + +/// Symbols-only nerd font. +pub const symbols_nerd_font = @embedFile("nerd_fonts_symbols_only"); + +/// Static jetbrains mono faces, currently unused. pub const regular = @embedFile("jetbrains_mono_regular"); pub const bold = @embedFile("jetbrains_mono_bold"); pub const italic = @embedFile("jetbrains_mono_italic"); pub const bold_italic = @embedFile("jetbrains_mono_bold_italic"); -/// Symbols-only nerd font. -pub const symbols_nerd_font = @embedFile("nerd_fonts_symbols_only"); - /// Emoji fonts pub const emoji = @embedFile("res/NotoColorEmoji.ttf"); pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf"); @@ -22,7 +26,6 @@ pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf"); /// Fonts with general properties pub const arabic = @embedFile("res/KawkabMono-Regular.ttf"); -pub const variable = @embedFile("res/Lilex-VF.ttf"); /// A font for testing which is patched with nerd font symbols. pub const test_nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); From c32f7adb10481929b4785b0231064ac1cc0d0480 Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Fri, 4 Jul 2025 00:26:43 +0200 Subject: [PATCH 056/119] Add linux kernel information to +version --- src/cli/version.zig | 41 ++++++++++++++++++++++------------------- src/os/kernel_info.zig | 27 +++++++++++++++++++++++++++ src/os/main.zig | 2 ++ 3 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 src/os/kernel_info.zig diff --git a/src/cli/version.zig b/src/cli/version.zig index a27d1050d..22608fa88 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -15,8 +15,6 @@ pub const Options = struct {}; /// The `version` command is used to display information about Ghostty. Recognized as /// either `+version` or `--version`. pub fn run(alloc: Allocator) !u8 { - _ = alloc; - const stdout = std.io.getStdOut().writer(); const tty = std.io.getStdOut().isTty(); @@ -34,32 +32,37 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print(" - channel: {s}\n", .{@tagName(build_config.release_channel)}); try stdout.print("Build Config\n", .{}); - try stdout.print(" - Zig version: {s}\n", .{builtin.zig_version_string}); - try stdout.print(" - build mode : {}\n", .{builtin.mode}); - try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); - try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); - try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); - try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); + try stdout.print(" - Zig version : {s}\n", .{builtin.zig_version_string}); + try stdout.print(" - build mode : {}\n", .{builtin.mode}); + try stdout.print(" - app runtime : {}\n", .{build_config.app_runtime}); + try stdout.print(" - font engine : {}\n", .{build_config.font_backend}); + try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); + try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); if (comptime build_config.app_runtime == .gtk) { - try stdout.print(" - desktop env: {s}\n", .{@tagName(internal_os.desktopEnvironment())}); - try stdout.print(" - GTK version:\n", .{}); - try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); - try stdout.print(" - libadwaita : enabled\n", .{}); - try stdout.print(" build : {}\n", .{adw_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); + if (comptime builtin.os.tag == .linux) { + const kernel_info = internal_os.getKernelInfo(alloc); + defer if (kernel_info) |k| alloc.free(k); + try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"}); + } + try stdout.print(" - desktop env : {s}\n", .{@tagName(internal_os.desktopEnvironment())}); + try stdout.print(" - GTK version :\n", .{}); + try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); + try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); + try stdout.print(" - libadwaita : enabled\n", .{}); + try stdout.print(" build : {}\n", .{adw_version.comptime_version}); + try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); if (comptime build_options.x11) { - try stdout.print(" - libX11 : enabled\n", .{}); + try stdout.print(" - libX11 : enabled\n", .{}); } else { - try stdout.print(" - libX11 : disabled\n", .{}); + try stdout.print(" - libX11 : disabled\n", .{}); } // We say `libwayland` since it is possible to build Ghostty without // Wayland integration but with Wayland-enabled GTK if (comptime build_options.wayland) { - try stdout.print(" - libwayland : enabled\n", .{}); + try stdout.print(" - libwayland : enabled\n", .{}); } else { - try stdout.print(" - libwayland : disabled\n", .{}); + try stdout.print(" - libwayland : disabled\n", .{}); } } return 0; diff --git a/src/os/kernel_info.zig b/src/os/kernel_info.zig new file mode 100644 index 000000000..9e3933dde --- /dev/null +++ b/src/os/kernel_info.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn getKernelInfo(alloc: std.mem.Allocator) ?[]const u8 { + if (comptime builtin.os.tag != .linux) return null; + const path = "/proc/sys/kernel/osrelease"; + var file = std.fs.openFileAbsolute(path, .{}) catch return null; + defer file.close(); + + // 128 bytes should be enough to hold the kernel information + const kernel_info = file.readToEndAlloc(alloc, 128) catch return null; + defer alloc.free(kernel_info); + return alloc.dupe(u8, std.mem.trim(u8, kernel_info, &std.ascii.whitespace)) catch return null; +} + +test "read /proc/sys/kernel/osrelease" { + if (comptime builtin.os.tag != .linux) return null; + const allocator = std.testing.allocator; + + const kernel_info = try getKernelInfo(allocator); + defer allocator.free(kernel_info); + + // Since we can't hardcode the info in tests, just check + // if something was read from the file + try std.testing.expect(kernel_info.len > 0); + try std.testing.expect(!std.mem.eql(u8, kernel_info, "")); +} diff --git a/src/os/main.zig b/src/os/main.zig index 906e3d150..7398fc779 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -14,6 +14,7 @@ const openpkg = @import("open.zig"); const pipepkg = @import("pipe.zig"); const resourcesdir = @import("resourcesdir.zig"); const systemd = @import("systemd.zig"); +const kernelInfo = @import("kernel_info.zig"); // Namespaces pub const args = @import("args.zig"); @@ -58,6 +59,7 @@ pub const pipe = pipepkg.pipe; pub const resourcesDir = resourcesdir.resourcesDir; pub const ResourcesDir = resourcesdir.ResourcesDir; pub const ShellEscapeWriter = shell.ShellEscapeWriter; +pub const getKernelInfo = kernelInfo.getKernelInfo; test { _ = i18n; From e494d94fb326c043e062ab5f60704e891f927371 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 21:14:14 -0700 Subject: [PATCH 057/119] Handle `exec` failures more gracefully Fixes #7792 Our error handling for `exec` failing within the forked process never actually worked! It triggered all sorts of issues. We didn't catch this before because it used to be exceptionally hard to fail an exec because we used to wrap ALL commands in a `/bin/sh -c`. However, we now support direction execution, most notably when you do `ghostty -e ` but also via the `direct:` prefix on configured commands. This fixes up our exec failure handling by printing a useful error message and avoiding any errdefers in the child which was causing the double-close. --- src/Command.zig | 27 +++++++++++++++++--- src/termio/Exec.zig | 60 ++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/Command.zig b/src/Command.zig index 7ed026efe..1bddf8b82 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -188,10 +188,31 @@ fn startPosix(self: *Command, arena: Allocator) !void { // Finally, replace our process. // Note: we must use the "p"-variant of exec here because we // do not guarantee our command is looked up already in the path. - _ = posix.execvpeZ(self.path, argsZ, envp) catch null; + const err = posix.execvpeZ(self.path, argsZ, envp); - // If we are executing this code, the exec failed. In that scenario, - // we return a very specific error that can be detected to determine + // If we are executing this code, the exec failed. We're in the + // child process so there isn't much we can do. We try to output + // something reasonable. Its important to note we MUST NOT return + // any other error condition from here on out. + const stderr = std.io.getStdErr().writer(); + switch (err) { + error.FileNotFound => stderr.print( + \\Requested executable not found. Please verify the command is on + \\the PATH and try again. + \\ + , + .{}, + ) catch {}, + + else => stderr.print( + \\exec syscall failed with unexpected error: {} + \\ + , + .{err}, + ) catch {}, + } + + // We return a very specific error that can be detected to determine // we're in the child. return error.ExecFailedInChild; } diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 598617a12..15b6b8cd4 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -90,15 +90,13 @@ pub fn threadEnter( // Start our subprocess const pty_fds = self.subprocess.start(alloc) catch |err| { // If we specifically got this error then we are in the forked - // process and our child failed to execute. In that case - if (err != error.Termio) return err; + // process and our child failed to execute. If we DIDN'T + // get this specific error then we're in the parent and + // we need to bubble it up. + if (err != error.ExecFailedInChild) return err; - // Output an error message about the exec faililng and exit. - // This generally should NOT happen because we always wrap - // our command execution either in login (macOS) or /bin/sh - // (Linux) which are usually guaranteed to exist. Still, we - // want to handle this scenario. - execFailedInChild() catch {}; + // We're in the child. Nothing more we can do but abnormal exit. + // The Command will output some additional information. posix.exit(1); }; errdefer self.subprocess.stop(); @@ -272,25 +270,6 @@ pub fn resize( return try self.subprocess.resize(grid_size, screen_size); } -/// This outputs an error message when exec failed and we are the -/// child process. This returns so the caller should probably exit -/// after calling this. -/// -/// Note that this usually is only called under very very rare -/// circumstances because we wrap our command execution in login -/// (macOS) or /bin/sh (Linux). So this output can be pretty crude -/// because it should never happen. Notably, this is not the error -/// users see when `command` is invalid. -fn execFailedInChild() !void { - const stderr = std.io.getStdErr().writer(); - try stderr.writeAll("exec failed\n"); - try stderr.writeAll("press any key to exit\n"); - - var buf: [1]u8 = undefined; - var reader = std.io.getStdIn().reader(); - _ = try reader.read(&buf); -} - fn processExitCommon(td: *termio.Termio.ThreadData, exit_code: u32) void { assert(td.backend == .exec); const execdata = &td.backend.exec; @@ -895,6 +874,12 @@ const Subprocess = struct { } { assert(self.pty == null and self.command == null); + // This function is funny because on POSIX systems it can + // fail in the forked process. This is flipped to true if + // we're in an error state in the forked process (child + // process). + var in_child: bool = false; + // Create our pty var pty = try Pty.open(.{ .ws_row = @intCast(self.grid_size.rows), @@ -903,14 +888,14 @@ const Subprocess = struct { .ws_ypixel = @intCast(self.screen_size.height), }); self.pty = pty; - errdefer { + errdefer if (!in_child) { if (comptime builtin.os.tag != .windows) { _ = posix.close(pty.slave); } pty.deinit(); self.pty = null; - } + }; log.debug("starting command command={s}", .{self.args}); @@ -1013,7 +998,22 @@ const Subprocess = struct { .data = self, .linux_cgroup = self.linux_cgroup, }; - try cmd.start(alloc); + + cmd.start(alloc) catch |err| { + // We have to do this because start on Windows can't + // ever return ExecFailedInChild + const StartError = error{ExecFailedInChild} || @TypeOf(err); + switch (@as(StartError, err)) { + // If we fail in our child we need to flag it so our + // errdefers don't run. + error.ExecFailedInChild => { + in_child = true; + return err; + }, + + else => return err, + } + }; errdefer killCommand(&cmd) catch |err| { log.warn("error killing command during cleanup err={}", .{err}); }; From 95d9b1e627abed0543806cc6f1abad3e1d60384b Mon Sep 17 00:00:00 2001 From: Kat <65649991+00-kat@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:24:55 +0000 Subject: [PATCH 058/119] Request translators to update the CODEOWNERS file. --- po/README_TRANSLATORS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/po/README_TRANSLATORS.md b/po/README_TRANSLATORS.md index ca1e45faa..c02a5bd48 100644 --- a/po/README_TRANSLATORS.md +++ b/po/README_TRANSLATORS.md @@ -148,6 +148,18 @@ const locales = [_][]const u8{ You should then be able to run `zig build` and see your translations in action. +Before opening a pull request with the new translation file, you should also add +your locale to the `CODEOWNERS` file. Find the `# Localization` section near the +bottom and add a line like so (where `xx_YY` is your locale): + +```diff + # Localization + /po/README_TRANSLATORS.md @ghostty-org/localization + /po/com.mitchellh.ghostty.pot @ghostty-org/localization + /po/zh_CN.UTF-8.po @ghostty-org/zh_CN ++/po/xx_YY.UTF-8.po @ghostty-org/xx_YY +``` + ## Style Guide These are general style guidelines for translations. Naturally, the specific From d8838cff0b1b1d9170568fa3fc99c69cc1f050a3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 07:30:24 -0700 Subject: [PATCH 059/119] macOS: remove @DeferredProperty usage from TerminalEntity This fixes an Apple Shortcuts crash for macOS 15 and earlier. Unfortunately it looks like we can't guard these with `@available`. I'm going to report an Apple Feedback about this but for now this gets shortcuts working on macOS 15 and earlier. --- .../App Intents/Entities/TerminalEntity.swift | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift index e29fbba3f..cc3b9f63a 100644 --- a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift +++ b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift @@ -14,26 +14,6 @@ struct TerminalEntity: AppEntity { @Property(title: "Kind") var kind: Kind - @MainActor - @DeferredProperty(title: "Full Contents") - @available(macOS 26.0, *) - var screenContents: String? { - get async { - guard let surfaceView else { return nil } - return surfaceView.cachedScreenContents.get() - } - } - - @MainActor - @DeferredProperty(title: "Visible Contents") - @available(macOS 26.0, *) - var visibleContents: String? { - get async { - guard let surfaceView else { return nil } - return surfaceView.cachedVisibleContents.get() - } - } - var screenshot: Image? static var typeDisplayRepresentation: TypeDisplayRepresentation { From eea073c97bd9c4de15a7179c1ad3bb252652fbfa Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 07:40:59 -0700 Subject: [PATCH 060/119] On wait-after-command (or abnormal exit), only close on encoded key Fixes #7794 This commit also resets some terminal state to give us a better chance of getting an encoded key, such as ensuring keyboard input is enabled and disabling any Kitty protocols. This shouldn't ever be set but just in case! --- src/Surface.zig | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index dc7b0e3bf..6d0f1584b 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1034,6 +1034,12 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { t.printString("Process exited. Press any key to close the terminal.") catch break :terminal; t.modes.set(.cursor_visible, false); + + // We also want to ensure that normal keyboard encoding is on + // so that we can close the terminal. We close the terminal on + // any key press that encodes a character. + t.modes.set(.disable_keyboard, false); + t.screen.kitty_keyboard.set(.set, .{}); } // Waiting after command we stop here. The terminal is updated, our @@ -2129,14 +2135,6 @@ pub fn keyCallback( if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed; } - // If our process is exited and we press a key then we close the - // surface. We may want to eventually move this to the apprt rather - // than in core. - if (self.child_exited and event.action == .press) { - self.close(); - return .closed; - } - // If this input event has text, then we hide the mouse if configured. // We only do this on pressed events to avoid hiding the mouse when we // change focus due to a keybinding (i.e. switching tabs). @@ -2231,6 +2229,14 @@ pub fn keyCallback( event, if (insp_ev) |*ev| ev else null, )) |write_req| { + // If our process is exited and we press a key that results in + // an encoded value, we close the surface. We want to eventually + // move this behavior to the apprt probably. + if (self.child_exited) { + self.close(); + return .closed; + } + errdefer write_req.deinit(); self.io.queueMessage(switch (write_req) { .small => |v| .{ .write_small = v }, From 7bd90e6ec4354e0a25f95225f1508293912612d4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 13:05:50 -0700 Subject: [PATCH 061/119] Build system can build macOS app bundle and open it `zig build run` on macOS now builds the app bundle via the `xcodebuild` CLI and runs it. The experience for running the app is now very similar to Linux or the prior GLFW build, where the app runs, blocks the zig command, and logs to the terminal. `xcodebuild` has its own build cache system that we can't really hook into so it runs on every `zig build run` command, but it does cache and I find its actually relatively fast so I think this is a good replacement for the glfw-based system. --- build.zig | 95 +++++++++++++--------- macos/Sources/App/macOS/AppDelegate.swift | 16 ++++ src/build/GhosttyXcodebuild.zig | 99 +++++++++++++++++++++++ src/build/XCFrameworkStep.zig | 3 + src/build/main.zig | 1 + 5 files changed, 177 insertions(+), 37 deletions(-) create mode 100644 src/build/GhosttyXcodebuild.zig diff --git a/build.zig b/build.zig index 80af88488..4bd6e0b46 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const builtin = @import("builtin"); const buildpkg = @import("src/build/main.zig"); @@ -47,6 +48,25 @@ pub fn build(b: *std.Build) !void { exe.install(); resources.install(); i18n.install(); + + // Run runs the Ghostty exe. We only do this if we are building + // an apprt. + { + const run_cmd = b.addRunArtifact(exe.exe); + if (b.args) |args| run_cmd.addArgs(args); + + // Set the proper resources dir so things like shell integration + // work correctly. If we're running `zig build run` in Ghostty, + // this also ensures it overwrites the release one with our debug + // build. + run_cmd.setEnvironmentVariable( + "GHOSTTY_RESOURCES_DIR", + b.getInstallPath(.prefix, "share/ghostty"), + ); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + } } // Libghostty @@ -55,52 +75,53 @@ pub fn build(b: *std.Build) !void { // heavily by Ghostty on macOS but it isn't built to be reusable yet. // As such, these build steps are lacking. For example, the Darwin // build only produces an xcframework. - if (config.app_runtime == .none) { - if (config.target.result.os.tag.isDarwin()) darwin: { - if (!config.emit_xcframework) break :darwin; - - // Build the xcframework - const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); - xcframework.install(); - - // The xcframework build always installs resources because our - // macOS xcode project contains references to them. - resources.install(); - i18n.install(); - - // If we aren't emitting docs we need to emit a placeholder so - // our macOS xcodeproject builds. - if (!config.emit_docs) { - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - const placeholder = wf.add(path, "emit-docs not true so no man pages"); - b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); - } - } else { - const libghostty_shared = try buildpkg.GhosttyLib.initShared(b, &deps); - const libghostty_static = try buildpkg.GhosttyLib.initStatic(b, &deps); + if (config.app_runtime == .none) none: { + if (!config.target.result.os.tag.isDarwin()) { + const libghostty_shared = try buildpkg.GhosttyLib.initShared( + b, + &deps, + ); + const libghostty_static = try buildpkg.GhosttyLib.initStatic( + b, + &deps, + ); libghostty_shared.installHeader(); // Only need one header libghostty_shared.install("libghostty.so"); libghostty_static.install("libghostty.a"); + break :none; } - } - // Run runs the Ghostty exe - { - const run_cmd = b.addRunArtifact(exe.exe); - if (b.args) |args| run_cmd.addArgs(args); + assert(config.target.result.os.tag.isDarwin()); + if (!config.emit_xcframework) break :none; - // Set the proper resources dir so things like shell integration - // work correctly. If we're running `zig build run` in Ghostty, - // this also ensures it overwrites the release one with our debug - // build. - run_cmd.setEnvironmentVariable( - "GHOSTTY_RESOURCES_DIR", - b.getInstallPath(.prefix, "share/ghostty"), + // Build the xcframework + const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); + xcframework.install(); + + // The xcframework build always installs resources because our + // macOS xcode project contains references to them. + resources.install(); + i18n.install(); + + // If we aren't emitting docs we need to emit a placeholder so + // our macOS xcodeproject builds. + if (!config.emit_docs) { + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + const placeholder = wf.add(path, "emit-docs not true so no man pages"); + b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); + } + + // Build our macOS app + const app = try buildpkg.GhosttyXcodebuild.init( + b, + &config, + &xcframework, ); + // Add a run command that opens our mac app. const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); + run_step.dependOn(&app.open.step); } // Tests diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 418005927..efc09ede9 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -255,6 +255,22 @@ class AppDelegate: NSObject, // Setup signal handlers setupSignals() + + // This is a hack used by our build scripts, specifically `zig build run`, + // to force our app to the foreground. + if ProcessInfo.processInfo.environment["GHOSTTY_MAC_ACTIVATE"] == "1" { + // This never gets called until we click the dock icon. This forces it + // activate immediately. + applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification)) + + // We run in the background, this forces us to the front. + DispatchQueue.main.async { + NSApp.setActivationPolicy(.regular) + NSApp.activate(ignoringOtherApps: true) + NSApp.unhide(nil) + NSApp.arrangeInFront(nil) + } + } } func applicationDidBecomeActive(_ notification: Notification) { diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig new file mode 100644 index 000000000..83ab0aed3 --- /dev/null +++ b/src/build/GhosttyXcodebuild.zig @@ -0,0 +1,99 @@ +const Ghostty = @This(); + +const std = @import("std"); +const RunStep = std.Build.Step.Run; +const Config = @import("Config.zig"); +const XCFramework = @import("GhosttyXCFramework.zig"); + +xcodebuild: *std.Build.Step.Run, +open: *std.Build.Step.Run, + +pub fn init( + b: *std.Build, + config: *const Config, + xcframework: *const XCFramework, +) !Ghostty { + const xc_config = switch (config.optimize) { + .Debug => "Debug", + .ReleaseSafe, + .ReleaseSmall, + .ReleaseFast, + => "Release", + }; + + // Our step to build the Ghostty macOS app. + const build = build: { + // External environment variables can mess up xcodebuild, so + // we create a new empty environment. + const env_map = try b.allocator.create(std.process.EnvMap); + env_map.* = .init(b.allocator); + + const build = RunStep.create(b, "xcodebuild"); + build.has_side_effects = true; + build.cwd = b.path("macos"); + build.env_map = env_map; + build.addArgs(&.{ + "xcodebuild", + "-target", + "Ghostty", + "-configuration", + xc_config, + }); + + // We need the xcframework + build.step.dependOn(xcframework.xcframework.step); + + // Expect success + build.expectExitCode(0); + + // Capture stdout/stderr so we don't pollute our zig build + _ = build.captureStdOut(); + _ = build.captureStdErr(); + break :build build; + }; + + // Our step to open the resulting Ghostty app. + const open = open: { + const open = RunStep.create(b, "run Ghostty app"); + open.has_side_effects = true; + open.cwd = b.path("macos"); + open.addArgs(&.{ + b.fmt( + "build/{s}/Ghostty.app/Contents/MacOS/ghostty", + .{xc_config}, + ), + }); + + // Open depends on the app + open.step.dependOn(&build.step); + + // This overrides our default behavior and forces logs to show + // up on stderr (in addition to the centralized macOS log). + open.setEnvironmentVariable("GHOSTTY_LOG", "1"); + + // This is hack so that we can activate the app and bring it to + // the front forcibly even though we're executing directly + // via the binary and not launch services. + open.setEnvironmentVariable("GHOSTTY_MAC_ACTIVATE", "1"); + + if (b.args) |args| { + open.addArgs(args); + } else { + // This tricks the app into thinking it's running from the + // app bundle so we don't execute our CLI mode. + open.setEnvironmentVariable("GHOSTTY_MAC_APP", "1"); + } + + break :open open; + }; + + return .{ + .xcodebuild = build, + .open = open, + }; +} + +pub fn install(self: *const Ghostty) void { + const b = self.xcodebuild.step.owner; + b.getInstallStep().dependOn(&self.xcodebuild.step); +} diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig index 823e5aac4..8a0d5dc67 100644 --- a/src/build/XCFrameworkStep.zig +++ b/src/build/XCFrameworkStep.zig @@ -55,6 +55,9 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep { } run.addArg("-output"); run.addArg(opts.out_path); + run.expectExitCode(0); + _ = run.captureStdOut(); + _ = run.captureStdErr(); break :run run; }; run_create.step.dependOn(&run_delete.step); diff --git a/src/build/main.zig b/src/build/main.zig index 3154d395f..f25ce1c23 100644 --- a/src/build/main.zig +++ b/src/build/main.zig @@ -15,6 +15,7 @@ pub const GhosttyFrameData = @import("GhosttyFrameData.zig"); pub const GhosttyLib = @import("GhosttyLib.zig"); pub const GhosttyResources = @import("GhosttyResources.zig"); pub const GhosttyI18n = @import("GhosttyI18n.zig"); +pub const GhosttyXcodebuild = @import("GhosttyXcodebuild.zig"); pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig"); pub const GhosttyWebdata = @import("GhosttyWebdata.zig"); pub const HelpStrings = @import("HelpStrings.zig"); From fb9c52ecf44ee1004de16a4b02a67d4258e66c14 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Jul 2025 13:43:30 -0700 Subject: [PATCH 062/119] Nuke GLFW from Orbit This deletes the GLFW apprt from the Ghostty codebase. The GLFW apprt was the original apprt used by Ghostty (well, before Ghostty even had the concept of an "apprt" -- it was all just a single application then). It let me iterate on the core terminal features, rendering, etc. without bothering about the UI. It was a good way to get started. But it has long since outlived its usefulness. We've had a stable GTK apprt for Linux (and Windows via WSL) and a native macOS app via libghostty for awhile now. The GLFW apprt only remained within the tree for a few reasons: 1. Primarily, it provided a faster feedback loop on macOS because building the macOS app historically required us to hop out of the zig build system and into Xcode, which is slow and cumbersome. 2. It was a convenient way to narrow whether a bug was in the core Ghostty codebase or in the apprt itself. If a bug was in both the glfw and macOS app then it was likely in the core. 3. It provided us a way on macOS to test OpenGL. All of these reasons are no longer valid. Respectively: 1. Our Zig build scripts now execute the `xcodebuild` CLI directly and can open the resulting app, stream logs, etc. This is the same experience we have on Linux. (Xcode has always been a dependency of building on macOS in general, so this is not cumbersome.) 2. We have a healthy group of maintainers, many of which have access to both macOS and Linux, so we can quickly narrow down bugs regardless of the apprt. 3. Our OpenGL renderer hasn't been compatible with macOS for some time now, so this is no longer a useful feature. At this point, the GLFW apprt is just a burden. It adds complexity across the board, and some people try to run Ghostty with it in the real world and get confused when it doesn't work (it's always been lacking in features and buggy compared to the other apprts). So, it's time to say goodbye. Its bittersweet because it is a big part of Ghostty's history, but we've grown up now and it's time to move on. Thank you, goodbye. (NOTE: If you are a user of the GLFW apprt, then please fork the project prior to this commit or start a new project based on it. We've warned against using it for a very, very long time now.) --- .github/workflows/test.yml | 88 +- PACKAGING.md | 8 - README.md | 6 - build.zig.zon | 1 - build.zig.zon.json | 5 - build.zig.zon.nix | 8 - build.zig.zon.txt | 1 - flatpak/zig-packages.json | 6 - pkg/glfw/Cursor.zig | 209 - pkg/glfw/GammaRamp.zig | 74 - pkg/glfw/Image.zig | 82 - pkg/glfw/Joystick.zig | 642 -- pkg/glfw/LICENSE | 26 - pkg/glfw/Monitor.zig | 599 -- pkg/glfw/VideoMode.zig | 50 - pkg/glfw/Window.zig | 3551 ---------- pkg/glfw/action.zig | 13 - pkg/glfw/allocator.zig | 143 - pkg/glfw/build.zig | 271 - pkg/glfw/build.zig.zon | 15 - pkg/glfw/c.zig | 6 - pkg/glfw/clipboard.zig | 71 - pkg/glfw/errors.zig | 338 - pkg/glfw/gamepad_axis.zig | 16 - pkg/glfw/gamepad_button.zig | 37 - pkg/glfw/hat.zig | 100 - pkg/glfw/internal_debug.zig | 14 - pkg/glfw/key.zig | 266 - pkg/glfw/main.zig | 586 -- pkg/glfw/mod.zig | 167 - pkg/glfw/mouse_button.zig | 23 - pkg/glfw/native.zig | 393 -- pkg/glfw/opengl.zig | 256 - pkg/glfw/shims.zig | 84 - pkg/glfw/time.zig | 153 - pkg/glfw/version.zig | 18 - pkg/glfw/vulkan.zig | 290 - ...fractional-scale-v1-client-protocol-code.h | 74 - .../fractional-scale-v1-client-protocol.h | 264 - ...inhibit-unstable-v1-client-protocol-code.h | 69 - ...idle-inhibit-unstable-v1-client-protocol.h | 232 - ...traints-unstable-v1-client-protocol-code.h | 109 - ...-constraints-unstable-v1-client-protocol.h | 667 -- ...pointer-unstable-v1-client-protocol-code.h | 80 - ...tive-pointer-unstable-v1-client-protocol.h | 297 - .../viewporter-client-protocol-code.h | 75 - .../viewporter-client-protocol.h | 398 -- .../wayland-client-protocol-code.h | 525 -- .../wayland-headers/wayland-client-protocol.h | 6236 ----------------- .../xdg-activation-v1-client-protocol-code.h | 85 - .../xdg-activation-v1-client-protocol.h | 415 -- ...oration-unstable-v1-client-protocol-code.h | 76 - ...g-decoration-unstable-v1-client-protocol.h | 378 - .../xdg-shell-client-protocol-code.h | 184 - .../xdg-shell-client-protocol.h | 2307 ------ src/apprt.zig | 20 +- src/apprt/embedded.zig | 2 +- src/apprt/glfw.zig | 1266 ---- src/build/Config.zig | 5 - src/build/SharedDeps.zig | 11 - src/config/Config.zig | 8 +- src/main_ghostty.zig | 1 - src/renderer/Metal.zig | 27 - src/renderer/OpenGL.zig | 45 +- src/renderer/generic.zig | 15 - src/termio/stream_handler.zig | 13 - 66 files changed, 22 insertions(+), 22478 deletions(-) delete mode 100644 pkg/glfw/Cursor.zig delete mode 100644 pkg/glfw/GammaRamp.zig delete mode 100644 pkg/glfw/Image.zig delete mode 100644 pkg/glfw/Joystick.zig delete mode 100644 pkg/glfw/LICENSE delete mode 100644 pkg/glfw/Monitor.zig delete mode 100644 pkg/glfw/VideoMode.zig delete mode 100644 pkg/glfw/Window.zig delete mode 100644 pkg/glfw/action.zig delete mode 100644 pkg/glfw/allocator.zig delete mode 100644 pkg/glfw/build.zig delete mode 100644 pkg/glfw/build.zig.zon delete mode 100644 pkg/glfw/c.zig delete mode 100644 pkg/glfw/clipboard.zig delete mode 100644 pkg/glfw/errors.zig delete mode 100644 pkg/glfw/gamepad_axis.zig delete mode 100644 pkg/glfw/gamepad_button.zig delete mode 100644 pkg/glfw/hat.zig delete mode 100644 pkg/glfw/internal_debug.zig delete mode 100644 pkg/glfw/key.zig delete mode 100644 pkg/glfw/main.zig delete mode 100644 pkg/glfw/mod.zig delete mode 100644 pkg/glfw/mouse_button.zig delete mode 100644 pkg/glfw/native.zig delete mode 100644 pkg/glfw/opengl.zig delete mode 100644 pkg/glfw/shims.zig delete mode 100644 pkg/glfw/time.zig delete mode 100644 pkg/glfw/version.zig delete mode 100644 pkg/glfw/vulkan.zig delete mode 100644 pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/viewporter-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/viewporter-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/wayland-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/wayland-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h delete mode 100644 pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h delete mode 100644 pkg/glfw/wayland-headers/xdg-shell-client-protocol.h delete mode 100644 src/apprt/glfw.zig diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d09603f4..1894e81fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,6 @@ jobs: - build-macos-tahoe - build-macos-matrix - build-windows - - build-windows-cross - flatpak-check-zig-cache - flatpak - test @@ -84,7 +83,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Build Benchmarks - run: nix develop -c zig build -Dapp-runtime=glfw -Demit-bench + run: nix develop -c zig build -Demit-bench build-flatpak: strategy: @@ -151,7 +150,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Test Build - run: nix develop -c zig build -Dapp-runtime=glfw + run: nix develop -c zig build build-linux-libghostty: runs-on: namespace-profile-ghostty-md @@ -374,33 +373,19 @@ jobs: - name: Test All run: | - # OpenGL - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape - - # Metal - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=freetype + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_harfbuzz + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_noshape - name: Build All run: | - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape - - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_harfbuzz + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_noshape build-snap: strategy: @@ -507,52 +492,6 @@ jobs: shell: pwsh run: Get-Content -Path ".\build.log" - build-windows-cross: - strategy: - fail-fast: false - matrix: - os: ["namespace-profile-ghostty-md"] - - target: [ - x86-windows-gnu, - x86_64-windows-gnu, - # We don't support cross-compiling to macOS or Linux because - # we require system libraries. - #aarch64-linux, - #x86_64-linux, - #aarch64-macos, - #x86_64-macos, - ] - runs-on: ${{ matrix.os }} - needs: test - env: - ZIG_LOCAL_CACHE_DIR: /zig/local-cache - ZIG_GLOBAL_CACHE_DIR: /zig/global-cache - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 - with: - path: | - /nix - /zig - - # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 - with: - name: ghostty - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - # Cross-compile the binary. We always use static building for this - # because its the only way to access the headers. - - name: Test Build - run: nix develop -c zig build -Dapp-runtime=glfw -Dtarget=${{ matrix.target }} - test: if: github.repository == 'ghostty-org/ghostty' runs-on: namespace-profile-ghostty-md @@ -585,9 +524,6 @@ jobs: - name: Test GTK Build run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs - - name: Test GLFW Build - run: nix develop -c zig build -Dapp-runtime=glfw - # This relies on the cache being populated by the commands above. - name: Test System Build run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p diff --git a/PACKAGING.md b/PACKAGING.md index d85f55de7..1483b8591 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -122,11 +122,3 @@ relevant to package maintainers: often necessary for system packages to specify a specific minimum Linux version, glibc, etc. Run `zig targets` to a get a full list of available targets. - -> [!WARNING] -> -> **The GLFW runtime is not meant for distribution.** The GLFW runtime -> (`-Dapp-runtime=glfw`) is meant for development and testing only. It is -> missing many features, has known memory leak scenarios, known crashes, -> and more. Please do not package the GLFW-based Ghostty runtime for -> distribution. diff --git a/README.md b/README.md index b59964e61..a761e25ce 100644 --- a/README.md +++ b/README.md @@ -194,12 +194,6 @@ omit the `-Doptimize` flag to build a debug build, and you may require additional dependencies since the source tarball includes some processed files that are not in the Git repository. -On Linux or macOS, you can use `zig build -Dapp-runtime=glfw run` for a quick -GLFW-based app for a faster development cycle while developing core -terminal features. Note that this app is missing many features and is also -known to crash in certain scenarios, so it is only meant for development -tasks. - Other useful commands: - `zig build test` for running unit tests. diff --git a/build.zig.zon b/build.zig.zon index 237720f35..fc1bf08fd 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -64,7 +64,6 @@ .cimgui = .{ .path = "./pkg/cimgui", .lazy = true }, .fontconfig = .{ .path = "./pkg/fontconfig", .lazy = true }, .freetype = .{ .path = "./pkg/freetype", .lazy = true }, - .glfw = .{ .path = "./pkg/glfw", .lazy = true }, .gtk4_layer_shell = .{ .path = "./pkg/gtk4-layer-shell", .lazy = true }, .harfbuzz = .{ .path = "./pkg/harfbuzz", .lazy = true }, .highway = .{ .path = "./pkg/highway", .lazy = true }, diff --git a/build.zig.zon.json b/build.zig.zon.json index 420893ef7..814b20b90 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -19,11 +19,6 @@ "url": "https://deps.files.ghostty.org/gettext-0.24.tar.gz", "hash": "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc=" }, - "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6": { - "name": "glfw", - "url": "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - "hash": "sha256-M3N1XUAlMebBo5X1Py+9YxjKXgZ6eacqWRCbUmwLKQo=" - }, "N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy": { "name": "glslang", "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 6e4b86606..9d50a6fc6 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -113,14 +113,6 @@ in hash = "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc="; }; } - { - name = "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6"; - path = fetchZigArtifact { - name = "glfw"; - url = "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz"; - hash = "sha256-M3N1XUAlMebBo5X1Py+9YxjKXgZ6eacqWRCbUmwLKQo="; - }; - } { name = "N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy"; path = fetchZigArtifact { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index f05a789dd..16180c39c 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -25,7 +25,6 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6 https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz -https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index daf7e5cea..8a911bb55 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -23,12 +23,6 @@ "dest": "vendor/p/N-V-__8AADcZkgn4cMhTUpIz6mShCKyqqB-NBtf_S2bHaTC-", "sha256": "c918503d593d70daf4844d175a13d816afacb667c06fba1ec9dcd5002c1518b7" }, - { - "type": "archive", - "url": "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - "dest": "vendor/p/N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6", - "sha256": "3373755d402531e6c1a395f53f2fbd6318ca5e067a79a72a59109b526c0b290a" - }, { "type": "archive", "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", diff --git a/pkg/glfw/Cursor.zig b/pkg/glfw/Cursor.zig deleted file mode 100644 index cd79e8848..000000000 --- a/pkg/glfw/Cursor.zig +++ /dev/null @@ -1,209 +0,0 @@ -//! Represents a cursor and provides facilities for setting cursor images. - -const std = @import("std"); -const testing = std.testing; - -const c = @import("c.zig").c; -const Image = @import("Image.zig"); - -const internal_debug = @import("internal_debug.zig"); - -const Cursor = @This(); - -ptr: *c.GLFWcursor, - -/// Standard system cursor shapes. -/// -/// These are the standard cursor shapes that can be requested from the platform (window system). -pub const Shape = enum(i32) { - /// The regular arrow cursor shape. - arrow = c.GLFW_ARROW_CURSOR, - - /// The text input I-beam cursor shape. - ibeam = c.GLFW_IBEAM_CURSOR, - - /// The crosshair cursor shape. - crosshair = c.GLFW_CROSSHAIR_CURSOR, - - /// The pointing hand cursor shape. - /// - /// NOTE: This supersedes the old `hand` enum. - pointing_hand = c.GLFW_POINTING_HAND_CURSOR, - - /// The horizontal resize/move arrow shape. - /// - /// The horizontal resize/move arrow shape. This is usually a horizontal double-headed arrow. - // - // NOTE: This supersedes the old `hresize` enum. - resize_ew = c.GLFW_RESIZE_EW_CURSOR, - - /// The vertical resize/move arrow shape. - /// - /// The vertical resize/move shape. This is usually a vertical double-headed arrow. - /// - /// NOTE: This supersedes the old `vresize` enum. - resize_ns = c.GLFW_RESIZE_NS_CURSOR, - - /// The top-left to bottom-right diagonal resize/move arrow shape. - /// - /// The top-left to bottom-right diagonal resize/move shape. This is usually a diagonal - /// double-headed arrow. - /// - /// macos: This shape is provided by a private system API and may fail CursorUnavailable in the - /// future. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - resize_nwse = c.GLFW_RESIZE_NWSE_CURSOR, - - /// The top-right to bottom-left diagonal resize/move arrow shape. - /// - /// The top-right to bottom-left diagonal resize/move shape. This is usually a diagonal - /// double-headed arrow. - /// - /// macos: This shape is provided by a private system API and may fail with CursorUnavailable - /// in the future. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - resize_nesw = c.GLFW_RESIZE_NESW_CURSOR, - - /// The omni-directional resize/move cursor shape. - /// - /// The omni-directional resize cursor/move shape. This is usually either a combined horizontal - /// and vertical double-headed arrow or a grabbing hand. - resize_all = c.GLFW_RESIZE_ALL_CURSOR, - - /// The operation-not-allowed shape. - /// - /// The operation-not-allowed shape. This is usually a circle with a diagonal line through it. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - not_allowed = c.GLFW_NOT_ALLOWED_CURSOR, -}; - -/// Creates a custom cursor. -/// -/// Creates a new custom cursor image that can be set for a window with glfw.Cursor.set. The cursor -/// can be destroyed with glfwCursor.destroy. Any remaining cursors are destroyed by glfw.terminate. -/// -/// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight bits per channel with -/// the red channel first. They are arranged canonically as packed sequential rows, starting from -/// the top-left corner. -/// -/// The cursor hotspot is specified in pixels, relative to the upper-left corner of the cursor -/// image. Like all other coordinate systems in GLFW, the X-axis points to the right and the Y-axis -/// points down. -/// -/// @param[in] image The desired cursor image. -/// @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot. -/// @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot. -/// @return The handle of the created cursor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.InvalidValue -/// null is returned in the event of an error. -/// -/// @pointer_lifetime The specified image data is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object, glfw.Cursor.destroy, glfw.Cursor.createStandard -pub inline fn create(image: Image, xhot: i32, yhot: i32) ?Cursor { - internal_debug.assertInitialized(); - const img = image.toC(); - if (c.glfwCreateCursor(&img, @as(c_int, @intCast(xhot)), @as(c_int, @intCast(yhot)))) |cursor| return Cursor{ .ptr = cursor }; - return null; -} - -/// Creates a cursor with a standard shape. -/// -/// Returns a cursor with a standard shape, that can be set for a window with glfw.Window.setCursor. -/// The images for these cursors come from the system cursor theme and their exact appearance will -/// vary between platforms. -/// -/// Most of these shapes are guaranteed to exist on every supported platform but a few may not be -/// present. See the table below for details. -/// -/// | Cursor shape | Windows | macOS | X11 | Wayland | -/// |------------------|---------|-----------------|-------------------|-------------------| -/// | `.arrow` | Yes | Yes | Yes | Yes | -/// | `.ibeam` | Yes | Yes | Yes | Yes | -/// | `.crosshair` | Yes | Yes | Yes | Yes | -/// | `.pointing_hand` | Yes | Yes | Yes | Yes | -/// | `.resize_ew` | Yes | Yes | Yes | Yes | -/// | `.resize_ns` | Yes | Yes | Yes | Yes | -/// | `.resize_nwse` | Yes | Yes1 | Maybe2 | Maybe2 | -/// | `.resize_nesw` | Yes | Yes1 | Maybe2 | Maybe2 | -/// | `.resize_all` | Yes | Yes | Yes | Yes | -/// | `.not_allowed` | Yes | Yes | Maybe2 | Maybe2 | -/// -/// 1. This uses a private system API and may fail in the future. -/// 2. This uses a newer standard that not all cursor themes support. -/// -/// If the requested shape is not available, this function emits a CursorUnavailable error -/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.CursorUnavailable. -/// null is returned in the event of an error. -/// -/// thread_safety: This function must only be called from the main thread. -/// -/// see also: cursor_object, glfwCreateCursor -pub inline fn createStandard(shape: Shape) ?Cursor { - internal_debug.assertInitialized(); - if (c.glfwCreateStandardCursor(@as(c_int, @intCast(@intFromEnum(shape))))) |cursor| return Cursor{ .ptr = cursor }; - return null; -} - -/// Destroys a cursor. -/// -/// This function destroys a cursor previously created with glfw.Cursor.create. Any remaining -/// cursors will be destroyed by glfw.terminate. -/// -/// If the specified cursor is current for any window, that window will be reverted to the default -/// cursor. This does not affect the cursor mode. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object, glfw.createCursor -pub inline fn destroy(self: Cursor) void { - internal_debug.assertInitialized(); - c.glfwDestroyCursor(self.ptr); -} - -test "create" { - const allocator = testing.allocator; - - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const image = try Image.init(allocator, 32, 32, 32 * 32 * 4); - defer image.deinit(allocator); - - const cursor = glfw.Cursor.create(image, 0, 0); - if (cursor) |cur| cur.destroy(); -} - -test "createStandard" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const cursor = glfw.Cursor.createStandard(.ibeam); - if (cursor) |cur| cur.destroy(); -} diff --git a/pkg/glfw/GammaRamp.zig b/pkg/glfw/GammaRamp.zig deleted file mode 100644 index 7b5a1c3fd..000000000 --- a/pkg/glfw/GammaRamp.zig +++ /dev/null @@ -1,74 +0,0 @@ -//! Gamma ramp for monitors and related functions. -//! -//! It may be .owned (e.g. in the case of a gamma ramp initialized by you for passing into -//! glfw.Monitor.setGammaRamp) or not .owned (e.g. in the case of one gotten via -//! glfw.Monitor.getGammaRamp.) If it is .owned, deinit should be called to free the memory. It is -//! safe to call deinit even if not .owned. -//! -//! see also: monitor_gamma, glfw.Monitor.getGammaRamp - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const GammaRamp = @This(); - -red: []u16, -green: []u16, -blue: []u16, -owned: ?[]u16, - -/// Initializes a new owned gamma ramp with the given array size and undefined values. -/// -/// see also: glfw.Monitor.getGammaRamp -pub inline fn init(allocator: mem.Allocator, size: usize) !GammaRamp { - const buf = try allocator.alloc(u16, size * 3); - return GammaRamp{ - .red = buf[size * 0 .. (size * 0) + size], - .green = buf[size * 1 .. (size * 1) + size], - .blue = buf[size * 2 .. (size * 2) + size], - .owned = buf, - }; -} - -/// Turns a GLFW / C gamma ramp into the nicer Zig type, and sets `.owned = false`. -/// -/// The returned memory is valid for as long as the GLFW C memory is valid. -pub inline fn fromC(native: c.GLFWgammaramp) GammaRamp { - return GammaRamp{ - .red = native.red[0..native.size], - .green = native.green[0..native.size], - .blue = native.blue[0..native.size], - .owned = null, - }; -} - -/// Turns the nicer Zig type into a GLFW / C gamma ramp, for passing into GLFW C functions. -/// -/// The returned memory is valid for as long as the Zig memory is valid. -pub inline fn toC(self: GammaRamp) c.GLFWgammaramp { - std.debug.assert(self.red.len == self.green.len); - std.debug.assert(self.red.len == self.blue.len); - return c.GLFWgammaramp{ - .red = &self.red[0], - .green = &self.green[0], - .blue = &self.blue[0], - .size = @as(c_uint, @intCast(self.red.len)), - }; -} - -/// Deinitializes the memory using the allocator iff `.owned = true`. -pub inline fn deinit(self: GammaRamp, allocator: mem.Allocator) void { - if (self.owned) |buf| allocator.free(buf); -} - -test "conversion" { - const allocator = testing.allocator; - - const ramp = try GammaRamp.init(allocator, 256); - defer ramp.deinit(allocator); - - const glfw = ramp.toC(); - _ = GammaRamp.fromC(glfw); -} diff --git a/pkg/glfw/Image.zig b/pkg/glfw/Image.zig deleted file mode 100644 index d32e5c310..000000000 --- a/pkg/glfw/Image.zig +++ /dev/null @@ -1,82 +0,0 @@ -//! Image data -//! -//! -//! This describes a single 2D image. See the documentation for each related function what the -//! expected pixel format is. -//! -//! see also: cursor_custom, window_icon -//! -//! It may be .owned (e.g. in the case of an image initialized by you for passing into glfw) or not -//! .owned (e.g. in the case of one gotten via glfw) If it is .owned, deinit should be called to -//! free the memory. It is safe to call deinit even if not .owned. - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const Image = @This(); - -/// The width of this image, in pixels. -width: u32, - -/// The height of this image, in pixels. -height: u32, - -/// The pixel data of this image, arranged left-to-right, top-to-bottom. -pixels: []u8, - -/// Whether or not the pixels data is owned by you (true) or GLFW (false). -owned: bool, - -/// Initializes a new owned image with the given size and pixel_data_len of undefined .pixel values. -pub inline fn init(allocator: mem.Allocator, width: u32, height: u32, pixel_data_len: usize) !Image { - const buf = try allocator.alloc(u8, pixel_data_len); - return Image{ - .width = width, - .height = height, - .pixels = buf, - .owned = true, - }; -} - -/// Turns a GLFW / C image into the nicer Zig type, and sets `.owned = false`. -/// -/// The length of pixel data must be supplied, as GLFW's image type does not itself describe the -/// number of bytes required per pixel / the length of the pixel data array. -/// -/// The returned memory is valid for as long as the GLFW C memory is valid. -pub inline fn fromC(native: c.GLFWimage, pixel_data_len: usize) Image { - return Image{ - .width = @as(u32, @intCast(native.width)), - .height = @as(u32, @intCast(native.height)), - .pixels = native.pixels[0..pixel_data_len], - .owned = false, - }; -} - -/// Turns the nicer Zig type into a GLFW / C image, for passing into GLFW C functions. -/// -/// The returned memory is valid for as long as the Zig memory is valid. -pub inline fn toC(self: Image) c.GLFWimage { - return c.GLFWimage{ - .width = @as(c_int, @intCast(self.width)), - .height = @as(c_int, @intCast(self.height)), - .pixels = &self.pixels[0], - }; -} - -/// Deinitializes the memory using the allocator iff `.owned = true`. -pub inline fn deinit(self: Image, allocator: mem.Allocator) void { - if (self.owned) allocator.free(self.pixels); -} - -test "conversion" { - const allocator = testing.allocator; - - const image = try Image.init(allocator, 256, 256, 256 * 256 * 4); - defer image.deinit(allocator); - - const glfw = image.toC(); - _ = Image.fromC(glfw, image.width * image.height * 4); -} diff --git a/pkg/glfw/Joystick.zig b/pkg/glfw/Joystick.zig deleted file mode 100644 index a8152513e..000000000 --- a/pkg/glfw/Joystick.zig +++ /dev/null @@ -1,642 +0,0 @@ -//! Represents a Joystick or gamepad -//! -//! It can be manually crafted via e.g. `glfw.Joystick{.jid = .one}`, but more -//! typically you'll want to discover the joystick using `glfw.Joystick.setCallback`. - -const std = @import("std"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); -const Action = @import("action.zig").Action; -const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis; -const GamepadButton = @import("gamepad_button.zig").GamepadButton; -const Hat = @import("hat.zig").Hat; - -const internal_debug = @import("internal_debug.zig"); - -const Joystick = @This(); - -/// The GLFW joystick ID. -jid: Id, - -/// Joystick IDs. -/// -/// See glfw.Joystick.setCallback for how these are used. -pub const Id = enum(c_int) { - one = c.GLFW_JOYSTICK_1, - two = c.GLFW_JOYSTICK_2, - three = c.GLFW_JOYSTICK_3, - four = c.GLFW_JOYSTICK_4, - five = c.GLFW_JOYSTICK_5, - six = c.GLFW_JOYSTICK_6, - seven = c.GLFW_JOYSTICK_7, - eight = c.GLFW_JOYSTICK_8, - nine = c.GLFW_JOYSTICK_9, - ten = c.GLFW_JOYSTICK_10, - eleven = c.GLFW_JOYSTICK_11, - twelve = c.GLFW_JOYSTICK_12, - thirteen = c.GLFW_JOYSTICK_13, - fourteen = c.GLFW_JOYSTICK_14, - fifteen = c.GLFW_JOYSTICK_15, - sixteen = c.GLFW_JOYSTICK_16, - pub const last = @as(@This(), @enumFromInt(c.GLFW_JOYSTICK_LAST)); -}; - -/// Gamepad input state -/// -/// This describes the input state of a gamepad. -/// -/// see also: gamepad, glfwGetGamepadState -const GamepadState = extern struct { - /// The states of each gamepad button (see gamepad_buttons), `glfw.Action.press` or `glfw.Action.release`. - /// - /// Use the enumeration helper e.g. `.getButton(.dpad_up)` to access these indices. - buttons: [15]u8, - - /// The states of each gamepad axis (see gamepad_axes), in the range -1.0 to 1.0 inclusive. - /// - /// Use the enumeration helper e.g. `.getAxis(.left_x)` to access these indices. - axes: [6]f32, - - /// Returns the state of the specified gamepad button. - pub fn getButton(self: @This(), which: GamepadButton) Action { - return @as(Action, @enumFromInt(self.buttons[@as(u32, @intCast(@intFromEnum(which)))])); - } - - /// Returns the status of the specified gamepad axis, in the range -1.0 to 1.0 inclusive. - pub fn getAxis(self: @This(), which: GamepadAxis) f32 { - return self.axes[@as(u32, @intCast(@intFromEnum(which)))]; - } -}; - -/// Returns whether the specified joystick is present. -/// -/// This function returns whether the specified joystick is present. -/// -/// There is no need to call this function before other functions that accept a joystick ID, as -/// they all check for presence before performing any other work. -/// -/// @return `true` if the joystick is present, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick -pub inline fn present(self: Joystick) bool { - internal_debug.assertInitialized(); - const is_present = c.glfwJoystickPresent(@intFromEnum(self.jid)); - return is_present == c.GLFW_TRUE; -} - -/// Returns the values of all axes of the specified joystick. -/// -/// This function returns the values of all axes of the specified joystick. Each element in the -/// array is a value between -1.0 and 1.0. -/// -/// If the specified joystick is not present this function will return null but will not generate -/// an error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of axis values, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is -/// terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_axis -/// Replaces `glfwGetJoystickPos`. -pub inline fn getAxes(self: Joystick) ?[]const f32 { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const axes = c.glfwGetJoystickAxes(@intFromEnum(self.jid), &count); - if (axes == null) return null; - return axes[0..@as(u32, @intCast(count))]; -} - -/// Returns the state of all buttons of the specified joystick. -/// -/// This function returns the state of all buttons of the specified joystick. Each element in the -/// array is either `glfw.Action.press` or `glfw.Action.release`. -/// -/// For backward compatibility with earlier versions that did not have glfw.Joystick.getHats, the -/// button array also includes all hats, each represented as four buttons. The hats are in the same -/// order as returned by glfw.Joystick.getHats and are in the order _up_, _right_, _down_ and -/// _left_. To disable these extra buttons, set the glfw.joystick_hat_buttons init hint before -/// initialization. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of button states, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_button -pub inline fn getButtons(self: Joystick) ?[]const u8 { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const buttons = c.glfwGetJoystickButtons(@intFromEnum(self.jid), &count); - if (buttons == null) return null; - return buttons[0..@as(u32, @intCast(count))]; -} - -/// Returns the state of all hats of the specified joystick. -/// -/// This function returns the state of all hats of the specified joystick. Each element in the array -/// is one of the following values: -/// -/// | Name | Value | -/// |---------------------------|---------------------------------------------| -/// | `glfw.RawHats.centered` | 0 | -/// | `glfw.RawHats.up` | 1 | -/// | `glfw.RawHats.right` | 2 | -/// | `glfw.RawHats.down` | 4 | -/// | `glfw.RawHats.left` | 8 | -/// | `glfw.RawHats.right_up` | `glfw.RawHats.right` \| `glfw.RawHats.up` | -/// | `glfw.RawHats.right_down` | `glfw.RawHats.right` \| `glfw.RawHats.down` | -/// | `glfw.RawHats.left_up` | `glfw.RawHats.left` \| `glfw.RawHats.up` | -/// | `glfw.RawHats.left_down` | `glfw.RawHats.left` \| `glfw.RawHats.down` | -/// -/// The diagonal directions are bitwise combinations of the primary (up, right, down and left) -/// directions, since the Zig GLFW wrapper returns a packed struct it is trivial to test for these: -/// -/// ``` -/// if (hats.up and hats.right) { -/// // up-right! -/// } -/// ``` -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of hat states, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected, this function is called -/// again for that joystick or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_hat -pub inline fn getHats(self: Joystick) ?[]const Hat { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const hats = c.glfwGetJoystickHats(@intFromEnum(self.jid), &count); - if (hats == null) return null; - const slice = hats[0..@as(u32, @intCast(count))]; - return @as(*const []const Hat, @ptrCast(&slice)).*; -} - -/// Returns the name of the specified joystick. -/// -/// This function returns the name, encoded as UTF-8, of the specified joystick. The returned string -/// is allocated and freed by GLFW. You should not free it yourself. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return The UTF-8 encoded name of the joystick, or null if the joystick is not present or an -/// error occurred. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_name -pub inline fn getName(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = c.glfwGetJoystickName(@intFromEnum(self.jid)); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; -} - -/// Returns the SDL compatible GUID of the specified joystick. -/// -/// This function returns the SDL compatible GUID, as a UTF-8 encoded hexadecimal string, of the -/// specified joystick. The returned string is allocated and freed by GLFW. You should not free it -/// yourself. -/// -/// The GUID is what connects a joystick to a gamepad mapping. A connected joystick will always have -/// a GUID even if there is no gamepad mapping assigned to it. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// The GUID uses the format introduced in SDL 2.0.5. This GUID tries to uniquely identify the make -/// and model of a joystick but does not identify a specific unit, e.g. all wired Xbox 360 -/// controllers will have the same GUID on that platform. The GUID for a unit may vary between -/// platforms depending on what hardware information the platform specific APIs provide. -/// -/// @return The UTF-8 encoded GUID of the joystick, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad -pub inline fn getGUID(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const guid_opt = c.glfwGetJoystickGUID(@intFromEnum(self.jid)); - return if (guid_opt) |guid| - std.mem.span(@as([*:0]const u8, @ptrCast(guid))) - else - null; -} - -/// Sets the user pointer of the specified joystick. -/// -/// This function sets the user-defined pointer of the specified joystick. The current value is -/// retained until the joystick is disconnected. The initial value is null. -/// -/// This function may be called from the joystick callback, even for a joystick that is being disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: joystick_userptr, glfw.Joystick.getUserPointer -pub inline fn setUserPointer(self: Joystick, comptime T: type, pointer: *T) void { - internal_debug.assertInitialized(); - c.glfwSetJoystickUserPointer(@intFromEnum(self.jid), @as(*anyopaque, @ptrCast(pointer))); -} - -/// Returns the user pointer of the specified joystick. -/// -/// This function returns the current value of the user-defined pointer of the specified joystick. -/// The initial value is null. -/// -/// This function may be called from the joystick callback, even for a joystick that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: joystick_userptr, glfw.Joystick.setUserPointer -pub inline fn getUserPointer(self: Joystick, comptime PointerType: type) ?PointerType { - internal_debug.assertInitialized(); - const ptr = c.glfwGetJoystickUserPointer(@intFromEnum(self.jid)); - if (ptr) |p| return @as(PointerType, @ptrCast(@alignCast(p))); - return null; -} - -/// Describes an event relating to a joystick. -pub const Event = enum(c_int) { - /// The device was connected. - connected = c.GLFW_CONNECTED, - - /// The device was disconnected. - disconnected = c.GLFW_DISCONNECTED, -}; - -/// Sets the joystick configuration callback. -/// -/// This function sets the joystick configuration callback, or removes the currently set callback. -/// This is called when a joystick is connected to or disconnected from the system. -/// -/// For joystick connection and disconnection events to be delivered on all platforms, you need to -/// call one of the event processing (see events) functions. Joystick disconnection may also be -/// detected and the callback called by joystick functions. The function will then return whatever -/// it returns if the joystick is not present. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `jid` The joystick that was connected or disconnected. -/// @callback_param `event` One of `.connected` or `.disconnected`. Future releases may add -/// more events. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_event -pub inline fn setCallback(comptime callback: ?fn (joystick: Joystick, event: Event) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - Joystick{ .jid = @as(Joystick.Id, @enumFromInt(jid)) }, - @as(Event, @enumFromInt(event)), - }); - } - }; - - if (c.glfwSetJoystickCallback(CWrapper.joystickCallbackWrapper) != null) return; - } else { - if (c.glfwSetJoystickCallback(null) != null) return; - } -} - -/// Adds the specified SDL_GameControllerDB gamepad mappings. -/// -/// This function parses the specified ASCII encoded string and updates the internal list with any -/// gamepad mappings it finds. This string may contain either a single gamepad mapping or many -/// mappings separated by newlines. The parser supports the full format of the `gamecontrollerdb.txt` -/// source file including empty lines and comments. -/// -/// See gamepad_mapping for a description of the format. -/// -/// If there is already a gamepad mapping for a given GUID in the internal list, it will be -/// replaced by the one passed to this function. If the library is terminated and re-initialized -/// the internal list will revert to the built-in default. -/// -/// @param[in] string The string containing the gamepad mappings. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue. -/// Returns a boolean indicating success. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.isGamepad, glfwGetGamepadName -/// -/// -/// @ingroup input -pub inline fn updateGamepadMappings(gamepad_mappings: [*:0]const u8) bool { - internal_debug.assertInitialized(); - return c.glfwUpdateGamepadMappings(gamepad_mappings) == c.GLFW_TRUE; -} - -/// Returns whether the specified joystick has a gamepad mapping. -/// -/// This function returns whether the specified joystick is both present and has a gamepad mapping. -/// -/// If the specified joystick is present but does not have a gamepad mapping this function will -/// return `false` but will not generate an error. Call glfw.Joystick.present to check if a -/// joystick is present regardless of whether it has a mapping. -/// -/// @return `true` if a joystick is both present and has a gamepad mapping, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Additionally returns false in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.getGamepadState -pub inline fn isGamepad(self: Joystick) bool { - internal_debug.assertInitialized(); - const is_gamepad = c.glfwJoystickIsGamepad(@intFromEnum(self.jid)); - return is_gamepad == c.GLFW_TRUE; -} - -/// Returns the human-readable gamepad name for the specified joystick. -/// -/// This function returns the human-readable name of the gamepad from the gamepad mapping assigned -/// to the specified joystick. -/// -/// If the specified joystick is not present or does not have a gamepad mapping this function will -/// return null, not an error. Call glfw.Joystick.present to check whether it is -/// present regardless of whether it has a mapping. -/// -/// @return The UTF-8 encoded name of the gamepad, or null if the joystick is not present or does -/// not have a mapping. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Additionally returns null in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected, the gamepad mappings are -/// updated or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.isGamepad -pub inline fn getGamepadName(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = c.glfwGetGamepadName(@intFromEnum(self.jid)); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; -} - -/// Retrieves the state of the joystick remapped as a gamepad. -/// -/// This function retrieves the state of the joystick remapped to an Xbox-like gamepad. -/// -/// If the specified joystick is not present or does not have a gamepad mapping this function will -/// return `false`. Call glfw.joystickPresent to check whether it is present regardless of whether -/// it has a mapping. -/// -/// The Guide button may not be available for input as it is often hooked by the system or the -/// Steam client. -/// -/// Not all devices have all the buttons or axes provided by GamepadState. Unavailable buttons -/// and axes will always report `glfw.Action.release` and 0.0 respectively. -/// -/// @param[in] jid The joystick (see joysticks) to query. -/// @param[out] state The gamepad input state of the joystick. -/// @return the gamepad input state if successful, or null if no joystick is connected or it has no -/// gamepad mapping. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Returns null in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.UpdateGamepadMappings, glfw.Joystick.isGamepad -pub inline fn getGamepadState(self: Joystick) ?GamepadState { - internal_debug.assertInitialized(); - var state: GamepadState = undefined; - const success = c.glfwGetGamepadState(@intFromEnum(self.jid), @as(*c.GLFWgamepadstate, @ptrCast(&state))); - return if (success == c.GLFW_TRUE) state else null; -} - -test "present" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.present(); -} - -test "getAxes" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getAxes(); -} - -test "getButtons" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getButtons(); -} - -test "getHats" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - if (joystick.getHats()) |hats| { - for (hats) |hat| { - if (hat.down and hat.up) { - // down-up! - } - } - } -} - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getName(); -} - -test "getGUID" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGUID(); -} - -test "setUserPointer_syntax" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - // Must be called from joystick callback, we cannot test it. - _ = joystick; - _ = setUserPointer; -} - -test "getUserPointer_syntax" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - // Must be called from joystick callback, we cannot test it. - _ = joystick; - _ = getUserPointer; -} - -test "setCallback" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - glfw.Joystick.setCallback((struct { - pub fn callback(joystick: Joystick, event: Event) void { - _ = joystick; - _ = event; - } - }).callback); -} - -test "updateGamepadMappings_syntax" { - // We don't have a gamepad mapping to test with, just confirm the syntax is good. - _ = updateGamepadMappings; -} - -test "isGamepad" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.isGamepad(); -} - -test "getGamepadName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGamepadName(); -} - -test "getGamepadState" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGamepadState(); - _ = (std.mem.zeroes(GamepadState)).getAxis(.left_x); - _ = (std.mem.zeroes(GamepadState)).getButton(.dpad_up); -} diff --git a/pkg/glfw/LICENSE b/pkg/glfw/LICENSE deleted file mode 100644 index 8c422bd23..000000000 --- a/pkg/glfw/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2021 Hexops Contributors (given via the Git commit history). -Copyright (c) 2025 Mitchell Hashimoto, Ghostty contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pkg/glfw/Monitor.zig b/pkg/glfw/Monitor.zig deleted file mode 100644 index 3b194965a..000000000 --- a/pkg/glfw/Monitor.zig +++ /dev/null @@ -1,599 +0,0 @@ -//! Monitor type and related functions - -const std = @import("std"); -const mem = std.mem; -const testing = std.testing; -const c = @import("c.zig").c; - -const GammaRamp = @import("GammaRamp.zig"); -const VideoMode = @import("VideoMode.zig"); - -const internal_debug = @import("internal_debug.zig"); - -const Monitor = @This(); - -handle: *c.GLFWmonitor, - -/// A monitor position, in screen coordinates, of the upper left corner of the monitor on the -/// virtual screen. -const Pos = struct { - /// The x coordinate. - x: u32, - /// The y coordinate. - y: u32, -}; - -/// Returns the position of the monitor's viewport on the virtual screen. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getPos(self: Monitor) Pos { - internal_debug.assertInitialized(); - var xpos: c_int = 0; - var ypos: c_int = 0; - c.glfwGetMonitorPos(self.handle, &xpos, &ypos); - return Pos{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)) }; -} - -/// The monitor workarea, in screen coordinates. -/// -/// This is the position of the upper-left corner of the work area of the monitor, along with the -/// work area size. The work area is defined as the area of the monitor not occluded by the -/// window system task bar where present. If no task bar exists then the work area is the -/// monitor resolution in screen coordinates. -const Workarea = struct { - x: u32, - y: u32, - width: u32, - height: u32, -}; - -/// Retrieves the work area of the monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// A zero value is returned in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_workarea -pub inline fn getWorkarea(self: Monitor) Workarea { - internal_debug.assertInitialized(); - var xpos: c_int = 0; - var ypos: c_int = 0; - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetMonitorWorkarea(self.handle, &xpos, &ypos, &width, &height); - return Workarea{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)), .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -/// The physical size, in millimetres, of the display area of a monitor. -const PhysicalSize = struct { - width_mm: u32, - height_mm: u32, -}; - -/// Returns the physical size of the monitor. -/// -/// Some platforms do not provide accurate monitor size information, either because the monitor -/// [EDID](https://en.wikipedia.org/wiki/Extended_display_identification_data) -/// data is incorrect or because the driver does not report it accurately. -/// -/// win32: On Windows 8 and earlier the physical size is calculated from -/// the current resolution and system DPI instead of querying the monitor EDID data -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getPhysicalSize(self: Monitor) PhysicalSize { - internal_debug.assertInitialized(); - var width_mm: c_int = 0; - var height_mm: c_int = 0; - c.glfwGetMonitorPhysicalSize(self.handle, &width_mm, &height_mm); - return PhysicalSize{ .width_mm = @as(u32, @intCast(width_mm)), .height_mm = @as(u32, @intCast(height_mm)) }; -} - -/// The content scale for a monitor. -/// -/// This is the ratio between the current DPI and the platform's default DPI. This is especially -/// important for text and any UI elements. If the pixel dimensions of your UI scaled by this look -/// appropriate on your machine then it should appear at a reasonable size on other machines -/// regardless of their DPI and scaling settings. This relies on the system DPI and scaling -/// settings being somewhat correct. -/// -/// The content scale may depend on both the monitor resolution and pixel density and on users -/// settings. It may be very different from the raw DPI calculated from the physical size and -/// current resolution. -const ContentScale = struct { - x_scale: f32, - y_scale: f32, -}; - -/// Returns the content scale for the monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// A zero value is returned in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_scale, glfw.Window.getContentScale -pub inline fn getContentScale(self: Monitor) ContentScale { - internal_debug.assertInitialized(); - var x_scale: f32 = 0; - var y_scale: f32 = 0; - c.glfwGetMonitorContentScale(self.handle, &x_scale, &y_scale); - return ContentScale{ .x_scale = @as(f32, @floatCast(x_scale)), .y_scale = @as(f32, @floatCast(y_scale)) }; -} - -/// Returns the name of the specified monitor. -/// -/// This function returns a human-readable name, encoded as UTF-8, of the specified monitor. The -/// name typically reflects the make and model of the monitor and is not guaranteed to be unique -/// among the connected monitors. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified monitor is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getName(self: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (c.glfwGetMonitorName(self.handle)) |name| return @as([*:0]const u8, @ptrCast(name)); - // `glfwGetMonitorName` returns `null` only for errors, but the only error is unreachable - // (NotInitialized) - unreachable; -} - -/// Sets the user pointer of the specified monitor. -/// -/// This function sets the user-defined pointer of the specified monitor. The current value is -/// retained until the monitor is disconnected. -/// -/// This function may be called from the monitor callback, even for a monitor that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: monitor_userptr, glfw.Monitor.getUserPointer -pub inline fn setUserPointer(self: Monitor, comptime T: type, ptr: *T) void { - internal_debug.assertInitialized(); - c.glfwSetMonitorUserPointer(self.handle, ptr); -} - -/// Returns the user pointer of the specified monitor. -/// -/// This function returns the current value of the user-defined pointer of the specified monitor. -/// -/// This function may be called from the monitor callback, even for a monitor that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: monitor_userptr, glfw.Monitor.setUserPointer -pub inline fn getUserPointer(self: Monitor, comptime T: type) ?*T { - internal_debug.assertInitialized(); - const ptr = c.glfwGetMonitorUserPointer(self.handle); - if (ptr == null) return null; - return @as(*T, @ptrCast(@alignCast(ptr.?))); -} - -/// Returns the available video modes for the specified monitor. -/// -/// This function returns an array of all video modes supported by the monitor. The returned slice -/// is sorted in ascending order, first by color bit depth (the sum of all channel depths) and -/// then by resolution area (the product of width and height), then resolution width and finally -/// by refresh rate. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// Returns null in the event of an error. -/// -/// The returned slice memory is owned by the caller. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_modes, glfw.Monitor.getVideoMode -/// -/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and -/// emits glfw.ErrorCode.FeatureUnavailable -/// -/// TODO(glfw): rewrite this to not require any allocation. -pub inline fn getVideoModes(self: Monitor, allocator: mem.Allocator) mem.Allocator.Error!?[]VideoMode { - internal_debug.assertInitialized(); - var count: c_int = 0; - if (c.glfwGetVideoModes(self.handle, &count)) |modes| { - const slice = try allocator.alloc(VideoMode, @as(u32, @intCast(count))); - var i: u32 = 0; - while (i < count) : (i += 1) { - slice[i] = VideoMode{ .handle = @as([*c]const c.GLFWvidmode, @ptrCast(modes))[i] }; - } - return slice; - } - return null; -} - -/// Returns the current mode of the specified monitor. -/// -/// This function returns the current video mode of the specified monitor. If you have created a -/// full screen window for that monitor, the return value will depend on whether that window is -/// iconified. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// Additionally returns null in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and will thus never be implemented and emits glfw.ErrorCode.FeatureUnavailable -/// -/// see also: monitor_modes, glfw.Monitor.getVideoModes -pub inline fn getVideoMode(self: Monitor) ?VideoMode { - internal_debug.assertInitialized(); - if (c.glfwGetVideoMode(self.handle)) |mode| return VideoMode{ .handle = mode.* }; - return null; -} - -/// Generates a gamma ramp and sets it for the specified monitor. -/// -/// This function generates an appropriately sized gamma ramp from the specified exponent and then -/// calls glfw.Monitor.setGammaRamp with it. The value must be a finite number greater than zero. -/// -/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction, -/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly -/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior. -/// -/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and -/// emits glfw.ErrorCode.FeatureUnavailable -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn setGamma(self: Monitor, gamma: f32) void { - internal_debug.assertInitialized(); - - std.debug.assert(!std.math.isNan(gamma)); - std.debug.assert(gamma >= 0); - std.debug.assert(gamma <= std.math.f32_max); - - c.glfwSetGamma(self.handle, gamma); -} - -/// Returns the current gamma ramp for the specified monitor. -/// -/// This function returns the current gamma ramp of the specified monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns null in the event of an error. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and returns glfw.ErrorCode.FeatureUnavailable. -/// -/// The returned gamma ramp is `.owned = true` by GLFW, and is valid until the monitor is -/// disconnected, this function is called again, or `glfw.terminate()` is called. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn getGammaRamp(self: Monitor) ?GammaRamp { - internal_debug.assertInitialized(); - if (c.glfwGetGammaRamp(self.handle)) |ramp| return .fromC(ramp.*); - return null; -} - -/// Sets the current gamma ramp for the specified monitor. -/// -/// This function sets the current gamma ramp for the specified monitor. The original gamma ramp -/// for that monitor is saved by GLFW the first time this function is called and is restored by -/// `glfw.terminate()`. -/// -/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction, -/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly -/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior. -/// -/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// The size of the specified gamma ramp should match the size of the current ramp for that -/// monitor. On win32, the gamma ramp size must be 256. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and returns glfw.ErrorCode.FeatureUnavailable. -/// -/// @pointer_lifetime The specified gamma ramp is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn setGammaRamp(self: Monitor, ramp: GammaRamp) void { - internal_debug.assertInitialized(); - c.glfwSetGammaRamp(self.handle, &ramp.toC()); -} - -/// Returns the currently connected monitors. -/// -/// This function returns a slice of all currently connected monitors. The primary monitor is -/// always first. If no monitors were found, this function returns an empty slice. -/// -/// The returned slice memory is owned by the caller. The underlying handles are owned by GLFW, and -/// are valid until the monitor configuration changes or `glfw.terminate` is called. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_monitors, monitor_event, glfw.monitor.getPrimary -pub inline fn getAll(allocator: mem.Allocator) mem.Allocator.Error![]Monitor { - internal_debug.assertInitialized(); - var count: c_int = 0; - if (c.glfwGetMonitors(&count)) |monitors| { - const slice = try allocator.alloc(Monitor, @as(u32, @intCast(count))); - var i: u32 = 0; - while (i < count) : (i += 1) { - slice[i] = Monitor{ .handle = @as([*c]const ?*c.GLFWmonitor, @ptrCast(monitors))[i].? }; - } - return slice; - } - // `glfwGetMonitors` returning null can be either an error or no monitors, but the only error is - // unreachable (NotInitialized) - return &[_]Monitor{}; -} - -/// Returns the primary monitor. -/// -/// This function returns the primary monitor. This is usually the monitor where elements like -/// the task bar or global menu bar are located. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_monitors, glfw.monitors.getAll -pub inline fn getPrimary() ?Monitor { - internal_debug.assertInitialized(); - if (c.glfwGetPrimaryMonitor()) |handle| return Monitor{ .handle = handle }; - return null; -} - -/// Describes an event relating to a monitor. -pub const Event = enum(c_int) { - /// The device was connected. - connected = c.GLFW_CONNECTED, - - /// The device was disconnected. - disconnected = c.GLFW_DISCONNECTED, -}; - -/// Sets the monitor configuration callback. -/// -/// This function sets the monitor configuration callback, or removes the currently set callback. -/// This is called when a monitor is connected to or disconnected from the system. Example: -/// -/// ``` -/// fn monitorCallback(monitor: glfw.Monitor, event: glfw.Monitor.Event, data: *MyData) void { -/// // data is the pointer you passed into setCallback. -/// // event is one of .connected or .disconnected -/// } -/// ... -/// glfw.Monitor.setCallback(MyData, &myData, monitorCallback) -/// ``` -/// -/// `event` may be one of .connected or .disconnected. More events may be added in the future. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_event -pub inline fn setCallback(comptime callback: ?fn (monitor: Monitor, event: Event) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - Monitor{ .handle = monitor.? }, - @as(Event, @enumFromInt(event)), - }); - } - }; - - if (c.glfwSetMonitorCallback(CWrapper.monitorCallbackWrapper) != null) return; - } else { - if (c.glfwSetMonitorCallback(null) != null) return; - } -} - -test "getAll" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const allocator = testing.allocator; - const monitors = try getAll(allocator); - defer allocator.free(monitors); -} - -test "getPrimary" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = getPrimary(); -} - -test "getPos" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getPos(); - } -} - -test "getWorkarea" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getWorkarea(); - } -} - -test "getPhysicalSize" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getPhysicalSize(); - } -} - -test "getContentScale" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getContentScale(); - } -} - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getName(); - } -} - -test "userPointer" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - var p = m.getUserPointer(u32); - try testing.expect(p == null); - var x: u32 = 5; - m.setUserPointer(u32, &x); - p = m.getUserPointer(u32); - try testing.expectEqual(p.?.*, 5); - } -} - -test "setCallback" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - setCallback(struct { - fn callback(monitor: Monitor, event: Event) void { - _ = monitor; - _ = event; - } - }.callback); -} - -test "getVideoModes" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - const allocator = testing.allocator; - const modes_maybe = try m.getVideoModes(allocator); - if (modes_maybe) |modes| { - defer allocator.free(modes); - } - } -} - -test "getVideoMode" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getVideoMode(); - } -} - -test "set_getGammaRamp" { - const allocator = testing.allocator; - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - if (m.getGammaRamp()) |ramp| { - // Set it to the exact same value; if we do otherwise an our tests fail it wouldn't call - // terminate and our made-up gamma ramp would get stuck. - m.setGammaRamp(ramp); - - // technically not needed here / noop because GLFW owns this gamma ramp. - defer ramp.deinit(allocator); - } - } -} diff --git a/pkg/glfw/VideoMode.zig b/pkg/glfw/VideoMode.zig deleted file mode 100644 index f433b8d05..000000000 --- a/pkg/glfw/VideoMode.zig +++ /dev/null @@ -1,50 +0,0 @@ -//! Monitor video modes and related functions -//! -//! see also: glfw.Monitor.getVideoMode - -const std = @import("std"); -const c = @import("c.zig").c; - -const VideoMode = @This(); - -handle: c.GLFWvidmode, - -/// Returns the width of the video mode, in screen coordinates. -pub inline fn getWidth(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.width)); -} - -/// Returns the height of the video mode, in screen coordinates. -pub inline fn getHeight(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.height)); -} - -/// Returns the bit depth of the red channel of the video mode. -pub inline fn getRedBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.redBits)); -} - -/// Returns the bit depth of the green channel of the video mode. -pub inline fn getGreenBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.greenBits)); -} - -/// Returns the bit depth of the blue channel of the video mode. -pub inline fn getBlueBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.blueBits)); -} - -/// Returns the refresh rate of the video mode, in Hz. -pub inline fn getRefreshRate(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.refreshRate)); -} - -test "getters" { - const x = std.mem.zeroes(VideoMode); - _ = x.getWidth(); - _ = x.getHeight(); - _ = x.getRedBits(); - _ = x.getGreenBits(); - _ = x.getBlueBits(); - _ = x.getRefreshRate(); -} diff --git a/pkg/glfw/Window.zig b/pkg/glfw/Window.zig deleted file mode 100644 index 804184f0e..000000000 --- a/pkg/glfw/Window.zig +++ /dev/null @@ -1,3551 +0,0 @@ -//! Window type and related functions - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const glfw = @import("main.zig"); -const Image = @import("Image.zig"); -const Monitor = @import("Monitor.zig"); -const Cursor = @import("Cursor.zig"); -const Key = @import("key.zig").Key; -const Action = @import("action.zig").Action; -const Mods = @import("mod.zig").Mods; -const MouseButton = @import("mouse_button.zig").MouseButton; - -const internal_debug = @import("internal_debug.zig"); - -const Window = @This(); - -handle: *c.GLFWwindow, - -/// Returns a Zig GLFW window from an underlying C GLFW window handle. -pub inline fn from(handle: *anyopaque) Window { - return Window{ .handle = @as(*c.GLFWwindow, @ptrCast(@alignCast(handle))) }; -} - -/// Resets all window hints to their default values. -/// -/// This function resets all window hints to their default values. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hints, glfw.Window.hint, glfw.Window.hintString -pub inline fn defaultHints() void { - internal_debug.assertInitialized(); - c.glfwDefaultWindowHints(); -} - -/// Window hints -const Hint = enum(c_int) { - resizable = c.GLFW_RESIZABLE, - visible = c.GLFW_VISIBLE, - decorated = c.GLFW_DECORATED, - focused = c.GLFW_FOCUSED, - auto_iconify = c.GLFW_AUTO_ICONIFY, - floating = c.GLFW_FLOATING, - maximized = c.GLFW_MAXIMIZED, - center_cursor = c.GLFW_CENTER_CURSOR, - transparent_framebuffer = c.GLFW_TRANSPARENT_FRAMEBUFFER, - focus_on_show = c.GLFW_FOCUS_ON_SHOW, - mouse_passthrough = c.GLFW_MOUSE_PASSTHROUGH, - position_x = c.GLFW_POSITION_X, - position_y = c.GLFW_POSITION_Y, - scale_to_monitor = c.GLFW_SCALE_TO_MONITOR, - - /// Framebuffer hints - red_bits = c.GLFW_RED_BITS, - green_bits = c.GLFW_GREEN_BITS, - blue_bits = c.GLFW_BLUE_BITS, - alpha_bits = c.GLFW_ALPHA_BITS, - depth_bits = c.GLFW_DEPTH_BITS, - stencil_bits = c.GLFW_STENCIL_BITS, - accum_red_bits = c.GLFW_ACCUM_RED_BITS, - accum_green_bits = c.GLFW_ACCUM_GREEN_BITS, - accum_blue_bits = c.GLFW_ACCUM_BLUE_BITS, - accum_alpha_bits = c.GLFW_ACCUM_ALPHA_BITS, - aux_buffers = c.GLFW_AUX_BUFFERS, - - /// Framebuffer MSAA samples - samples = c.GLFW_SAMPLES, - - /// Monitor refresh rate - refresh_rate = c.GLFW_REFRESH_RATE, - - /// OpenGL stereoscopic rendering - stereo = c.GLFW_STEREO, - - /// Framebuffer sRGB - srgb_capable = c.GLFW_SRGB_CAPABLE, - - /// Framebuffer double buffering - doublebuffer = c.GLFW_DOUBLEBUFFER, - - client_api = c.GLFW_CLIENT_API, - context_creation_api = c.GLFW_CONTEXT_CREATION_API, - - context_version_major = c.GLFW_CONTEXT_VERSION_MAJOR, - context_version_minor = c.GLFW_CONTEXT_VERSION_MINOR, - - context_robustness = c.GLFW_CONTEXT_ROBUSTNESS, - context_release_behavior = c.GLFW_CONTEXT_RELEASE_BEHAVIOR, - context_no_error = c.GLFW_CONTEXT_NO_ERROR, - // NOTE: This supersedes opengl_debug_context / GLFW_OPENGL_DEBUG_CONTEXT - context_debug = c.GLFW_CONTEXT_DEBUG, - - opengl_forward_compat = c.GLFW_OPENGL_FORWARD_COMPAT, - opengl_profile = c.GLFW_OPENGL_PROFILE, - - /// macOS specific - cocoa_retina_framebuffer = c.GLFW_COCOA_RETINA_FRAMEBUFFER, - - /// macOS specific - cocoa_frame_name = c.GLFW_COCOA_FRAME_NAME, - - /// macOS specific - cocoa_graphics_switching = c.GLFW_COCOA_GRAPHICS_SWITCHING, - - /// X11 specific - x11_class_name = c.GLFW_X11_CLASS_NAME, - - /// X11 specific - x11_instance_name = c.GLFW_X11_INSTANCE_NAME, - - /// Windows specific - win32_keyboard_menu = c.GLFW_WIN32_KEYBOARD_MENU, - - /// Allows specification of the Wayland app_id. - wayland_app_id = c.GLFW_WAYLAND_APP_ID, -}; - -/// Window hints -pub const Hints = struct { - // Note: The defaults here are directly from the GLFW source of the glfwDefaultWindowHints function - resizable: bool = true, - visible: bool = true, - decorated: bool = true, - focused: bool = true, - auto_iconify: bool = true, - floating: bool = false, - maximized: bool = false, - center_cursor: bool = true, - transparent_framebuffer: bool = false, - focus_on_show: bool = true, - mouse_passthrough: bool = false, - position_x: c_int = @intFromEnum(Position.any), - position_y: c_int = @intFromEnum(Position.any), - - scale_to_monitor: bool = false, - - /// Framebuffer hints - red_bits: ?PositiveCInt = 8, - green_bits: ?PositiveCInt = 8, - blue_bits: ?PositiveCInt = 8, - alpha_bits: ?PositiveCInt = 8, - depth_bits: ?PositiveCInt = 24, - stencil_bits: ?PositiveCInt = 8, - accum_red_bits: ?PositiveCInt = 0, - accum_green_bits: ?PositiveCInt = 0, - accum_blue_bits: ?PositiveCInt = 0, - accum_alpha_bits: ?PositiveCInt = 0, - aux_buffers: ?PositiveCInt = 0, - - /// Framebuffer MSAA samples - samples: ?PositiveCInt = 0, - - /// Monitor refresh rate - refresh_rate: ?PositiveCInt = null, - - /// OpenGL stereoscopic rendering - stereo: bool = false, - - /// Framebuffer sRGB - srgb_capable: bool = false, - - /// Framebuffer double buffering - doublebuffer: bool = true, - - client_api: ClientAPI = .opengl_api, - context_creation_api: ContextCreationAPI = .native_context_api, - - context_version_major: c_int = 1, - context_version_minor: c_int = 0, - - context_robustness: ContextRobustness = .no_robustness, - context_release_behavior: ContextReleaseBehavior = .any_release_behavior, - - /// Note: disables the context creating errors, - /// instead turning them into undefined behavior. - context_no_error: bool = false, - context_debug: bool = false, - - opengl_forward_compat: bool = false, - - opengl_profile: OpenGLProfile = .opengl_any_profile, - - /// macOS specific - cocoa_retina_framebuffer: bool = true, - - /// macOS specific - cocoa_frame_name: [:0]const u8 = "", - - /// macOS specific - cocoa_graphics_switching: bool = false, - - /// X11 specific - x11_class_name: [:0]const u8 = "", - - /// X11 specific - x11_instance_name: [:0]const u8 = "", - - /// Windows specific - win32_keyboard_menu: bool = false, - - /// Allows specification of the Wayland app_id. - wayland_app_id: [:0]const u8 = "", - - pub const PositiveCInt = std.math.IntFittingRange(0, std.math.maxInt(c_int)); - - pub const ClientAPI = enum(c_int) { - opengl_api = c.GLFW_OPENGL_API, - opengl_es_api = c.GLFW_OPENGL_ES_API, - no_api = c.GLFW_NO_API, - }; - - pub const ContextCreationAPI = enum(c_int) { - native_context_api = c.GLFW_NATIVE_CONTEXT_API, - egl_context_api = c.GLFW_EGL_CONTEXT_API, - osmesa_context_api = c.GLFW_OSMESA_CONTEXT_API, - }; - - pub const ContextRobustness = enum(c_int) { - no_robustness = c.GLFW_NO_ROBUSTNESS, - no_reset_notification = c.GLFW_NO_RESET_NOTIFICATION, - lose_context_on_reset = c.GLFW_LOSE_CONTEXT_ON_RESET, - }; - - pub const ContextReleaseBehavior = enum(c_int) { - any_release_behavior = c.GLFW_ANY_RELEASE_BEHAVIOR, - release_behavior_flush = c.GLFW_RELEASE_BEHAVIOR_FLUSH, - release_behavior_none = c.GLFW_RELEASE_BEHAVIOR_NONE, - }; - - pub const OpenGLProfile = enum(c_int) { - opengl_any_profile = c.GLFW_OPENGL_ANY_PROFILE, - opengl_compat_profile = c.GLFW_OPENGL_COMPAT_PROFILE, - opengl_core_profile = c.GLFW_OPENGL_CORE_PROFILE, - }; - - pub const Position = enum(c_int) { - /// By default, newly created windows use the placement recommended by the window system, - /// - /// To create the window at a specific position, make it initially invisible using the - /// Window.Hint.visible hint, set its Window.Hint.position and then Window.hide() it. - /// - /// To create the window at a specific position, set the Window.Hint.position_x and - /// Window.Hint.position_y hints before creation. To restore the default behavior, set - /// either or both hints back to Window.Hints.Position.any - any = @bitCast(c.GLFW_ANY_POSITION), - }; - - fn set(hints: Hints) void { - internal_debug.assertInitialized(); - inline for (comptime std.meta.fieldNames(Hint)) |field_name| { - const hint_tag = @intFromEnum(@field(Hint, field_name)); - const hint_value = @field(hints, field_name); - switch (@TypeOf(hint_value)) { - bool => c.glfwWindowHint(hint_tag, @intFromBool(hint_value)), - ?PositiveCInt => c.glfwWindowHint(hint_tag, if (hint_value) |unwrapped| unwrapped else glfw.dont_care), - c_int => c.glfwWindowHint(hint_tag, hint_value), - - ClientAPI, - ContextCreationAPI, - ContextRobustness, - ContextReleaseBehavior, - OpenGLProfile, - Position, - => c.glfwWindowHint(hint_tag, @intFromEnum(hint_value)), - - [:0]const u8 => c.glfwWindowHintString(hint_tag, hint_value.ptr), - - else => unreachable, - } - } - } -}; - -/// Creates a window and its associated context. -/// -/// This function creates a window and its associated OpenGL or OpenGL ES context. Most of the -/// options controlling how the window and its context should be created are specified with window -/// hints using `glfw.Window.hint`. -/// -/// Successful creation does not change which context is current. Before you can use the newly -/// created context, you need to make it current using `glfw.makeContextCurrent`. For -/// information about the `share` parameter, see context_sharing. -/// -/// The created window, framebuffer and context may differ from what you requested, as not all -/// parameters and hints are hard constraints. This includes the size of the window, especially for -/// full screen windows. To query the actual attributes of the created window, framebuffer and -/// context, see glfw.Window.getAttrib, glfw.Window.getSize and glfw.window.getFramebufferSize. -/// -/// To create a full screen window, you need to specify the monitor the window will cover. If no -/// monitor is specified, the window will be windowed mode. Unless you have a way for the user to -/// choose a specific monitor, it is recommended that you pick the primary monitor. For more -/// information on how to query connected monitors, see @ref monitor_monitors. -/// -/// For full screen windows, the specified size becomes the resolution of the window's _desired -/// video mode_. As long as a full screen window is not iconified, the supported video mode most -/// closely matching the desired video mode is set for the specified monitor. For more information -/// about full screen windows, including the creation of so called _windowed full screen_ or -/// _borderless full screen_ windows, see window_windowed_full_screen. -/// -/// Once you have created the window, you can switch it between windowed and full screen mode with -/// glfw.Window.setMonitor. This will not affect its OpenGL or OpenGL ES context. -/// -/// By default, newly created windows use the placement recommended by the window system. To create -/// the window at a specific position, make it initially invisible using the `visible` window -/// hint, set its position and then show it. -/// -/// As long as at least one full screen window is not iconified, the screensaver is prohibited from -/// starting. -/// -/// Window systems put limits on window sizes. Very large or very small window dimensions may be -/// overridden by the window system on creation. Check the actual size after creation. -/// -/// The swap interval is not set during window creation and the initial value may vary depending on -/// driver settings and defaults. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum, glfw.ErrorCode.InvalidValue, -/// glfw.ErrorCode.APIUnavailable, glfw.ErrorCode.VersionUnavailable, glfw.ErrorCode.FormatUnavailable and -/// glfw.ErrorCode.PlatformError. -/// Returns null in the event of an error. -/// -/// Parameters are as follows: -/// -/// * `width` The desired width, in screen coordinates, of the window. -/// * `height` The desired height, in screen coordinates, of the window. -/// * `title` The initial, UTF-8 encoded window title. -/// * `monitor` The monitor to use for full screen mode, or `null` for windowed mode. -/// * `share` The window whose context to share resources with, or `null` to not share resources. -/// -/// win32: Window creation will fail if the Microsoft GDI software OpenGL implementation is the -/// only one available. -/// -/// win32: If the executable has an icon resource named `GLFW_ICON`, it will be set as the initial -/// icon for the window. If no such icon is present, the `IDI_APPLICATION` icon will be used -/// instead. To set a different icon, see glfw.Window.setIcon. -/// -/// win32: The context to share resources with must not be current on any other thread. -/// -/// macos: The OS only supports forward-compatible core profile contexts for OpenGL versions 3.2 -/// and later. Before creating an OpenGL context of version 3.2 or later you must set the -/// `glfw.opengl_forward_compat` and `glfw.opengl_profile` hints accordingly. OpenGL 3.0 and 3.1 -/// contexts are not supported at all on macOS. -/// -/// macos: The OS only supports core profile contexts for OpenGL versions 3.2 and later. Before -/// creating an OpenGL context of version 3.2 or later you must set the `glfw.opengl_profile` hint -/// accordingly. OpenGL 3.0 and 3.1 contexts are not supported at all on macOS. -/// -/// macos: The GLFW window has no icon, as it is not a document window, but the dock icon will be -/// the same as the application bundle's icon. For more information on bundles, see the -/// [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) -/// in the Mac Developer Library. -/// -/// macos: On OS X 10.10 and later the window frame will not be rendered at full resolution on -/// Retina displays unless the glfw.cocoa_retina_framebuffer hint is true (1) and the `NSHighResolutionCapable` -/// key is enabled in the application bundle's `Info.plist`. For more information, see -/// [High Resolution Guidelines for OS X](https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html) -/// in the Mac Developer Library. The GLFW test and example programs use a custom `Info.plist` -/// template for this, which can be found as `CMake/Info.plist.in` in the source tree. -/// -/// macos: When activating frame autosaving with glfw.cocoa_frame_name, the specified window size -/// and position may be overridden by previously saved values. -/// -/// x11: Some window managers will not respect the placement of initially hidden windows. -/// -/// x11: Due to the asynchronous nature of X11, it may take a moment for a window to reach its -/// requested state. This means you may not be able to query the final size, position or other -/// attributes directly after window creation. -/// -/// x11: The class part of the `WM_CLASS` window property will by default be set to the window title -/// passed to this function. The instance part will use the contents of the `RESOURCE_NAME` -/// environment variable, if present and not empty, or fall back to the window title. Set the glfw.x11_class_name -/// and glfw.x11_instance_name window hints to override this. -/// -/// wayland: Compositors should implement the xdg-decoration protocol for GLFW to decorate the -/// window properly. If this protocol isn't supported, or if the compositor prefers client-side -/// decorations, a very simple fallback frame will be drawn using the wp_viewporter protocol. A -/// compositor can still emit close, maximize or fullscreen events, using for instance a keybind -/// mechanism. If neither of these protocols is supported, the window won't be decorated. -/// -/// wayland: A full screen window will not attempt to change the mode, no matter what the -/// requested size or refresh rate. -/// -/// wayland: Screensaver inhibition requires the idle-inhibit protocol to be implemented in the -/// user's compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_creation, glfw.Window.destroy -pub inline fn create( - width: u32, - height: u32, - title: [*:0]const u8, - monitor: ?Monitor, - share: ?Window, - hints: Hints, -) ?Window { - internal_debug.assertInitialized(); - const ignore_hints_struct = if (comptime @import("builtin").is_test) testing_ignore_window_hints_struct else false; - if (!ignore_hints_struct) hints.set(); - - if (c.glfwCreateWindow( - @as(c_int, @intCast(width)), - @as(c_int, @intCast(height)), - &title[0], - if (monitor) |m| m.handle else null, - if (share) |w| w.handle else null, - )) |handle| return from(handle); - return null; -} - -var testing_ignore_window_hints_struct = if (@import("builtin").is_test) false else @as(void, {}); - -/// Destroys the specified window and its context. -/// -/// This function destroys the specified window and its context. On calling this function, no -/// further callbacks will be called for that window. -/// -/// If the context of the specified window is current on the main thread, it is detached before -/// being destroyed. -/// -/// note: The context of the specified window must not be current on any other thread when this -/// function is called. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_creation, glfw.Window.create -pub inline fn destroy(self: Window) void { - internal_debug.assertInitialized(); - c.glfwDestroyWindow(self.handle); -} - -/// Checks the close flag of the specified window. -/// -/// This function returns the value of the close flag of the specified window. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_close -pub inline fn shouldClose(self: Window) bool { - internal_debug.assertInitialized(); - return c.glfwWindowShouldClose(self.handle) == c.GLFW_TRUE; -} - -/// Sets the close flag of the specified window. -/// -/// This function sets the value of the close flag of the specified window. This can be used to -/// override the user's attempt to close the window, or to signal that it should be closed. -/// -/// @thread_safety This function may be called from any thread. Access is not -/// synchronized. -/// -/// see also: window_close -pub inline fn setShouldClose(self: Window, value: bool) void { - internal_debug.assertInitialized(); - const boolean = if (value) c.GLFW_TRUE else c.GLFW_FALSE; - c.glfwSetWindowShouldClose(self.handle, boolean); -} - -/// Sets the UTF-8 encoded title of the specified window. -/// -/// This function sets the window title, encoded as UTF-8, of the specified window. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// macos: The window title will not be updated until the next time you process events. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_title -pub inline fn setTitle(self: Window, title: [*:0]const u8) void { - internal_debug.assertInitialized(); - c.glfwSetWindowTitle(self.handle, title); -} - -/// Sets the icon for the specified window. -/// -/// This function sets the icon of the specified window. If passed an array of candidate images, -/// those of or closest to the sizes desired by the system are selected. If no images are -/// specified, the window reverts to its default icon. -/// -/// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight bits per channel with -/// the red channel first. They are arranged canonically as packed sequential rows, starting from -/// the top-left corner. -/// -/// The desired image sizes varies depending on platform and system settings. The selected images -/// will be rescaled as needed. Good sizes include 16x16, 32x32 and 48x48. -/// -/// @pointer_lifetime The specified image data is copied before this function returns. -/// -/// macos: Regular windows do not have icons on macOS. This function will emit FeatureUnavailable. -/// The dock icon will be the same as the application bundle's icon. For more information on -/// bundles, see the [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) -/// in the Mac Developer Library. -/// -/// wayland: There is no existing protocol to change an icon, the window will thus inherit the one -/// defined in the application's desktop file. This function will emit glfw.ErrorCode.FeatureUnavailable. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue, glfw.ErrorCode.FeatureUnavailable -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_icon -pub inline fn setIcon(self: Window, allocator: mem.Allocator, images: ?[]const Image) mem.Allocator.Error!void { - internal_debug.assertInitialized(); - if (images) |im| { - const tmp = try allocator.alloc(c.GLFWimage, im.len); - defer allocator.free(tmp); - for (im, 0..) |img, index| tmp[index] = img.toC(); - c.glfwSetWindowIcon(self.handle, @as(c_int, @intCast(im.len)), &tmp[0]); - } else c.glfwSetWindowIcon(self.handle, 0, null); -} - -pub const Pos = struct { - x: i64, - y: i64, -}; - -/// Retrieves the position of the content area of the specified window. -/// -/// This function retrieves the position, in screen coordinates, of the upper-left corner of the -/// content area of the specified window. -/// -/// Possible errors include glfw.ErrorCode.FeatureUnavailable. -/// Additionally returns a zero value in the event of an error. -/// -/// wayland: There is no way for an application to retrieve the global position of its windows, -/// this function will always emit glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos glfw.Window.setPos -pub inline fn getPos(self: Window) Pos { - internal_debug.assertInitialized(); - var x: c_int = 0; - var y: c_int = 0; - c.glfwGetWindowPos(self.handle, &x, &y); - return Pos{ .x = @as(i64, @intCast(x)), .y = @as(i64, @intCast(y)) }; -} - -/// Sets the position of the content area of the specified window. -/// -/// This function sets the position, in screen coordinates, of the upper-left corner of the content -/// area of the specified windowed mode window. If the window is a full screen window, this -/// function does nothing. -/// -/// __Do not use this function__ to move an already visible window unless you have very good -/// reasons for doing so, as it will confuse and annoy the user. -/// -/// The window manager may put limits on what positions are allowed. GLFW cannot and should not -/// override these limits. -/// -/// Possible errors include glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: There is no way for an application to set the global position of its windows, this -/// function will always emit glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos, glfw.Window.getPos -pub inline fn setPos(self: Window, pos: Pos) void { - internal_debug.assertInitialized(); - c.glfwSetWindowPos(self.handle, @as(c_int, @intCast(pos.x)), @as(c_int, @intCast(pos.y))); -} - -pub const Size = struct { - width: u32, - height: u32, -}; - -/// Retrieves the size of the content area of the specified window. -/// -/// This function retrieves the size, in screen coordinates, of the content area of the specified -/// window. If you wish to retrieve the size of the framebuffer of the window in pixels, see -/// glfw.Window.getFramebufferSize. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size, glfw.Window.setSize -pub inline fn getSize(self: Window) Size { - internal_debug.assertInitialized(); - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetWindowSize(self.handle, &width, &height); - return Size{ .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -/// Sets the size of the content area of the specified window. -/// -/// This function sets the size, in screen coordinates, of the content area of the specified window. -/// -/// For full screen windows, this function updates the resolution of its desired video mode and -/// switches to the video mode closest to it, without affecting the window's context. As the -/// context is unaffected, the bit depths of the framebuffer remain unchanged. -/// -/// If you wish to update the refresh rate of the desired video mode in addition to its resolution, -/// see glfw.Window.setMonitor. -/// -/// The window manager may put limits on what sizes are allowed. GLFW cannot and should not -/// override these limits. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// wayland: A full screen window will not attempt to change the mode, no matter what the requested -/// size. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size, glfw.Window.getSize, glfw.Window.SetMonitor -pub inline fn setSize(self: Window, size: Size) void { - internal_debug.assertInitialized(); - c.glfwSetWindowSize(self.handle, @as(c_int, @intCast(size.width)), @as(c_int, @intCast(size.height))); -} - -/// A size with option width/height, used to represent e.g. constraints on a windows size while -/// allowing specific axis to be unconstrained (null) if desired. -pub const SizeOptional = struct { - width: ?u32 = null, - height: ?u32 = null, -}; - -/// Sets the size limits of the specified window's content area. -/// -/// This function sets the size limits of the content area of the specified window. If the window -/// is full screen, the size limits only take effect/ once it is made windowed. If the window is not -/// resizable, this function does nothing. -/// -/// The size limits are applied immediately to a windowed mode window and may cause it to be resized. -/// -/// The maximum dimensions must be greater than or equal to the minimum dimensions. glfw.dont_care -/// may be used for any width/height parameter. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// If you set size limits and an aspect ratio that conflict, the results are undefined. -/// -/// wayland: The size limits will not be applied until the window is actually resized, either by -/// the user or by the compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_sizelimits, glfw.Window.setAspectRatio -pub inline fn setSizeLimits(self: Window, min: SizeOptional, max: SizeOptional) void { - internal_debug.assertInitialized(); - - if (min.width != null and max.width != null) { - std.debug.assert(min.width.? <= max.width.?); - } - if (min.height != null and max.height != null) { - std.debug.assert(min.height.? <= max.height.?); - } - - c.glfwSetWindowSizeLimits( - self.handle, - if (min.width) |min_width| @as(c_int, @intCast(min_width)) else glfw.dont_care, - if (min.height) |min_height| @as(c_int, @intCast(min_height)) else glfw.dont_care, - if (max.width) |max_width| @as(c_int, @intCast(max_width)) else glfw.dont_care, - if (max.height) |max_height| @as(c_int, @intCast(max_height)) else glfw.dont_care, - ); -} - -/// Sets the aspect ratio of the specified window. -/// -/// This function sets the required aspect ratio of the content area of the specified window. If -/// the window is full screen, the aspect ratio only takes effect once it is made windowed. If the -/// window is not resizable, this function does nothing. -/// -/// The aspect ratio is specified as a numerator and a denominator and both values must be greater -/// than zero. For example, the common 16:9 aspect ratio is specified as 16 and 9, respectively. -/// -/// If the numerator AND denominator is set to `glfw.dont_care` then the aspect ratio limit is -/// disabled. -/// -/// The aspect ratio is applied immediately to a windowed mode window and may cause it to be -/// resized. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// If you set size limits and an aspect ratio that conflict, the results are undefined. -/// -/// wayland: The aspect ratio will not be applied until the window is actually resized, either by -/// the user or by the compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_sizelimits, glfw.Window.setSizeLimits -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnimplemented -pub inline fn setAspectRatio(self: Window, numerator: ?u32, denominator: ?u32) void { - internal_debug.assertInitialized(); - - if (numerator != null and denominator != null) { - std.debug.assert(numerator.? > 0); - std.debug.assert(denominator.? > 0); - } - - c.glfwSetWindowAspectRatio( - self.handle, - if (numerator) |numerator_unwrapped| @as(c_int, @intCast(numerator_unwrapped)) else glfw.dont_care, - if (denominator) |denominator_unwrapped| @as(c_int, @intCast(denominator_unwrapped)) else glfw.dont_care, - ); -} - -/// Retrieves the size of the framebuffer of the specified window. -/// -/// This function retrieves the size, in pixels, of the framebuffer of the specified window. If you -/// wish to retrieve the size of the window in screen coordinates, see @ref glfwGetWindowSize. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_fbsize, glfwWindow.setFramebufferSizeCallback -pub inline fn getFramebufferSize(self: Window) Size { - internal_debug.assertInitialized(); - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetFramebufferSize(self.handle, &width, &height); - return Size{ .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -pub const FrameSize = struct { - left: u32, - top: u32, - right: u32, - bottom: u32, -}; - -/// Retrieves the size of the frame of the window. -/// -/// This function retrieves the size, in screen coordinates, of each edge of the frame of the -/// specified window. This size includes the title bar, if the window has one. The size of the -/// frame may vary depending on the window-related hints used to create it. -/// -/// Because this function retrieves the size of each window frame edge and not the offset along a -/// particular coordinate axis, the retrieved values will always be zero or positive. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size -pub inline fn getFrameSize(self: Window) FrameSize { - internal_debug.assertInitialized(); - var left: c_int = 0; - var top: c_int = 0; - var right: c_int = 0; - var bottom: c_int = 0; - c.glfwGetWindowFrameSize(self.handle, &left, &top, &right, &bottom); - return FrameSize{ - .left = @as(u32, @intCast(left)), - .top = @as(u32, @intCast(top)), - .right = @as(u32, @intCast(right)), - .bottom = @as(u32, @intCast(bottom)), - }; -} - -pub const ContentScale = struct { - x_scale: f32, - y_scale: f32, -}; - -/// Retrieves the content scale for the specified window. -/// -/// This function retrieves the content scale for the specified window. The content scale is the -/// ratio between the current DPI and the platform's default DPI. This is especially important for -/// text and any UI elements. If the pixel dimensions of your UI scaled by this look appropriate on -/// your machine then it should appear at a reasonable size on other machines regardless of their -/// DPI and scaling settings. This relies on the system DPI and scaling settings being somewhat -/// correct. -/// -/// On platforms where each monitors can have its own content scale, the window content scale will -/// depend on which monitor the system considers the window to be on. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_scale, glfwSetWindowContentScaleCallback, glfwGetMonitorContentScale -pub inline fn getContentScale(self: Window) ContentScale { - internal_debug.assertInitialized(); - var x_scale: f32 = 0; - var y_scale: f32 = 0; - c.glfwGetWindowContentScale(self.handle, &x_scale, &y_scale); - return ContentScale{ .x_scale = x_scale, .y_scale = y_scale }; -} - -/// Returns the opacity of the whole window. -/// -/// This function returns the opacity of the window, including any decorations. -/// -/// The opacity (or alpha) value is a positive finite number between zero and one, where zero is -/// fully transparent and one is fully opaque. If the system does not support whole window -/// transparency, this function always returns one. -/// -/// The initial opacity value for newly created windows is one. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_transparency, glfw.Window.setOpacity -pub inline fn getOpacity(self: Window) f32 { - internal_debug.assertInitialized(); - const opacity = c.glfwGetWindowOpacity(self.handle); - return opacity; -} - -/// Sets the opacity of the whole window. -/// -/// This function sets the opacity of the window, including any decorations. -/// -/// The opacity (or alpha) value is a positive finite number between zero and one, where zero is -/// fully transparent and one is fully opaque. -/// -/// The initial opacity value for newly created windows is one. -/// -/// A window created with framebuffer transparency may not use whole window transparency. The -/// results of doing this are undefined. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_transparency, glfw.Window.getOpacity -pub inline fn setOpacity(self: Window, opacity: f32) void { - internal_debug.assertInitialized(); - c.glfwSetWindowOpacity(self.handle, opacity); -} - -/// Iconifies the specified window. -/// -/// This function iconifies (minimizes) the specified window if it was previously restored. If the -/// window is already iconified, this function does nothing. -/// -/// If the specified window is a full screen window, GLFW restores the original video mode of the -/// monitor. The window's desired video mode is set again when the window is restored. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// wayland: Once a window is iconified, glfw.Window.restorebe able to restore it. This is a design -/// decision of the xdg-shell protocol. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.restore, glfw.Window.maximize -pub inline fn iconify(self: Window) void { - internal_debug.assertInitialized(); - c.glfwIconifyWindow(self.handle); -} - -/// Restores the specified window. -/// -/// This function restores the specified window if it was previously iconified (minimized) or -/// maximized. If the window is already restored, this function does nothing. -/// -/// If the specified window is an iconified full screen window, its desired video mode is set -/// again for its monitor when the window is restored. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.iconify, glfw.Window.maximize -pub inline fn restore(self: Window) void { - internal_debug.assertInitialized(); - c.glfwRestoreWindow(self.handle); -} - -/// Maximizes the specified window. -/// -/// This function maximizes the specified window if it was previously not maximized. If the window -/// is already maximized, this function does nothing. -/// -/// If the specified window is a full screen window, this function does nothing. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.iconify, glfw.Window.restore -pub inline fn maximize(self: Window) void { - internal_debug.assertInitialized(); - c.glfwMaximizeWindow(self.handle); -} - -/// Makes the specified window visible. -/// -/// This function makes the specified window visible if it was previously hidden. If the window is -/// already visible or is in full screen mode, this function does nothing. -/// -/// By default, windowed mode windows are focused when shown Set the glfw.focus_on_show window hint -/// to change this behavior for all newly created windows, or change the -/// behavior for an existing window with glfw.Window.setAttrib. -/// -/// wayland: Because Wayland wants every frame of the desktop to be complete, this function does -/// not immediately make the window visible. Instead it will become visible the next time the window -/// framebuffer is updated after this call. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hide, glfw.Window.hide -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnavailable -pub inline fn show(self: Window) void { - internal_debug.assertInitialized(); - c.glfwShowWindow(self.handle); -} - -/// Hides the specified window. -/// -/// This function hides the specified window if it was previously visible. If the window is already -/// hidden or is in full screen mode, this function does nothing. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hide, glfw.Window.show -pub inline fn hide(self: Window) void { - internal_debug.assertInitialized(); - c.glfwHideWindow(self.handle); -} - -/// Brings the specified window to front and sets input focus. -/// -/// This function brings the specified window to front and sets input focus. The window should -/// already be visible and not iconified. -/// -/// By default, both windowed and full screen mode windows are focused when initially created. Set -/// the glfw.focused to disable this behavior. -/// -/// Also by default, windowed mode windows are focused when shown with glfw.Window.show. Set the -/// glfw.focus_on_show to disable this behavior. -/// -/// __Do not use this function__ to steal focus from other applications unless you are certain that -/// is what the user wants. Focus stealing can be extremely disruptive. -/// -/// For a less disruptive way of getting the user's attention, see [attention requests (window_attention). -/// -/// wayland It is not possible for an application to set the input focus. This function will emit -/// glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_focus, window_attention -pub inline fn focus(self: Window) void { - internal_debug.assertInitialized(); - c.glfwFocusWindow(self.handle); -} - -/// Requests user attention to the specified window. -/// -/// This function requests user attention to the specified window. On platforms where this is not -/// supported, attention is requested to the application as a whole. -/// -/// Once the user has given attention, usually by focusing the window or application, the system will end the request automatically. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// macos: Attention is requested to the application as a whole, not the -/// specific window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_attention -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnimplemented -pub inline fn requestAttention(self: Window) void { - internal_debug.assertInitialized(); - c.glfwRequestWindowAttention(self.handle); -} - -/// Swaps the front and back buffers of the specified window. -/// -/// This function swaps the front and back buffers of the specified window when rendering with -/// OpenGL or OpenGL ES. If the swap interval is greater than zero, the GPU driver waits the -/// specified number of screen updates before swapping the buffers. -/// -/// The specified window must have an OpenGL or OpenGL ES context. Specifying a window without a -/// context will generate glfw.ErrorCode.NoWindowContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see `vkQueuePresentKHR` -/// instead. -/// -/// @param[in] window The window whose buffers to swap. -/// -/// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. -/// -/// __EGL:__ The context of the specified window must be current on the calling thread. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: buffer_swap, glfwSwapInterval -pub inline fn swapBuffers(self: Window) void { - internal_debug.assertInitialized(); - c.glfwSwapBuffers(self.handle); -} - -/// Returns the monitor that the window uses for full screen mode. -/// -/// This function returns the handle of the monitor that the specified window is in full screen on. -/// -/// @return The monitor, or null if the window is in windowed mode. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_monitor, glfw.Window.setMonitor -pub inline fn getMonitor(self: Window) ?Monitor { - internal_debug.assertInitialized(); - if (c.glfwGetWindowMonitor(self.handle)) |monitor| return Monitor{ .handle = monitor }; - return null; -} - -/// Sets the mode, monitor, video mode and placement of a window. -/// -/// This function sets the monitor that the window uses for full screen mode or, if the monitor is -/// null, makes it windowed mode. -/// -/// When setting a monitor, this function updates the width, height and refresh rate of the desired -/// video mode and switches to the video mode closest to it. The window position is ignored when -/// setting a monitor. -/// -/// When the monitor is null, the position, width and height are used to place the window content -/// area. The refresh rate is ignored when no monitor is specified. -/// -/// If you only wish to update the resolution of a full screen window or the size of a windowed -/// mode window, see @ref glfwSetWindowSize. -/// -/// When a window transitions from full screen to windowed mode, this function restores any -/// previous window settings such as whether it is decorated, floating, resizable, has size or -/// aspect ratio limits, etc. -/// -/// @param[in] window The window whose monitor, size or video mode to set. -/// @param[in] monitor The desired monitor, or null to set windowed mode. -/// @param[in] xpos The desired x-coordinate of the upper-left corner of the content area. -/// @param[in] ypos The desired y-coordinate of the upper-left corner of the content area. -/// @param[in] width The desired with, in screen coordinates, of the content area or video mode. -/// @param[in] height The desired height, in screen coordinates, of the content area or video mode. -/// @param[in] refreshRate The desired refresh rate, in Hz, of the video mode, or `glfw.dont_care`. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// The OpenGL or OpenGL ES context will not be destroyed or otherwise affected by any resizing or -/// mode switching, although you may need to update your viewport if the framebuffer size has -/// changed. -/// -/// wayland: The desired window position is ignored, as there is no way for an application to set -/// this property. -/// -/// wayland: Setting the window to full screen will not attempt to change the mode, no matter what -/// the requested size or refresh rate. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_monitor, window_full_screen, glfw.Window.getMonitor, glfw.Window.setSize -pub inline fn setMonitor(self: Window, monitor: ?Monitor, xpos: i32, ypos: i32, width: u32, height: u32, refresh_rate: ?u32) void { - internal_debug.assertInitialized(); - c.glfwSetWindowMonitor( - self.handle, - if (monitor) |m| m.handle else null, - @as(c_int, @intCast(xpos)), - @as(c_int, @intCast(ypos)), - @as(c_int, @intCast(width)), - @as(c_int, @intCast(height)), - if (refresh_rate) |refresh_rate_unwrapped| @as(c_int, @intCast(refresh_rate_unwrapped)) else glfw.dont_care, - ); -} - -/// Window attributes -pub const Attrib = enum(c_int) { - iconified = c.GLFW_ICONIFIED, - resizable = c.GLFW_RESIZABLE, - visible = c.GLFW_VISIBLE, - decorated = c.GLFW_DECORATED, - focused = c.GLFW_FOCUSED, - auto_iconify = c.GLFW_AUTO_ICONIFY, - floating = c.GLFW_FLOATING, - maximized = c.GLFW_MAXIMIZED, - transparent_framebuffer = c.GLFW_TRANSPARENT_FRAMEBUFFER, - hovered = c.GLFW_HOVERED, - focus_on_show = c.GLFW_FOCUS_ON_SHOW, - mouse_passthrough = c.GLFW_MOUSE_PASSTHROUGH, - doublebuffer = c.GLFW_DOUBLEBUFFER, - - client_api = c.GLFW_CLIENT_API, - context_creation_api = c.GLFW_CONTEXT_CREATION_API, - context_version_major = c.GLFW_CONTEXT_VERSION_MAJOR, - context_version_minor = c.GLFW_CONTEXT_VERSION_MINOR, - context_revision = c.GLFW_CONTEXT_REVISION, - - context_robustness = c.GLFW_CONTEXT_ROBUSTNESS, - context_release_behavior = c.GLFW_CONTEXT_RELEASE_BEHAVIOR, - context_no_error = c.GLFW_CONTEXT_NO_ERROR, - context_debug = c.GLFW_CONTEXT_DEBUG, - - opengl_forward_compat = c.GLFW_OPENGL_FORWARD_COMPAT, - opengl_profile = c.GLFW_OPENGL_PROFILE, -}; - -/// Returns an attribute of the specified window. -/// -/// This function returns the value of an attribute of the specified window or its OpenGL or OpenGL -/// ES context. -/// -/// @param[in] attrib The window attribute (see window_attribs) whose value to return. -/// @return The value of the attribute, or zero if an error occurred. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// Framebuffer related hints are not window attributes. See window_attribs_fb for more information. -/// -/// Zero is a valid value for many window and context related attributes so you cannot use a return -/// value of zero as an indication of errors. However, this function should not fail as long as it -/// is passed valid arguments and the library has been initialized. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// wayland: The Wayland protocol provides no way to check whether a window is iconified, so -/// glfw.Window.Attrib.iconified always returns `false`. -/// -/// see also: window_attribs, glfw.Window.setAttrib -pub inline fn getAttrib(self: Window, attrib: Attrib) i32 { - internal_debug.assertInitialized(); - return c.glfwGetWindowAttrib(self.handle, @intFromEnum(attrib)); -} - -/// Sets an attribute of the specified window. -/// -/// This function sets the value of an attribute of the specified window. -/// -/// The supported attributes are glfw.decorated, glfw.resizable, glfw.floating, glfw.auto_iconify, -/// glfw.focus_on_show. -/// -/// Some of these attributes are ignored for full screen windows. The new value will take effect -/// if the window is later made windowed. -/// -/// Some of these attributes are ignored for windowed mode windows. The new value will take effect -/// if the window is later made full screen. -/// -/// @param[in] attrib A supported window attribute. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum, glfw.ErrorCode.InvalidValue, -/// glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable -/// -/// Calling glfw.Window.getAttrib will always return the latest -/// value, even if that value is ignored by the current mode of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_attribs, glfw.Window.getAttrib -/// -pub inline fn setAttrib(self: Window, attrib: Attrib, value: bool) void { - internal_debug.assertInitialized(); - std.debug.assert(switch (attrib) { - .decorated, - .resizable, - .floating, - .auto_iconify, - .focus_on_show, - .mouse_passthrough, - .doublebuffer, - => true, - else => false, - }); - c.glfwSetWindowAttrib(self.handle, @intFromEnum(attrib), if (value) c.GLFW_TRUE else c.GLFW_FALSE); -} - -/// Sets the user pointer of the specified window. -/// -/// This function sets the user-defined pointer of the specified window. The current value is -/// retained until the window is destroyed. The initial value is null. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_userptr, glfw.Window.getUserPointer -pub inline fn setUserPointer(self: Window, pointer: ?*anyopaque) void { - internal_debug.assertInitialized(); - c.glfwSetWindowUserPointer(self.handle, pointer); -} - -/// Returns the user pointer of the specified window. -/// -/// This function returns the current value of the user-defined pointer of the specified window. -/// The initial value is null. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_userptr, glfw.Window.setUserPointer -pub inline fn getUserPointer(self: Window, comptime T: type) ?*T { - internal_debug.assertInitialized(); - if (c.glfwGetWindowUserPointer(self.handle)) |user_pointer| return @as(?*T, @ptrCast(@alignCast(user_pointer))); - return null; -} - -/// Sets the position callback for the specified window. -/// -/// This function sets the position callback of the specified window, which is called when the -/// window is moved. The callback is provided with the position, in screen coordinates, of the -/// upper-left corner of the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `window` the window that moved. -/// @callback_param `xpos` the new x-coordinate, in screen coordinates, of the upper-left corner of -/// the content area of the window. -/// @callback_param `ypos` the new y-coordinate, in screen coordinates, of the upper-left corner of -/// the content area of the window. -/// -/// wayland: This callback will never be called, as there is no way for an application to know its -/// global position. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos -pub inline fn setPosCallback(self: Window, comptime callback: ?fn (window: Window, xpos: i32, ypos: i32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(i32, @intCast(xpos)), - @as(i32, @intCast(ypos)), - }); - } - }; - - if (c.glfwSetWindowPosCallback(self.handle, CWrapper.posCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowPosCallback(self.handle, null) != null) return; - } -} - -/// Sets the size callback for the specified window. -/// -/// This function sets the size callback of the specified window, which is called when the window -/// is resized. The callback is provided with the size, in screen coordinates, of the content area -/// of the window. -/// -/// @callback_param `window` the window that was resized. -/// @callback_param `width` the new width, in screen coordinates, of the window. -/// @callback_param `height` the new height, in screen coordinates, of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size -pub inline fn setSizeCallback(self: Window, comptime callback: ?fn (window: Window, width: i32, height: i32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(i32, @intCast(width)), - @as(i32, @intCast(height)), - }); - } - }; - - if (c.glfwSetWindowSizeCallback(self.handle, CWrapper.sizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowSizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the close callback for the specified window. -/// -/// This function sets the close callback of the specified window, which is called when the user -/// attempts to close the window, for example by clicking the close widget in the title bar. -/// -/// The close flag is set before this callback is called, but you can modify it at any time with -/// glfw.Window.setShouldClose. -/// -/// The close callback is not triggered by glfw.Window.destroy. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `window` the window that the user attempted to close. -/// -/// macos: Selecting Quit from the application menu will trigger the close callback for all -/// windows. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_close -pub inline fn setCloseCallback(self: Window, comptime callback: ?fn (window: Window) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - }); - } - }; - - if (c.glfwSetWindowCloseCallback(self.handle, CWrapper.closeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowCloseCallback(self.handle, null) != null) return; - } -} - -/// Sets the refresh callback for the specified window. -/// -/// This function sets the refresh callback of the specified window, which is -/// called when the content area of the window needs to be redrawn, for example -/// if the window has been exposed after having been covered by another window. -/// -/// On compositing window systems such as Aero, Compiz, Aqua or Wayland, where -/// the window contents are saved off-screen, this callback may be called only -/// very infrequently or never at all. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose content needs to be refreshed. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_refresh -pub inline fn setRefreshCallback(self: Window, comptime callback: ?fn (window: Window) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - }); - } - }; - - if (c.glfwSetWindowRefreshCallback(self.handle, CWrapper.refreshCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowRefreshCallback(self.handle, null) != null) return; - } -} - -/// Sets the focus callback for the specified window. -/// -/// This function sets the focus callback of the specified window, which is -/// called when the window gains or loses input focus. -/// -/// After the focus callback is called for a window that lost input focus, -/// synthetic key and mouse button release events will be generated for all such -/// that had been pressed. For more information, see @ref glfwSetKeyCallback -/// and @ref glfwSetMouseButtonCallback. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose input focus has changed. -/// @callback_param `focused` `true` if the window was given input focus, or `false` if it lost it. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_focus -pub inline fn setFocusCallback(self: Window, comptime callback: ?fn (window: Window, focused: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - focused == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowFocusCallback(self.handle, CWrapper.focusCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowFocusCallback(self.handle, null) != null) return; - } -} - -/// Sets the iconify callback for the specified window. -/// -/// This function sets the iconification callback of the specified window, which -/// is called when the window is iconified or restored. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window which was iconified or restored. -/// @callback_param `iconified` `true` if the window was iconified, or `false` if it was restored. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify -pub inline fn setIconifyCallback(self: Window, comptime callback: ?fn (window: Window, iconified: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - iconified == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowIconifyCallback(self.handle, CWrapper.iconifyCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowIconifyCallback(self.handle, null) != null) return; - } -} - -/// Sets the maximize callback for the specified window. -/// -/// This function sets the maximization callback of the specified window, which -/// is called when the window is maximized or restored. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window which was maximized or restored. -/// @callback_param `maximized` `true` if the window was maximized, or `false` if it was restored. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_maximize -// GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, GLFWwindowmaximizefun callback); -pub inline fn setMaximizeCallback(self: Window, comptime callback: ?fn (window: Window, maximized: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - maximized == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowMaximizeCallback(self.handle, CWrapper.maximizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowMaximizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the framebuffer resize callback for the specified window. -/// -/// This function sets the framebuffer resize callback of the specified window, -/// which is called when the framebuffer of the specified window is resized. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose framebuffer was resized. -/// @callback_param `width` the new width, in pixels, of the framebuffer. -/// @callback_param `height` the new height, in pixels, of the framebuffer. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_fbsize -pub inline fn setFramebufferSizeCallback(self: Window, comptime callback: ?fn (window: Window, width: u32, height: u32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(u32, @intCast(width)), - @as(u32, @intCast(height)), - }); - } - }; - - if (c.glfwSetFramebufferSizeCallback(self.handle, CWrapper.framebufferSizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetFramebufferSizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the window content scale callback for the specified window. -/// -/// This function sets the window content scale callback of the specified window, -/// which is called when the content scale of the specified window changes. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose content scale changed. -/// @callback_param `xscale` the new x-axis content scale of the window. -/// @callback_param `yscale` the new y-axis content scale of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_scale, glfw.Window.getContentScale -pub inline fn setContentScaleCallback(self: Window, comptime callback: ?fn (window: Window, xscale: f32, yscale: f32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xscale, - yscale, - }); - } - }; - - if (c.glfwSetWindowContentScaleCallback(self.handle, CWrapper.windowScaleCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowContentScaleCallback(self.handle, null) != null) return; - } -} - -pub const InputMode = enum(c_int) { - cursor = c.GLFW_CURSOR, - sticky_keys = c.GLFW_STICKY_KEYS, - sticky_mouse_buttons = c.GLFW_STICKY_MOUSE_BUTTONS, - lock_key_mods = c.GLFW_LOCK_KEY_MODS, - raw_mouse_motion = c.GLFW_RAW_MOUSE_MOTION, -}; - -/// A cursor input mode to be supplied to `glfw.Window.setInputModeCursor` -pub const InputModeCursor = enum(c_int) { - /// Makes the cursor visible and behaving normally. - normal = c.GLFW_CURSOR_NORMAL, - - /// Makes the cursor invisible when it is over the content area of the window but does not - /// restrict it from leaving. - hidden = c.GLFW_CURSOR_HIDDEN, - - /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This is useful - /// for implementing for example 3D camera controls. - disabled = c.GLFW_CURSOR_DISABLED, - - /// Makes the cursor visible but confines it to the content area of the window. - captured = c.GLFW_CURSOR_CAPTURED, -}; - -/// Sets the input mode of the cursor, whether it should behave normally, be hidden, or grabbed. -pub inline fn setInputModeCursor(self: Window, value: InputModeCursor) void { - return self.setInputMode(InputMode.cursor, value); -} - -/// Gets the current input mode of the cursor. -pub inline fn getInputModeCursor(self: Window) InputModeCursor { - return @as(InputModeCursor, @enumFromInt(self.getInputMode(InputMode.cursor))); -} - -/// Sets the input mode of sticky keys, if enabled a key press will ensure that `glfw.Window.getKey` -/// return `.press` the next time it is called even if the key had been released before the call. -/// -/// This is useful when you are only interested in whether keys have been pressed but not when or -/// in which order. -pub inline fn setInputModeStickyKeys(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.sticky_keys, enabled); -} - -/// Tells if the sticky keys input mode is enabled. -pub inline fn getInputModeStickyKeys(self: Window) bool { - return self.getInputMode(InputMode.sticky_keys) == 1; -} - -/// Sets the input mode of sticky mouse buttons, if enabled a mouse button press will ensure that -/// `glfw.Window.getMouseButton` return `.press` the next time it is called even if the button had -/// been released before the call. -/// -/// This is useful when you are only interested in whether buttons have been pressed but not when -/// or in which order. -pub inline fn setInputModeStickyMouseButtons(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.sticky_mouse_buttons, enabled); -} - -/// Tells if the sticky mouse buttons input mode is enabled. -pub inline fn getInputModeStickyMouseButtons(self: Window) bool { - return self.getInputMode(InputMode.sticky_mouse_buttons) == 1; -} - -/// Sets the input mode of locking key modifiers, if enabled callbacks that receive modifier bits -/// will also have the glfw.mod.caps_lock bit set when the event was generated with Caps Lock on, -/// and the glfw.mod.num_lock bit when Num Lock was on. -pub inline fn setInputModeLockKeyMods(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.lock_key_mods, enabled); -} - -/// Tells if the locking key modifiers input mode is enabled. -pub inline fn getInputModeLockKeyMods(self: Window) bool { - return self.getInputMode(InputMode.lock_key_mods) == 1; -} - -/// Sets whether the raw mouse motion input mode is enabled, if enabled unscaled and unaccelerated -/// mouse motion events will be sent, otherwise standard mouse motion events respecting the user's -/// OS settings will be sent. -/// -/// If raw motion is not supported, attempting to set this will emit glfw.ErrorCode.FeatureUnavailable. -/// Call glfw.rawMouseMotionSupported to check for support. -pub inline fn setInputModeRawMouseMotion(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.raw_mouse_motion, enabled); -} - -/// Tells if the raw mouse motion input mode is enabled. -pub inline fn getInputModeRawMouseMotion(self: Window) bool { - return self.getInputMode(InputMode.raw_mouse_motion) == 1; -} - -/// Returns the value of an input option for the specified window. -/// -/// Consider using one of the following variants instead, if applicable, as they'll give you a -/// typed return value: -/// -/// * `glfw.Window.getInputModeCursor` -/// * `glfw.Window.getInputModeStickyKeys` -/// * `glfw.Window.getInputModeStickyMouseButtons` -/// * `glfw.Window.getInputModeLockKeyMods` -/// * `glfw.Window.getInputModeRawMouseMotion` -/// -/// This function returns the value of an input option for the specified window. The mode must be -/// one of the `glfw.Window.InputMode` enumerations. -/// -/// Boolean values, such as for `glfw.Window.InputMode.raw_mouse_motion`, are returned as integers. -/// You may convert to a boolean using `== 1`. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: glfw.Window.setInputMode -pub inline fn getInputMode(self: Window, mode: InputMode) i32 { - internal_debug.assertInitialized(); - const value = c.glfwGetInputMode(self.handle, @intFromEnum(mode)); - return @as(i32, @intCast(value)); -} - -/// Sets an input option for the specified window. -/// -/// Consider using one of the following variants instead, if applicable, as they'll guide you to -/// the right input value via enumerations: -/// -/// * `glfw.Window.setInputModeCursor` -/// * `glfw.Window.setInputModeStickyKeys` -/// * `glfw.Window.setInputModeStickyMouseButtons` -/// * `glfw.Window.setInputModeLockKeyMods` -/// * `glfw.Window.setInputModeRawMouseMotion` -/// -/// @param[in] mode One of the `glfw.Window.InputMode` enumerations. -/// @param[in] value The new value of the specified input mode. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: glfw.Window.getInputMode -pub inline fn setInputMode(self: Window, mode: InputMode, value: anytype) void { - internal_debug.assertInitialized(); - const T = @TypeOf(value); - std.debug.assert(switch (mode) { - .cursor => switch (@import("shims.zig").typeInfo(T)) { - .@"enum" => T == InputModeCursor, - .enum_literal => @hasField(InputModeCursor, @tagName(value)), - else => false, - }, - .sticky_keys => T == bool, - .sticky_mouse_buttons => T == bool, - .lock_key_mods => T == bool, - .raw_mouse_motion => T == bool, - }); - const int_value: c_int = switch (@import("shims.zig").typeInfo(T)) { - .@"enum", - .enum_literal, - => @intFromEnum(@as(InputModeCursor, value)), - else => @intFromBool(value), - }; - c.glfwSetInputMode(self.handle, @intFromEnum(mode), int_value); -} - -/// Returns the last reported press state of a keyboard key for the specified window. -/// -/// This function returns the last press state reported for the specified key to the specified -/// window. The returned state is one of `true` (pressed) or `false` (released). -/// -/// * `glfw.Action.repeat` is only reported to the key callback. -/// -/// If the `glfw.sticky_keys` input mode is enabled, this function returns `glfw.Action.press` the -/// first time you call it for a key that was pressed, even if that key has already been released. -/// -/// The key functions deal with physical keys, with key tokens (see keys) named after their use on -/// the standard US keyboard layout. If you want to input text, use the Unicode character callback -/// instead. -/// -/// The modifier key bit masks (see mods) are not key tokens and cannot be used with this function. -/// -/// __Do not use this function__ to implement text input, use glfw.Window.setCharCallback instead. -/// -/// @param[in] window The desired window. -/// @param[in] key The desired keyboard key (see keys). `glfw.key.unknown` is not a valid key for -/// this function. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_key -pub inline fn getKey(self: Window, key: Key) Action { - internal_debug.assertInitialized(); - const state = c.glfwGetKey(self.handle, @intFromEnum(key)); - return @as(Action, @enumFromInt(state)); -} - -/// Returns the last reported state of a mouse button for the specified window. -/// -/// This function returns whether the specified mouse button is pressed or not. -/// -/// If the glfw.sticky_mouse_buttons input mode is enabled, this function returns `true` the first -/// time you call it for a mouse button that was pressed, even if that mouse button has already been -/// released. -/// -/// @param[in] button The desired mouse button. -/// @return One of `true` (if pressed) or `false` (if released) -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_mouse_button -pub inline fn getMouseButton(self: Window, button: MouseButton) Action { - internal_debug.assertInitialized(); - const state = c.glfwGetMouseButton(self.handle, @intFromEnum(button)); - return @as(Action, @enumFromInt(state)); -} - -pub const CursorPos = struct { - xpos: f64, - ypos: f64, -}; - -/// Retrieves the position of the cursor relative to the content area of the window. -/// -/// This function returns the position of the cursor, in screen coordinates, relative to the -/// upper-left corner of the content area of the specified window. -/// -/// If the cursor is disabled (with `glfw.cursor_disabled`) then the cursor position is unbounded -/// and limited only by the minimum and maximum values of a `f64`. -/// -/// The coordinate can be converted to their integer equivalents with the `floor` function. Casting -/// directly to an integer type works for positive coordinates, but fails for negative ones. -/// -/// Any or all of the position arguments may be null. If an error occurs, all non-null position -/// arguments will be set to zero. -/// -/// @param[in] window The desired window. -/// @param[out] xpos Where to store the cursor x-coordinate, relative to the left edge of the -/// content area, or null. -/// @param[out] ypos Where to store the cursor y-coordinate, relative to the to top edge of the -/// content area, or null. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos, glfw.Window.setCursorPos -pub inline fn getCursorPos(self: Window) CursorPos { - internal_debug.assertInitialized(); - var pos: CursorPos = undefined; - c.glfwGetCursorPos(self.handle, &pos.xpos, &pos.ypos); - return pos; -} - -/// Sets the position of the cursor, relative to the content area of the window. -/// -/// This function sets the position, in screen coordinates, of the cursor relative to the upper-left -/// corner of the content area of the specified window. The window must have input focus. If the -/// window does not have input focus when this function is called, it fails silently. -/// -/// __Do not use this function__ to implement things like camera controls. GLFW already provides the -/// `glfw.cursor_disabled` cursor mode that hides the cursor, transparently re-centers it and -/// provides unconstrained cursor motion. See glfw.Window.setInputMode for more information. -/// -/// If the cursor mode is `glfw.cursor_disabled` then the cursor position is unconstrained and -/// limited only by the minimum and maximum values of a `double`. -/// -/// @param[in] window The desired window. -/// @param[in] xpos The desired x-coordinate, relative to the left edge of the content area. -/// @param[in] ypos The desired y-coordinate, relative to the top edge of the content area. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: This function will only work when the cursor mode is `glfw.cursor_disabled`, otherwise -/// it will do nothing. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos, glfw.Window.getCursorPos -pub inline fn setCursorPos(self: Window, xpos: f64, ypos: f64) void { - internal_debug.assertInitialized(); - c.glfwSetCursorPos(self.handle, xpos, ypos); -} - -/// Sets the cursor for the window. -/// -/// This function sets the cursor image to be used when the cursor is over the content area of the -/// specified window. The set cursor will only be visible when the cursor mode (see cursor_mode) of -/// the window is `glfw.Cursor.normal`. -/// -/// On some platforms, the set cursor may not be visible unless the window also has input focus. -/// -/// @param[in] cursor The cursor to set, or null to switch back to the default arrow cursor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object -pub inline fn setCursor(self: Window, cursor: ?Cursor) void { - internal_debug.assertInitialized(); - c.glfwSetCursor(self.handle, if (cursor) |cs| cs.ptr else null); -} - -/// Sets the key callback. -/// -/// This function sets the key callback of the specified window, which is called when a key is -/// pressed, repeated or released. -/// -/// The key functions deal with physical keys, with layout independent key tokens (see keys) named -/// after their values in the standard US keyboard layout. If you want to input text, use the -/// character callback (see glfw.Window.setCharCallback) instead. -/// -/// When a window loses input focus, it will generate synthetic key release events for all pressed -/// keys. You can tell these events from user-generated events by the fact that the synthetic ones -/// are generated after the focus loss event has been processed, i.e. after the window focus -/// callback (see glfw.Window.setFocusCallback) has been called. -/// -/// The scancode of a key is specific to that platform or sometimes even to that machine. Scancodes -/// are intended to allow users to bind keys that don't have a GLFW key token. Such keys have `key` -/// set to `glfw.key.unknown`, their state is not saved and so it cannot be queried with -/// glfw.Window.getKey. -/// -/// Sometimes GLFW needs to generate synthetic key events, in which case the scancode may be zero. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new key callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] key The keyboard key (see keys) that was pressed or released. -/// @callback_param[in] scancode The platform-specific scancode of the key. -/// @callback_param[in] action `glfw.Action.press`, `glfw.Action.release` or `glfw.Action.repeat`. -/// Future releases may add more actions. -/// @callback_param[in] mods Bit field describing which modifier keys (see mods) were held down. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_key -pub inline fn setKeyCallback(self: Window, comptime callback: ?fn (window: Window, key: Key, scancode: i32, action: Action, mods: Mods) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(Key, @enumFromInt(key)), - @as(i32, @intCast(scancode)), - @as(Action, @enumFromInt(action)), - Mods.fromInt(mods), - }); - } - }; - - if (c.glfwSetKeyCallback(self.handle, CWrapper.keyCallbackWrapper) != null) return; - } else { - if (c.glfwSetKeyCallback(self.handle, null) != null) return; - } -} - -/// Sets the Unicode character callback. -/// -/// This function sets the character callback of the specified window, which is called when a -/// Unicode character is input. -/// -/// The character callback is intended for Unicode text input. As it deals with characters, it is -/// keyboard layout dependent, whereas the key callback (see glfw.Window.setKeyCallback) is not. -/// Characters do not map 1:1 to physical keys, as a key may produce zero, one or more characters. -/// If you want to know whether a specific physical key was pressed or released, see the key -/// callback instead. -/// -/// The character callback behaves as system text input normally does and will not be called if -/// modifier keys are held down that would prevent normal text input on that platform, for example a -/// Super (Command) key on macOS or Alt key on Windows. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] codepoint The Unicode code point of the character. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_char -pub inline fn setCharCallback(self: Window, comptime callback: ?fn (window: Window, codepoint: u21) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(u21, @intCast(codepoint)), - }); - } - }; - - if (c.glfwSetCharCallback(self.handle, CWrapper.charCallbackWrapper) != null) return; - } else { - if (c.glfwSetCharCallback(self.handle, null) != null) return; - } -} - -/// Sets the mouse button callback. -/// -/// This function sets the mouse button callback of the specified window, which is called when a -/// mouse button is pressed or released. -/// -/// When a window loses input focus, it will generate synthetic mouse button release events for all -/// pressed mouse buttons. You can tell these events from user-generated events by the fact that the -/// synthetic ones are generated after the focus loss event has been processed, i.e. after the -/// window focus callback (see glfw.Window.setFocusCallback) has been called. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] button The mouse button that was pressed or released. -/// @callback_param[in] action One of `glfw.Action.press` or `glfw.Action.release`. Future releases -/// may add more actions. -/// @callback_param[in] mods Bit field describing which modifier keys (see mods) were held down. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_mouse_button -pub inline fn setMouseButtonCallback(self: Window, comptime callback: ?fn (window: Window, button: MouseButton, action: Action, mods: Mods) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(MouseButton, @enumFromInt(button)), - @as(Action, @enumFromInt(action)), - Mods.fromInt(mods), - }); - } - }; - - if (c.glfwSetMouseButtonCallback(self.handle, CWrapper.mouseButtonCallbackWrapper) != null) return; - } else { - if (c.glfwSetMouseButtonCallback(self.handle, null) != null) return; - } -} - -/// Sets the cursor position callback. -/// -/// This function sets the cursor position callback of the specified window, which is called when -/// the cursor is moved. The callback is provided with the position, in screen coordinates, relative -/// to the upper-left corner of the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] xpos The new cursor x-coordinate, relative to the left edge of the content -/// area. -/// callback_@param[in] ypos The new cursor y-coordinate, relative to the top edge of the content -/// area. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos -pub inline fn setCursorPosCallback(self: Window, comptime callback: ?fn (window: Window, xpos: f64, ypos: f64) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xpos, - ypos, - }); - } - }; - - if (c.glfwSetCursorPosCallback(self.handle, CWrapper.cursorPosCallbackWrapper) != null) return; - } else { - if (c.glfwSetCursorPosCallback(self.handle, null) != null) return; - } -} - -/// Sets the cursor enter/leave callback. -/// -/// This function sets the cursor boundary crossing callback of the specified window, which is -/// called when the cursor enters or leaves the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] entered `true` if the cursor entered the window's content area, or `false` -/// if it left it. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_enter -pub inline fn setCursorEnterCallback(self: Window, comptime callback: ?fn (window: Window, entered: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - entered == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetCursorEnterCallback(self.handle, CWrapper.cursorEnterCallbackWrapper) != null) return; - } else { - if (c.glfwSetCursorEnterCallback(self.handle, null) != null) return; - } -} - -/// Sets the scroll callback. -/// -/// This function sets the scroll callback of the specified window, which is called when a scrolling -/// device is used, such as a mouse wheel or scrolling area of a touchpad. -/// -/// The scroll callback receives all scrolling input, like that from a mouse wheel or a touchpad -/// scrolling area. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new scroll callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] xoffset The scroll offset along the x-axis. -/// @callback_param[in] yoffset The scroll offset along the y-axis. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: scrolling -pub inline fn setScrollCallback(self: Window, comptime callback: ?fn (window: Window, xoffset: f64, yoffset: f64) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xoffset, - yoffset, - }); - } - }; - - if (c.glfwSetScrollCallback(self.handle, CWrapper.scrollCallbackWrapper) != null) return; - } else { - if (c.glfwSetScrollCallback(self.handle, null) != null) return; - } -} - -/// Sets the path drop callback. -/// -/// This function sets the path drop callback of the specified window, which is called when one or -/// more dragged paths are dropped on the window. -/// -/// Because the path array and its strings may have been generated specifically for that event, they -/// are not guaranteed to be valid after the callback has returned. If you wish to use them after -/// the callback returns, you need to make a deep copy. -/// -/// @param[in] callback The new file drop callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] path_count The number of dropped paths. -/// @callback_param[in] paths The UTF-8 encoded file and/or directory path names. -/// -/// @callback_pointer_lifetime The path array and its strings are valid until the callback function -/// returns. -/// -/// wayland: File drop is currently unimplemented. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: path_drop -pub inline fn setDropCallback(self: Window, comptime callback: ?fn (window: Window, paths: [][*:0]const u8) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as([*][*:0]const u8, @ptrCast(paths))[0..@as(u32, @intCast(path_count))], - }); - } - }; - - if (c.glfwSetDropCallback(self.handle, CWrapper.dropCallbackWrapper) != null) return; - } else { - if (c.glfwSetDropCallback(self.handle, null) != null) return; - } -} - -/// For testing purposes only; see glfw.Window.Hints and glfw.Window.create for the public API. -/// Sets the specified window hint to the desired value. -/// -/// This function sets hints for the next call to glfw.Window.create. The hints, once set, retain -/// their values until changed by a call to this function or glfw.window.defaultHints, or until the -/// library is terminated. -/// -/// This function does not check whether the specified hint values are valid. If you set hints to -/// invalid values this will instead be reported by the next call to glfw.createWindow. -/// -/// Some hints are platform specific. These may be set on any platform but they will only affect -/// their specific platform. Other platforms will ignore them. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// -/// @pointer_lifetime in the event that value is of a str type, the specified string is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hints, glfw.Window.defaultHints -inline fn hint(h: Hint, value: anytype) void { - internal_debug.assertInitialized(); - const value_type = @TypeOf(value); - const value_type_info: @import("shims.zig").std.builtin.Type = @import("shims.zig").typeInfo(value_type); - - switch (value_type_info) { - .int, .comptime_int => { - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(value))); - }, - .bool => { - const int_value = @intFromBool(value); - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(int_value))); - }, - .@"enum" => { - const int_value = @intFromEnum(value); - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(int_value))); - }, - .array => |arr_type| { - if (arr_type.child != u8) { - @compileError("expected array of u8, got " ++ @typeName(arr_type)); - } - c.glfwWindowHintString(@intFromEnum(h), &value[0]); - }, - .pointer => |pointer_info| { - const pointed_type = @import("shims.zig").typeInfo(pointer_info.child); - switch (pointed_type) { - .array => |arr_type| { - if (arr_type.child != u8) { - @compileError("expected pointer to array of u8, got " ++ @typeName(arr_type)); - } - }, - else => @compileError("expected pointer to array, got " ++ @typeName(pointed_type)), - } - - c.glfwWindowHintString(@intFromEnum(h), &value[0]); - }, - else => { - @compileError("expected a int, bool, enum, array, or pointer, got " ++ @typeName(value_type)); - }, - } -} - -test "defaultHints" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - defaultHints(); -} - -test "hint comptime int" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.focused, 1); - defaultHints(); -} - -test "hint int" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const focused: i32 = 1; - - hint(.focused, focused); - defaultHints(); -} - -test "hint bool" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.focused, true); - defaultHints(); -} - -test "hint enum(u1)" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const MyEnum = enum(u1) { - true = 1, - false = 0, - }; - - hint(.focused, MyEnum.true); - defaultHints(); -} - -test "hint enum(i32)" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const MyEnum = enum(i32) { - true = 1, - false = 0, - }; - - hint(.focused, MyEnum.true); - defaultHints(); -} - -test "hint array str" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const str_arr = [_]u8{ 'm', 'y', 'c', 'l', 'a', 's', 's' }; - - hint(.x11_class_name, str_arr); - defaultHints(); -} - -test "hint pointer str" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.x11_class_name, "myclass"); -} - -test "createWindow" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); -} - -test "setShouldClose" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - window.setShouldClose(true); - defer window.destroy(); -} - -test "setTitle" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setTitle("Updated title!"); -} - -test "setIcon" { - const allocator = testing.allocator; - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - // Create an all-red icon image. - const width: u32 = 48; - const height: u32 = 48; - const icon = try Image.init(allocator, width, height, width * height * 4); - var x: u32 = 0; - var y: u32 = 0; - while (y <= height) : (y += 1) { - while (x <= width) : (x += 1) { - icon.pixels[(x * y * 4) + 0] = 255; // red - icon.pixels[(x * y * 4) + 1] = 0; // green - icon.pixels[(x * y * 4) + 2] = 0; // blue - icon.pixels[(x * y * 4) + 3] = 255; // alpha - } - } - try window.setIcon(allocator, &[_]Image{icon}); - - icon.deinit(allocator); // glfw copies it. -} - -test "getPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getPos(); -} - -test "setPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.setPos(.{ .x = 0, .y = 0 }); -} - -test "getSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getSize(); -} - -test "setSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.setSize(.{ .width = 640, .height = 480 }); -} - -test "setSizeLimits" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setSizeLimits( - .{ .width = 720, .height = 480 }, - .{ .width = 1080, .height = 1920 }, - ); -} - -test "setAspectRatio" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setAspectRatio(4, 3); -} - -test "getFramebufferSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getFramebufferSize(); -} - -test "getFrameSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getFrameSize(); -} - -test "getContentScale" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getContentScale(); -} - -test "getOpacity" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getOpacity(); -} - -test "iconify" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.iconify(); -} - -test "restore" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.restore(); -} - -test "maximize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.maximize(); -} - -test "show" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.show(); -} - -test "hide" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.hide(); -} - -test "focus" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.focus(); -} - -test "requestAttention" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.requestAttention(); -} - -test "swapBuffers" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.swapBuffers(); -} - -test "getMonitor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getMonitor(); -} - -test "setMonitor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMonitor(null, 10, 10, 640, 480, 60); -} - -test "getAttrib" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getAttrib(.focused); -} - -test "setAttrib" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setAttrib(.decorated, false); -} - -test "setUserPointer" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const T = struct { name: []const u8 }; - var my_value = T{ .name = "my window!" }; - - window.setUserPointer(&my_value); -} - -test "getUserPointer" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const T = struct { name: []const u8 }; - var my_value = T{ .name = "my window!" }; - - window.setUserPointer(&my_value); - const got = window.getUserPointer(T); - std.debug.assert(&my_value == got); -} - -test "setPosCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setPosCallback((struct { - fn callback(_window: Window, xpos: i32, ypos: i32) void { - _ = _window; - _ = xpos; - _ = ypos; - } - }).callback); -} - -test "setSizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setSizeCallback((struct { - fn callback(_window: Window, width: i32, height: i32) void { - _ = _window; - _ = width; - _ = height; - } - }).callback); -} - -test "setCloseCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCloseCallback((struct { - fn callback(_window: Window) void { - _ = _window; - } - }).callback); -} - -test "setRefreshCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setRefreshCallback((struct { - fn callback(_window: Window) void { - _ = _window; - } - }).callback); -} - -test "setFocusCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setFocusCallback((struct { - fn callback(_window: Window, focused: bool) void { - _ = _window; - _ = focused; - } - }).callback); -} - -test "setIconifyCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setIconifyCallback((struct { - fn callback(_window: Window, iconified: bool) void { - _ = _window; - _ = iconified; - } - }).callback); -} - -test "setMaximizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMaximizeCallback((struct { - fn callback(_window: Window, maximized: bool) void { - _ = _window; - _ = maximized; - } - }).callback); -} - -test "setFramebufferSizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setFramebufferSizeCallback((struct { - fn callback(_window: Window, width: u32, height: u32) void { - _ = _window; - _ = width; - _ = height; - } - }).callback); -} - -test "setContentScaleCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setContentScaleCallback((struct { - fn callback(_window: Window, xscale: f32, yscale: f32) void { - _ = _window; - _ = xscale; - _ = yscale; - } - }).callback); -} - -test "setDropCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setDropCallback((struct { - fn callback(_window: Window, paths: [][*:0]const u8) void { - _ = _window; - _ = paths; - } - }).callback); -} - -test "getInputModeCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeCursor(); -} - -test "setInputModeCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeCursor(.hidden); -} - -test "getInputModeStickyKeys" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeStickyKeys(); -} - -test "setInputModeStickyKeys" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeStickyKeys(false); -} - -test "getInputModeStickyMouseButtons" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeStickyMouseButtons(); -} - -test "setInputModeStickyMouseButtons" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeStickyMouseButtons(false); -} - -test "getInputModeLockKeyMods" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeLockKeyMods(); -} - -test "setInputModeLockKeyMods" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeLockKeyMods(false); -} - -test "getInputModeRawMouseMotion" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeRawMouseMotion(); -} - -test "setInputModeRawMouseMotion" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeRawMouseMotion(false); -} - -test "getInputMode" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputMode(glfw.Window.InputMode.raw_mouse_motion) == 1; -} - -test "setInputMode" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - // Boolean values. - window.setInputMode(glfw.Window.InputMode.sticky_mouse_buttons, true); - - // Integer values. - window.setInputMode(glfw.Window.InputMode.cursor, glfw.Window.InputModeCursor.hidden); -} - -test "getKey" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getKey(glfw.Key.escape); -} - -test "getMouseButton" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getMouseButton(.left); -} - -test "getCursorPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getCursorPos(); -} - -test "setCursorPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorPos(0, 0); -} - -test "setCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const cursor = glfw.Cursor.createStandard(.ibeam); - if (cursor) |cur| { - window.setCursor(cur); - defer cur.destroy(); - } -} - -test "setKeyCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setKeyCallback((struct { - fn callback(_window: Window, key: Key, scancode: i32, action: Action, mods: Mods) void { - _ = _window; - _ = key; - _ = scancode; - _ = action; - _ = mods; - } - }).callback); -} - -test "setCharCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCharCallback((struct { - fn callback(_window: Window, codepoint: u21) void { - _ = _window; - _ = codepoint; - } - }).callback); -} - -test "setMouseButtonCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMouseButtonCallback((struct { - fn callback(_window: Window, button: MouseButton, action: Action, mods: Mods) void { - _ = _window; - _ = button; - _ = action; - _ = mods; - } - }).callback); -} - -test "setCursorPosCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorPosCallback((struct { - fn callback(_window: Window, xpos: f64, ypos: f64) void { - _ = _window; - _ = xpos; - _ = ypos; - } - }).callback); -} - -test "setCursorEnterCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorEnterCallback((struct { - fn callback(_window: Window, entered: bool) void { - _ = _window; - _ = entered; - } - }).callback); -} - -test "setScrollCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setScrollCallback((struct { - fn callback(_window: Window, xoffset: f64, yoffset: f64) void { - _ = _window; - _ = xoffset; - _ = yoffset; - } - }).callback); -} - -test "hint-attribute default value parity" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - testing_ignore_window_hints_struct = true; - const window_a = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window_a: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window_a.destroy(); - - testing_ignore_window_hints_struct = false; - const window_b = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window_b: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window_b.destroy(); - - inline for (comptime std.enums.values(Window.Hint)) |hint_tag| { - if (@hasField(Window.Attrib, @tagName(hint_tag))) { - const attrib_tag = @field(Window.Attrib, @tagName(hint_tag)); - switch (attrib_tag) { - .resizable, - .visible, - .decorated, - .auto_iconify, - .floating, - .maximized, - .transparent_framebuffer, - .focus_on_show, - .mouse_passthrough, - .doublebuffer, - .client_api, - .context_creation_api, - .context_version_major, - .context_version_minor, - .context_robustness, - .context_release_behavior, - .context_no_error, // Note: at the time of writing this, GLFW does not list the default value for this hint in the documentation - .context_debug, - .opengl_forward_compat, - .opengl_profile, - => { - const expected = window_a.getAttrib(attrib_tag); - const actual = window_b.getAttrib(attrib_tag); - - testing.expectEqual(expected, actual) catch |err| { - std.debug.print("On attribute '{}'.\n", .{hint_tag}); - return err; - }; - }, - - // This attribute is based on a check for which window is currently in focus, - // and the default value, as of writing this comment, is 'true', which means - // that first window_a takes focus, and then window_b takes focus, meaning - // that we can't actually test for the default value. - .focused => continue, - - .iconified, - .hovered, - .context_revision, - => unreachable, - } - } - // Future: we could consider hint values that can't be retrieved via attributes: - // center_cursor - // mouse_passthrough - // scale_to_monitor - // red_bits - // green_bits - // blue_bits - // alpha_bits - // depth_bits - // stencil_bits - // accum_red_bits - // accum_green_bits - // accum_blue_bits - // accum_alpha_bits - // aux_buffers - // samples - - // refresh_rate - // stereo - // srgb_capable - // doublebuffer - - // platform specific, and thus not considered: - // cocoa_retina_framebuffer - // cocoa_frame_name - // cocoa_graphics_switching - } -} diff --git a/pkg/glfw/action.zig b/pkg/glfw/action.zig deleted file mode 100644 index 59314709f..000000000 --- a/pkg/glfw/action.zig +++ /dev/null @@ -1,13 +0,0 @@ -const c = @import("c.zig").c; - -/// Key and button actions -pub const Action = enum(c_int) { - /// The key or mouse button was released. - release = c.GLFW_RELEASE, - - /// The key or mouse button was pressed. - press = c.GLFW_PRESS, - - /// The key was held down until it repeated. - repeat = c.GLFW_REPEAT, -}; diff --git a/pkg/glfw/allocator.zig b/pkg/glfw/allocator.zig deleted file mode 100644 index d6517cf3e..000000000 --- a/pkg/glfw/allocator.zig +++ /dev/null @@ -1,143 +0,0 @@ -// TODO: implement custom allocator support - -// /*! @brief -// * -// * @sa @ref init_allocator -// * @sa @ref glfwInitAllocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef struct GLFWallocator -// { -// GLFWallocatefun allocate; -// GLFWreallocatefun reallocate; -// GLFWdeallocatefun deallocate; -// void* user; -// } GLFWallocator; - -// /*! @brief The function pointer type for memory allocation callbacks. -// * -// * This is the function pointer type for memory allocation callbacks. A memory -// * allocation callback function has the following signature: -// * @code -// * void* function_name(size_t size, void* user) -// * @endcode -// * -// * This function must return either a memory block at least `size` bytes long, -// * or `NULL` if allocation failed. Note that not all parts of GLFW handle allocation -// * failures gracefully yet. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * Any memory allocated by this function will be deallocated during library -// * termination or earlier. -// * -// * The size will always be greater than zero. Allocations of size zero are filtered out -// * before reaching the custom allocator. -// * -// * @param[in] size The minimum size, in bytes, of the memory block. -// * @param[in] user The user-defined pointer from the allocator. -// * @return The address of the newly allocated memory block, or `NULL` if an -// * error occurred. -// * -// * @pointer_lifetime The returned memory block must be valid at least until it -// * is deallocated. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void* (* GLFWallocatefun)(size_t size, void* user); - -// /*! @brief The function pointer type for memory reallocation callbacks. -// * -// * This is the function pointer type for memory reallocation callbacks. -// * A memory reallocation callback function has the following signature: -// * @code -// * void* function_name(void* block, size_t size, void* user) -// * @endcode -// * -// * This function must return a memory block at least `size` bytes long, or -// * `NULL` if allocation failed. Note that not all parts of GLFW handle allocation -// * failures gracefully yet. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * Any memory allocated by this function will be deallocated during library -// * termination or earlier. -// * -// * The block address will never be `NULL` and the size will always be greater than zero. -// * Reallocations of a block to size zero are converted into deallocations. Reallocations -// * of `NULL` to a non-zero size are converted into regular allocations. -// * -// * @param[in] block The address of the memory block to reallocate. -// * @param[in] size The new minimum size, in bytes, of the memory block. -// * @param[in] user The user-defined pointer from the allocator. -// * @return The address of the newly allocated or resized memory block, or -// * `NULL` if an error occurred. -// * -// * @pointer_lifetime The returned memory block must be valid at least until it -// * is deallocated. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void* (* GLFWreallocatefun)(void* block, size_t size, void* user); - -// /*! @brief The function pointer type for memory deallocation callbacks. -// * -// * This is the function pointer type for memory deallocation callbacks. -// * A memory deallocation callback function has the following signature: -// * @code -// * void function_name(void* block, void* user) -// * @endcode -// * -// * This function may deallocate the specified memory block. This memory block -// * will have been allocated with the same allocator. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * The block address will never be `NULL`. Deallocations of `NULL` are filtered out -// * before reaching the custom allocator. -// * -// * @param[in] block The address of the memory block to deallocate. -// * @param[in] user The user-defined pointer from the allocator. -// * -// * @pointer_lifetime The specified memory block will not be accessed by GLFW -// * after this function is called. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void (* GLFWdeallocatefun)(void* block, void* user); diff --git a/pkg/glfw/build.zig b/pkg/glfw/build.zig deleted file mode 100644 index 142a558da..000000000 --- a/pkg/glfw/build.zig +++ /dev/null @@ -1,271 +0,0 @@ -const std = @import("std"); -const apple_sdk = @import("apple_sdk"); - -pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const module = b.addModule("glfw", .{ - .root_source_file = b.path("main.zig"), - .target = target, - .optimize = optimize, - }); - - const lib = try buildLib(b, module, .{ - .target = target, - .optimize = optimize, - }); - - const test_exe: ?*std.Build.Step.Compile = if (target.query.isNative()) exe: { - const exe = b.addTest(.{ - .name = "test", - .root_source_file = b.path("main.zig"), - .target = target, - .optimize = optimize, - }); - if (target.result.os.tag.isDarwin()) { - try apple_sdk.addPaths(b, exe); - } - - const tests_run = b.addRunArtifact(exe); - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&tests_run.step); - - // Uncomment this if we're debugging tests - b.installArtifact(exe); - - break :exe exe; - } else null; - - if (b.systemIntegrationOption("glfw3", .{})) { - module.linkSystemLibrary("glfw3", dynamic_link_opts); - if (test_exe) |exe| exe.linkSystemLibrary2("glfw3", dynamic_link_opts); - } else { - module.linkLibrary(lib); - b.installArtifact(lib); - if (test_exe) |exe| exe.linkLibrary(lib); - } -} - -fn buildLib( - b: *std.Build, - module: *std.Build.Module, - options: anytype, -) !*std.Build.Step.Compile { - const target = options.target; - const optimize = options.optimize; - - const use_x11 = b.option( - bool, - "x11", - "Build with X11. Only useful on Linux", - ) orelse true; - const use_wl = b.option( - bool, - "wayland", - "Build with Wayland. Only useful on Linux", - ) orelse true; - - const use_opengl = b.option( - bool, - "opengl", - "Build with OpenGL; deprecated on MacOS", - ) orelse false; - const use_gles = b.option( - bool, - "gles", - "Build with GLES; not supported on MacOS", - ) orelse false; - const use_metal = b.option( - bool, - "metal", - "Build with Metal; only supported on MacOS", - ) orelse true; - - const lib = b.addStaticLibrary(.{ - .name = "glfw", - .target = target, - .optimize = optimize, - }); - lib.linkLibC(); - - const upstream = b.lazyDependency("glfw", .{}) orelse return lib; - lib.addIncludePath(upstream.path("include")); - module.addIncludePath(upstream.path("include")); - lib.installHeadersDirectory(upstream.path("include/GLFW"), "GLFW", .{}); - - switch (target.result.os.tag) { - .windows => { - lib.linkSystemLibrary("gdi32"); - lib.linkSystemLibrary("user32"); - lib.linkSystemLibrary("shell32"); - - if (use_opengl) { - lib.linkSystemLibrary("opengl32"); - } - - if (use_gles) { - lib.linkSystemLibrary("GLESv3"); - } - - const flags = [_][]const u8{"-D_GLFW_WIN32"}; - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &base_sources, - .flags = &flags, - }); - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &windows_sources, - .flags = &flags, - }); - }, - - .macos => { - try apple_sdk.addPaths(b, lib); - - // Transitive dependencies, explicit linkage of these works around - // ziglang/zig#17130 - lib.linkFramework("CFNetwork"); - lib.linkFramework("ApplicationServices"); - lib.linkFramework("ColorSync"); - lib.linkFramework("CoreText"); - lib.linkFramework("ImageIO"); - - // Direct dependencies - lib.linkSystemLibrary("objc"); - lib.linkFramework("IOKit"); - lib.linkFramework("CoreFoundation"); - lib.linkFramework("AppKit"); - lib.linkFramework("CoreServices"); - lib.linkFramework("CoreGraphics"); - lib.linkFramework("Foundation"); - - if (use_metal) { - lib.linkFramework("Metal"); - } - - if (use_opengl) { - lib.linkFramework("OpenGL"); - } - - const flags = [_][]const u8{"-D_GLFW_COCOA"}; - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &base_sources, - .flags = &flags, - }); - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &macos_sources, - .flags = &flags, - }); - }, - - // everything that isn't windows or mac is linux :P - else => { - var sources = std.BoundedArray([]const u8, 64).init(0) catch unreachable; - var flags = std.BoundedArray([]const u8, 16).init(0) catch unreachable; - - sources.appendSlice(&base_sources) catch unreachable; - sources.appendSlice(&linux_sources) catch unreachable; - - if (use_x11) { - lib.linkSystemLibrary2("X11", dynamic_link_opts); - lib.linkSystemLibrary2("xkbcommon", dynamic_link_opts); - sources.appendSlice(&linux_x11_sources) catch unreachable; - flags.append("-D_GLFW_X11") catch unreachable; - } - - if (use_wl) { - lib.linkSystemLibrary2("wayland-client", dynamic_link_opts); - - lib.root_module.addCMacro("WL_MARSHAL_FLAG_DESTROY", "1"); - lib.addIncludePath(b.path("wayland-headers")); - - sources.appendSlice(&linux_wl_sources) catch unreachable; - flags.append("-D_GLFW_WAYLAND") catch unreachable; - flags.append("-Wno-implicit-function-declaration") catch unreachable; - } - - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = sources.slice(), - .flags = flags.slice(), - }); - }, - } - - return lib; -} - -// For dynamic linking, we prefer dynamic linking and to search by -// mode first. Mode first will search all paths for a dynamic library -// before falling back to static. -const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ - .preferred_link_mode = .dynamic, - .search_strategy = .mode_first, -}; - -const base_sources = [_][]const u8{ - "src/context.c", - "src/egl_context.c", - "src/init.c", - "src/input.c", - "src/monitor.c", - "src/null_init.c", - "src/null_joystick.c", - "src/null_monitor.c", - "src/null_window.c", - "src/osmesa_context.c", - "src/platform.c", - "src/vulkan.c", - "src/window.c", -}; - -const linux_sources = [_][]const u8{ - "src/linux_joystick.c", - "src/posix_module.c", - "src/posix_poll.c", - "src/posix_thread.c", - "src/posix_time.c", - "src/xkb_unicode.c", -}; - -const linux_wl_sources = [_][]const u8{ - "src/wl_init.c", - "src/wl_monitor.c", - "src/wl_window.c", -}; - -const linux_x11_sources = [_][]const u8{ - "src/glx_context.c", - "src/x11_init.c", - "src/x11_monitor.c", - "src/x11_window.c", -}; - -const windows_sources = [_][]const u8{ - "src/wgl_context.c", - "src/win32_init.c", - "src/win32_joystick.c", - "src/win32_module.c", - "src/win32_monitor.c", - "src/win32_thread.c", - "src/win32_time.c", - "src/win32_window.c", -}; - -const macos_sources = [_][]const u8{ - // C sources - "src/cocoa_time.c", - "src/posix_module.c", - "src/posix_thread.c", - - // ObjC sources - "src/cocoa_init.m", - "src/cocoa_joystick.m", - "src/cocoa_monitor.m", - "src/cocoa_window.m", - "src/nsgl_context.m", -}; diff --git a/pkg/glfw/build.zig.zon b/pkg/glfw/build.zig.zon deleted file mode 100644 index 467f2c327..000000000 --- a/pkg/glfw/build.zig.zon +++ /dev/null @@ -1,15 +0,0 @@ -.{ - .name = .glfw, - .version = "3.4.0", - .fingerprint = 0x3bbe0a5c667e2c62, - .paths = .{""}, - .dependencies = .{ - .glfw = .{ - .url = "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - .hash = "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6", - .lazy = true, - }, - - .apple_sdk = .{ .path = "../apple-sdk" }, - }, -} diff --git a/pkg/glfw/c.zig b/pkg/glfw/c.zig deleted file mode 100644 index 58599025b..000000000 --- a/pkg/glfw/c.zig +++ /dev/null @@ -1,6 +0,0 @@ -pub const c = @cImport({ - // Must be uncommented for vulkan.zig to work - // @cDefine("GLFW_INCLUDE_VULKAN", "1"); - @cDefine("GLFW_INCLUDE_NONE", "1"); - @cInclude("GLFW/glfw3.h"); -}); diff --git a/pkg/glfw/clipboard.zig b/pkg/glfw/clipboard.zig deleted file mode 100644 index a7e2d0e2f..000000000 --- a/pkg/glfw/clipboard.zig +++ /dev/null @@ -1,71 +0,0 @@ -const std = @import("std"); - -const c = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// Sets the clipboard to the specified string. -/// -/// This function sets the system clipboard to the specified, UTF-8 encoded string. -/// -/// @param[in] string A UTF-8 encoded string. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @pointer_lifetime The specified string is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: clipboard, glfwGetClipboardString -pub inline fn setClipboardString(value: [*:0]const u8) void { - internal_debug.assertInitialized(); - c.glfwSetClipboardString(null, value); -} - -/// Returns the contents of the clipboard as a string. -/// -/// This function returns the contents of the system clipboard, if it contains or is convertible to -/// a UTF-8 encoded string. If the clipboard is empty or if its contents cannot be converted, -/// glfw.ErrorCode.FormatUnavailable is returned. -/// -/// @return The contents of the clipboard as a UTF-8 encoded string. -/// -/// Possible errors include glfw.ErrorCode.FormatUnavailable and glfw.ErrorCode.PlatformError. -/// null is returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the next call to glfw.getClipboardString or glfw.setClipboardString -/// or until the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: clipboard, glfwSetClipboardString -pub inline fn getClipboardString() ?[:0]const u8 { - internal_debug.assertInitialized(); - if (c.glfwGetClipboardString(null)) |c_str| return std.mem.span(@as([*:0]const u8, @ptrCast(c_str))); - return null; -} - -test "setClipboardString" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - glfw.setClipboardString("hello mach"); -} - -test "getClipboardString" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getClipboardString(); -} diff --git a/pkg/glfw/errors.zig b/pkg/glfw/errors.zig deleted file mode 100644 index b9721fd05..000000000 --- a/pkg/glfw/errors.zig +++ /dev/null @@ -1,338 +0,0 @@ -//! Errors - -const testing = @import("std").testing; -const mem = @import("std").mem; -const c = @import("c.zig").c; - -/// Errors that GLFW can produce. -pub const ErrorCode = error{ - /// GLFW has not been initialized. - /// - /// This occurs if a GLFW function was called that must not be called unless the library is - /// initialized. - NotInitialized, - - /// No context is current for this thread. - /// - /// This occurs if a GLFW function was called that needs and operates on the current OpenGL or - /// OpenGL ES context but no context is current on the calling thread. One such function is - /// glfw.SwapInterval. - NoCurrentContext, - - /// One of the arguments to the function was an invalid enum value. - /// - /// One of the arguments to the function was an invalid enum value, for example requesting - /// glfw.red_bits with glfw.getWindowAttrib. - InvalidEnum, - - /// One of the arguments to the function was an invalid value. - /// - /// One of the arguments to the function was an invalid value, for example requesting a - /// non-existent OpenGL or OpenGL ES version like 2.7. - /// - /// Requesting a valid but unavailable OpenGL or OpenGL ES version will instead result in a - /// glfw.ErrorCode.VersionUnavailable error. - InvalidValue, - - /// A memory allocation failed. - OutOfMemory, - - /// GLFW could not find support for the requested API on the system. - /// - /// The installed graphics driver does not support the requested API, or does not support it - /// via the chosen context creation API. Below are a few examples. - /// - /// Some pre-installed Windows graphics drivers do not support OpenGL. AMD only supports - /// OpenGL ES via EGL, while Nvidia and Intel only support it via a WGL or GLX extension. macOS - /// does not provide OpenGL ES at all. The Mesa EGL, OpenGL and OpenGL ES libraries do not - /// interface with the Nvidia binary driver. Older graphics drivers do not support Vulkan. - APIUnavailable, - - /// The requested OpenGL or OpenGL ES version (including any requested context or framebuffer - /// hints) is not available on this machine. - /// - /// The machine does not support your requirements. If your application is sufficiently - /// flexible, downgrade your requirements and try again. Otherwise, inform the user that their - /// machine does not match your requirements. - /// - /// Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 comes out - /// before the 4.x series gets that far, also fail with this error and not glfw.ErrorCode.InvalidValue, - /// because GLFW cannot know what future versions will exist. - VersionUnavailable, - - /// A platform-specific error occurred that does not match any of the more specific categories. - /// - /// A bug or configuration error in GLFW, the underlying operating system or its drivers, or a - /// lack of required resources. Report the issue to our [issue tracker](https://github.com/glfw/glfw/issues). - PlatformError, - - /// The requested format is not supported or available. - /// - /// If emitted during window creation, the requested pixel format is not supported. - /// - /// If emitted when querying the clipboard, the contents of the clipboard could not be - /// converted to the requested format. - /// - /// If emitted during window creation, one or more hard constraints did not match any of the - /// available pixel formats. If your application is sufficiently flexible, downgrade your - /// requirements and try again. Otherwise, inform the user that their machine does not match - /// your requirements. - /// - /// If emitted when querying the clipboard, ignore the error or report it to the user, as - /// appropriate. - FormatUnavailable, - - /// The specified window does not have an OpenGL or OpenGL ES context. - /// - /// A window that does not have an OpenGL or OpenGL ES context was passed to a function that - /// requires it to have one. - NoWindowContext, - - /// The specified cursor shape is not available. - /// - /// The specified standard cursor shape is not available, either because the - /// current platform cursor theme does not provide it or because it is not - /// available on the platform. - /// - /// analysis: Platform or system settings limitation. Pick another standard cursor shape or - /// create a custom cursor. - CursorUnavailable, - - /// The requested feature is not provided by the platform. - /// - /// The requested feature is not provided by the platform, so GLFW is unable to - /// implement it. The documentation for each function notes if it could emit - /// this error. - /// - /// analysis: Platform or platform version limitation. The error can be ignored - /// unless the feature is critical to the application. - /// - /// A function call that emits this error has no effect other than the error and - /// updating any existing out parameters. - /// - FeatureUnavailable, - - /// The requested feature is not implemented for the platform. - /// - /// The requested feature has not yet been implemented in GLFW for this platform. - /// - /// analysis: An incomplete implementation of GLFW for this platform, hopefully - /// fixed in a future release. The error can be ignored unless the feature is - /// critical to the application. - /// - /// A function call that emits this error has no effect other than the error and - /// updating any existing out parameters. - /// - FeatureUnimplemented, - - /// Platform unavailable or no matching platform was found. - /// - /// If emitted during initialization, no matching platform was found. If glfw.InitHint.platform - /// is set to `.any_platform`, GLFW could not detect any of the platforms supported by this - /// library binary, except for the Null platform. If set to a specific platform, it is either - /// not supported by this library binary or GLFW was not able to detect it. - /// - /// If emitted by a native access function, GLFW was initialized for a different platform - /// than the function is for. - /// - /// analysis: Failure to detect any platform usually only happens on non-macOS Unix - /// systems, either when no window system is running or the program was run from - /// a terminal that does not have the necessary environment variables. Fall back to - /// a different platform if possible or notify the user that no usable platform was - /// detected. - /// - /// Failure to detect a specific platform may have the same cause as above or be because - /// support for that platform was not compiled in. Call glfw.platformSupported to - /// check whether a specific platform is supported by a library binary. - /// - PlatformUnavailable, -}; - -/// An error produced by GLFW and the description associated with it. -pub const Error = struct { - error_code: ErrorCode, - description: [:0]const u8, -}; - -fn convertError(e: c_int) ErrorCode!void { - return switch (e) { - c.GLFW_NO_ERROR => {}, - c.GLFW_NOT_INITIALIZED => ErrorCode.NotInitialized, - c.GLFW_NO_CURRENT_CONTEXT => ErrorCode.NoCurrentContext, - c.GLFW_INVALID_ENUM => ErrorCode.InvalidEnum, - c.GLFW_INVALID_VALUE => ErrorCode.InvalidValue, - c.GLFW_OUT_OF_MEMORY => ErrorCode.OutOfMemory, - c.GLFW_API_UNAVAILABLE => ErrorCode.APIUnavailable, - c.GLFW_VERSION_UNAVAILABLE => ErrorCode.VersionUnavailable, - c.GLFW_PLATFORM_ERROR => ErrorCode.PlatformError, - c.GLFW_FORMAT_UNAVAILABLE => ErrorCode.FormatUnavailable, - c.GLFW_NO_WINDOW_CONTEXT => ErrorCode.NoWindowContext, - c.GLFW_CURSOR_UNAVAILABLE => ErrorCode.CursorUnavailable, - c.GLFW_FEATURE_UNAVAILABLE => ErrorCode.FeatureUnavailable, - c.GLFW_FEATURE_UNIMPLEMENTED => ErrorCode.FeatureUnimplemented, - c.GLFW_PLATFORM_UNAVAILABLE => ErrorCode.PlatformUnavailable, - else => unreachable, - }; -} - -/// Clears the last error and the error description pointer for the calling thread. Does nothing if -/// no error has occurred since the last call. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn clearError() void { - _ = c.glfwGetError(null); -} - -/// Returns and clears the last error for the calling thread. -/// -/// This function returns and clears the error code of the last error that occurred on the calling -/// thread, along with a UTF-8 encoded human-readable description of it. If no error has occurred -/// since the last call, it returns GLFW_NO_ERROR (zero) and the description pointer is set to -/// `NULL`. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the next error occurs or the library is -/// terminated. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getError() ?Error { - var desc: [*c]const u8 = null; - convertError(c.glfwGetError(&desc)) catch |error_code| { - return .{ - .error_code = error_code, - .description = mem.sliceTo(desc, 0), - }; - }; - return null; -} - -pub inline fn mustGetError() Error { - return getError() orelse { - @panic("glfw: mustGetError called but no error is present"); - }; -} - -/// Returns and clears the last error for the calling thread. -/// -/// This function returns and clears the error code of the last error that occurred on the calling -/// thread. If no error has occurred since the last call, it returns GLFW_NO_ERROR (zero). -/// -/// @return The last error code for the calling thread, or @ref GLFW_NO_ERROR (zero). -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getErrorCode() ErrorCode!void { - return convertError(c.glfwGetError(null)); -} - -/// Returns and clears the last error code for the calling thread. If no error is present, this -/// function panics. -pub inline fn mustGetErrorCode() ErrorCode { - try getErrorCode(); - @panic("glfw: mustGetErrorCode called but no error is present"); -} - -/// Returns and clears the last error description for the calling thread. -/// -/// This function returns a UTF-8 encoded human-readable description of the last error that occured -/// on the calling thread. If no error has occurred since the last call, it returns null. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the next error occurs or the library is -/// terminated. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getErrorString() ?[:0]const u8 { - var desc: [*c]const u8 = null; - const error_code = c.glfwGetError(&desc); - if (error_code != c.GLFW_NO_ERROR) { - return mem.sliceTo(desc, 0); - } - return null; -} - -/// Returns and clears the last error description for the calling thread. If no error is present, -/// this function panics. -pub inline fn mustGetErrorString() [:0]const u8 { - return getErrorString() orelse { - @panic("glfw: mustGetErrorString called but no error is present"); - }; -} - -/// Sets the error callback. -/// -/// This function sets the error callback, which is called with an error code -/// and a human-readable description each time a GLFW error occurs. -/// -/// The error code is set before the callback is called. Calling @ref -/// glfwGetError from the error callback will return the same value as the error -/// code argument. -/// -/// The error callback is called on the thread where the error occurred. If you -/// are using GLFW from multiple threads, your error callback needs to be -/// written accordingly. -/// -/// Because the description string may have been generated specifically for that -/// error, it is not guaranteed to be valid after the callback has returned. If -/// you wish to use it after the callback returns, you need to make a copy. -/// -/// Once set, the error callback remains set even after the library has been -/// terminated. -/// -/// @param[in] callback The new callback, or `NULL` to remove the currently set -/// callback. -/// -/// @callback_param `error_code` An error code. Future releases may add more error codes. -/// @callback_param `description` A UTF-8 encoded string describing the error. -/// -/// @errors None. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function must only be called from the main thread. -pub fn setErrorCallback(comptime callback: ?fn (error_code: ErrorCode, description: [:0]const u8) void) void { - if (callback) |user_callback| { - const CWrapper = struct { - pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.c) void { - convertError(err_int) catch |error_code| { - user_callback(error_code, mem.sliceTo(c_description, 0)); - }; - } - }; - - _ = c.glfwSetErrorCallback(CWrapper.errorCallbackWrapper); - return; - } - - _ = c.glfwSetErrorCallback(null); -} - -test "set error callback" { - const TestStruct = struct { - pub fn callback(_: ErrorCode, _: [:0]const u8) void {} - }; - setErrorCallback(TestStruct.callback); -} - -test "error string" { - try testing.expect(getErrorString() == null); -} - -test "error code" { - try getErrorCode(); -} - -test "error code and string" { - try testing.expect(getError() == null); -} - -test "clear error" { - clearError(); -} diff --git a/pkg/glfw/gamepad_axis.zig b/pkg/glfw/gamepad_axis.zig deleted file mode 100644 index 97fdf3748..000000000 --- a/pkg/glfw/gamepad_axis.zig +++ /dev/null @@ -1,16 +0,0 @@ -const c = @import("c.zig").c; - -/// Gamepad axes. -/// -/// See glfw.getGamepadState for how these are used. -pub const GamepadAxis = enum(c_int) { - left_x = c.GLFW_GAMEPAD_AXIS_LEFT_X, - left_y = c.GLFW_GAMEPAD_AXIS_LEFT_Y, - right_x = c.GLFW_GAMEPAD_AXIS_RIGHT_X, - right_y = c.GLFW_GAMEPAD_AXIS_RIGHT_Y, - left_trigger = c.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, - right_trigger = c.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, -}; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const last = GamepadAxis.right_trigger; diff --git a/pkg/glfw/gamepad_button.zig b/pkg/glfw/gamepad_button.zig deleted file mode 100644 index ac47ebea0..000000000 --- a/pkg/glfw/gamepad_button.zig +++ /dev/null @@ -1,37 +0,0 @@ -const c = @import("c.zig").c; - -/// Gamepad buttons. -/// -/// See glfw.getGamepadState for how these are used. -pub const GamepadButton = enum(c_int) { - a = c.GLFW_GAMEPAD_BUTTON_A, - b = c.GLFW_GAMEPAD_BUTTON_B, - x = c.GLFW_GAMEPAD_BUTTON_X, - y = c.GLFW_GAMEPAD_BUTTON_Y, - left_bumper = c.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, - right_bumper = c.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, - back = c.GLFW_GAMEPAD_BUTTON_BACK, - start = c.GLFW_GAMEPAD_BUTTON_START, - guide = c.GLFW_GAMEPAD_BUTTON_GUIDE, - left_thumb = c.GLFW_GAMEPAD_BUTTON_LEFT_THUMB, - right_thumb = c.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, - dpad_up = c.GLFW_GAMEPAD_BUTTON_DPAD_UP, - dpad_right = c.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, - dpad_down = c.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, - dpad_left = c.GLFW_GAMEPAD_BUTTON_DPAD_LEFT, -}; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const last = GamepadButton.dpad_left; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const cross = GamepadButton.a; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const circle = GamepadButton.b; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const square = GamepadButton.x; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const triangle = GamepadButton.y; diff --git a/pkg/glfw/hat.zig b/pkg/glfw/hat.zig deleted file mode 100644 index ffbb4a1c8..000000000 --- a/pkg/glfw/hat.zig +++ /dev/null @@ -1,100 +0,0 @@ -const c = @import("c.zig").c; - -// must be in sync with GLFW C constants in hat state group, search for "@defgroup hat_state Joystick hat states" -/// A bitmask of all Joystick hat states -/// -/// See glfw.Joystick.getHats for how these are used. -pub const Hat = packed struct(u8) { - up: bool = false, - right: bool = false, - down: bool = false, - left: bool = false, - _padding: u4 = 0, - - pub inline fn centered(self: Hat) bool { - return self.up == false and self.right == false and self.down == false and self.left == false; - } - - inline fn verifyIntType(comptime IntType: type) void { - comptime { - switch (@import("shims.zig").typeInfo(IntType)) { - .int => {}, - else => @compileError("Int was not of int type"), - } - } - } - - pub inline fn toInt(self: Hat, comptime IntType: type) IntType { - verifyIntType(IntType); - return @as(IntType, @intCast(@as(u8, @bitCast(self)))); - } - - pub inline fn fromInt(flags: anytype) Hat { - verifyIntType(@TypeOf(flags)); - return @as(Hat, @bitCast(@as(u8, @intCast(flags)))); - } -}; - -/// Holds all GLFW hat values in their raw form. -pub const RawHat = struct { - pub const centered = c.GLFW_HAT_CENTERED; - pub const up = c.GLFW_HAT_UP; - pub const right = c.GLFW_HAT_RIGHT; - pub const down = c.GLFW_HAT_DOWN; - pub const left = c.GLFW_HAT_LEFT; - - pub const right_up = right | up; - pub const right_down = right | down; - pub const left_up = left | up; - pub const left_down = left | down; -}; - -test "from int, single" { - const std = @import("std"); - - try std.testing.expectEqual(Hat{ - .up = true, - .right = false, - .down = false, - .left = false, - ._padding = 0, - }, Hat.fromInt(RawHat.up)); -} - -test "from int, multi" { - const std = @import("std"); - - try std.testing.expectEqual(Hat{ - .up = true, - .right = false, - .down = true, - .left = true, - ._padding = 0, - }, Hat.fromInt(RawHat.up | RawHat.down | RawHat.left)); -} - -test "to int, single" { - const std = @import("std"); - - var v = Hat{ - .up = true, - .right = false, - .down = false, - .left = false, - ._padding = 0, - }; - try std.testing.expectEqual(v.toInt(c_int), RawHat.up); -} - -test "to int, multi" { - const std = @import("std"); - - var v = Hat{ - .up = true, - .right = false, - .down = true, - .left = true, - ._padding = 0, - }; - try std.testing.expectEqual(v.toInt(c_int), RawHat.up | RawHat.down | RawHat.left); -} diff --git a/pkg/glfw/internal_debug.zig b/pkg/glfw/internal_debug.zig deleted file mode 100644 index 6e0ab4f1e..000000000 --- a/pkg/glfw/internal_debug.zig +++ /dev/null @@ -1,14 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const is_debug = builtin.mode == .Debug; -var glfw_initialized = if (is_debug) false else @as(void, {}); -pub inline fn toggleInitialized() void { - if (is_debug) glfw_initialized = !glfw_initialized; -} -pub inline fn assertInitialized() void { - if (is_debug) std.debug.assert(glfw_initialized); -} -pub inline fn assumeInitialized() void { - if (is_debug) glfw_initialized = true; -} diff --git a/pkg/glfw/key.zig b/pkg/glfw/key.zig deleted file mode 100644 index 27b13119c..000000000 --- a/pkg/glfw/key.zig +++ /dev/null @@ -1,266 +0,0 @@ -//! Keyboard key IDs. -//! -//! See glfw.setKeyCallback for how these are used. -//! -//! These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), but re-arranged to -//! map to 7-bit ASCII for printable keys (function keys are put in the 256+ range). -//! -//! The naming of the key codes follow these rules: -//! -//! - The US keyboard layout is used -//! - Names of printable alphanumeric characters are used (e.g. "a", "r", "three", etc.) -//! - For non-alphanumeric characters, Unicode:ish names are used (e.g. "comma", "left_bracket", -//! etc.). Note that some names do not correspond to the Unicode standard (usually for brevity) -//! - Keys that lack a clear US mapping are named "world_x" -//! - For non-printable keys, custom names are used (e.g. "F4", "backspace", etc.) - -const std = @import("std"); - -const cc = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// enum containing all glfw keys -pub const Key = enum(c_int) { - /// The unknown key - unknown = cc.GLFW_KEY_UNKNOWN, - - /// Printable keys - space = cc.GLFW_KEY_SPACE, - apostrophe = cc.GLFW_KEY_APOSTROPHE, - comma = cc.GLFW_KEY_COMMA, - minus = cc.GLFW_KEY_MINUS, - period = cc.GLFW_KEY_PERIOD, - slash = cc.GLFW_KEY_SLASH, - zero = cc.GLFW_KEY_0, - one = cc.GLFW_KEY_1, - two = cc.GLFW_KEY_2, - three = cc.GLFW_KEY_3, - four = cc.GLFW_KEY_4, - five = cc.GLFW_KEY_5, - six = cc.GLFW_KEY_6, - seven = cc.GLFW_KEY_7, - eight = cc.GLFW_KEY_8, - nine = cc.GLFW_KEY_9, - semicolon = cc.GLFW_KEY_SEMICOLON, - equal = cc.GLFW_KEY_EQUAL, - a = cc.GLFW_KEY_A, - b = cc.GLFW_KEY_B, - c = cc.GLFW_KEY_C, - d = cc.GLFW_KEY_D, - e = cc.GLFW_KEY_E, - f = cc.GLFW_KEY_F, - g = cc.GLFW_KEY_G, - h = cc.GLFW_KEY_H, - i = cc.GLFW_KEY_I, - j = cc.GLFW_KEY_J, - k = cc.GLFW_KEY_K, - l = cc.GLFW_KEY_L, - m = cc.GLFW_KEY_M, - n = cc.GLFW_KEY_N, - o = cc.GLFW_KEY_O, - p = cc.GLFW_KEY_P, - q = cc.GLFW_KEY_Q, - r = cc.GLFW_KEY_R, - s = cc.GLFW_KEY_S, - t = cc.GLFW_KEY_T, - u = cc.GLFW_KEY_U, - v = cc.GLFW_KEY_V, - w = cc.GLFW_KEY_W, - x = cc.GLFW_KEY_X, - y = cc.GLFW_KEY_Y, - z = cc.GLFW_KEY_Z, - left_bracket = cc.GLFW_KEY_LEFT_BRACKET, - backslash = cc.GLFW_KEY_BACKSLASH, - right_bracket = cc.GLFW_KEY_RIGHT_BRACKET, - grave_accent = cc.GLFW_KEY_GRAVE_ACCENT, - world_1 = cc.GLFW_KEY_WORLD_1, // non-US #1 - world_2 = cc.GLFW_KEY_WORLD_2, // non-US #2 - - // Function keys - escape = cc.GLFW_KEY_ESCAPE, - enter = cc.GLFW_KEY_ENTER, - tab = cc.GLFW_KEY_TAB, - backspace = cc.GLFW_KEY_BACKSPACE, - insert = cc.GLFW_KEY_INSERT, - delete = cc.GLFW_KEY_DELETE, - right = cc.GLFW_KEY_RIGHT, - left = cc.GLFW_KEY_LEFT, - down = cc.GLFW_KEY_DOWN, - up = cc.GLFW_KEY_UP, - page_up = cc.GLFW_KEY_PAGE_UP, - page_down = cc.GLFW_KEY_PAGE_DOWN, - home = cc.GLFW_KEY_HOME, - end = cc.GLFW_KEY_END, - caps_lock = cc.GLFW_KEY_CAPS_LOCK, - scroll_lock = cc.GLFW_KEY_SCROLL_LOCK, - num_lock = cc.GLFW_KEY_NUM_LOCK, - print_screen = cc.GLFW_KEY_PRINT_SCREEN, - pause = cc.GLFW_KEY_PAUSE, - F1 = cc.GLFW_KEY_F1, - F2 = cc.GLFW_KEY_F2, - F3 = cc.GLFW_KEY_F3, - F4 = cc.GLFW_KEY_F4, - F5 = cc.GLFW_KEY_F5, - F6 = cc.GLFW_KEY_F6, - F7 = cc.GLFW_KEY_F7, - F8 = cc.GLFW_KEY_F8, - F9 = cc.GLFW_KEY_F9, - F10 = cc.GLFW_KEY_F10, - F11 = cc.GLFW_KEY_F11, - F12 = cc.GLFW_KEY_F12, - F13 = cc.GLFW_KEY_F13, - F14 = cc.GLFW_KEY_F14, - F15 = cc.GLFW_KEY_F15, - F16 = cc.GLFW_KEY_F16, - F17 = cc.GLFW_KEY_F17, - F18 = cc.GLFW_KEY_F18, - F19 = cc.GLFW_KEY_F19, - F20 = cc.GLFW_KEY_F20, - F21 = cc.GLFW_KEY_F21, - F22 = cc.GLFW_KEY_F22, - F23 = cc.GLFW_KEY_F23, - F24 = cc.GLFW_KEY_F24, - F25 = cc.GLFW_KEY_F25, - kp_0 = cc.GLFW_KEY_KP_0, - kp_1 = cc.GLFW_KEY_KP_1, - kp_2 = cc.GLFW_KEY_KP_2, - kp_3 = cc.GLFW_KEY_KP_3, - kp_4 = cc.GLFW_KEY_KP_4, - kp_5 = cc.GLFW_KEY_KP_5, - kp_6 = cc.GLFW_KEY_KP_6, - kp_7 = cc.GLFW_KEY_KP_7, - kp_8 = cc.GLFW_KEY_KP_8, - kp_9 = cc.GLFW_KEY_KP_9, - kp_decimal = cc.GLFW_KEY_KP_DECIMAL, - kp_divide = cc.GLFW_KEY_KP_DIVIDE, - kp_multiply = cc.GLFW_KEY_KP_MULTIPLY, - kp_subtract = cc.GLFW_KEY_KP_SUBTRACT, - kp_add = cc.GLFW_KEY_KP_ADD, - kp_enter = cc.GLFW_KEY_KP_ENTER, - kp_equal = cc.GLFW_KEY_KP_EQUAL, - left_shift = cc.GLFW_KEY_LEFT_SHIFT, - left_control = cc.GLFW_KEY_LEFT_CONTROL, - left_alt = cc.GLFW_KEY_LEFT_ALT, - left_super = cc.GLFW_KEY_LEFT_SUPER, - right_shift = cc.GLFW_KEY_RIGHT_SHIFT, - right_control = cc.GLFW_KEY_RIGHT_CONTROL, - right_alt = cc.GLFW_KEY_RIGHT_ALT, - right_super = cc.GLFW_KEY_RIGHT_SUPER, - menu = cc.GLFW_KEY_MENU, - - pub inline fn last() Key { - return @as(Key, @enumFromInt(cc.GLFW_KEY_LAST)); - } - - /// Returns the layout-specific name of the specified printable key. - /// - /// This function returns the name of the specified printable key, encoded as UTF-8. This is - /// typically the character that key would produce without any modifier keys, intended for - /// displaying key bindings to the user. For dead keys, it is typically the diacritic it would add - /// to a character. - /// - /// __Do not use this function__ for text input (see input_char). You will break text input for many - /// languages even if it happens to work for yours. - /// - /// If the key is `glfw.key.unknown`, the scancode is used to identify the key, otherwise the - /// scancode is ignored. If you specify a non-printable key, or `glfw.key.unknown` and a scancode - /// that maps to a non-printable key, this function returns null but does not emit an error. - /// - /// This behavior allows you to always pass in the arguments in the key callback (see input_key) - /// without modification. - /// - /// The printable keys are: - /// - /// - `glfw.Key.apostrophe` - /// - `glfw.Key.comma` - /// - `glfw.Key.minus` - /// - `glfw.Key.period` - /// - `glfw.Key.slash` - /// - `glfw.Key.semicolon` - /// - `glfw.Key.equal` - /// - `glfw.Key.left_bracket` - /// - `glfw.Key.right_bracket` - /// - `glfw.Key.backslash` - /// - `glfw.Key.world_1` - /// - `glfw.Key.world_2` - /// - `glfw.Key.0` to `glfw.key.9` - /// - `glfw.Key.a` to `glfw.key.z` - /// - `glfw.Key.kp_0` to `glfw.key.kp_9` - /// - `glfw.Key.kp_decimal` - /// - `glfw.Key.kp_divide` - /// - `glfw.Key.kp_multiply` - /// - `glfw.Key.kp_subtract` - /// - `glfw.Key.kp_add` - /// - `glfw.Key.kp_equal` - /// - /// Names for printable keys depend on keyboard layout, while names for non-printable keys are the - /// same across layouts but depend on the application language and should be localized along with - /// other user interface text. - /// - /// @param[in] key The key to query, or `glfw.key.unknown`. - /// @param[in] scancode The scancode of the key to query. - /// @return The UTF-8 encoded, layout-specific name of the key, or null. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// Also returns null in the event of an error. - /// - /// The contents of the returned string may change when a keyboard layout change event is received. - /// - /// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it - /// yourself. It is valid until the library is terminated. - /// - /// @thread_safety This function must only be called from the main thread. - /// - /// see also: input_key_name - pub inline fn getName(self: Key, scancode: i32) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = cc.glfwGetKeyName(@intFromEnum(self), @as(c_int, @intCast(scancode))); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; - } - - /// Returns the platform-specific scancode of the specified key. - /// - /// This function returns the platform-specific scancode of the specified key. - /// - /// If the key is `glfw.key.UNKNOWN` or does not exist on the keyboard this method will return `-1`. - /// - /// @param[in] key Any named key (see keys). - /// @return The platform-specific scancode for the key. - /// - /// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. - /// Additionally returns -1 in the event of an error. - /// - /// @thread_safety This function may be called from any thread. - pub inline fn getScancode(self: Key) i32 { - internal_debug.assertInitialized(); - return cc.glfwGetKeyScancode(@intFromEnum(self)); - } -}; - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.Key.a.getName(0); -} - -test "getScancode" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.Key.a.getScancode(); -} diff --git a/pkg/glfw/main.zig b/pkg/glfw/main.zig deleted file mode 100644 index 329f17ed2..000000000 --- a/pkg/glfw/main.zig +++ /dev/null @@ -1,586 +0,0 @@ -const std = @import("std"); -const testing = std.testing; - -const c = @import("c.zig").c; - -const key = @import("key.zig"); -const errors = @import("errors.zig"); - -/// Possible value for various window hints, etc. -pub const dont_care = c.GLFW_DONT_CARE; - -pub const getError = errors.getError; -pub const mustGetError = errors.mustGetError; -pub const getErrorCode = errors.getErrorCode; -pub const mustGetErrorCode = errors.mustGetErrorCode; -pub const getErrorString = errors.getErrorString; -pub const mustGetErrorString = errors.mustGetErrorString; -pub const setErrorCallback = errors.setErrorCallback; -pub const clearError = errors.clearError; -pub const ErrorCode = errors.ErrorCode; -pub const Error = errors.Error; - -pub const Action = @import("action.zig").Action; -pub const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis; -pub const GamepadButton = @import("gamepad_button.zig").GamepadButton; -pub const gamepad_axis = @import("gamepad_axis.zig"); -pub const gamepad_button = @import("gamepad_button.zig"); -pub const GammaRamp = @import("GammaRamp.zig"); -pub const Image = @import("Image.zig"); -pub const Joystick = @import("Joystick.zig"); -pub const Monitor = @import("Monitor.zig"); -pub const mouse_button = @import("mouse_button.zig"); -pub const MouseButton = mouse_button.MouseButton; -pub const version = @import("version.zig"); -pub const VideoMode = @import("VideoMode.zig"); -pub const Window = @import("Window.zig"); -pub const Cursor = @import("Cursor.zig"); -pub const Native = @import("native.zig").Native; -pub const BackendOptions = @import("native.zig").BackendOptions; -pub const Key = key.Key; -pub const setClipboardString = @import("clipboard.zig").setClipboardString; -pub const getClipboardString = @import("clipboard.zig").getClipboardString; -pub const makeContextCurrent = @import("opengl.zig").makeContextCurrent; -pub const getCurrentContext = @import("opengl.zig").getCurrentContext; -pub const swapInterval = @import("opengl.zig").swapInterval; -pub const extensionSupported = @import("opengl.zig").extensionSupported; -pub const GLProc = @import("opengl.zig").GLProc; -pub const getProcAddress = @import("opengl.zig").getProcAddress; -pub const getTime = @import("time.zig").getTime; -pub const setTime = @import("time.zig").setTime; -pub const getTimerValue = @import("time.zig").getTimerValue; -pub const getTimerFrequency = @import("time.zig").getTimerFrequency; -pub const Hat = @import("hat.zig").Hat; -pub const RawHat = @import("hat.zig").RawHat; -pub const Mods = @import("mod.zig").Mods; -pub const RawMods = @import("mod.zig").RawMods; - -const internal_debug = @import("internal_debug.zig"); - -/// If GLFW was already initialized in your program, e.g. you are embedding Zig code into an existing -/// program that has already called glfwInit via the C API for you - then you need to tell mach/glfw -/// that it has in fact been initialized already, otherwise when you call other methods mach/glfw -/// would panic thinking glfw.init has not been called yet. -pub fn assumeInitialized() void { - internal_debug.assumeInitialized(); -} - -/// Initializes the GLFW library. -/// -/// This function initializes the GLFW library. Before most GLFW functions can be used, GLFW must -/// be initialized, and before an application terminates GLFW should be terminated in order to free -/// any resources allocated during or after initialization. -/// -/// If this function fails, it calls glfw.Terminate before returning. If it succeeds, you should -/// call glfw.Terminate before the application exits. -/// -/// Additional calls to this function after successful initialization but before termination will -/// return immediately with no error. -/// -/// The glfw.InitHint.platform init hint controls which platforms are considered during -/// initialization. This also depends on which platforms the library was compiled to support. -/// -/// macos: This function will change the current directory of the application to the -/// `Contents/Resources` subdirectory of the application's bundle, if present. This can be disabled -/// with `glfw.InitHint.cocoa_chdir_resources`. -/// -/// macos: This function will create the main menu and dock icon for the application. If GLFW finds -/// a `MainMenu.nib` it is loaded and assumed to contain a menu bar. Otherwise a minimal menu bar is -/// created manually with common commands like Hide, Quit and About. The About entry opens a minimal -/// about dialog with information from the application's bundle. The menu bar and dock icon can be -/// disabled entirely with `glfw.InitHint.cocoa_menubar`. -/// -/// x11: This function will set the `LC_CTYPE` category of the application locale according to the -/// current environment if that category is still "C". This is because the "C" locale breaks -/// Unicode text input. -/// -/// Possible errors include glfw.ErrorCode.PlatformUnavailable, glfw.ErrorCode.PlatformError. -/// Returns a bool indicating success. -/// -/// @thread_safety This function must only be called from the main thread. -pub inline fn init(hints: InitHints) bool { - internal_debug.toggleInitialized(); - internal_debug.assertInitialized(); - errdefer { - internal_debug.assertInitialized(); - internal_debug.toggleInitialized(); - } - - inline for (comptime std.meta.fieldNames(InitHints)) |field_name| { - const init_hint = @field(InitHint, field_name); - const init_value = @field(hints, field_name); - if (@TypeOf(init_value) == PlatformType) { - initHint(init_hint, @intFromEnum(init_value)); - } else { - initHint(init_hint, init_value); - } - } - - return c.glfwInit() == c.GLFW_TRUE; -} - -// TODO: implement custom allocator support -// -// /*! @brief Sets the init allocator to the desired value. -// * -// * To use the default allocator, call this function with a `NULL` argument. -// * -// * If you specify an allocator struct, every member must be a valid function -// * pointer. If any member is `NULL`, this function emits @ref -// * GLFW_INVALID_VALUE and the init allocator is unchanged. -// * -// * @param[in] allocator The allocator to use at the next initialization, or -// * `NULL` to use the default one. -// * -// * @errors Possible errors include @ref GLFW_INVALID_VALUE. -// * -// * @pointer_lifetime The specified allocator is copied before this function -// * returns. -// * -// * @thread_safety This function must only be called from the main thread. -// * -// * @sa @ref init_allocator -// * @sa @ref glfwInit -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// GLFWAPI void glfwInitAllocator(const GLFWallocator* allocator); - -/// Terminates the GLFW library. -/// -/// This function destroys all remaining windows and cursors, restores any modified gamma ramps -/// and frees any other allocated resources. Once this function is called, you must again call -/// glfw.init successfully before you will be able to use most GLFW functions. -/// -/// If GLFW has been successfully initialized, this function should be called before the -/// application exits. If initialization fails, there is no need to call this function, as it is -/// called by glfw.init before it returns failure. -/// -/// This function has no effect if GLFW is not initialized. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// warning: The contexts of any remaining windows must not be current on any other thread when -/// this function is called. -/// -/// reentrancy: This function must not be called from a callback. -/// -/// thread_safety: This function must only be called from the main thread. -pub inline fn terminate() void { - internal_debug.assertInitialized(); - internal_debug.toggleInitialized(); - c.glfwTerminate(); -} - -/// Initialization hints for passing into glfw.init -pub const InitHints = struct { - /// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier - /// versions of GLFW that did not have glfwGetJoystickHats. - joystick_hat_buttons: bool = true, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// Specifies whether to set the current directory to the application to the Contents/Resources - /// subdirectory of the application's bundle, if present. - cocoa_chdir_resources: bool = true, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// specifies whether to create a basic menu bar, either from a nib or manually, when the first - /// window is created, which is when AppKit is initialized. - cocoa_menubar: bool = true, - - /// Platform selection init hint. - /// - /// Possible values are `PlatformType` enums. - platform: PlatformType = .any, -}; - -/// Initialization hints for passing into glfw.initHint -const InitHint = enum(c_int) { - /// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier - /// versions of GLFW that did not have glfwGetJoystickHats. - /// - /// Possible values are `true` and `false`. - joystick_hat_buttons = c.GLFW_JOYSTICK_HAT_BUTTONS, - - /// ANGLE rendering backend init hint. - /// - /// Possible values are `AnglePlatformType` enums. - angle_platform_type = c.GLFW_ANGLE_PLATFORM_TYPE, - - /// Platform selection init hint. - /// - /// Possible values are `PlatformType` enums. - platform = c.GLFW_PLATFORM, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// Specifies whether to set the current directory to the application to the Contents/Resources - /// subdirectory of the application's bundle, if present. - /// - /// Possible values are `true` and `false`. - cocoa_chdir_resources = c.GLFW_COCOA_CHDIR_RESOURCES, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// specifies whether to create a basic menu bar, either from a nib or manually, when the first - /// window is created, which is when AppKit is initialized. - /// - /// Possible values are `true` and `false`. - cocoa_menubar = c.GLFW_COCOA_MENUBAR, - - /// X11 specific init hint. - x11_xcb_vulkan_surface = c.GLFW_X11_XCB_VULKAN_SURFACE, - - /// Wayland specific init hint. - /// - /// Possible values are `WaylandLibdecorInitHint` enums. - wayland_libdecor = c.GLFW_WAYLAND_LIBDECOR, -}; - -/// Angle platform type hints for glfw.InitHint.angle_platform_type -pub const AnglePlatformType = enum(c_int) { - none = c.GLFW_ANGLE_PLATFORM_TYPE_NONE, - opengl = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGL, - opengles = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGLES, - d3d9 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D9, - d3d11 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D11, - vulkan = c.GLFW_ANGLE_PLATFORM_TYPE_VULKAN, - metal = c.GLFW_ANGLE_PLATFORM_TYPE_METAL, -}; - -/// Wayland libdecor hints for glfw.InitHint.wayland_libdecor -/// -/// libdecor is important for GNOME, since GNOME does not implement server side decorations on -/// wayland. libdecor is loaded dynamically at runtime, so in general enabling it is always -/// safe to do. It is enabled by default. -pub const WaylandLibdecorInitHint = enum(c_int) { - wayland_prefer_libdecor = c.GLFW_WAYLAND_PREFER_LIBDECOR, - wayland_disable_libdecor = c.GLFW_WAYLAND_DISABLE_LIBDECOR, -}; - -/// Platform type hints for glfw.InitHint.platform -pub const PlatformType = enum(c_int) { - /// Enables automatic platform detection. - /// Will default to X11 on wayland. - any = c.GLFW_ANY_PLATFORM, - win32 = c.GLFW_PLATFORM_WIN32, - cocoa = c.GLFW_PLATFORM_COCOA, - wayland = c.GLFW_PLATFORM_WAYLAND, - x11 = c.GLFW_PLATFORM_X11, - null = c.GLFW_PLATFORM_NULL, -}; - -/// Sets the specified init hint to the desired value. -/// -/// This function sets hints for the next initialization of GLFW. -/// -/// The values you set hints to are never reset by GLFW, but they only take effect during -/// initialization. Once GLFW has been initialized, any values you set will be ignored until the -/// library is terminated and initialized again. -/// -/// Some hints are platform specific. These may be set on any platform but they will only affect -/// their specific platform. Other platforms will ignore them. Setting these hints requires no -/// platform specific headers or functions. -/// -/// @param hint: The init hint to set. -/// @param value: The new value of the init hint. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.InvalidValue. -/// -/// @remarks This function may be called before glfw.init. -/// -/// @thread_safety This function must only be called from the main thread. -fn initHint(hint: InitHint, value: anytype) void { - switch (@import("shims.zig").typeInfo(@TypeOf(value))) { - .int, .comptime_int => { - c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(value))); - }, - .bool => c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(@intFromBool(value)))), - else => @compileError("expected a int or bool, got " ++ @typeName(@TypeOf(value))), - } -} - -/// Returns a string describing the compile-time configuration. -/// -/// This function returns the compile-time generated version string of the GLFW library binary. It -/// describes the version, platform, compiler and any platform or operating system specific -/// compile-time options. It should not be confused with the OpenGL or OpenGL ES version string, -/// queried with `glGetString`. -/// -/// __Do not use the version string__ to parse the GLFW library version. Use the glfw.version -/// constants instead. -/// -/// __Do not use the version string__ to parse what platforms are supported. The -/// `glfw.platformSupported` function lets you query platform support. -/// -/// returns: The ASCII encoded GLFW version string. -/// -/// remark: This function may be called before @ref glfw.Init. -/// -/// pointer_lifetime: The returned string is static and compile-time generated. -/// -/// thread_safety: This function may be called from any thread. -pub inline fn getVersionString() [:0]const u8 { - return std.mem.span(@as([*:0]const u8, @ptrCast(c.glfwGetVersionString()))); -} - -/// Returns the currently selected platform. -/// -/// This function returns the platform that was selected during initialization. The returned value -/// will be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`, -/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`. -/// -/// thread_safety: This function may be called from any thread. -pub fn getPlatform() PlatformType { - internal_debug.assertInitialized(); - return @as(PlatformType, @enumFromInt(c.glfwGetPlatform())); -} - -/// Returns whether the library includes support for the specified platform. -/// -/// This function returns whether the library was compiled with support for the specified platform. -/// The platform must be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`, -/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`. -/// -/// remark: This function may be called before glfw.Init. -/// -/// thread_safety: This function may be called from any thread. -pub fn platformSupported(platform: PlatformType) bool { - internal_debug.assertInitialized(); - return c.glfwPlatformSupported(@intFromEnum(platform)) == c.GLFW_TRUE; -} - -/// Processes all pending events. -/// -/// This function processes only those events that are already in the event queue and then returns -/// immediately. Processing events will cause the window and input callbacks associated with those -/// events to be called. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout -pub inline fn pollEvents() void { - internal_debug.assertInitialized(); - c.glfwPollEvents(); -} - -/// Waits until events are queued and processes them. -/// -/// This function puts the calling thread to sleep until at least one event is available in the -/// event queue. Once one or more events are available, it behaves exactly like glfw.pollEvents, -/// i.e. the events in the queue are processed and the function then returns immediately. -/// Processing events will cause the window and input callbacks associated with those events to be -/// called. -/// -/// Since not all events are associated with callbacks, this function may return without a callback -/// having been called even if you are monitoring all callbacks. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.pollEvents, glfw.waitEventsTimeout -pub inline fn waitEvents() void { - internal_debug.assertInitialized(); - c.glfwWaitEvents(); -} - -/// Waits with timeout until events are queued and processes them. -/// -/// This function puts the calling thread to sleep until at least one event is available in the -/// event queue, or until the specified timeout is reached. If one or more events are available, it -/// behaves exactly like glfw.pollEvents, i.e. the events in the queue are processed and the -/// function then returns immediately. Processing events will cause the window and input callbacks -/// associated with those events to be called. -/// -/// The timeout value must be a positive finite number. -/// -/// Since not all events are associated with callbacks, this function may return without a callback -/// having been called even if you are monitoring all callbacks. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// @param[in] timeout The maximum amount of time, in seconds, to wait. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.pollEvents, glfw.waitEvents -pub inline fn waitEventsTimeout(timeout: f64) void { - internal_debug.assertInitialized(); - std.debug.assert(!std.math.isNan(timeout)); - std.debug.assert(timeout >= 0); - std.debug.assert(timeout <= std.math.floatMax(f64)); - c.glfwWaitEventsTimeout(timeout); -} - -/// Posts an empty event to the event queue. -/// -/// This function posts an empty event from the current thread to the event queue, causing -/// glfw.waitEvents or glfw.waitEventsTimeout to return. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout -pub inline fn postEmptyEvent() void { - internal_debug.assertInitialized(); - c.glfwPostEmptyEvent(); -} - -/// Returns whether raw mouse motion is supported. -/// -/// This function returns whether raw mouse motion is supported on the current system. This status -/// does not change after GLFW has been initialized so you only need to check this once. If you -/// attempt to enable raw motion on a system that does not support it, glfw.ErrorCode.PlatformError -/// will be emitted. -/// -/// Raw mouse motion is closer to the actual motion of the mouse across a surface. It is not -/// affected by the scaling and acceleration applied to the motion of the desktop cursor. That -/// processing is suitable for a cursor while raw motion is better for controlling for example a 3D -/// camera. Because of this, raw mouse motion is only provided when the cursor is disabled. -/// -/// @return `true` if raw mouse motion is supported on the current machine, or `false` otherwise. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: raw_mouse_motion, glfw.setInputMode -pub inline fn rawMouseMotionSupported() bool { - internal_debug.assertInitialized(); - return c.glfwRawMouseMotionSupported() == c.GLFW_TRUE; -} - -pub fn basicTest() !void { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const start = std.time.milliTimestamp(); - while (std.time.milliTimestamp() < start + 1000 and !window.shouldClose()) { - c.glfwPollEvents(); - } -} - -test { - std.testing.refAllDeclsRecursive(@This()); -} - -test "getVersionString" { - std.debug.print("\nGLFW version v{}.{}.{}\n", .{ version.major, version.minor, version.revision }); - std.debug.print("\nstring: {s}\n", .{getVersionString()}); -} - -test "init" { - _ = init(.{ .cocoa_chdir_resources = true }); - if (getErrorString()) |err| { - std.log.err("failed to initialize GLFW: {?s}", .{err}); - std.process.exit(1); - } - defer terminate(); -} - -test "pollEvents" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - pollEvents(); -} - -test "waitEventsTimeout" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - waitEventsTimeout(0.25); -} - -test "postEmptyEvent_and_waitEvents" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - postEmptyEvent(); - waitEvents(); -} - -test "rawMouseMotionSupported" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - _ = rawMouseMotionSupported(); -} - -test "basic" { - try basicTest(); -} diff --git a/pkg/glfw/mod.zig b/pkg/glfw/mod.zig deleted file mode 100644 index aa9c9371f..000000000 --- a/pkg/glfw/mod.zig +++ /dev/null @@ -1,167 +0,0 @@ -//! Modifier key flags -//! -//! See glfw.setKeyCallback for how these are used. - -const c = @import("c.zig").c; - -// must be in sync with GLFW C constants in modifier group, search for "@defgroup mods Modifier key flags" -/// A bitmask of all key modifiers -pub const Mods = packed struct(u8) { - shift: bool = false, - control: bool = false, - alt: bool = false, - super: bool = false, - caps_lock: bool = false, - num_lock: bool = false, - _padding: u2 = 0, - - inline fn verifyIntType(comptime IntType: type) void { - comptime { - switch (@import("shims.zig").typeInfo(IntType)) { - .int => {}, - else => @compileError("Int was not of int type"), - } - } - } - - pub inline fn toInt(self: Mods, comptime IntType: type) IntType { - verifyIntType(IntType); - return @as(IntType, @intCast(@as(u8, @bitCast(self)))); - } - - pub inline fn fromInt(flags: anytype) Mods { - verifyIntType(@TypeOf(flags)); - return @as(Mods, @bitCast(@as(u8, @intCast(flags)))); - } -}; - -/// Holds all GLFW mod values in their raw form. -pub const RawMods = struct { - /// If this bit is set one or more Shift keys were held down. - pub const shift = c.GLFW_MOD_SHIFT; - - /// If this bit is set one or more Control keys were held down. - pub const control = c.GLFW_MOD_CONTROL; - - /// If this bit is set one or more Alt keys were held down. - pub const alt = c.GLFW_MOD_ALT; - - /// If this bit is set one or more Super keys were held down. - pub const super = c.GLFW_MOD_SUPER; - - /// If this bit is set the Caps Lock key is enabled and the glfw.lock_key_mods input mode is set. - pub const caps_lock = c.GLFW_MOD_CAPS_LOCK; - - /// If this bit is set the Num Lock key is enabled and the glfw.lock_key_mods input mode is set. - pub const num_lock = c.GLFW_MOD_NUM_LOCK; -}; - -test "shift int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "shift int and alt to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift | RawMods.alt; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == true); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "super int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.super; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == false); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == true); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "num lock int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.num_lock; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == false); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == true); -} - -test "all int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift | RawMods.control | - RawMods.alt | RawMods.super | - RawMods.caps_lock | RawMods.num_lock; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == true); - try std.testing.expect(mod.alt == true); - try std.testing.expect(mod.super == true); - try std.testing.expect(mod.caps_lock == true); - try std.testing.expect(mod.num_lock == true); -} - -test "shift bitmask to int" { - const std = @import("std"); - - const mod = Mods{ .shift = true }; - const int_mod = mod.toInt(c_int); - - try std.testing.expectEqual(int_mod, RawMods.shift); -} - -test "shift and alt bitmask to int" { - const std = @import("std"); - - const mod = Mods{ .shift = true, .alt = true }; - const int_mod = mod.toInt(c_int); - - try std.testing.expectEqual(int_mod, RawMods.shift | RawMods.alt); -} - -test "all bitmask to int" { - const std = @import("std"); - - const mod = Mods{ - .shift = true, - .control = true, - .alt = true, - .super = true, - .caps_lock = true, - .num_lock = true, - }; - const int_mod = mod.toInt(c_int); - - const expected = RawMods.shift | RawMods.control | - RawMods.alt | RawMods.super | - RawMods.caps_lock | RawMods.num_lock; - - try std.testing.expectEqual(int_mod, expected); -} diff --git a/pkg/glfw/mouse_button.zig b/pkg/glfw/mouse_button.zig deleted file mode 100644 index 847049f5e..000000000 --- a/pkg/glfw/mouse_button.zig +++ /dev/null @@ -1,23 +0,0 @@ -const c = @import("c.zig").c; - -/// Mouse button IDs. -/// -/// See glfw.setMouseButtonCallback for how these are used. -pub const MouseButton = enum(c_int) { - // We use left/right/middle aliases here because those are more common and we cannot have - // duplicate values in a Zig enum. - left = c.GLFW_MOUSE_BUTTON_1, - right = c.GLFW_MOUSE_BUTTON_2, - middle = c.GLFW_MOUSE_BUTTON_3, - four = c.GLFW_MOUSE_BUTTON_4, - five = c.GLFW_MOUSE_BUTTON_5, - six = c.GLFW_MOUSE_BUTTON_6, - seven = c.GLFW_MOUSE_BUTTON_7, - eight = c.GLFW_MOUSE_BUTTON_8, -}; - -/// Not in the MouseButton enumeration as it is a duplicate value which is forbidden. -pub const last = MouseButton.eight; -pub const one = MouseButton.left; -pub const two = MouseButton.right; -pub const three = MouseButton.middle; diff --git a/pkg/glfw/native.zig b/pkg/glfw/native.zig deleted file mode 100644 index 6b8f1831a..000000000 --- a/pkg/glfw/native.zig +++ /dev/null @@ -1,393 +0,0 @@ -//! Native access functions -const std = @import("std"); - -const Window = @import("Window.zig"); -const Monitor = @import("Monitor.zig"); - -const internal_debug = @import("internal_debug.zig"); - -pub const BackendOptions = struct { - win32: bool = false, - wgl: bool = false, - cocoa: bool = false, - nsgl: bool = false, - x11: bool = false, - glx: bool = false, - wayland: bool = false, - egl: bool = false, - osmesa: bool = false, -}; - -/// This function returns a type which allows provides an interface to access -/// the native handles based on backends selected. -/// -/// The available window API options are: -/// * win32 -/// * cocoa -/// * x11 -/// * wayland -/// -/// The available context API options are: -/// -/// * wgl -/// * nsgl -/// * glx -/// * egl -/// * osmesa -/// -/// The chosen backends must match those the library was compiled for. Failure to do so -/// will cause a link-time error. -pub fn Native(comptime options: BackendOptions) type { - const native = @cImport({ - // @cDefine("GLFW_INCLUDE_VULKAN", "1"); - @cDefine("GLFW_INCLUDE_NONE", "1"); - if (options.win32) @cDefine("GLFW_EXPOSE_NATIVE_WIN32", "1"); - if (options.wgl) @cDefine("GLFW_EXPOSE_NATIVE_WGL", "1"); - if (options.cocoa) @cDefine("GLFW_EXPOSE_NATIVE_COCOA", "1"); - if (options.nsgl) @cDefine("GLFW_EXPOSE_NATIVE_NGSL", "1"); - if (options.x11) @cDefine("GLFW_EXPOSE_NATIVE_X11", "1"); - if (options.glx) @cDefine("GLFW_EXPOSE_NATIVE_GLX", "1"); - if (options.wayland) @cDefine("GLFW_EXPOSE_NATIVE_WAYLAND", "1"); - if (options.egl) @cDefine("GLFW_EXPOSE_NATIVE_EGL", "1"); - if (options.osmesa) @cDefine("GLFW_EXPOSE_NATIVE_OSMESA", "1"); - @cDefine("__kernel_ptr_semantics", ""); - @cInclude("GLFW/glfw3.h"); - @cInclude("GLFW/glfw3native.h"); - }); - - return struct { - /// Returns the adapter device name of the specified monitor. - /// - /// return: The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`) of the - /// specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Adapter(monitor: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Adapter(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |adapter| return adapter; - // `glfwGetWin32Adapter` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the display device name of the specified monitor. - /// - /// return: The UTF-8 encoded display device name (for example `\\.\DISPLAY1\Monitor0`) - /// of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Monitor(monitor: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Monitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |mon| return mon; - // `glfwGetWin32Monitor` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `HWND` of the specified window. - /// - /// The `HDC` associated with the window can be queried with the - /// [GetDC](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc) - /// function. - /// ``` - /// const dc = std.os.windows.user32.GetDC(native.getWin32Window(window)); - /// ``` - /// This DC is private and does not need to be released. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Window(window: Window) std.os.windows.HWND { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Window(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |win| - return @as(std.os.windows.HWND, @ptrCast(win)); - // `glfwGetWin32Window` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `HGLRC` of the specified window. - /// - /// The `HDC` associated with the window can be queried with the - /// [GetDC](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc) - /// function. - /// ``` - /// const dc = std.os.windows.user32.GetDC(native.getWin32Window(window)); - /// ``` - /// This DC is private and does not need to be released. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext - /// null is returned in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWGLContext(window: Window) ?std.os.windows.HGLRC { - internal_debug.assertInitialized(); - if (native.glfwGetWGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return context; - return null; - } - - /// Returns the `CGDirectDisplayID` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getCocoaMonitor(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const mon = native.glfwGetCocoaMonitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle))); - if (mon != native.kCGNullDirectDisplay) return mon; - // `glfwGetCocoaMonitor` returns `kCGNullDirectDisplay` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `NSWindow` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getCocoaWindow(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - return native.glfwGetCocoaWindow(@as(*native.GLFWwindow, @ptrCast(window.handle))); - } - - /// Returns the `NSWindow` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getNSGLContext(window: Window) u32 { - internal_debug.assertInitialized(); - return native.glfwGetNSGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle))); - } - - /// Returns the `Display` used by GLFW. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Display() *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetX11Display()) |display| return @as(*anyopaque, @ptrCast(display)); - // `glfwGetX11Display` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `RRCrtc` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Adapter(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const adapter = native.glfwGetX11Adapter(@as(*native.GLFWMonitor, @ptrCast(monitor.handle))); - if (adapter != 0) return adapter; - // `glfwGetX11Adapter` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `RROutput` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Monitor(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const mon = native.glfwGetX11Monitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle))); - if (mon != 0) return mon; - // `glfwGetX11Monitor` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `Window` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Window(window: Window) u32 { - internal_debug.assertInitialized(); - const win = native.glfwGetX11Window(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (win != 0) return @as(u32, @intCast(win)); - // `glfwGetX11Window` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Sets the current primary selection to the specified string. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// - /// The specified string is copied before this function returns. - /// - /// thread_safety: This function must only be called from the main thread. - pub fn setX11SelectionString(string: [*:0]const u8) void { - internal_debug.assertInitialized(); - native.glfwSetX11SelectionString(string); - } - - /// Returns the contents of the current primary selection as a string. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// The returned string is allocated and freed by GLFW. You should not free it - /// yourself. It is valid until the next call to getX11SelectionString or - /// setX11SelectionString, or until the library is terminated. - /// - /// thread_safety: This function must only be called from the main thread. - pub fn getX11SelectionString() ?[*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetX11SelectionString()) |str| return str; - return null; - } - - /// Returns the `GLXContext` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getGLXContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetGLXContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return @as(*anyopaque, @ptrCast(context)); - return null; - } - - /// Returns the `GLXWindow` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getGLXWindow(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const win = native.glfwGetGLXWindow(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (win != 0) return @as(*anyopaque, @ptrCast(win)); - return null; - } - - /// Returns the `*wl_display` used by GLFW. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandDisplay() *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandDisplay()) |display| return @as(*anyopaque, @ptrCast(display)); - // `glfwGetWaylandDisplay` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `*wl_output` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandMonitor(monitor: Monitor) *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandMonitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |mon| return @as(*anyopaque, @ptrCast(mon)); - // `glfwGetWaylandMonitor` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `*wl_surface` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandWindow(window: Window) *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandWindow(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |win| return @as(*anyopaque, @ptrCast(win)); - // `glfwGetWaylandWindow` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `EGLDisplay` used by GLFW. - /// - /// remark: Because EGL is initialized on demand, this function will return `EGL_NO_DISPLAY` - /// until the first context has been created via EGL. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getEGLDisplay() *anyopaque { - internal_debug.assertInitialized(); - const display = native.glfwGetEGLDisplay(); - if (display != native.EGL_NO_DISPLAY) return @as(*anyopaque, @ptrCast(display)); - // `glfwGetEGLDisplay` returns `EGL_NO_DISPLAY` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `EGLContext` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety This function may be called from any thread. Access is not synchronized. - pub fn getEGLContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const context = native.glfwGetEGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (context != native.EGL_NO_CONTEXT) return @as(*anyopaque, @ptrCast(context)); - return null; - } - - /// Returns the `EGLSurface` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NotInitalized and glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety This function may be called from any thread. Access is not synchronized. - pub fn getEGLSurface(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const surface = native.glfwGetEGLSurface(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (surface != native.EGL_NO_SURFACE) return @as(*anyopaque, @ptrCast(surface)); - return null; - } - - pub const OSMesaColorBuffer = struct { - width: c_int, - height: c_int, - format: c_int, - buffer: *anyopaque, - }; - - /// Retrieves the color buffer associated with the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaColorBuffer(window: Window) ?OSMesaColorBuffer { - internal_debug.assertInitialized(); - var buf: OSMesaColorBuffer = undefined; - if (native.glfwGetOSMesaColorBuffer( - @as(*native.GLFWwindow, @ptrCast(window.handle)), - &buf.width, - &buf.height, - &buf.format, - &buf.buffer, - ) == native.GLFW_TRUE) return buf; - return null; - } - - pub const OSMesaDepthBuffer = struct { - width: c_int, - height: c_int, - bytes_per_value: c_int, - buffer: *anyopaque, - }; - - /// Retrieves the depth buffer associated with the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaDepthBuffer(window: Window) ?OSMesaDepthBuffer { - internal_debug.assertInitialized(); - var buf: OSMesaDepthBuffer = undefined; - if (native.glfwGetOSMesaDepthBuffer( - @as(*native.GLFWwindow, @ptrCast(window.handle)), - &buf.width, - &buf.height, - &buf.bytes_per_value, - &buf.buffer, - ) == native.GLFW_TRUE) return buf; - return null; - } - - /// Returns the 'OSMesaContext' of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetOSMesaContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return @as(*anyopaque, @ptrCast(context)); - return null; - } - }; -} diff --git a/pkg/glfw/opengl.zig b/pkg/glfw/opengl.zig deleted file mode 100644 index 8fe2efbed..000000000 --- a/pkg/glfw/opengl.zig +++ /dev/null @@ -1,256 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); - -const internal_debug = @import("internal_debug.zig"); - -/// Makes the context of the specified window current for the calling thread. -/// -/// This function makes the OpenGL or OpenGL ES context of the specified window current on the -/// calling thread. A context must only be made current on a single thread at a time and each -/// thread can have only a single current context at a time. -/// -/// When moving a context between threads, you must make it non-current on the old thread before -/// making it current on the new one. -/// -/// By default, making a context non-current implicitly forces a pipeline flush. On machines that -/// support `GL_KHR_context_flush_control`, you can control whether a context performs this flush -/// by setting the glfw.context_release_behavior hint. -/// -/// The specified window must have an OpenGL or OpenGL ES context. Specifying a window without a -/// context will generate glfw.ErrorCode.NoWindowContext. -/// -/// @param[in] window The window whose context to make current, or null to -/// detach the current context. -/// -/// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_current, glfwGetCurrentContext -pub inline fn makeContextCurrent(window: ?Window) void { - internal_debug.assertInitialized(); - if (window) |w| c.glfwMakeContextCurrent(w.handle) else c.glfwMakeContextCurrent(null); -} - -/// Returns the window whose context is current on the calling thread. -/// -/// This function returns the window whose OpenGL or OpenGL ES context is current on the calling -/// thread. -/// -/// Returns he window whose context is current, or null if no window's context is current. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_current, glfwMakeContextCurrent -pub inline fn getCurrentContext() ?Window { - internal_debug.assertInitialized(); - if (c.glfwGetCurrentContext()) |handle| return .from(handle); - return null; -} - -/// Sets the swap interval for the current context. -/// -/// This function sets the swap interval for the current OpenGL or OpenGL ES context, i.e. the -/// number of screen updates to wait from the time glfw.SwapBuffers was called before swapping the -/// buffers and returning. This is sometimes called _vertical synchronization_, _vertical retrace -/// synchronization_ or just _vsync_. -/// -/// A context that supports either of the `WGL_EXT_swap_control_tear` and `GLX_EXT_swap_control_tear` -/// extensions also accepts _negative_ swap intervals, which allows the driver to swap immediately -/// even if a frame arrives a little bit late. You can check for these extensions with glfw.extensionSupported. -/// -/// A context must be current on the calling thread. Calling this function without a current context -/// will cause glfw.ErrorCode.NoCurrentContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see the present mode -/// of your swapchain instead. -/// -/// @param[in] interval The minimum number of screen updates to wait for until the buffers are -/// swapped by glfw.swapBuffers. -/// -/// Possible errors include glfw.ErrorCode.NoCurrentContext and glfw.ErrorCode.PlatformError. -/// -/// This function is not called during context creation, leaving the swap interval set to whatever -/// is the default for that API. This is done because some swap interval extensions used by -/// GLFW do not allow the swap interval to be reset to zero once it has been set to a non-zero -/// value. -/// -/// Some GPU drivers do not honor the requested swap interval, either because of a user setting -/// that overrides the application's request or due to bugs in the driver. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: buffer_swap, glfwSwapBuffers -pub inline fn swapInterval(interval: i32) void { - internal_debug.assertInitialized(); - c.glfwSwapInterval(@as(c_int, @intCast(interval))); -} - -/// Returns whether the specified extension is available. -/// -/// This function returns whether the specified API extension (see context_glext) is supported by -/// the current OpenGL or OpenGL ES context. It searches both for client API extension and context -/// creation API extensions. -/// -/// A context must be current on the calling thread. Calling this function without a current -/// context will cause glfw.ErrorCode.NoCurrentContext. -/// -/// As this functions retrieves and searches one or more extension strings each call, it is -/// recommended that you cache its results if it is going to be used frequently. The extension -/// strings will not change during the lifetime of a context, so there is no danger in doing this. -/// -/// This function does not apply to Vulkan. If you are using Vulkan, see glfw.getRequiredInstanceExtensions, -/// `vkEnumerateInstanceExtensionProperties` and `vkEnumerateDeviceExtensionProperties` instead. -/// -/// @param[in] extension The ASCII encoded name of the extension. -/// @return `true` if the extension is available, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.NoCurrentContext, glfw.ErrorCode.InvalidValue -/// and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_glext, glfw.getProcAddress -pub inline fn extensionSupported(extension: [:0]const u8) bool { - internal_debug.assertInitialized(); - - std.debug.assert(extension.len != 0); - std.debug.assert(extension[0] != 0); - - return c.glfwExtensionSupported(extension.ptr) == c.GLFW_TRUE; -} - -/// Client API function pointer type. -/// -/// Generic function pointer used for returning client API function pointers. -/// -/// see also: context_glext, glfwGetProcAddress -pub const GLProc = *const fn () callconv(if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) .Stdcall else .C) void; - -/// Returns the address of the specified function for the current context. -/// -/// This function returns the address of the specified OpenGL or OpenGL ES core or extension -/// function (see context_glext), if it is supported by the current context. -/// -/// A context must be current on the calling thread. Calling this function without a current -/// context will cause glfw.ErrorCode.NoCurrentContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see glfw.getInstanceProcAddress, -/// `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` instead. -/// -/// @param[in] procname The ASCII encoded name of the function. -/// @return The address of the function, or null if an error occurred. -/// -/// To maintain ABI compatability with the C glfwGetProcAddress, as it is commonly passed into -/// libraries expecting that exact ABI, this function does not return an error. Instead, if -/// glfw.ErrorCode.NotInitialized, glfw.ErrorCode.NoCurrentContext, or glfw.ErrorCode.PlatformError -/// would occur this function will panic. You should ensure a valid OpenGL context exists and the -/// GLFW is initialized before calling this function. -/// -/// The address of a given function is not guaranteed to be the same between contexts. -/// -/// This function may return a non-null address despite the associated version or extension -/// not being available. Always check the context version or extension string first. -/// -/// @pointer_lifetime The returned function pointer is valid until the context is destroyed or the -/// library is terminated. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_glext, glfwExtensionSupported -pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.c) ?GLProc { - internal_debug.assertInitialized(); - if (c.glfwGetProcAddress(proc_name)) |proc_address| return @ptrCast(proc_address); - return null; -} - -test "makeContextCurrent" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); -} - -test "getCurrentContext" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const current_context = glfw.getCurrentContext(); - std.debug.assert(current_context == null); -} - -test "swapInterval" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - glfw.swapInterval(1); -} - -test "getProcAddress" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - _ = glfw.getProcAddress("foobar"); -} - -test "extensionSupported" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - _ = glfw.extensionSupported("foobar"); -} diff --git a/pkg/glfw/shims.zig b/pkg/glfw/shims.zig deleted file mode 100644 index c731bb910..000000000 --- a/pkg/glfw/shims.zig +++ /dev/null @@ -1,84 +0,0 @@ -// Zig 0.14.0-dev changed the names of all 'std.builtin.Type' fields. -const old_std_builtin_type_field_names = @hasField(@import("std").builtin.Type, "Type"); - -pub const std = struct { - pub const builtin = struct { - pub const Type = if (old_std_builtin_type_field_names) union(enum) { - type: void, - void: void, - bool: void, - noreturn: void, - int: Int, - float: Float, - pointer: Pointer, - array: Array, - @"struct": Struct, - comptime_float: void, - comptime_int: void, - undefined: void, - null: void, - optional: Optional, - error_union: ErrorUnion, - error_set: ErrorSet, - @"enum": Enum, - @"union": Union, - @"fn": Fn, - @"opaque": Opaque, - frame: Frame, - @"anyframe": AnyFrame, - vector: Vector, - enum_literal: void, - - pub const Int = @import("std").builtin.Type.Int; - pub const Float = @import("std").builtin.Type.Float; - pub const Pointer = @import("std").builtin.Type.Pointer; - pub const Array = @import("std").builtin.Type.Array; - pub const ContainerLayout = @import("std").builtin.Type.ContainerLayout; - pub const StructField = @import("std").builtin.Type.StructField; - pub const Struct = @import("std").builtin.Type.Struct; - pub const Optional = @import("std").builtin.Type.Optional; - pub const ErrorUnion = @import("std").builtin.Type.ErrorUnion; - pub const Error = @import("std").builtin.Type.Error; - pub const ErrorSet = @import("std").builtin.Type.ErrorSet; - pub const EnumField = @import("std").builtin.Type.EnumField; - pub const Enum = @import("std").builtin.Type.Enum; - pub const UnionField = @import("std").builtin.Type.UnionField; - pub const Union = @import("std").builtin.Type.Union; - pub const Fn = @import("std").builtin.Type.Fn; - pub const Opaque = @import("std").builtin.Type.Opaque; - pub const Frame = @import("std").builtin.Type.Frame; - pub const AnyFrame = @import("std").builtin.Type.AnyFrame; - pub const Vector = @import("std").builtin.Type.Vector; - pub const Declaration = @import("std").builtin.Type.Declaration; - } else @import("std").builtin.Type; - }; -}; - -pub fn typeInfo(comptime T: type) std.builtin.Type { - return if (old_std_builtin_type_field_names) switch (@typeInfo(T)) { - .Type => .type, - .Void => .void, - .Bool => .bool, - .NoReturn => .noreturn, - .Int => |x| .{ .int = x }, - .Float => |x| .{ .float = x }, - .Pointer => |x| .{ .pointer = x }, - .Array => |x| .{ .array = x }, - .Struct => |x| .{ .@"struct" = x }, - .ComptimeFloat => .comptime_float, - .ComptimeInt => .comptime_int, - .Undefined => .undefined, - .Null => .null, - .Optional => |x| .{ .optional = x }, - .ErrorUnion => |x| .{ .error_union = x }, - .ErrorSet => |x| .{ .error_set = x }, - .Enum => |x| .{ .@"enum" = x }, - .Union => |x| .{ .@"union" = x }, - .Fn => |x| .{ .@"fn" = x }, - .Opaque => |x| .{ .@"opaque" = x }, - .Frame => |x| .{ .frame = x }, - .AnyFrame => .@"anyframe", - .Vector => |x| .{ .vector = x }, - .EnumLiteral => .enum_literal, - } else @typeInfo(T); -} diff --git a/pkg/glfw/time.zig b/pkg/glfw/time.zig deleted file mode 100644 index c3432b105..000000000 --- a/pkg/glfw/time.zig +++ /dev/null @@ -1,153 +0,0 @@ -const std = @import("std"); - -const c = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// Returns the GLFW time. -/// -/// This function returns the current GLFW time, in seconds. Unless the time -/// has been set using @ref glfwSetTime it measures time elapsed since GLFW was -/// initialized. -/// -/// This function and @ref glfwSetTime are helper functions on top of glfw.getTimerFrequency -/// and glfw.getTimerValue. -/// -/// The resolution of the timer is system dependent, but is usually on the order -/// of a few micro- or nanoseconds. It uses the highest-resolution monotonic -/// time source on each supported operating system. -/// -/// @return The current time, in seconds, or zero if an -/// error occurred. -/// -/// @thread_safety This function may be called from any thread. Reading and -/// writing of the internal base time is not atomic, so it needs to be -/// externally synchronized with calls to @ref glfwSetTime. -/// -/// see also: time -pub inline fn getTime() f64 { - internal_debug.assertInitialized(); - const time = c.glfwGetTime(); - if (time != 0) return time; - // `glfwGetTime` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -/// Sets the GLFW time. -/// -/// This function sets the current GLFW time, in seconds. The value must be a positive finite -/// number less than or equal to 18446744073.0, which is approximately 584.5 years. -/// -/// This function and @ref glfwGetTime are helper functions on top of glfw.getTimerFrequency and -/// glfw.getTimerValue. -/// -/// @param[in] time The new value, in seconds. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue. -/// -/// The upper limit of GLFW time is calculated as `floor((2^64 - 1) / 10^9)` and is due to -/// implementations storing nanoseconds in 64 bits. The limit may be increased in the future. -/// -/// @thread_safety This function may be called from any thread. Reading and writing of the internal -/// base time is not atomic, so it needs to be externally synchronized with calls to glfw.getTime. -/// -/// see also: time -pub inline fn setTime(time: f64) void { - internal_debug.assertInitialized(); - - std.debug.assert(!std.math.isNan(time)); - std.debug.assert(time >= 0); - // assert time is lteq to largest number of seconds representable by u64 with nanosecond precision - std.debug.assert(time <= max_time: { - const @"2^64" = std.math.maxInt(u64); - break :max_time @divTrunc(@"2^64", std.time.ns_per_s); - }); - - c.glfwSetTime(time); -} - -/// Returns the current value of the raw timer. -/// -/// This function returns the current value of the raw timer, measured in `1/frequency` seconds. To -/// get the frequency, call glfw.getTimerFrequency. -/// -/// @return The value of the timer, or zero if an error occurred. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: time, glfw.getTimerFrequency -pub inline fn getTimerValue() u64 { - internal_debug.assertInitialized(); - const value = c.glfwGetTimerValue(); - if (value != 0) return value; - // `glfwGetTimerValue` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -/// Returns the frequency, in Hz, of the raw timer. -/// -/// This function returns the frequency, in Hz, of the raw timer. -/// -/// @return The frequency of the timer, in Hz, or zero if an error occurred. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: time, glfw.getTimerValue -pub inline fn getTimerFrequency() u64 { - internal_debug.assertInitialized(); - const frequency = c.glfwGetTimerFrequency(); - if (frequency != 0) return frequency; - // `glfwGetTimerFrequency` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -test "getTime" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = getTime(); -} - -test "setTime" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.setTime(1234); -} - -test "getTimerValue" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getTimerValue(); -} - -test "getTimerFrequency" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getTimerFrequency(); -} diff --git a/pkg/glfw/version.zig b/pkg/glfw/version.zig deleted file mode 100644 index e83a4d708..000000000 --- a/pkg/glfw/version.zig +++ /dev/null @@ -1,18 +0,0 @@ -//! GLFW version info - -const c = @import("c.zig").c; - -/// The major version number of the GLFW library. -/// -/// This is incremented when the API is changed in non-compatible ways. -pub const major = c.GLFW_VERSION_MAJOR; - -/// The minor version number of the GLFW library. -/// -/// This is incremented when features are added to the API but it remains backward-compatible. -pub const minor = c.GLFW_VERSION_MINOR; - -/// The revision number of the GLFW library. -/// -/// This is incremented when a bug fix release is made that does not contain any API changes. -pub const revision = c.GLFW_VERSION_REVISION; diff --git a/pkg/glfw/vulkan.zig b/pkg/glfw/vulkan.zig deleted file mode 100644 index 1b84145d5..000000000 --- a/pkg/glfw/vulkan.zig +++ /dev/null @@ -1,290 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); - -const internal_debug = @import("internal_debug.zig"); - -/// Sets the desired Vulkan `vkGetInstanceProcAddr` function. -/// -/// This function sets the `vkGetInstanceProcAddr` function that GLFW will use for all -/// Vulkan related entry point queries. -/// -/// This feature is mostly useful on macOS, if your copy of the Vulkan loader is in -/// a location where GLFW cannot find it through dynamic loading, or if you are still -/// using the static library version of the loader. -/// -/// If set to `NULL`, GLFW will try to load the Vulkan loader dynamically by its standard -/// name and get this function from there. This is the default behavior. -/// -/// The standard name of the loader is `vulkan-1.dll` on Windows, `libvulkan.so.1` on -/// Linux and other Unix-like systems and `libvulkan.1.dylib` on macOS. If your code is -/// also loading it via these names then you probably don't need to use this function. -/// -/// The function address you set is never reset by GLFW, but it only takes effect during -/// initialization. Once GLFW has been initialized, any updates will be ignored until the -/// library is terminated and initialized again. -/// -/// remark: This function may be called before glfw.Init. -/// -/// thread_safety: This function must only be called from the main thread. -pub fn initVulkanLoader(loader_function: ?VKGetInstanceProcAddr) void { - c.glfwInitVulkanLoader(loader_function orelse null); -} - -pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.c) ?VKProc; - -/// Returns whether the Vulkan loader and an ICD have been found. -/// -/// This function returns whether the Vulkan loader and any minimally functional ICD have been -/// found. -/// -/// The availability of a Vulkan loader and even an ICD does not by itself guarantee that surface -/// creation or even instance creation is possible. Call glfw.getRequiredInstanceExtensions -/// to check whether the extensions necessary for Vulkan surface creation are available and -/// glfw.getPhysicalDevicePresentationSupport to check whether a queue family of a physical device -/// supports image presentation. -/// -/// @return `true` if Vulkan is minimally available, or `false` otherwise. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn vulkanSupported() bool { - internal_debug.assertInitialized(); - const supported = c.glfwVulkanSupported(); - return supported == c.GLFW_TRUE; -} - -/// Returns the Vulkan instance extensions required by GLFW. -/// -/// This function returns an array of names of Vulkan instance extensions required by GLFW for -/// creating Vulkan surfaces for GLFW windows. If successful, the list will always contain -/// `VK_KHR_surface`, so if you don't require any additional extensions you can pass this list -/// directly to the `VkInstanceCreateInfo` struct. -/// -/// If Vulkan is not available on the machine, this function returns null and generates a -/// glfw.ErrorCode.APIUnavailable error. Call glfw.vulkanSupported to check whether Vulkan is at -/// least minimally available. -/// -/// If Vulkan is available but no set of extensions allowing window surface creation was found, -/// this function returns null. You may still use Vulkan for off-screen rendering and compute work. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable. -/// Returns null in the event of an error. -/// -/// Additional extensions may be required by future versions of GLFW. You should check if any -/// extensions you wish to enable are already in the returned array, as it is an error to specify -/// an extension more than once in the `VkInstanceCreateInfo` struct. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the library is terminated. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: vulkan_ext, glfwCreateWindowSurface -pub inline fn getRequiredInstanceExtensions() ?[][*:0]const u8 { - internal_debug.assertInitialized(); - var count: u32 = 0; - if (c.glfwGetRequiredInstanceExtensions(&count)) |extensions| return @as([*][*:0]const u8, @ptrCast(extensions))[0..count]; - return null; -} - -/// Vulkan API function pointer type. -/// -/// Generic function pointer used for returning Vulkan API function pointers. -/// -/// see also: vulkan_proc, glfw.getInstanceProcAddress -pub const VKProc = *const fn () callconv(if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) .Stdcall else .C) void; - -/// Returns the address of the specified Vulkan instance function. -/// -/// This function returns the address of the specified Vulkan core or extension function for the -/// specified instance. If instance is set to null it can return any function exported from the -/// Vulkan loader, including at least the following functions: -/// -/// - `vkEnumerateInstanceExtensionProperties` -/// - `vkEnumerateInstanceLayerProperties` -/// - `vkCreateInstance` -/// - `vkGetInstanceProcAddr` -/// -/// If Vulkan is not available on the machine, this function returns null and generates a -/// glfw.ErrorCode.APIUnavailable error. Call glfw.vulkanSupported to check whether Vulkan is at -/// least minimally available. -/// -/// This function is equivalent to calling `vkGetInstanceProcAddr` with a platform-specific query -/// of the Vulkan loader as a fallback. -/// -/// @param[in] instance The Vulkan instance to query, or null to retrieve functions related to -/// instance creation. -/// @param[in] procname The ASCII encoded name of the function. -/// @return The address of the function, or null if an error occurred. -/// -/// To maintain ABI compatability with the C glfwGetInstanceProcAddress, as it is commonly passed -/// into libraries expecting that exact ABI, this function does not return an error. Instead, if -/// glfw.ErrorCode.NotInitialized or glfw.ErrorCode.APIUnavailable would occur this function will panic. -/// You may check glfw.vulkanSupported prior to invoking this function. -/// -/// @pointer_lifetime The returned function pointer is valid until the library is terminated. -/// -/// @thread_safety This function may be called from any thread. -pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.c) ?VKProc { - internal_debug.assertInitialized(); - if (c.glfwGetInstanceProcAddress(if (vk_instance) |v| @as(c.VkInstance, @ptrCast(v)) else null, proc_name)) |proc_address| return proc_address; - return null; -} - -/// Returns whether the specified queue family can present images. -/// -/// This function returns whether the specified queue family of the specified physical device -/// supports presentation to the platform GLFW was built for. -/// -/// If Vulkan or the required window surface creation instance extensions are not available on the -/// machine, or if the specified instance was not created with the required extensions, this -/// function returns `GLFW_FALSE` and generates a glfw.ErrorCode.APIUnavailable error. Call -/// glfw.vulkanSupported to check whether Vulkan is at least minimally available and -/// glfw.getRequiredInstanceExtensions to check what instance extensions are required. -/// -/// @param[in] instance The instance that the physical device belongs to. -/// @param[in] device The physical device that the queue family belongs to. -/// @param[in] queuefamily The index of the queue family to query. -/// @return `true` if the queue family supports presentation, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable and glfw.ErrorCode.PlatformError. -/// Returns false in the event of an error. -/// -/// macos: This function currently always returns `true`, as the `VK_MVK_macos_surface` and -/// 'VK_EXT_metal_surface' extension does not provide a `vkGetPhysicalDevice*PresentationSupport` type function. -/// -/// @thread_safety This function may be called from any thread. For synchronization details of -/// Vulkan objects, see the Vulkan specification. -/// -/// see also: vulkan_present -pub inline fn getPhysicalDevicePresentationSupport( - vk_instance: *anyopaque, - vk_physical_device: *anyopaque, - queue_family: u32, -) bool { - internal_debug.assertInitialized(); - return c.glfwGetPhysicalDevicePresentationSupport( - @as(c.VkInstance, @ptrCast(vk_instance)), - @as(c.VkPhysicalDevice, @ptrCast(vk_physical_device)), - queue_family, - ) == c.GLFW_TRUE; -} - -/// Creates a Vulkan surface for the specified window. -/// -/// This function creates a Vulkan surface for the specified window. -/// -/// If the Vulkan loader or at least one minimally functional ICD were not found, this function -/// returns `VK_ERROR_INITIALIZATION_FAILED` and generates a glfw.ErrorCode.APIUnavailable error. Call -/// glfw.vulkanSupported to check whether Vulkan is at least minimally available. -/// -/// If the required window surface creation instance extensions are not available or if the -/// specified instance was not created with these extensions enabled, this function returns `VK_ERROR_EXTENSION_NOT_PRESENT` -/// and generates a glfw.ErrorCode.APIUnavailable error. Call glfw.getRequiredInstanceExtensions to -/// check what instance extensions are required. -/// -/// The window surface cannot be shared with another API so the window must have been created with -/// the client api hint set to `GLFW_NO_API` otherwise it generates a glfw.ErrorCode.InvalidValue error -/// and returns `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`. -/// -/// The window surface must be destroyed before the specified Vulkan instance. It is the -/// responsibility of the caller to destroy the window surface. GLFW does not destroy it for you. -/// Call `vkDestroySurfaceKHR` to destroy the surface. -/// -/// @param[in] vk_instance The Vulkan instance to create the surface in. -/// @param[in] window The window to create the surface for. -/// @param[in] vk_allocation_callbacks The allocator to use, or null to use the default -/// allocator. -/// @param[out] surface Where to store the handle of the surface. This is set -/// to `VK_NULL_HANDLE` if an error occurred. -/// @return `VkResult` type, `VK_SUCCESS` if successful, or a Vulkan error code if an -/// error occurred. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable, glfw.ErrorCode.PlatformError and glfw.ErrorCode.InvalidValue -/// Returns a bool indicating success. -/// -/// If an error occurs before the creation call is made, GLFW returns the Vulkan error code most -/// appropriate for the error. Appropriate use of glfw.vulkanSupported and glfw.getRequiredInstanceExtensions -/// should eliminate almost all occurrences of these errors. -/// -/// macos: GLFW prefers the `VK_EXT_metal_surface` extension, with the `VK_MVK_macos_surface` -/// extension as a fallback. The name of the selected extension, if any, is included in the array -/// returned by glfw.getRequiredInstanceExtensions. -/// -/// macos: This function currently only supports the `VK_MVK_macos_surface` extension from MoltenVK. -/// -/// macos: This function creates and sets a `CAMetalLayer` instance for the window content view, -/// which is required for MoltenVK to function. -/// -/// x11: By default GLFW prefers the `VK_KHR_xcb_surface` extension, with the `VK_KHR_xlib_surface` -/// extension as a fallback. You can make `VK_KHR_xlib_surface` the preferred extension by setting -/// glfw.InitHints.x11_xcb_vulkan_surface. The name of the selected extension, if any, is included -/// in the array returned by glfw.getRequiredInstanceExtensions. -/// -/// @thread_safety This function may be called from any thread. For synchronization details of -/// Vulkan objects, see the Vulkan specification. -/// -/// see also: vulkan_surface, glfw.getRequiredInstanceExtensions -pub inline fn createWindowSurface(vk_instance: anytype, window: Window, vk_allocation_callbacks: anytype, vk_surface_khr: anytype) i32 { - internal_debug.assertInitialized(); - // zig-vulkan uses enums to represent opaque pointers: - // pub const Instance = enum(usize) { null_handle = 0, _ }; - const instance: c.VkInstance = switch (@import("shims.zig").typeInfo(@TypeOf(vk_instance))) { - .@"enum" => @as(c.VkInstance, @ptrFromInt(@intFromEnum(vk_instance))), - else => @as(c.VkInstance, @ptrCast(vk_instance)), - }; - - return c.glfwCreateWindowSurface( - instance, - window.handle, - if (vk_allocation_callbacks == null) null else @as(*const c.VkAllocationCallbacks, @ptrCast(@alignCast(vk_allocation_callbacks))), - @as(*c.VkSurfaceKHR, @ptrCast(@alignCast(vk_surface_khr))), - ); -} - -test "vulkanSupported" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.vulkanSupported(); -} - -test "getRequiredInstanceExtensions" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getRequiredInstanceExtensions(); -} - -test "getInstanceProcAddress" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - // syntax check only, we don't have a real vulkan instance and so this function would panic. - _ = glfw.getInstanceProcAddress; -} - -test "syntax" { - // Best we can do for these two functions in terms of testing in lieu of an actual Vulkan - // context. - _ = getPhysicalDevicePresentationSupport; - _ = createWindowSurface; - _ = initVulkanLoader; -} diff --git a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h deleted file mode 100644 index f847c1eaa..000000000 --- a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2022 Kenny Levinsen - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wp_fractional_scale_v1_interface; - -static const struct wl_interface *fractional_scale_v1_types[] = { - NULL, - &wp_fractional_scale_v1_interface, - &wl_surface_interface, -}; - -static const struct wl_message wp_fractional_scale_manager_v1_requests[] = { - { "destroy", "", fractional_scale_v1_types + 0 }, - { "get_fractional_scale", "no", fractional_scale_v1_types + 1 }, -}; - -WL_PRIVATE const struct wl_interface wp_fractional_scale_manager_v1_interface = { - "wp_fractional_scale_manager_v1", 1, - 2, wp_fractional_scale_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message wp_fractional_scale_v1_requests[] = { - { "destroy", "", fractional_scale_v1_types + 0 }, -}; - -static const struct wl_message wp_fractional_scale_v1_events[] = { - { "preferred_scale", "u", fractional_scale_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wp_fractional_scale_v1_interface = { - "wp_fractional_scale_v1", 1, - 1, wp_fractional_scale_v1_requests, - 1, wp_fractional_scale_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h b/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h deleted file mode 100644 index 8df7558a7..000000000 --- a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h +++ /dev/null @@ -1,264 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H -#define FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_fractional_scale_v1 The fractional_scale_v1 protocol - * Protocol for requesting fractional surface scales - * - * @section page_desc_fractional_scale_v1 Description - * - * This protocol allows a compositor to suggest for surfaces to render at - * fractional scales. - * - * A client can submit scaled content by utilizing wp_viewport. This is done by - * creating a wp_viewport object for the surface and setting the destination - * rectangle to the surface size before the scale factor is applied. - * - * The buffer size is calculated by multiplying the surface size by the - * intended scale. - * - * The wl_surface buffer scale should remain set to 1. - * - * If a surface has a surface-local size of 100 px by 50 px and wishes to - * submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should - * be used and the wp_viewport destination rectangle should be 100 px by 50 px. - * - * For toplevel surfaces, the size is rounded halfway away from zero. The - * rounding algorithm for subsurface position and size is not defined. - * - * @section page_ifaces_fractional_scale_v1 Interfaces - * - @subpage page_iface_wp_fractional_scale_manager_v1 - fractional surface scale information - * - @subpage page_iface_wp_fractional_scale_v1 - fractional scale interface to a wl_surface - * @section page_copyright_fractional_scale_v1 Copyright - *