diff --git a/image/desktop_payload.py b/image/desktop_payload.py index dc06f16b..9ff908a5 100644 --- a/image/desktop_payload.py +++ b/image/desktop_payload.py @@ -45,6 +45,16 @@ def prepare_defaults(root, payload, environment, inventory): KITTY_INCLUDE = "# BEGIN CYBEXOS MANAGED INCLUDE\ninclude cybexos.conf\n# END CYBEXOS MANAGED INCLUDE\n" +def prepare_app_launchers(root, payload): + """Package the same desktop launchers that checkout installs deploy.""" + applications = payload / 'usr/share/cybexos/user-seed/.local/share/applications' + applications.mkdir(parents=True, exist_ok=True) + for name in ('nvim-kitty', 'brave-origin', 'chatgpt', 'com.onepassword.OnePassword'): + target = applications / (name + '.desktop') + shutil.copyfile(root / 'roles/dotfiles/files' / target.name, target) + target.chmod(0o644) + + def prepare_session(root, payload, inventory): """Session pieces the workstation installs per user, packaged once. diff --git a/image/package b/image/package index 465aef3b..17b8de36 100755 --- a/image/package +++ b/image/package @@ -14,7 +14,7 @@ import urllib.request import jinja2 import yaml from application_payload import relativize_seed_links, include_applications -from desktop_payload import prepare_defaults, prepare_session, split_seed, prepare_managed_defaults +from desktop_payload import prepare_defaults, prepare_session, split_seed, prepare_managed_defaults, prepare_app_launchers from provision_payload import prepare_provision from release_metadata import install_update_channel, render_spec @@ -112,8 +112,7 @@ def main(): content = (ROOT / "roles/dotfiles/templates" / (name + ".desktop.j2")).read_text() write(f"{seed}/.local/share/applications/{name}.desktop", content.replace("{{ primary_home }}/.local/bin/", "")) - for name in ("nvim-kitty", "brave-origin", "chatgpt", "com.onepassword.OnePassword"): - copy(f"roles/dotfiles/files/{name}.desktop", f"{seed}/.local/share/applications/{name}.desktop") + prepare_app_launchers(ROOT, payload) t3_desktop = (ROOT / "roles/dotfiles/templates/t3code-nightly.desktop.j2").read_text() write(f"{seed}/.local/share/applications/t3code-nightly.desktop", t3_desktop.replace("{{ primary_home }}/.local/bin/", "")) diff --git a/image/test_installation_parity.py b/image/test_installation_parity.py index cdb92ec8..ec1c1b7c 100644 --- a/image/test_installation_parity.py +++ b/image/test_installation_parity.py @@ -188,6 +188,37 @@ def test_packaged_policy_is_the_same_source_used_by_checkout(self): with self.assertRaisesRegex(ValueError, 'both installer schemas'): defaults(Path('fixture')) + def test_chatgpt_launcher_matches_checkout_and_iso_at_native_display_scale(self): + from desktop_payload import prepare_app_launchers + from test_desktop_payload import INIT + + launcher = '.local/share/applications/chatgpt.desktop' + (self.home / launcher).parent.mkdir(parents=True) + task = next(task for task in yaml.safe_load( + (ROOT / 'roles/dotfiles/tasks/main.yml').read_text()) + if task.get('name') == 'Install MIME defaults and desktop launchers') + task['become'] = False + task['loop'] = [item for item in task['loop'] if item['name'] == 'chatgpt.desktop'] + task['ansible.builtin.copy']['src'] = str(ROOT / 'roles/dotfiles/files') + '/{{ item.name }}' + playbook = self.root / 'chatgpt-launcher.yml' + playbook.write_text(yaml.safe_dump([{ + 'hosts': 'localhost', 'connection': 'local', 'gather_facts': False, + 'vars': {'primary_home': str(self.home), 'primary_user': 'fixture', + 'features': {'proprietary_apps': True}}, + 'tasks': [task], + }])) + result = subprocess.run(['ansible-playbook', '-i', 'localhost,', str(playbook)], + env=os.environ, text=True, capture_output=True, timeout=30) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + payload = self.root / 'payload' + prepare_app_launchers(ROOT, payload) + iso_home = self.root / 'iso-account' + INIT.seed_applications(payload / 'usr/share/cybexos/user-seed', iso_home) + self.assertEqual((self.home / launcher).read_bytes(), (iso_home / launcher).read_bytes()) + self.assertIn('Exec=chatgpt --ozone-platform=wayland %U', + (iso_home / launcher).read_text().splitlines()) + if __name__ == '__main__': unittest.main() diff --git a/roles/dotfiles/files/chatgpt.desktop b/roles/dotfiles/files/chatgpt.desktop index 91db0543..93fe462d 100644 --- a/roles/dotfiles/files/chatgpt.desktop +++ b/roles/dotfiles/files/chatgpt.desktop @@ -2,7 +2,7 @@ Name=ChatGPT Comment=ChatGPT by OpenAI GenericName=AI assistant -Exec=chatgpt %U +Exec=chatgpt --ozone-platform=wayland %U Icon=chatgpt Type=Application Terminal=false diff --git a/tests/quickshell/deployment-convergence.test.cjs b/tests/quickshell/deployment-convergence.test.cjs index d64aa6ee..f6f7b97d 100644 --- a/tests/quickshell/deployment-convergence.test.cjs +++ b/tests/quickshell/deployment-convergence.test.cjs @@ -136,8 +136,8 @@ test("ChatGPT launcher follows portable desktop scaling", () => { assert.match(dotfileTasks, /Install MIME defaults and desktop launchers[\s\S]{0,1200}?name: chatgpt\.desktop[\s\S]{0,120}?features\.proprietary_apps/, "the launcher must be deployed as a per-user package override"); - assert.match(launcher, /^Exec=chatgpt %U$/m, - "ChatGPT follows the display scale selected by the desktop"); + assert.match(launcher, /^Exec=chatgpt --ozone-platform=wayland %U$/m, + "native Wayland follows the display scale even when XWayland scaling is disabled"); }); test("disabled Docker and Tailscale retire activation and imported trust", () => { diff --git a/tests/verify-system b/tests/verify-system index d5a325fc..dfa451ce 100755 --- a/tests/verify-system +++ b/tests/verify-system @@ -668,8 +668,8 @@ if feature_enabled proprietary_apps; then check 'ChatGPT repository key matches the reviewed OpenAI key' \ cmp -s "$repo_root/roles/apps/files/chatgpt-repository-key.asc" \ /etc/pki/rpm-gpg/RPM-GPG-KEY-chatgpt - check 'ChatGPT launcher follows the desktop display scale' \ - grep -qx 'Exec=chatgpt %U' \ + check 'ChatGPT launcher uses native Wayland for desktop display scaling' \ + grep -qx 'Exec=chatgpt --ozone-platform=wayland %U' \ "$HOME/.local/share/applications/chatgpt.desktop" check 'Brave restore, locale spellcheck, and 1Password policy is valid JSON' \ jq -e --arg language "${regional_locale%%_*}" \