diff --git a/src/vocalinux/ui/settings_about.py b/src/vocalinux/ui/settings_about.py index d1277f2..2b99bdd 100644 --- a/src/vocalinux/ui/settings_about.py +++ b/src/vocalinux/ui/settings_about.py @@ -1,4 +1,4 @@ -"""About page content and trusted family links for Settings.""" +"""About page content and trusted project links for Settings.""" import logging import os @@ -20,41 +20,29 @@ from ..version import __copyright__, __url__, __version__ logger = logging.getLogger(__name__) VOCULINUX_SITE_URL = "https://voculinux.com" -VOCAHQ_SITE_URL = "https://vocahq.com" -VOCAMAC_SITE_URL = "https://vocamac.com" -VOCAWIN_SITE_URL = "https://vocawin.com" -VOCAPHONE_SITE_URL = "https://vocaphone.vocahq.com" -VOCAGATEWAY_SITE_URL = "https://vocagateway.vocahq.com" -GITHUB_REPO_URL = __url__ -GITHUB_ISSUES_URL = "https://github.com/VocaHQ/vocalinux/issues" -VOCAHQ_DISCORD_URL = "https://discord.gg/t6muquAJbm" -VOCAHQ_X_URL = "https://x.com/vocahq" -VOCAHQ_MAILTO_URL = "mailto:hello@vocahq.com" +# The fork's own source, issue and release host. The update checker reads the +# same host, so About and the updater agree on where releases live. +SOURCE_REPO_URL = __url__ +SOURCE_ISSUES_URL = f"{SOURCE_REPO_URL}/issues" +# The one upstream credit: the project this fork was made from. +UPSTREAM_REPO_URL = "https://github.com/VocaHQ/vocalinux" +UPSTREAM_CREDIT_LINE = f"Voculinux is a hard fork of Vocalinux by VocaHQ: {UPSTREAM_REPO_URL}" # About page links that are not release URLs. _ABOUT_OPEN_URLS = frozenset( { VOCULINUX_SITE_URL, - VOCAHQ_SITE_URL, - VOCAMAC_SITE_URL, - VOCAWIN_SITE_URL, - VOCAPHONE_SITE_URL, - VOCAGATEWAY_SITE_URL, - GITHUB_REPO_URL, - GITHUB_ISSUES_URL, - VOCAHQ_DISCORD_URL, - VOCAHQ_X_URL, - VOCAHQ_MAILTO_URL, + SOURCE_REPO_URL, + SOURCE_ISSUES_URL, + UPSTREAM_REPO_URL, } ) -# Official Talk-to-us marks from VocaHQ/.github brand/vocahq/social (commit 61c8eee). -# Do not redraw. fill is currentColor; paint to match the dialog foreground so -# the marks stay visible on both light and dark surfaces. +# Currentcolor brand marks. fill is currentColor; paint to match the dialog +# foreground so the marks stay visible on both light and dark surfaces. _ABOUT_INK = "#14231C" _ABOUT_INK_ON_DARK = "#E6E1D8" _ABOUT_ICON_TEXT_PX = 18 -_FAMILY_ICON_PX = 22 def _color_luminance(color: Any) -> float: @@ -119,55 +107,6 @@ def _about_ink_hex( return _ABOUT_INK_ON_DARK if dark else _ABOUT_INK -# Official platform marks from VocaHQ/.github brand/promo/cards/platform -# (currentColor copies live in web/public/brand/platforms). Home is a simple -# house so HQ does not reuse a product lockup. -_VOCAHQ_FAMILY_LINKS = ( - ( - VOCAHQ_SITE_URL, - "VocaHQ", - "Family site", - ("platform-home",), - "Open vocahq.com", - ), - ( - VOCULINUX_SITE_URL, - "Voculinux", - "Linux, available now", - ("platform-linux",), - "Open voculinux.com", - ), - ( - VOCAMAC_SITE_URL, - "VocaMac", - "macOS, available now", - ("platform-apple",), - "Open vocamac.com", - ), - ( - VOCAWIN_SITE_URL, - "VocaWin", - "Windows, unsigned beta", - ("platform-windows",), - "Open vocawin.com", - ), - ( - VOCAPHONE_SITE_URL, - "VocaPhone", - "Android beta / iOS TestFlight", - ("platform-android", "platform-apple"), - "Open vocaphone.vocahq.com", - ), - ( - VOCAGATEWAY_SITE_URL, - "VocaGateway", - "Self-hosted, headless", - ("platform-server",), - "Open vocagateway.vocahq.com", - ), -) - - def _can_open_url(url: str) -> bool: """Return True for this project's release URLs or the About page allowlist.""" return bool(url) and (url in _ABOUT_OPEN_URLS or is_trusted_release_url(url)) @@ -239,50 +178,6 @@ class AboutSectionMixin(Gtk.Dialog): button.connect("clicked", lambda *_args, dest=url: self._open_web_url(dest)) return button - def _family_tile( - self: Any, - url: str, - title: str, - subtitle: str, - icon_names: tuple[str, ...], - tooltip: str, - dark: Optional[bool] = None, - ) -> Gtk.Button: - """Compact icon + name tile that opens a family site.""" - from .settings_dialog import _set_accessible_name - - inner = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=16) - marks = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) - marks.set_valign(Gtk.Align.CENTER) - for icon_name in icon_names: - icon = self._about_mark_image( - icon_name, - pixel_size=_FAMILY_ICON_PX, - dark=dark, - ) - if icon is not None: - marks.pack_start(icon, False, False, 0) - inner.pack_start(marks, False, False, 0) - text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - name_label = Gtk.Label(label=title, xalign=0) - name_label.get_style_context().add_class("family-tile-title") - status_label = Gtk.Label(label=subtitle, xalign=0) - status_label.get_style_context().add_class("family-tile-subtitle") - status_label.set_line_wrap(True) - text.pack_start(name_label, False, False, 0) - text.pack_start(status_label, False, False, 0) - inner.pack_start(text, True, True, 0) - - button = Gtk.Button() - button.add(inner) - button.get_style_context().add_class("family-tile") - button.set_tooltip_text(tooltip) - button.set_hexpand(True) - button.set_halign(Gtk.Align.FILL) - _set_accessible_name(button, tooltip) - button.connect("clicked", lambda *_args, dest=url: self._open_web_url(dest)) - return button - def _build_about_section(self: Any) -> None: """Build the About page using the same PreferenceRow cards as other pages.""" about_tab = getattr(self, "about_tab") @@ -320,7 +215,7 @@ class AboutSectionMixin(Gtk.Dialog): app_group = PreferencesGroup( title="Voculinux", description=( - "Available now on Linux (X11 and Wayland). After a model is downloaded, " + "Voice dictation for Linux (X11 and Wayland). After a model is downloaded, " "speech is processed on this PC." ), keywords=("about", "version", "app"), @@ -353,8 +248,8 @@ class AboutSectionMixin(Gtk.Dialog): ) link_box.pack_start( self._about_open_button( - GITHUB_REPO_URL, - "Open the Voculinux GitHub repository", + SOURCE_REPO_URL, + "Open the Voculinux source repository", label="Source code", width=120, dark=dark, @@ -365,9 +260,9 @@ class AboutSectionMixin(Gtk.Dialog): ) links_row = PreferenceRow( title="Open source", - subtitle="Product site at voculinux.com, source on GitHub.", + subtitle="Product site at voculinux.com, source on the project's Gitea host.", widget=link_box, - keywords=("website", "voculinux.com", "github", "source", "repo", "code", "agpl"), + keywords=("website", "voculinux.com", "gitea", "source", "repo", "code", "agpl"), ) app_group.add_row(links_row) @@ -381,53 +276,22 @@ class AboutSectionMixin(Gtk.Dialog): talk_group = PreferencesGroup( title="Talk to us", - description=( - "Bugs, feedback, and feature ideas open a GitHub issue. You pick the " - "template on the next screen." - ), - keywords=("github", "discord", "contact", "bug", "feedback", "email"), + description=("Bugs, feedback, and feature ideas go to the Voculinux issue tracker."), + keywords=("issues", "bug", "feedback", "contact", "report", "idea"), ) report_btn = self._about_open_button( - GITHUB_ISSUES_URL, - "Open GitHub issues for a bug or idea", + SOURCE_ISSUES_URL, + "Open Voculinux issues for a bug or idea", label="Report a bug or idea", - icon_name="github", width=200, dark=dark, ) talk_group.add_row( PreferenceRow( - title="GitHub issues", - subtitle="https://github.com/VocaHQ/vocalinux/issues", + title="Issues", + subtitle=SOURCE_ISSUES_URL, widget=report_btn, - keywords=("github", "issue", "bug", "idea"), - ) - ) - contact_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) - for url, label, tooltip, icon_name, width in ( - (VOCAHQ_DISCORD_URL, "Join Discord", "Open the VocaHQ Discord", "discord", 140), - (VOCAHQ_X_URL, "Follow on X", "Open X @vocahq", "x", 130), - (VOCAHQ_MAILTO_URL, "Email us", "Email hello@vocahq.com", "mail", 120), - ): - contact_box.pack_start( - self._about_open_button( - url, - tooltip, - label=label, - icon_name=icon_name, - width=width, - dark=dark, - ), - False, - False, - 0, - ) - talk_group.add_row( - PreferenceRow( - title="Community", - subtitle="Discord, X, and email", - widget=contact_box, - keywords=("discord", "x", "twitter", "email", "mail"), + keywords=("issue", "bug", "idea"), ) ) about_tab.pack_start(talk_group, False, False, 0) @@ -519,24 +383,21 @@ class AboutSectionMixin(Gtk.Dialog): about_tab.pack_start(self.release_notes_group, False, False, 0) self.release_notes_group.hide() - family_group = PreferencesGroup( - title="Part of VocaHQ", - description="Private dictation for Linux, Mac, Windows, and phone.", - keywords=("vocahq", "vocamac", "vocawin", "vocaphone", "vocagateway", "family"), + origin_group = PreferencesGroup( + title="Origin", + keywords=("upstream", "fork", "origin", "credit", "project"), ) - family_grid = Gtk.Grid() - family_grid.set_column_spacing(8) - family_grid.set_row_spacing(4) - family_grid.set_column_homogeneous(True) - family_grid.set_margin_start(8) - family_grid.set_margin_end(8) - family_grid.set_margin_top(4) - family_grid.set_margin_bottom(8) - for index, (url, title, subtitle, icon_names, open_name) in enumerate(_VOCAHQ_FAMILY_LINKS): - tile = self._family_tile(url, title, subtitle, icon_names, open_name, dark=dark) - family_grid.attach(tile, index % 2, index // 2, 1, 1) - family_row = Gtk.ListBoxRow() - family_row.set_activatable(False) - family_row.add(family_grid) - family_group.add_row(family_row) - about_tab.pack_start(family_group, False, False, 0) + upstream_row = PreferenceRow( + title="Upstream project", + subtitle=UPSTREAM_CREDIT_LINE, + widget=self._about_open_button( + UPSTREAM_REPO_URL, + "Open the upstream project repository", + label="Upstream", + width=110, + dark=dark, + ), + keywords=("upstream", "fork", "credit"), + ) + origin_group.add_row(upstream_row) + about_tab.pack_start(origin_group, False, False, 0) diff --git a/src/vocalinux/ui/settings_dialog.py b/src/vocalinux/ui/settings_dialog.py index 8596c52..d9ea290 100644 --- a/src/vocalinux/ui/settings_dialog.py +++ b/src/vocalinux/ui/settings_dialog.py @@ -151,18 +151,10 @@ from .settings_about import ( # noqa: F401 _ABOUT_INK, _ABOUT_INK_ON_DARK, _ABOUT_OPEN_URLS, - _FAMILY_ICON_PX, - _VOCAHQ_FAMILY_LINKS, - GITHUB_ISSUES_URL, - GITHUB_REPO_URL, - VOCAGATEWAY_SITE_URL, - VOCAHQ_DISCORD_URL, - VOCAHQ_MAILTO_URL, - VOCAHQ_SITE_URL, - VOCAHQ_X_URL, - VOCAMAC_SITE_URL, - VOCAPHONE_SITE_URL, - VOCAWIN_SITE_URL, + SOURCE_ISSUES_URL, + SOURCE_REPO_URL, + UPSTREAM_CREDIT_LINE, + UPSTREAM_REPO_URL, VOCULINUX_SITE_URL, AboutSectionMixin, _about_ink_hex, @@ -318,7 +310,9 @@ def _engine_from_display(display_name: str) -> str: return display_name.lower() -MODEL_SELECTION_DOC_URL = "https://github.com/VocaHQ/vocalinux/blob/main/docs/MODEL_SELECTION.md" +MODEL_SELECTION_DOC_URL = ( + "https://git.bongbetic.com/xavierk/voculinux/src/branch/main/docs/MODEL_SELECTION.md" +) def _model_display_name(model_name: str) -> str: diff --git a/src/vocalinux/version.py b/src/vocalinux/version.py index 55e7a9b..eb45cf2 100644 --- a/src/vocalinux/version.py +++ b/src/vocalinux/version.py @@ -8,5 +8,5 @@ __author__ = "Jatin K Malik" __email__ = "jatinkrmalik@gmail.com" __license__ = "AGPL-3.0-only" __copyright__ = "Copyright 2023-2026 Jatin K Malik" -__url__ = "https://github.com/VocaHQ/vocalinux" +__url__ = "https://git.bongbetic.com/xavierk/voculinux" __description__ = "Free, offline voice dictation for Linux - speak and type anywhere" diff --git a/tests/test_recommendation_is_actionable.py b/tests/test_recommendation_is_actionable.py index b623cdd..ddf22f6 100644 --- a/tests/test_recommendation_is_actionable.py +++ b/tests/test_recommendation_is_actionable.py @@ -131,5 +131,5 @@ def test_recommendation_does_not_change_picker_or_config_state( def test_model_selection_docs_url_is_stable(settings_dialog: Any) -> None: assert ( settings_dialog.MODEL_SELECTION_DOC_URL - == "https://github.com/VocaHQ/vocalinux/blob/main/docs/MODEL_SELECTION.md" + == "https://git.bongbetic.com/xavierk/voculinux/src/branch/main/docs/MODEL_SELECTION.md" ) diff --git a/tests/test_settings_dialog.py b/tests/test_settings_dialog.py index f0739b4..79efbde 100644 --- a/tests/test_settings_dialog.py +++ b/tests/test_settings_dialog.py @@ -1329,7 +1329,7 @@ class TestSettingsNavigation(unittest.TestCase): class TestAboutPage(unittest.TestCase): - """About page: this app, VocaHQ family, talk-to-us. Updater stays.""" + """About page: this app, talk-to-us, origin credit. Updater stays.""" @classmethod def setUpClass(cls): @@ -1343,7 +1343,7 @@ class TestAboutPage(unittest.TestCase): def test_three_groups_keep_updater(self) -> None: self.assertIn('title="Voculinux"', self.about) - self.assertIn('title="Part of VocaHQ"', self.about) + self.assertIn('title="Origin"', self.about) self.assertIn('title="Talk to us"', self.about) self.assertIn('title="Updates"', self.about) self.assertIn("What's New", self.about) @@ -1352,12 +1352,8 @@ class TestAboutPage(unittest.TestCase): self.assertIn("self.update_checks_switch", self.about) def test_product_status_and_honest_network_copy(self): - self.assertIn("Available now on Linux (X11 and Wayland)", self.about) + self.assertIn("Voice dictation for Linux (X11 and Wayland)", self.about) self.assertIn("After a model is downloaded", self.about) - self.assertIn("Private dictation for Linux, Mac, Windows, and phone.", self.about) - self.assertIn("Android beta / iOS TestFlight", self.source) - self.assertIn("Windows, unsigned beta", self.source) - self.assertIn("Self-hosted, headless", self.source) self.assertNotIn("iOS source build", self.source) self.assertNotIn("SmartScreen", self.about) self.assertNotIn("100% offline", self.about) @@ -1367,28 +1363,44 @@ class TestAboutPage(unittest.TestCase): # License is named once, on the License row, not in the intro or links. self.assertEqual(self.about.count("GNU AGPL v3"), 1) - def test_family_and_talk_urls(self): - for url in ( - "https://voculinux.com", - "https://vocahq.com", - "https://vocamac.com", - "https://vocawin.com", - "https://vocaphone.vocahq.com", - "https://vocagateway.vocahq.com", - "https://github.com/VocaHQ/vocalinux/issues", - "https://discord.gg/t6muquAJbm", - "https://x.com/vocahq", - "mailto:hello@vocahq.com", - ): - self.assertIn(url, self.source) + def test_links_point_at_the_fork_and_one_credit(self): + from vocalinux.ui.settings_about import ( + SOURCE_ISSUES_URL, + SOURCE_REPO_URL, + UPSTREAM_CREDIT_LINE, + UPSTREAM_REPO_URL, + VOCULINUX_SITE_URL, + ) + + self.assertEqual(VOCULINUX_SITE_URL, "https://voculinux.com") + self.assertEqual(SOURCE_REPO_URL, "https://git.bongbetic.com/xavierk/voculinux") + self.assertEqual(SOURCE_ISSUES_URL, f"{SOURCE_REPO_URL}/issues") + self.assertEqual(UPSTREAM_REPO_URL, "https://github.com/VocaHQ/vocalinux") + self.assertEqual(UPSTREAM_CREDIT_LINE.count(UPSTREAM_REPO_URL), 1) self.assertIn('label="Report a bug or idea"', self.about) - self.assertIn("Join Discord", self.about) - self.assertIn("Follow on X", self.about) - self.assertIn("Email us", self.about) - self.assertIn("https://github.com/VocaHQ/vocalinux/issues", self.about) - self.assertIn("https://discord.gg/t6muquAJbm", self.source) - self.assertIn("https://x.com/vocahq", self.source) - self.assertIn("mailto:hello@vocahq.com", self.source) + self.assertIn("SOURCE_ISSUES_URL", self.about) + self.assertIn("UPSTREAM_CREDIT_LINE", self.about) + + def test_family_and_social_links_are_gone(self): + """Voca family sites and social channels are not Voculinux's to offer.""" + for banned in ( + "Part of VocaHQ", + "_VOCAHQ_FAMILY_LINKS", + "_family_tile", + "family_grid", + "Join Discord", + "Follow on X", + "Email us", + "discord.gg", + "x.com/", + "mailto:", + "vocahq.com", + "vocamac.com", + "vocawin.com", + "vocaphone.vocahq.com", + "vocagateway.vocahq.com", + ): + self.assertNotIn(banned, self.source, banned) def test_open_buttons_have_unique_accessible_names(self): helper = self.source.split("def _about_open_button")[1].split("\n def ")[0] @@ -1396,13 +1408,9 @@ class TestAboutPage(unittest.TestCase): self.assertIn('if label == "Open"', helper) for name in ( "Open voculinux.com website", - "Open vocahq.com", - "Open voculinux.com", - "Open vocamac.com", - "Open vocawin.com", - "Open vocaphone.vocahq.com", - "Open vocagateway.vocahq.com", - "Open the Voculinux GitHub repository", + "Open the Voculinux source repository", + "Open Voculinux issues for a bug or idea", + "Open the upstream project repository", "Open the latest release page", ): self.assertIn(name, self.source) @@ -1412,12 +1420,13 @@ class TestAboutPage(unittest.TestCase): self.assertIn("VOCULINUX_SITE_URL", self.about) self.assertIn("Product site at voculinux.com", self.about) self.assertNotIn("Open the Voculinux GitHub page", self.about) + self.assertNotIn("GitHub", self.about) def test_source_code_row_sits_beside_website(self): self.assertIn('label="Website"', self.about) self.assertIn('label="Source code"', self.about) - self.assertIn("GITHUB_REPO_URL", self.about) - self.assertIn("Open the Voculinux GitHub repository", self.about) + self.assertIn("SOURCE_REPO_URL", self.about) + self.assertIn("Open the Voculinux source repository", self.about) self.assertLess(self.about.find('label="Website"'), self.about.find('label="Source code"')) def test_talk_to_us_sits_above_updates(self): @@ -1434,52 +1443,34 @@ class TestAboutPage(unittest.TestCase): self.assertNotIn('title="Latest Release"', self.about) self.assertIn("self.latest_release_row = self.release_notes_row", self.about) - def test_family_uses_platform_icons(self): - self.assertIn("def _family_tile", self.source) - self.assertIn("platform-linux", self.source) - self.assertIn("platform-apple", self.source) - self.assertIn("platform-windows", self.source) - self.assertIn("platform-android", self.source) - self.assertIn("platform-server", self.source) - self.assertIn("platform-home", self.source) - self.assertIn('("platform-android", "platform-apple")', self.source) - self.assertIn("family_grid.attach", self.about) - self.assertIn("image.set_margin_end(8)", self.source) - helper = self.source.split("def _family_tile")[1].split("\n def ")[0] - self.assertIn("spacing=16", helper) + def test_origin_group_names_the_upstream_credit_once(self): + self.assertIn("origin_group = PreferencesGroup(", self.about) + self.assertIn("UPSTREAM_CREDIT_LINE", self.about) + self.assertIn("UPSTREAM_REPO_URL", self.about) + self.assertLess( + self.about.find('title="Updates"'), + self.about.find('title="Origin"'), + ) def test_can_open_about_urls(self): from vocalinux.ui.settings_dialog import ( - GITHUB_ISSUES_URL, - GITHUB_REPO_URL, - VOCAGATEWAY_SITE_URL, - VOCAHQ_DISCORD_URL, - VOCAHQ_MAILTO_URL, - VOCAHQ_SITE_URL, - VOCAHQ_X_URL, - VOCAMAC_SITE_URL, - VOCAPHONE_SITE_URL, - VOCAWIN_SITE_URL, + SOURCE_ISSUES_URL, + SOURCE_REPO_URL, + UPSTREAM_REPO_URL, VOCULINUX_SITE_URL, _can_open_url, ) for url in ( VOCULINUX_SITE_URL, - VOCAHQ_SITE_URL, - VOCAMAC_SITE_URL, - VOCAWIN_SITE_URL, - VOCAPHONE_SITE_URL, - VOCAGATEWAY_SITE_URL, - GITHUB_REPO_URL, - GITHUB_ISSUES_URL, - VOCAHQ_DISCORD_URL, - VOCAHQ_X_URL, - VOCAHQ_MAILTO_URL, + SOURCE_REPO_URL, + SOURCE_ISSUES_URL, + UPSTREAM_REPO_URL, "https://git.bongbetic.com/xavierk/voculinux/releases/tag/v0.15.0", ): self.assertTrue(_can_open_url(url), url) self.assertFalse(_can_open_url("https://github.com/VocaHQ/vocalinux/releases/tag/v0.15.0")) + self.assertFalse(_can_open_url("https://vocahq.com")) self.assertFalse(_can_open_url("https://evil.example/payload")) self.assertFalse(_can_open_url("mailto:evil@example.com")) self.assertFalse(_can_open_url("")) @@ -1594,7 +1585,6 @@ class TestAboutPage(unittest.TestCase): self.assertIn("def _about_ink_hex(", self.source) self.assertIn("def _about_surface_is_dark(", self.source) self.assertIn('replace("currentColor", ink)', self.source) - self.assertIn("_FAMILY_ICON_PX", self.source) class TestUpdatePreferences(unittest.TestCase):