This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch tmux-integration
in repository terminology.
View the commit online.
commit b9dbac82eba232fa9f54cd3bdef9185db57473d2
Author: Cedric BAIL <[email protected]>
AuthorDate: Mon Mar 30 17:59:49 2026 -0600
feat(macos): add .app bundle, native menu bar, Cocoa integration, and docs
Add macOS-specific support:
- .app bundle generation via meson (Info.plist, launcher binary, icns
generation, symlink-based app structure)
- Native menu bar with New Window, Copy, Paste, Quit via elm_win
main menu API, guarded by __APPLE__ ifdefs
- Cocoa reopen handler to create a new window when the Dock icon is
clicked with no visible windows
- Move window position (evas_object_move) before evas_object_show
so EFL respects the initial placement on macOS
- Add macOS build and install instructions to README
---
README.md | 39 ++++++++++++++
data/macos/Info.plist.in | 11 ++++
src/bin/main.c | 1 +
src/bin/win.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 180 insertions(+)
diff --git a/README.md b/README.md
index e198f50e..ba0f6098 100644
--- a/README.md
+++ b/README.md
@@ -447,3 +447,42 @@ Then the following environment need to be set:
* ``ELM_DISPLAY=fb``
* ``ELM_ACCEL=none``
* ``EVAS_FB_DEV=/dev/fb0``
+
+## macOS
+
+On macOS, the build system produces a `Terminology.app` bundle that can be
+installed into `/Applications` or run directly from the build directory.
+
+### Building on macOS
+
+NLS (native language support) must be disabled because macOS ships a
+`libintl` that conflicts with the linker:
+
+```sh
+meson setup build -Dnls=false -Dtests=true
+ninja -C build
+```
+
+The `.app` bundle is generated at `build/data/macos/Terminology.app`.
+
+### Installing the .app bundle
+
+Copy the bundle to `/Applications` so that macOS registers the URL scheme
+and the Dock icon:
+
+```sh
+cp -R build/data/macos/Terminology.app /Applications/
+```
+
+After first launch, macOS will recognise the `terminology://` URL scheme
+automatically. You can verify with:
+
+```sh
+open "terminology://"
+```
+
+### Running tests
+
+```sh
+./build/src/bin/tytest all
+```
diff --git a/data/macos/Info.plist.in b/data/macos/Info.plist.in
index b9334ba3..9b1f7087 100644
--- a/data/macos/Info.plist.in
+++ b/data/macos/Info.plist.in
@@ -24,5 +24,16 @@
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © The Enlightenment Project. Licensed under BSD.</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>Terminology URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>terminology</string>
+ </array>
+ </dict>
+ </array>
</dict>
</plist>
diff --git a/src/bin/main.c b/src/bin/main.c
index 9858ee32..ee20bfdb 100644
--- a/src/bin/main.c
+++ b/src/bin/main.c
@@ -215,6 +215,7 @@ _cb_reopen(Eina_Bool has_visible EINA_UNUSED)
if (win_term_set(wn, term) < 0)
{
+ win_free(wn);
return EINA_FALSE;
}
diff --git a/src/bin/win.c b/src/bin/win.c
index 2e35eb9e..20950678 100644
--- a/src/bin/win.c
+++ b/src/bin/win.c
@@ -1195,6 +1195,135 @@ terms_from_win_object(Evas_Object *win)
return wn->terms;
}
+#ifdef __APPLE__
+/* Find the focused Win, or fall back to list head */
+static Win *
+_macos_focused_win_get(void)
+{
+ Eina_List *l;
+ Win *wn;
+
+ EINA_LIST_FOREACH(wins, l, wn)
+ {
+ if (wn->focused) return wn;
+ }
+ /* No focused window — return list head as last resort */
+ DBG("No focused Win found, falling back to list head");
+ return wins ? (Win *)eina_list_data_get(wins) : NULL;
+}
+
+static void
+_cb_macos_new_window(void *data EINA_UNUSED,
+ Evas_Object *obj EINA_UNUSED,
+ void *event_info EINA_UNUSED)
+{
+ Win *wn;
+ Term *term;
+
+ wn = win_new(NULL, NULL, NULL, NULL, main_config_get(),
+ EINA_FALSE, EINA_FALSE, EINA_FALSE, EINA_FALSE, EINA_FALSE);
+ if (!wn)
+ {
+ ERR("macOS New Window: win_new() failed");
+ return;
+ }
+ /* Initial size hints — win_sizing_handle() below will correct to theme geometry */
+ term = term_new(wn, win_config_get(wn), NULL, EINA_FALSE,
+ NULL, 80, 24, EINA_FALSE, NULL);
+ if (!term)
+ {
+ ERR("macOS New Window: term_new() failed");
+ win_free(wn);
+ return;
+ }
+ if (win_term_set(wn, term) < 0)
+ {
+ ERR("macOS New Window: win_term_set() failed");
+ term_unref(term);
+ win_free(wn);
+ return;
+ }
+ win_sizing_handle(wn);
+ evas_object_show(win_evas_object_get(wn));
+}
+
+static void
+_cb_macos_copy(void *data EINA_UNUSED,
+ Evas_Object *obj EINA_UNUSED,
+ void *event_info EINA_UNUSED)
+{
+ Win *wn;
+ Term *term;
+
+ wn = _macos_focused_win_get();
+ if (!wn) return;
+ if ((!wn->child) || (!wn->child->focused_term_get)) return;
+ term = wn->child->focused_term_get(wn->child);
+ if (!term) return;
+ termio_take_selection(term->termio, ELM_SEL_TYPE_CLIPBOARD);
+}
+
+static void
+_cb_macos_paste(void *data EINA_UNUSED,
+ Evas_Object *obj EINA_UNUSED,
+ void *event_info EINA_UNUSED)
+{
+ Win *wn;
+ Term *term;
+
+ wn = _macos_focused_win_get();
+ if (!wn) return;
+ if ((!wn->child) || (!wn->child->focused_term_get)) return;
+ term = wn->child->focused_term_get(wn->child);
+ if (!term) return;
+ termio_paste_selection(term->termio, ELM_SEL_TYPE_CLIPBOARD);
+}
+
+static void
+_cb_macos_quit(void *data EINA_UNUSED,
+ Evas_Object *obj EINA_UNUSED,
+ void *event_info EINA_UNUSED)
+{
+ elm_exit();
+}
+
+static void
+_win_macos_menu_setup(Evas_Object *win)
+{
+ static Eina_Bool done = EINA_FALSE;
+ Evas_Object *menu;
+ Elm_Object_Item *app_item, *file_item, *edit_item;
+
+ if (done) return;
+
+ menu = elm_win_main_menu_get(win);
+ if (!menu) return;
+ done = EINA_TRUE; /* committed to this menu — prevent duplicate setup */
+
+ /* "Terminology" app menu — macOS convention: first = app name */
+ app_item = elm_menu_item_add(menu, NULL, NULL, "Terminology", NULL, NULL);
+ if (!app_item) return;
+ elm_menu_item_add(menu, app_item, NULL, "Quit Terminology",
+ _cb_macos_quit, NULL);
+
+ /* "File" menu */
+ file_item = elm_menu_item_add(menu, NULL, NULL, "File", NULL, NULL);
+ if (!file_item) return;
+ elm_menu_item_add(menu, file_item, NULL, "New Window",
+ _cb_macos_new_window, NULL);
+
+ /* "Edit" menu */
+ edit_item = elm_menu_item_add(menu, NULL, NULL, "Edit", NULL, NULL);
+ if (!edit_item) return;
+ elm_menu_item_add(menu, edit_item, NULL, "Copy",
+ _cb_macos_copy, NULL);
+ elm_menu_item_add(menu, edit_item, NULL, "Paste",
+ _cb_macos_paste, NULL);
+ /* TODO: investigate setting keyboard equivalents (Cmd+C/V) via Cocoa bridge */
+
+ done = EINA_TRUE;
+}
+#endif /* __APPLE__ */
#ifdef __APPLE__
/* Find the focused Win, or fall back to list head */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.