From 7dd32335ca67d71fc01a79e96ec975fde5d5b97b Mon Sep 17 00:00:00 2001 From: vii Date: Fri, 18 Sep 2026 21:13:53 -0500 Subject: [PATCH 01/12] fix(input): advance the TextBox caret past trailing whitespace The final cluster rect SDL_ttf reports stops at the last inked glyph, so a caret at the end of a single-line field did not move when a trailing space was typed. Use the laid-out width there; wrapped layouts keep the clamp because their width is the widest line rather than the last one. --- src/runtime/roblox_text_surface_overlay.cc | 17 +++++++++------ tests/roblox_text_display_state_test.cc | 25 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/runtime/roblox_text_surface_overlay.cc b/src/runtime/roblox_text_surface_overlay.cc index 958611b..07cd8d5 100644 --- a/src/runtime/roblox_text_surface_overlay.cc +++ b/src/runtime/roblox_text_surface_overlay.cc @@ -244,7 +244,7 @@ struct TextBytePosition { }; TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset, - int text_width) { + int text_width, bool single_line) { if (layout == nullptr) { return {}; } @@ -259,7 +259,11 @@ TextBytePosition TextPosition(TTF_Text* layout, std::size_t byte_offset, position.y = substring.rect.y; position.height = substring.rect.h; if ((substring.flags & TTF_SUBSTRING_TEXT_END) != 0) { - position.x = std::min(text_width, substring.rect.x + substring.rect.w); + // Trailing whitespace has no ink, so the final cluster rect stops short of + // the laid-out width that a caret at the end of a single line sits behind. + position.x = single_line ? text_width + : std::min(text_width, + substring.rect.x + substring.rect.w); } return position; } @@ -501,11 +505,12 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() { TextBytePosition selection_end_position; if (layout != nullptr) { (void)TTF_GetTextSize(layout, &text_width, &text_height); - caret_position = TextPosition(layout, caret_byte, text_width); - selection_begin_position = - TextPosition(layout, selection_begin_byte, text_width); + caret_position = + TextPosition(layout, caret_byte, text_width, !wrapped_layout); + selection_begin_position = TextPosition(layout, selection_begin_byte, + text_width, !wrapped_layout); selection_end_position = - TextPosition(layout, selection_end_byte, text_width); + TextPosition(layout, selection_end_byte, text_width, !wrapped_layout); } if (!text.value.empty()) { // SDL_ttf implementations have historically differed on whether fg.a is diff --git a/tests/roblox_text_display_state_test.cc b/tests/roblox_text_display_state_test.cc index 5931957..f19b460 100644 --- a/tests/roblox_text_display_state_test.cc +++ b/tests/roblox_text_display_state_test.cc @@ -569,6 +569,31 @@ TEST(RobloxTextDisplayStateTest, WrapsMultilineTextInsideNativeTextBox) { EXPECT_TRUE(overlay.Shutdown().ok()); } +TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) { + RobloxTextSurfaceOverlay overlay; + ASSERT_TRUE(overlay.Initialize({800, 600}).ok()); + RobloxTextDisplaySink sink = overlay.sink(); + const auto rightmost_pixel = [&](const std::string& text, + RobloxTextDisplayEvent event) { + RobloxTextDisplayUpdate update = + Show(1, text, static_cast(text.size())); + update.event = event; + update.font_size = 18.0F; + sink.update(sink.context, update); + MocktailTextOverlayFrameInfo frame; + EXPECT_TRUE(overlay.QueryFrame(&frame)); + std::vector rgba(frame.rgba_bytes); + EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size())); + return FindAlphaBounds(frame, rgba).maximum_x; + }; + + const int typed = rightmost_pixel("hi", RobloxTextDisplayEvent::kShow); + const int spaced = rightmost_pixel("hi ", RobloxTextDisplayEvent::kUpdate); + + EXPECT_GT(spaced, typed); + EXPECT_TRUE(overlay.Shutdown().ok()); +} + } // namespace } // namespace runtime } // namespace mocktail From 02108a9bb127eeca2e0cb98ffb118fbc3e0d1135 Mon Sep 17 00:00:00 2001 From: vii Date: Fri, 18 Sep 2026 22:10:07 -0500 Subject: [PATCH 02/12] perf(input): reuse the TextBox overlay fonts across rasters Every keystroke re-ran TTF_Init, a fontconfig sort, six TTF_OpenFont calls and TTF_Quit on the render thread. Hold the opened fonts on the overlay and reopen them only when the Roblox font or the point size changes, keeping the current set when a reopen fails. Measured over 100 rasters, 1.59 ms to 0.12 ms per keystroke. --- include/runtime/roblox_text_surface_overlay.h | 8 ++ src/runtime/roblox_text_surface_overlay.cc | 81 +++++++++++++------ tests/roblox_text_display_state_test.cc | 27 +++++++ 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/include/runtime/roblox_text_surface_overlay.h b/include/runtime/roblox_text_surface_overlay.h index 6a6b0f4..1ce7879 100644 --- a/include/runtime/roblox_text_surface_overlay.h +++ b/include/runtime/roblox_text_surface_overlay.h @@ -11,6 +11,8 @@ #include "mocktail/status.h" #include "runtime/roblox_text_display_state.h" +struct TTF_Font; + namespace mocktail { namespace runtime { @@ -39,6 +41,8 @@ class RobloxTextSurfaceOverlay final { const RobloxTextDisplayUpdate& update); void ApplyUpdate(const RobloxTextDisplayUpdate& update); Status RasterizeLocked(); + Status OpenFontsLocked(std::int32_t font, float point_size); + void CloseFontsLocked(); void ClearFrameLocked(); void RecordFailureLocked(Status status); @@ -46,10 +50,14 @@ class RobloxTextSurfaceOverlay final { RobloxTextDisplayState state_; RobloxTextOverlayViewport viewport_; std::vector rgba_; + std::vector fonts_; + std::int32_t fonts_font_ = 0; + float fonts_point_size_ = 0.0F; std::uint64_t state_revision_ = 0; std::uint64_t raster_revision_ = 0; Status failure_; bool initialized_ = false; + bool ttf_initialized_ = false; bool failure_logged_ = false; bool ready_logged_ = false; }; diff --git a/src/runtime/roblox_text_surface_overlay.cc b/src/runtime/roblox_text_surface_overlay.cc index 07cd8d5..5982d70 100644 --- a/src/runtime/roblox_text_surface_overlay.cc +++ b/src/runtime/roblox_text_surface_overlay.cc @@ -301,6 +301,7 @@ Status RobloxTextSurfaceOverlay::Shutdown() { } state_.Reset(); ClearFrameLocked(); + CloseFontsLocked(); viewport_ = {}; initialized_ = false; if (owned_active_overlay) { @@ -445,10 +446,6 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() { const bool wrapped_layout = presentation.multiline || presentation.text_wrapped; - if (!TTF_Init()) { - SecureClear(&candidate); - return RasterizationFailure("unable to initialize SDL3_ttf"); - } const RobloxTextFontSelection font_selection = ResolveRobloxTextFont(presentation.font); const float native_point_size = @@ -458,23 +455,12 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() { ? std::clamp(native_point_size * font_selection.size_scale, 1.0F, 512.0F) : std::clamp(static_cast(height) * 0.38F, 12.0F, 28.0F); - const std::vector font_files = ResolveFontFiles(font_selection); - std::vector fonts; - fonts.reserve(font_files.size()); - for (const std::string& file : font_files) { - TTF_Font* font = TTF_OpenFont(file.c_str(), point_size); - if (font != nullptr) { - fonts.push_back(font); - } - } - if (fonts.empty()) { - TTF_Quit(); + if (Status status = OpenFontsLocked(presentation.font, point_size); + !status.ok()) { SecureClear(&candidate); - return RasterizationFailure("unable to open a Roblox or fallback font"); - } - for (std::size_t index = 1; index < fonts.size(); ++index) { - (void)TTF_AddFallbackFont(fonts.front(), fonts[index]); + return status; } + const std::vector& fonts = fonts_; if (wrapped_layout) { TTF_HorizontalAlignment alignment = TTF_HORIZONTAL_ALIGN_LEFT; if (presentation.x_alignment == 1) { @@ -629,11 +615,6 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() { if (layout != nullptr) { TTF_DestroyText(layout); } - TTF_ClearFallbackFonts(fonts.front()); - for (auto iterator = fonts.rbegin(); iterator != fonts.rend(); ++iterator) { - TTF_CloseFont(*iterator); - } - TTF_Quit(); if (!status.ok()) { SecureClear(&candidate); @@ -651,6 +632,58 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() { return Status::Ok(); } +Status RobloxTextSurfaceOverlay::OpenFontsLocked(std::int32_t font, + float point_size) { + if (!fonts_.empty() && fonts_font_ == font && + fonts_point_size_ == point_size) { + return Status::Ok(); + } + if (!TTF_Init()) { + return RasterizationFailure("unable to initialize SDL3_ttf"); + } + const std::vector files = + ResolveFontFiles(ResolveRobloxTextFont(font)); + std::vector opened; + opened.reserve(files.size()); + for (const std::string& file : files) { + TTF_Font* candidate = TTF_OpenFont(file.c_str(), point_size); + if (candidate != nullptr) { + opened.push_back(candidate); + } + } + // A failed reopen leaves the fonts already rendering in place. + if (opened.empty()) { + TTF_Quit(); + return RasterizationFailure("unable to open a Roblox or fallback font"); + } + for (std::size_t index = 1; index < opened.size(); ++index) { + (void)TTF_AddFallbackFont(opened.front(), opened[index]); + } + CloseFontsLocked(); + fonts_ = std::move(opened); + ttf_initialized_ = true; + fonts_font_ = font; + fonts_point_size_ = point_size; + return Status::Ok(); +} + +void RobloxTextSurfaceOverlay::CloseFontsLocked() { + if (!fonts_.empty()) { + TTF_ClearFallbackFonts(fonts_.front()); + for (auto iterator = fonts_.rbegin(); iterator != fonts_.rend(); + ++iterator) { + TTF_CloseFont(*iterator); + } + fonts_.clear(); + } + fonts_font_ = 0; + fonts_point_size_ = 0.0F; + if (ttf_initialized_) { + TTF_Quit(); + ttf_initialized_ = false; + } +} + void RobloxTextSurfaceOverlay::ClearFrameLocked() { SecureClear(&rgba_); raster_revision_ = 0; diff --git a/tests/roblox_text_display_state_test.cc b/tests/roblox_text_display_state_test.cc index f19b460..46b7c1d 100644 --- a/tests/roblox_text_display_state_test.cc +++ b/tests/roblox_text_display_state_test.cc @@ -594,6 +594,33 @@ TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) { EXPECT_TRUE(overlay.Shutdown().ok()); } +TEST(RobloxTextDisplayStateTest, ReopensFontsAfterPointSizeChange) { + RobloxTextSurfaceOverlay overlay; + ASSERT_TRUE(overlay.Initialize({800, 600}).ok()); + RobloxTextDisplaySink sink = overlay.sink(); + const std::string text = "size"; + const auto text_width = [&](float font_size, RobloxTextDisplayEvent event) { + RobloxTextDisplayUpdate update = Show(1, text, 4); + update.event = event; + update.area_height = 64; + update.font_size = font_size; + sink.update(sink.context, update); + MocktailTextOverlayFrameInfo frame; + EXPECT_TRUE(overlay.QueryFrame(&frame)); + std::vector rgba(frame.rgba_bytes); + EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size())); + const auto bounds = FindAlphaBounds(frame, rgba); + EXPECT_TRUE(bounds.valid()); + return bounds.valid() ? bounds.maximum_x - bounds.minimum_x : 0; + }; + + const int small = text_width(14.0F, RobloxTextDisplayEvent::kShow); + const int large = text_width(28.0F, RobloxTextDisplayEvent::kUpdate); + + EXPECT_GT(large, small); + EXPECT_TRUE(overlay.Shutdown().ok()); +} + } // namespace } // namespace runtime } // namespace mocktail From ff905fe5b7f990897bd0db555727a2cea5542efc Mon Sep 17 00:00:00 2001 From: vii Date: Sat, 19 Sep 2026 01:10:54 -0500 Subject: [PATCH 03/12] test(input): pin the caret position before a trailing space SDL_ttf marks only the zero-length substring past the end of the text with TTF_SUBSTRING_TEXT_END, so a caret left of a trailing space keeps the cluster position. Cover both carets so the end-of-text branch cannot swallow the one beside it. --- .../{mocktail-bin => nightcap-bin}/.SRCINFO | 0 .../{mocktail-bin => nightcap-bin}/PKGBUILD | 0 .../{mocktail-git => nightcap-git}/.SRCINFO | 0 .../{mocktail-git => nightcap-git}/PKGBUILD | 0 packaging/aur/{mocktail => nightcap}/.SRCINFO | 0 packaging/aur/{mocktail => nightcap}/PKGBUILD | 0 tests/roblox_text_display_state_test.cc | 27 +++++++++++++++++++ 7 files changed, 27 insertions(+) rename packaging/aur/{mocktail-bin => nightcap-bin}/.SRCINFO (100%) rename packaging/aur/{mocktail-bin => nightcap-bin}/PKGBUILD (100%) rename packaging/aur/{mocktail-git => nightcap-git}/.SRCINFO (100%) rename packaging/aur/{mocktail-git => nightcap-git}/PKGBUILD (100%) rename packaging/aur/{mocktail => nightcap}/.SRCINFO (100%) rename packaging/aur/{mocktail => nightcap}/PKGBUILD (100%) diff --git a/packaging/aur/mocktail-bin/.SRCINFO b/packaging/aur/nightcap-bin/.SRCINFO similarity index 100% rename from packaging/aur/mocktail-bin/.SRCINFO rename to packaging/aur/nightcap-bin/.SRCINFO diff --git a/packaging/aur/mocktail-bin/PKGBUILD b/packaging/aur/nightcap-bin/PKGBUILD similarity index 100% rename from packaging/aur/mocktail-bin/PKGBUILD rename to packaging/aur/nightcap-bin/PKGBUILD diff --git a/packaging/aur/mocktail-git/.SRCINFO b/packaging/aur/nightcap-git/.SRCINFO similarity index 100% rename from packaging/aur/mocktail-git/.SRCINFO rename to packaging/aur/nightcap-git/.SRCINFO diff --git a/packaging/aur/mocktail-git/PKGBUILD b/packaging/aur/nightcap-git/PKGBUILD similarity index 100% rename from packaging/aur/mocktail-git/PKGBUILD rename to packaging/aur/nightcap-git/PKGBUILD diff --git a/packaging/aur/mocktail/.SRCINFO b/packaging/aur/nightcap/.SRCINFO similarity index 100% rename from packaging/aur/mocktail/.SRCINFO rename to packaging/aur/nightcap/.SRCINFO diff --git a/packaging/aur/mocktail/PKGBUILD b/packaging/aur/nightcap/PKGBUILD similarity index 100% rename from packaging/aur/mocktail/PKGBUILD rename to packaging/aur/nightcap/PKGBUILD diff --git a/tests/roblox_text_display_state_test.cc b/tests/roblox_text_display_state_test.cc index 46b7c1d..6420b0a 100644 --- a/tests/roblox_text_display_state_test.cc +++ b/tests/roblox_text_display_state_test.cc @@ -594,6 +594,33 @@ TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) { EXPECT_TRUE(overlay.Shutdown().ok()); } +TEST(RobloxTextDisplayStateTest, KeepsCaretBeforeTrailingSpaceDistinct) { + RobloxTextSurfaceOverlay overlay; + ASSERT_TRUE(overlay.Initialize({800, 600}).ok()); + RobloxTextDisplaySink sink = overlay.sink(); + const std::string text = "hi "; + const auto rightmost_pixel = [&](int32_t cursor_utf16, + RobloxTextDisplayEvent event) { + RobloxTextDisplayUpdate update = Show(1, text, cursor_utf16); + update.event = event; + update.font_size = 18.0F; + sink.update(sink.context, update); + MocktailTextOverlayFrameInfo frame; + EXPECT_TRUE(overlay.QueryFrame(&frame)); + std::vector rgba(frame.rgba_bytes); + EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size())); + const auto bounds = FindAlphaBounds(frame, rgba); + EXPECT_TRUE(bounds.valid()); + return bounds.valid() ? bounds.maximum_x : -1; + }; + + const int before_space = rightmost_pixel(2, RobloxTextDisplayEvent::kShow); + const int after_space = rightmost_pixel(3, RobloxTextDisplayEvent::kUpdate); + + EXPECT_GT(after_space, before_space); + EXPECT_TRUE(overlay.Shutdown().ok()); +} + TEST(RobloxTextDisplayStateTest, ReopensFontsAfterPointSizeChange) { RobloxTextSurfaceOverlay overlay; ASSERT_TRUE(overlay.Initialize({800, 600}).ok()); From 265b47644023f2b6c8e35e3b2d20845e4a6334a7 Mon Sep 17 00:00:00 2001 From: vii Date: Sat, 19 Sep 2026 01:11:16 -0500 Subject: [PATCH 04/12] docs(packaging): list Nightcap releases in the AppStream metadata The releases block carried upstream's 1.0.x history, so a software centre read the app as newer than every Nightcap tag and would never offer 0.3.x as an update. Describe the 0.x line instead, and name Nightcap in the project description. --- ...o.github.CoderDayton.nightcap.metainfo.xml | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/packaging/io.github.CoderDayton.nightcap.metainfo.xml b/packaging/io.github.CoderDayton.nightcap.metainfo.xml index f3a5ae8..95f0f28 100644 --- a/packaging/io.github.CoderDayton.nightcap.metainfo.xml +++ b/packaging/io.github.CoderDayton.nightcap.metainfo.xml @@ -21,8 +21,9 @@ Sign in to Roblox, browse experiences, and play them on Linux.

- Mocktail is an independent community project. It is not affiliated with - Roblox Corporation or VinegarHQ, and the Roblox client is not included. + Nightcap is an independent community project, forked from Mocktail. It + is not affiliated with Roblox Corporation or VinegarHQ, and the Roblox + client is not included.

@@ -56,45 +57,38 @@ mocktail - +
    -
  • Fixed login issues
  • -
  • Fixed low-resolution textures, skybox rendering and HiDPI scaling.
  • -
  • Added vc support.
  • -
  • Added Xbox and PlayStation gamepad support.
  • -
  • Added Nix support
  • -
  • Improved text input, fullscreen mode, window sizing, and mouse controls.
  • -
  • Updated support for Roblox 2.736.1408.
  • +
  • Added a custom Discord presence: set the title, state, and icons.
  • +
  • Added --profile to record a trace of the renderer's work.
  • +
  • Faster texture loading: compressed textures decode and resample on worker threads.
  • +
  • Lower CPU use while playing.
  • +
  • Fixed GameMode pinning the game to a few CPU cores.
- +
    -
  • Fixed issues with downloading Roblox on first launch
  • -
  • Added fflags.json for custom FFlags
  • -
  • Added a message if your cookies have expired
  • +
  • Discord Rich Presence is on by default.
  • +
  • Added input.raw_mouse for mouse look in the host's own units.
  • +
  • Fixed mouse look after a long turn.
  • +
  • Fixed a first-run failure with "cannot lock appStorage".
  • +
  • Values in fflags.json now win over the performance preset.
- +
    -
  • Fixed the “Roblox is out of date” bug
  • -
  • Improved logging
  • -
  • Fixed issues with Discord RPC where the game you were playing wasn’t displayed
  • +
  • First Nightcap release, forked from Mocktail.
  • +
  • Added ETC2 and EAC texture emulation on Vulkan.
  • +
  • Added PNG texture overrides.
  • +
  • Lower input latency.
  • +
  • Roblox now sees the real host RAM and display size.
- - -
    -
  • Fix issues with GameMode
  • -
  • Add Discord RPC support
  • -
-
-
-
From 8be3618cd5050e367829986acecc7efe1596222e Mon Sep 17 00:00:00 2001 From: vii Date: Sat, 19 Sep 2026 01:12:12 -0500 Subject: [PATCH 05/12] feat(packaging): ship DEB, RPM and pacman packages with each release The release workflow now builds the three native packages beside the AppImage and attaches them to the GitHub release with a checksum each. Nothing is pushed to a distribution repository. Packages are named nightcap and take their version from the release tag instead of upstream's project version, so they no longer claim to be Mocktail 1.0.4. Each variant conflicts with the others because they all own /usr/bin/mocktail. The AUR workflow no longer clones or pushes aur.archlinux.org; it refreshes nightcap-git metadata on demand and uploads it as an artifact. The recipes move to packaging/aur/nightcap{,-bin,-git}, point at this repository, and version from the newest release tag. --- .github/workflows/aur.yml | 90 +++------------ .github/workflows/release.yml | 164 +++++++++++++++++++++++++++- CMakeLists.txt | 39 ++++--- README.md | 22 +++- packaging/arch/PKGBUILD | 12 +- packaging/aur/nightcap-bin/.SRCINFO | 18 +-- packaging/aur/nightcap-bin/PKGBUILD | 23 ++-- packaging/aur/nightcap-git/.SRCINFO | 14 ++- packaging/aur/nightcap-git/PKGBUILD | 29 +++-- packaging/aur/nightcap/.SRCINFO | 16 +-- packaging/aur/nightcap/PKGBUILD | 19 ++-- scripts/package_version.sh | 18 +++ site/index.html | 5 + tests/native_packaging_test.sh | 85 +++++++++++--- 14 files changed, 378 insertions(+), 176 deletions(-) create mode 100755 scripts/package_version.sh diff --git a/.github/workflows/aur.yml b/.github/workflows/aur.yml index af296fc..431beb6 100644 --- a/.github/workflows/aur.yml +++ b/.github/workflows/aur.yml @@ -1,8 +1,6 @@ -name: Publish AUR packages +name: Check AUR packages on: - push: - branches: [main] workflow_dispatch: permissions: @@ -13,8 +11,8 @@ concurrency: cancel-in-progress: true jobs: - publish: - name: Publish AUR packages + metadata: + name: Refresh AUR metadata runs-on: ubuntu-24.04 timeout-minutes: 15 steps: @@ -27,10 +25,9 @@ jobs: shell: bash run: | set -Eeuo pipefail - package_dir="$GITHUB_WORKSPACE/packaging/aur/mocktail-git" - version="$(sed -n \ - 's/^[[:space:]]*VERSION[[:space:]]\+\([0-9][0-9.]*\).*/\1/p' \ - CMakeLists.txt | head -n 1)" + package_dir="$GITHUB_WORKSPACE/packaging/aur/nightcap-git" + version="$(git describe --tags --abbrev=0 --match='v[0-9]*')" + version="${version#v}" [[ "$version" =~ ^[0-9]+(\.[0-9]+)*$ ]] pkgver="${version}.r$(git rev-list --count HEAD).g$(git rev-parse --short=7 HEAD)" sed -i "s/^pkgver=.*/pkgver=${pkgver}/" "$package_dir/PKGBUILD" @@ -39,76 +36,21 @@ jobs: --volume "$package_dir:/pkg" \ archlinux:base-devel bash -Eeuo pipefail -c ' useradd --create-home builder - install -d -o builder -g builder /tmp/mocktail-git + install -d -o builder -g builder /tmp/nightcap-git install -o builder -g builder -m 0644 \ - /pkg/PKGBUILD /tmp/mocktail-git/PKGBUILD + /pkg/PKGBUILD /tmp/nightcap-git/PKGBUILD runuser -u builder -- bash -c \ - "cd /tmp/mocktail-git && makepkg --printsrcinfo" \ + "cd /tmp/nightcap-git && makepkg --printsrcinfo" \ > /pkg/.SRCINFO ' grep -Fqx "pkgver=${pkgver}" "$package_dir/PKGBUILD" grep -Fqx $'\tpkgver = '"${pkgver}" "$package_dir/.SRCINFO" - printf 'AUR_PKGVER=%s\n' "$pkgver" >> "$GITHUB_ENV" - - name: Publish to the AUR - shell: bash - env: - AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - AUR_SSH_KEY_PASSPHRASE: ${{ secrets.AUR_SSH_KEY_PASSPHRASE }} - run: | - set -Eeuo pipefail - [[ -n "$AUR_SSH_PRIVATE_KEY" ]] - [[ -n "$AUR_SSH_KEY_PASSPHRASE" ]] - - private_key="$RUNNER_TEMP/aur_ed25519" - askpass="$RUNNER_TEMP/aur_askpass" - known_hosts="$RUNNER_TEMP/aur_known_hosts" - printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$private_key" - unset AUR_SSH_PRIVATE_KEY - chmod 0600 "$private_key" - printf '%s\n' \ - '#!/usr/bin/env bash' \ - 'printf '\''%s\n'\'' "$AUR_SSH_KEY_PASSPHRASE"' \ - > "$askpass" - chmod 0700 "$askpass" - eval "$(ssh-agent -s)" - trap 'ssh-agent -k >/dev/null; rm -f -- "$private_key" "$askpass"' EXIT - DISPLAY=:0 SSH_ASKPASS="$askpass" SSH_ASKPASS_REQUIRE=force \ - ssh-add "$private_key" "$known_hosts" - fingerprint="$(ssh-keygen -lf "$known_hosts" -E sha256 | awk '{print $2}')" - [[ "$fingerprint" == \ - 'SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4' ]] - - export GIT_SSH_COMMAND="ssh -F /dev/null -i $private_key \ - -o BatchMode=yes -o IdentitiesOnly=yes \ - -o StrictHostKeyChecking=yes \ - -o UserKnownHostsFile=$known_hosts" - publish_aur_package() { - local package_name="$1" - local commit_message="$2" - local source_dir="packaging/aur/$package_name" - local aur_dir="$RUNNER_TEMP/${package_name}-aur" - - git clone --branch master --single-branch \ - "ssh://aur@aur.archlinux.org/${package_name}.git" "$aur_dir" - install -m 0644 "$source_dir/PKGBUILD" "$aur_dir/PKGBUILD" - install -m 0644 "$source_dir/.SRCINFO" "$aur_dir/.SRCINFO" - - git -C "$aur_dir" config user.name komaruworld - git -C "$aur_dir" config user.email rtysus228@gmail.com - git -C "$aur_dir" add PKGBUILD .SRCINFO - if git -C "$aur_dir" diff --cached --quiet; then - printf '%s is already published\n' "$package_name" - return - fi - git -C "$aur_dir" commit -m "$commit_message" - git -C "$aur_dir" push origin HEAD:master - } - - publish_aur_package mocktail-git "Update to ${AUR_PKGVER}" - publish_aur_package mocktail 'make builds stable' - publish_aur_package mocktail-bin 'make builds stable' + - name: Upload AUR metadata + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: Nightcap-AUR + path: packaging/aur/nightcap-git/.SRCINFO + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index abb57dc..2571885 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,10 +126,153 @@ jobs: if-no-files-found: error retention-days: 14 + deb: + name: DEB (Ubuntu 26.04) + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + submodules: recursive + + - name: Build DEB + shell: bash + run: | + set -Eeuo pipefail + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace \ + --env MOCKTAIL_PACKAGE_VERSION="$(./scripts/package_version.sh)" \ + ubuntu:26.04 bash -Eeuo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install --yes --no-install-recommends \ + build-essential cmake git ninja-build pkg-config lld fakeroot \ + dpkg-dev file libsdl3-dev libsdl3-ttf-dev \ + libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \ + libyaml-dev libelf-dev libminizip-dev libcapstone-dev \ + libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \ + libutf8proc-dev libfontconfig1-dev libegl-dev \ + libvulkan-dev libplacebo-dev zlib1g-dev libpng-dev + cmake -S . -B build-deb -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DMOCKTAIL_PACKAGE_NAME=nightcap \ + -DMOCKTAIL_PACKAGE_VERSION="$MOCKTAIL_PACKAGE_VERSION" \ + -DBUILD_TESTING=OFF + cmake --build build-deb --parallel 4 + cpack --config build-deb/CPackConfig.cmake \ + -G DEB -B dist/deb + dpkg-deb --info dist/deb/*.deb >/dev/null + ' + + - name: Upload DEB + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: Nightcap-DEB + path: dist/deb/*.deb + if-no-files-found: error + retention-days: 14 + + rpm: + name: RPM (Fedora 44) + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + submodules: recursive + + - name: Build RPM + shell: bash + run: | + set -Eeuo pipefail + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace \ + --env MOCKTAIL_PACKAGE_VERSION="$(./scripts/package_version.sh)" \ + fedora:44 bash -Eeuo pipefail -c ' + dnf install --assumeyes \ + gcc-c++ cmake git ninja-build pkgconf-pkg-config lld rpm-build \ + SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \ + nlohmann-json-devel libyaml-devel elfutils-libelf-devel \ + minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \ + webkitgtk6.0-devel utf8proc-devel fontconfig-devel \ + libglvnd-devel vulkan-headers vulkan-loader-devel \ + libplacebo-devel zlib-ng-compat-devel libpng-devel + cmake -S . -B build-rpm -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DMOCKTAIL_PACKAGE_NAME=nightcap \ + -DMOCKTAIL_PACKAGE_VERSION="$MOCKTAIL_PACKAGE_VERSION" \ + -DBUILD_TESTING=OFF + cmake --build build-rpm --parallel 4 + cpack --config build-rpm/CPackConfig.cmake \ + -G RPM -B dist/rpm + rpm --query --package --info dist/rpm/*.rpm >/dev/null + ' + + - name: Upload RPM + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: Nightcap-RPM + path: dist/rpm/*.rpm + if-no-files-found: error + retention-days: 14 + + arch: + name: Pacman package (Arch Linux) + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + submodules: recursive + + - name: Build pacman package + shell: bash + run: | + set -Eeuo pipefail + version="$(./scripts/package_version.sh)" + sed -i "s/^pkgver=.*/pkgver=${version}/" packaging/arch/PKGBUILD + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace \ + ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c ' + pacman -Syu --needed --noconfirm \ + cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \ + nlohmann-json libyaml libelf minizip capstone gtk4 \ + libadwaita webkitgtk-6.0 libutf8proc fontconfig libpng \ + libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib + useradd --create-home builder + chown -R builder:builder /workspace + runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \ + makepkg --dir /workspace/packaging/arch \ + --noconfirm --cleanbuild --force + install -d dist/arch + cp packaging/arch/*.pkg.tar.zst dist/arch/ + pacman -Qp dist/arch/*.pkg.tar.zst >/dev/null + ' + + - name: Upload pacman package + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: Nightcap-Arch + path: dist/arch/*.pkg.tar.zst + if-no-files-found: error + retention-days: 14 + publish: name: Publish GitHub release if: startsWith(github.ref, 'refs/tags/v') - needs: [appimage] + needs: [appimage, deb, rpm, arch] runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: @@ -138,6 +281,13 @@ jobs: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Download packages + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: 'Nightcap-*' + merge-multiple: true + path: release + - name: Download AppImage uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -152,7 +302,12 @@ jobs: set -Eeuo pipefail tag="${GITHUB_REF_NAME}" mv release/Mocktail-x86_64.AppImage release/Nightcap-x86_64.AppImage - (cd release && sha256sum Nightcap-x86_64.AppImage > Nightcap-x86_64.AppImage.sha256) + ( + cd release + for asset in Nightcap-x86_64.AppImage *.deb *.rpm *.pkg.tar.zst; do + sha256sum "$asset" > "$asset.sha256" + done + ) ./scripts/release_notes.sh "$tag" > release/notes.md # The Flatpak workflow creates the same release; whichever job # arrives second sees "already exists" and continues. @@ -165,4 +320,7 @@ jobs: --title "Nightcap $tag" --notes-file release/notes.md gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \ release/Nightcap-x86_64.AppImage \ - release/Nightcap-x86_64.AppImage.sha256 + release/Nightcap-x86_64.AppImage.sha256 \ + release/*.deb release/*.deb.sha256 \ + release/*.rpm release/*.rpm.sha256 \ + release/*.pkg.tar.zst release/*.pkg.tar.zst.sha256 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0df5b53..e70f30c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -786,23 +786,29 @@ install(CODE " endif() ") +set(MOCKTAIL_PACKAGE_NAMES mocktail mocktail-nightly nightcap nightcap-nightly) set(MOCKTAIL_PACKAGE_NAME "mocktail" CACHE STRING "Native package name" ) +set(MOCKTAIL_PACKAGE_VERSION "${PROJECT_VERSION}" CACHE STRING + "Native package version" +) set(MOCKTAIL_PACKAGE_VERSION_SUFFIX "" CACHE STRING "Native package version suffix" ) -if(NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail" AND - NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly") +if(NOT MOCKTAIL_PACKAGE_NAME IN_LIST MOCKTAIL_PACKAGE_NAMES) + string(JOIN ", " MOCKTAIL_PACKAGE_NAMES_TEXT ${MOCKTAIL_PACKAGE_NAMES}) message(FATAL_ERROR - "MOCKTAIL_PACKAGE_NAME must be mocktail or mocktail-nightly") + "MOCKTAIL_PACKAGE_NAME must be one of ${MOCKTAIL_PACKAGE_NAMES_TEXT}") endif() set(CPACK_PACKAGE_NAME "${MOCKTAIL_PACKAGE_NAME}") -set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}") -set(CPACK_PACKAGE_VENDOR "Mocktail Project") -set(CPACK_PACKAGE_CONTACT "komaruworld ") -set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/komaruworld/mocktail") +set(CPACK_PACKAGE_VERSION + "${MOCKTAIL_PACKAGE_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}" +) +set(CPACK_PACKAGE_VENDOR "Nightcap") +set(CPACK_PACKAGE_CONTACT "vii ") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/CoderDayton/nightcap") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") set(CPACK_PACKAGE_DESCRIPTION "Runs the Android x86-64 Roblox client on Linux." @@ -812,7 +818,7 @@ set(CPACK_PACKAGING_INSTALL_PREFIX "/usr") set(CPACK_STRIP_FILES TRUE) set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "komaruworld ") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "vii ") set(CPACK_DEBIAN_PACKAGE_SECTION "games") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) @@ -822,15 +828,14 @@ set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0") set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games") set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) -if(MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly") - set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail") - set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail") - set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail") -else() - set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail-nightly") - set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail-nightly") - set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail-nightly") -endif() +# Every variant installs the same /usr/bin/mocktail and /usr/share/mocktail, +# so only one of them can be present at a time. +set(MOCKTAIL_PACKAGE_SIBLINGS ${MOCKTAIL_PACKAGE_NAMES}) +list(REMOVE_ITEM MOCKTAIL_PACKAGE_SIBLINGS "${MOCKTAIL_PACKAGE_NAME}") +string(JOIN ", " MOCKTAIL_PACKAGE_CONFLICTS ${MOCKTAIL_PACKAGE_SIBLINGS}) +set(CPACK_DEBIAN_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}") +set(CPACK_DEBIAN_PACKAGE_REPLACES "${MOCKTAIL_PACKAGE_CONFLICTS}") +set(CPACK_RPM_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}") include(CPack) add_dependencies(mocktail all_stubs) diff --git a/README.md b/README.md index 3ac2d4f..6f6efa8 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,24 @@ chmod +x Nightcap-x86_64.AppImage ./Nightcap-x86_64.AppImage ``` -Or build and install it from source using the steps under -[Building](#building). There are no AUR, DEB, or RPM packages of Nightcap. -Upstream ships those for unmodified Mocktail, see -[komaruworld/mocktail](https://github.com/komaruworld/mocktail#readme). +## Distribution packages + +Each release also carries a DEB, an RPM, and a pacman package, with a +`.sha256` beside every file on the +[releases page](https://github.com/CoderDayton/nightcap/releases/latest): + +```bash +sudo apt install ./nightcap_-1_amd64.deb +sudo dnf install ./nightcap--1.x86_64.rpm +sudo pacman -U nightcap--1-x86_64.pkg.tar.zst +``` + +They install `/usr/bin/mocktail`, so they conflict with upstream's `mocktail` +package. Nightcap is not in any distribution repository or the AUR; the +recipes under `packaging/aur` are kept for local builds only. + +Or build and install from source using the steps under +[Building](#building). ## How it works diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 57b0bbb..6284e50 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -1,12 +1,14 @@ -# Maintainer: komaruworld +# Maintainer: vii -pkgname=mocktail -pkgver=1.0.3 -pkgrel=2 +pkgname=nightcap +pkgver=0.3.1 +pkgrel=1 pkgdesc='Android x86-64 Roblox compatibility runtime for Linux' arch=('x86_64') -url='https://github.com/komaruworld/mocktail' +url='https://github.com/CoderDayton/nightcap' license=('Apache-2.0') +provides=('mocktail') +conflicts=('mocktail') depends=( 'capstone' 'curl' diff --git a/packaging/aur/nightcap-bin/.SRCINFO b/packaging/aur/nightcap-bin/.SRCINFO index 5bcf3a1..7fd41dd 100644 --- a/packaging/aur/nightcap-bin/.SRCINFO +++ b/packaging/aur/nightcap-bin/.SRCINFO @@ -1,8 +1,8 @@ -pkgbase = mocktail-bin +pkgbase = nightcap-bin pkgdesc = Android x86-64 Roblox compatibility runtime for Linux (prebuilt) - pkgver = 1.0.4 + pkgver = 0.3.1 pkgrel = 1 - url = https://github.com/komaruworld/mocktail + url = https://github.com/CoderDayton/nightcap arch = x86_64 license = Apache-2.0 depends = capstone @@ -30,12 +30,14 @@ pkgbase = mocktail-bin depends = zlib optdepends = gamemode: Feral GameMode integration provides = mocktail + provides = nightcap conflicts = mocktail - conflicts = mocktail-git - noextract = mocktail-1.0.4-x86_64.pkg.tar.zst + conflicts = nightcap + conflicts = nightcap-git + noextract = nightcap-0.3.1-1-x86_64.pkg.tar.zst options = !debug options = !strip - source = mocktail-1.0.4-x86_64.pkg.tar.zst::https://github.com/komaruworld/mocktail/releases/download/1.0.4/mocktail-1.0.4-x86_64.pkg.tar.zst - sha256sums = d5cc7216e88fd2889369e8eb7fba4276b2746c2b084dce566aaaa7505bc2ffb7 + source = nightcap-0.3.1-1-x86_64.pkg.tar.zst::https://github.com/CoderDayton/nightcap/releases/download/v0.3.1/nightcap-0.3.1-1-x86_64.pkg.tar.zst + sha256sums = SKIP -pkgname = mocktail-bin +pkgname = nightcap-bin diff --git a/packaging/aur/nightcap-bin/PKGBUILD b/packaging/aur/nightcap-bin/PKGBUILD index b6752d5..3695011 100644 --- a/packaging/aur/nightcap-bin/PKGBUILD +++ b/packaging/aur/nightcap-bin/PKGBUILD @@ -1,11 +1,11 @@ -# Maintainer: komaruworld +# Maintainer: vii -pkgname=mocktail-bin -pkgver=1.0.4 +pkgname=nightcap-bin +pkgver=0.3.1 pkgrel=1 pkgdesc='Android x86-64 Roblox compatibility runtime for Linux (prebuilt)' arch=('x86_64') -url='https://github.com/komaruworld/mocktail' +url='https://github.com/CoderDayton/nightcap' license=('Apache-2.0') depends=( 'capstone' @@ -33,17 +33,18 @@ depends=( 'zlib' ) optdepends=('gamemode: Feral GameMode integration') -provides=('mocktail') -conflicts=('mocktail' 'mocktail-git') +provides=('mocktail' 'nightcap') +conflicts=('mocktail' 'nightcap' 'nightcap-git') options=('!debug' '!strip') -# Release asset names do not include the AUR package release. -_archive="mocktail-${pkgver}-${CARCH}.pkg.tar.zst" -source=("${_archive}::https://github.com/komaruworld/mocktail/releases/download/${pkgver}/${_archive}") +# The release asset carries the pacman package release built by release.yml. +_archive="nightcap-${pkgver}-1-${CARCH}.pkg.tar.zst" +source=("${_archive}::https://github.com/CoderDayton/nightcap/releases/download/v${pkgver}/${_archive}") noextract=("${_archive}") -sha256sums=('d5cc7216e88fd2889369e8eb7fba4276b2746c2b084dce566aaaa7505bc2ffb7') +# Replace with the checksum published beside the release asset. +sha256sums=('SKIP') package() { bsdtar -xf "${srcdir}/${_archive}" -C "${pkgdir}" usr - install -Dm644 "${pkgdir}/usr/share/licenses/mocktail/LICENSE" \ + install -Dm644 "${pkgdir}/usr/share/licenses/nightcap/LICENSE" \ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" } diff --git a/packaging/aur/nightcap-git/.SRCINFO b/packaging/aur/nightcap-git/.SRCINFO index 76e6507..339d54e 100644 --- a/packaging/aur/nightcap-git/.SRCINFO +++ b/packaging/aur/nightcap-git/.SRCINFO @@ -1,8 +1,8 @@ -pkgbase = mocktail-git +pkgbase = nightcap-git pkgdesc = Android x86-64 Roblox compatibility runtime for Linux - pkgver = 1.0.2.r24.g9bdd737 + pkgver = 0.3.1.r0.g0000000 pkgrel = 1 - url = https://github.com/komaruworld/mocktail + url = https://github.com/CoderDayton/nightcap arch = x86_64 license = Apache-2.0 makedepends = cmake>=3.20 @@ -37,10 +37,12 @@ pkgbase = mocktail-git depends = zlib optdepends = gamemode: Feral GameMode integration provides = mocktail + provides = nightcap conflicts = mocktail - conflicts = mocktail-bin + conflicts = nightcap + conflicts = nightcap-bin options = !debug - source = mocktail::git+https://github.com/komaruworld/mocktail.git#branch=main + source = nightcap::git+https://github.com/CoderDayton/nightcap.git#branch=main sha256sums = SKIP -pkgname = mocktail-git +pkgname = nightcap-git diff --git a/packaging/aur/nightcap-git/PKGBUILD b/packaging/aur/nightcap-git/PKGBUILD index 5856f82..c437798 100644 --- a/packaging/aur/nightcap-git/PKGBUILD +++ b/packaging/aur/nightcap-git/PKGBUILD @@ -1,11 +1,11 @@ -# Maintainer: komaruworld +# Maintainer: vii -pkgname=mocktail-git -pkgver=1.0.2.r24.g9bdd737 +pkgname=nightcap-git +pkgver=0.3.1.r0.g0000000 pkgrel=1 pkgdesc='Android x86-64 Roblox compatibility runtime for Linux' arch=('x86_64') -url='https://github.com/komaruworld/mocktail' +url='https://github.com/CoderDayton/nightcap' license=('Apache-2.0') depends=( 'capstone' @@ -42,31 +42,30 @@ makedepends=( 'vulkan-headers' ) optdepends=('gamemode: Feral GameMode integration') -provides=('mocktail') -conflicts=('mocktail' 'mocktail-bin') +provides=('mocktail' 'nightcap') +conflicts=('mocktail' 'nightcap' 'nightcap-bin') options=('!debug') source=( - 'mocktail::git+https://github.com/komaruworld/mocktail.git#branch=main' + 'nightcap::git+https://github.com/CoderDayton/nightcap.git#branch=main' ) sha256sums=( 'SKIP' ) pkgver() { - cd mocktail + cd nightcap local version - version="$(sed -n \ - 's/^[[:space:]]*VERSION[[:space:]]\+\([0-9][0-9.]*\).*/\1/p' \ - CMakeLists.txt | head -n 1)" + version="$(git describe --tags --abbrev=0 --match='v[0-9]*' 2>/dev/null)" + version="${version#v}" printf '%s.r%s.g%s' \ - "${version}" \ + "${version:-0.0.0}" \ "$(git rev-list --count HEAD)" \ "$(git rev-parse --short=7 HEAD)" } prepare() { - cd mocktail + cd nightcap sed -i \ 's|add_subdirectory(third_party/Vulkan-Headers EXCLUDE_FROM_ALL)|find_package(VulkanHeaders CONFIG REQUIRED)|' \ @@ -74,7 +73,7 @@ prepare() { } build() { - cmake -S mocktail -B build -G Ninja \ + cmake -S nightcap -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib \ @@ -87,6 +86,6 @@ build() { package() { DESTDIR="${pkgdir}" cmake --install build - install -Dm644 mocktail/LICENSE \ + install -Dm644 nightcap/LICENSE \ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" } diff --git a/packaging/aur/nightcap/.SRCINFO b/packaging/aur/nightcap/.SRCINFO index 7088c55..d2be0f0 100644 --- a/packaging/aur/nightcap/.SRCINFO +++ b/packaging/aur/nightcap/.SRCINFO @@ -1,8 +1,8 @@ -pkgbase = mocktail +pkgbase = nightcap pkgdesc = Android x86-64 Roblox compatibility runtime for Linux - pkgver = 1.0.4 + pkgver = 0.3.1 pkgrel = 1 - url = https://github.com/komaruworld/mocktail + url = https://github.com/CoderDayton/nightcap arch = x86_64 license = Apache-2.0 makedepends = cmake>=3.20 @@ -36,11 +36,13 @@ pkgbase = mocktail depends = webkitgtk-6.0 depends = zlib optdepends = gamemode: Feral GameMode integration - conflicts = mocktail-bin - conflicts = mocktail-git + provides = mocktail + conflicts = mocktail + conflicts = nightcap-bin + conflicts = nightcap-git options = !debug options = !lto - source = mocktail::git+https://github.com/komaruworld/mocktail.git#tag=1.0.4 + source = nightcap::git+https://github.com/CoderDayton/nightcap.git#tag=v0.3.1 sha256sums = SKIP -pkgname = mocktail +pkgname = nightcap diff --git a/packaging/aur/nightcap/PKGBUILD b/packaging/aur/nightcap/PKGBUILD index 2a985bb..69c6233 100644 --- a/packaging/aur/nightcap/PKGBUILD +++ b/packaging/aur/nightcap/PKGBUILD @@ -1,11 +1,11 @@ -# Maintainer: komaruworld +# Maintainer: vii -pkgname=mocktail -pkgver=1.0.4 +pkgname=nightcap +pkgver=0.3.1 pkgrel=1 pkgdesc='Android x86-64 Roblox compatibility runtime for Linux' arch=('x86_64') -url='https://github.com/komaruworld/mocktail' +url='https://github.com/CoderDayton/nightcap' license=('Apache-2.0') depends=( 'capstone' @@ -42,17 +42,18 @@ makedepends=( 'vulkan-headers' ) optdepends=('gamemode: Feral GameMode integration') -conflicts=('mocktail-bin' 'mocktail-git') +provides=('mocktail') +conflicts=('mocktail' 'nightcap-bin' 'nightcap-git') options=('!debug' '!lto') source=( - "mocktail::git+https://github.com/komaruworld/mocktail.git#tag=${pkgver}" + "nightcap::git+https://github.com/CoderDayton/nightcap.git#tag=v${pkgver}" ) sha256sums=( 'SKIP' ) prepare() { - cd mocktail + cd nightcap sed -i \ 's|add_subdirectory(third_party/Vulkan-Headers EXCLUDE_FROM_ALL)|find_package(VulkanHeaders CONFIG REQUIRED)|' \ @@ -60,7 +61,7 @@ prepare() { } build() { - cmake -S mocktail -B build -G Ninja \ + cmake -S nightcap -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib \ @@ -73,6 +74,6 @@ build() { package() { DESTDIR="${pkgdir}" cmake --install build - install -Dm644 mocktail/LICENSE \ + install -Dm644 nightcap/LICENSE \ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" } diff --git a/scripts/package_version.sh b/scripts/package_version.sh new file mode 100755 index 0000000..89f0024 --- /dev/null +++ b/scripts/package_version.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Modified by vii from komaruworld/mocktail. See README "About this fork". +# Copyright 2026 Mocktail Project Authors +# SPDX-License-Identifier: Apache-2.0 +# +# Prints the native package version for the current ref. A release tag gives +# its own version; anything else gives 0.0.0, which sorts below every release. + +set -Eeuo pipefail + +ref="${1:-${GITHUB_REF_NAME:-}}" +version="${ref#v}" + +if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + printf '%s\n' "${version}" +else + printf '0.0.0\n' +fi diff --git a/site/index.html b/site/index.html index 25bd8c1..6ab54b4 100644 --- a/site/index.html +++ b/site/index.html @@ -22,6 +22,10 @@

Flatpak

flatpak install --user https://coderdayton.github.io/nightcap/nightcap.flatpakref flatpak run io.github.CoderDayton.nightcap +

DEB, RPM and pacman

+ sudo apt install ./nightcap_<version>-1_amd64.deb +sudo dnf install ./nightcap-<version>-1.x86_64.rpm +sudo pacman -U nightcap-<version>-1-x86_64.pkg.tar.zst

Build from source

git clone --recurse-submodules https://github.com/CoderDayton/nightcap.git cd nightcap && make build @@ -40,6 +44,7 @@ cd nightcap && make build
    +
  • Added DEB, RPM, and pacman packages to every release.
  • +
  • Smoother frames: textures no longer decode on the thread that submits the frame.
  • +
  • Fixed the mouse pointer jumping when a right-click camera drag ended.
  • +
  • Fixed the text caret not moving when a trailing space was typed.
  • +
  • Faster typing in text boxes.
  • +
+ +
    From 65adb3bddc9f39f9ada6fda590c70b2502460282 Mon Sep 17 00:00:00 2001 From: vii Date: Sun, 20 Sep 2026 12:33:26 -0500 Subject: [PATCH 12/12] fix(packaging): fail the release when a package glob matches nothing An unmatched glob reached sha256sum and gh as literal text. The publish job now collects the assets into an array and stops if a package is missing. --- .github/workflows/release.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2571885..feacf6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -302,9 +302,25 @@ jobs: set -Eeuo pipefail tag="${GITHUB_REF_NAME}" mv release/Mocktail-x86_64.AppImage release/Nightcap-x86_64.AppImage + # An unmatched glob would otherwise reach sha256sum and gh as its own + # literal text, so every package pattern has to match before upload. + shopt -s nullglob + assets=(Nightcap-x86_64.AppImage) + for glob in '*.deb' '*.rpm' '*.pkg.tar.zst'; do + matched=(release/$glob) + if [[ ${#matched[@]} -eq 0 ]]; then + echo "::error::no ${glob} package among the release artifacts" + exit 1 + fi + assets+=("${matched[@]#release/}") + done + uploads=() + for asset in "${assets[@]}"; do + uploads+=("release/$asset" "release/$asset.sha256") + done ( cd release - for asset in Nightcap-x86_64.AppImage *.deb *.rpm *.pkg.tar.zst; do + for asset in "${assets[@]}"; do sha256sum "$asset" > "$asset.sha256" done ) @@ -319,8 +335,4 @@ jobs: gh release edit "$tag" --repo "$GITHUB_REPOSITORY" \ --title "Nightcap $tag" --notes-file release/notes.md gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \ - release/Nightcap-x86_64.AppImage \ - release/Nightcap-x86_64.AppImage.sha256 \ - release/*.deb release/*.deb.sha256 \ - release/*.rpm release/*.rpm.sha256 \ - release/*.pkg.tar.zst release/*.pkg.tar.zst.sha256 + "${uploads[@]}"