Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4e65cdb
feat(flatpak): add a build-only mode
CoderDayton Oct 9, 2026
2f89e6f
docs(roadmap): drop controller options from the settings UI
CoderDayton Oct 9, 2026
8c3ae9d
feat(runtime): add a config.yaml editor that keeps comments and layout
CoderDayton Oct 9, 2026
789ef35
feat(runtime): save config.yaml in one step after checking it loads
CoderDayton Oct 9, 2026
3820e40
feat(ui): add a settings window that edits config.yaml
CoderDayton Oct 9, 2026
f9a6d09
feat(ui): make the number and text settings rows editable
CoderDayton Oct 9, 2026
311dd05
feat(ui): add the fflags.json editor and show the Nightcap logo
CoderDayton Oct 9, 2026
64dca75
feat(ui): open the settings window with --settings and from the app menu
CoderDayton Oct 9, 2026
569ce83
fix(ui): keep unsaved text and name the file in save errors
CoderDayton Oct 9, 2026
710a352
docs: describe the settings window
CoderDayton Oct 9, 2026
774df91
feat(ui): add a touch input switch and harden the FFlag editor
CoderDayton Oct 10, 2026
5ed4ced
feat(ui): pick the audio devices from a list
CoderDayton Oct 12, 2026
393d7c5
fix(ui): say when a device name is shared by two devices
CoderDayton Oct 12, 2026
6da8709
feat(ui): add an undo button to the saved notice
CoderDayton Oct 12, 2026
3495979
feat(ui): add an open config folder row
CoderDayton Oct 13, 2026
69ffac2
feat(ui): add a switch for automatic Roblox updates
CoderDayton Oct 13, 2026
622abce
fix(ui): show the saved notice only when the file changed
CoderDayton Oct 13, 2026
9a09d80
fix(ui): label an input device given by number
CoderDayton Oct 13, 2026
7827d21
fix(ui): hide undo after saving fflags.json
CoderDayton Oct 13, 2026
8df79a5
fix(ui): ask before closing with text that is not applied
CoderDayton Oct 13, 2026
bacbd41
feat(ui): cover every config setting and restart Nightcap from settings
CoderDayton Oct 16, 2026
4274784
update(ui): polish the settings window
CoderDayton Oct 16, 2026
852f377
fix(ui): focus a switched-off expander from the changed count
CoderDayton Oct 16, 2026
7aeded2
update(ui): drop the changed counts and always show reset buttons
CoderDayton Oct 16, 2026
b7a9a02
update(ui): grey out the Fleasion port under hosts routing
CoderDayton Oct 16, 2026
c5dc1e2
refactor(runtime): share the helper program lookup
CoderDayton Oct 16, 2026
3c60f62
fix(settings): harden restart and cut redundant saves and parses
CoderDayton Oct 16, 2026
325644c
fix(ui): give the loading and failure windows Nightcap's app id
CoderDayton Oct 17, 2026
0c40524
fix(settings): close review gaps in restart and stepped saves
CoderDayton Oct 17, 2026
f5b2925
docs: cover the restart offer, shortcuts and the dock icon fix
CoderDayton Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ Release notes on GitHub are taken from the matching section here.

### Added

- Added a settings window. Open it from the app menu (right-click Nightcap, then Settings) or with
`mocktail --settings`. It changes `config.yaml` and `fflags.json` for you, keeps your comments, and does
not save a value Nightcap could not start with. Changes apply the next time Nightcap starts. If Nightcap
is open when you change something, the window offers to restart it for you, with the same options and
environment it was started with. It opens on the main menu. Every `config.yaml` setting has a row,
including the device, the window size, the proxy, Fleasion and the update source. A setting you changed
from its default gets a button that resets it.
- An asset overlay folder, `~/.local/share/mocktail/asset_overlay`. A file there replaces the file Roblox
ships at the same path, such as a cursor, a font or a sky texture, and it stays in place when Roblox
updates. The layout matches Sober's `asset_overlay` folder. `MOCKTAIL_ASSET_OVERLAY_DIR` moves it. Thanks to
Expand All @@ -26,6 +33,7 @@ Release notes on GitHub are taken from the matching section here.

- Joining a game from a website link while already in a game no longer freezes Nightcap. It leaves the
current game and joins the new one, and leaving that game returns to the home screen.
- The loading window no longer gets a dock icon of its own. It now shows under Nightcap's icon.

### Changed

Expand Down
36 changes: 35 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,11 @@ add_library(mocktail_runtime STATIC
src/runtime/asset_overlay_names.cc
src/runtime/runtime_config.cc
src/runtime/runtime_config_bootstrap.cc
src/runtime/runtime_config_editor.cc
src/runtime/runtime_config_store.cc
src/runtime/runtime_config_file.cc
src/runtime/runtime_paths.cc
src/runtime/running_instance.cc
src/runtime/session_log.cc
src/runtime/single_instance_lock.cc
src/runtime/startup_state_machine.cc
Expand Down Expand Up @@ -354,6 +357,8 @@ target_link_libraries(mocktail_runtime PRIVATE
# Keep GTK out of the process that loads Android libraries.
add_library(mocktail_failure_dialog_runtime STATIC
src/runtime/failure_dialog.cc
src/runtime/helper_program.cc
src/runtime/settings_launcher.cc
)
add_library(Mocktail::FailureDialog ALIAS mocktail_failure_dialog_runtime)
target_include_directories(mocktail_failure_dialog_runtime PUBLIC include)
Expand All @@ -365,9 +370,34 @@ mocktail_apply_compile_options(mocktail_failure_dialog_runtime)
add_executable(mocktail_failure_dialog
src/ui/mocktail_failure_dialog.cc
)
target_include_directories(mocktail_failure_dialog PRIVATE include)
target_link_libraries(mocktail_failure_dialog PRIVATE PkgConfig::LIBADWAITA)
mocktail_apply_compile_options(mocktail_failure_dialog)

add_library(mocktail_settings_model STATIC
src/ui/settings_model.cc
)
add_library(Mocktail::SettingsModel ALIAS mocktail_settings_model)
target_include_directories(mocktail_settings_model PUBLIC include)
target_link_libraries(mocktail_settings_model PUBLIC
Mocktail::Runtime
mocktail_services
)
mocktail_apply_compile_options(mocktail_settings_model)

add_executable(mocktail_settings
src/ui/mocktail_settings.cc
)
target_include_directories(mocktail_settings PRIVATE
${MOCKTAIL_PLATFORM_GENERATED_INCLUDE_DIR}
)
target_link_libraries(mocktail_settings PRIVATE
Mocktail::SettingsModel
PkgConfig::LIBADWAITA
SDL3::SDL3
)
mocktail_apply_compile_options(mocktail_settings)

add_library(mocktail_services STATIC
src/services/http_client.cc
src/services/auth_service.cc
Expand Down Expand Up @@ -669,6 +699,7 @@ install(TARGETS
mocktail
mocktail_updater
mocktail_failure_dialog
mocktail_settings
mocktail_webview_helper
mocktail_audio_sdl
mocktail_opensles
Expand Down Expand Up @@ -707,6 +738,9 @@ install(CODE "
string(REPLACE \"Exec=env SDL_VIDEODRIVER=wayland,x11 mocktail %u\"
\"Exec=env SDL_VIDEODRIVER=wayland,x11 \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/mocktail %u\"
MOCKTAIL_DESKTOP_ENTRY \"\${MOCKTAIL_DESKTOP_ENTRY}\")
string(REPLACE \"Exec=mocktail --settings\"
\"Exec=\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/mocktail --settings\"
MOCKTAIL_DESKTOP_ENTRY \"\${MOCKTAIL_DESKTOP_ENTRY}\")
file(WRITE \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
\"\${MOCKTAIL_DESKTOP_ENTRY}\")
file(INSTALL \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
Expand Down Expand Up @@ -810,7 +844,7 @@ set(CPACK_RPM_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}")
include(CPack)

add_dependencies(mocktail all_stubs)
add_dependencies(mocktail mocktail_failure_dialog)
add_dependencies(mocktail mocktail_failure_dialog mocktail_settings)
add_dependencies(mocktail mocktail_webview_helper)

if(BUILD_TESTING)
Expand Down
10 changes: 8 additions & 2 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
## How do I turn Discord RPC off?

RPC is on by default. It never signs in to Discord and never reads an
account token. To turn it off, launch Nightcap once to create the config,
then close it. Open `config.yaml`:
account token.

The quick way: open Settings (right-click Nightcap in the app menu, or run
`mocktail --settings`), go to Discord, and switch off "Show activity in
Discord". Then restart Nightcap.

To do it by hand, launch Nightcap once to create the config, then close
it. Open `config.yaml`:

- Native / AppImage: `~/.config/mocktail/config.yaml`
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/config/mocktail/config.yaml`
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
.DEFAULT_GOAL := all
.PHONY: all build release debug test apk install register-url-handler release-runtime portable portable-test standalone appimage flatpak run-flatpak run run-smoke run-unlimited run-game run-input run-resize run-network auto-run run-gles run-angle update-roblox update-auto update-auto-launch payload-status payload-rollback support-bundle clean submodules help
.PHONY: all build release debug test apk install register-url-handler release-runtime portable portable-test standalone appimage flatpak flatpak-build run-flatpak run run-smoke run-unlimited run-game run-input run-resize run-network auto-run run-gles run-angle update-roblox update-auto update-auto-launch payload-status payload-rollback support-bundle clean submodules help

BUILD_DIR := build
BINARY := $(BUILD_DIR)/mocktail
Expand Down Expand Up @@ -91,6 +91,13 @@ flatpak: ## Build and install the local x86-64 Flatpak
--manifest "$(FLATPAK_MANIFEST)" \
--jobs "$(FLATPAK_JOBS)"

flatpak-build: ## Build the local Flatpak without installing it
@./scripts/build_flatpak.sh \
--no-install \
--build-dir "$(FLATPAK_BUILD_DIR)" \
--manifest "$(FLATPAK_MANIFEST)" \
--jobs "$(FLATPAK_JOBS)"

run-flatpak: ## Run the installed Flatpak
@flatpak run io.github.CoderDayton.nightcap

Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ removing the marker.

Put a JSON object in `$XDG_CONFIG_HOME/mocktail/fflags.json` (usually
`~/.config/mocktail/fflags.json`). Nightcap applies it on the next launch.
The Advanced page of the [settings window](#settings) edits this file and
does not save text that Nightcap could not load.

PC profiles use the legacy Charts page by default to avoid the blank screen
caused by `Color3` errors in the newer SDUI page. An explicit
Expand All @@ -149,7 +151,25 @@ it yourself turns the switching off and your value applies everywhere. See
```
## Settings

Edit `~/.config/mocktail/config.yaml`. Useful options:
Open the settings window from the app menu: right-click Nightcap and pick
Settings. Or run:

```sh
mocktail --settings
```

With the Flatpak, run
`flatpak run io.github.CoderDayton.nightcap --settings`. To open it with a
key, bind that command to a keyboard shortcut in your desktop's settings. If
Settings is missing from the menu right after you install, log out and back
in.

The window edits `~/.config/mocktail/config.yaml`. It keeps your comments
and layout, saves each change at once, and does not save a value Nightcap
could not start with. Changes apply the next time Nightcap starts. If
Nightcap is open, the window offers to restart it, except in the Flatpak.

You can also edit the file by hand. Useful options:

- `graphics.frame_rate_limit: display` to unlock FPS to your monitor's rate.
- `graphics.vsync: off` for the lowest lag, at the cost of tearing.
Expand Down Expand Up @@ -226,7 +246,8 @@ Roblox website links open the build tree copy without installing, run
To build and install the Flatpak from source, install `flatpak-builder` and run
`make flatpak`. It builds with 2 parallel jobs to keep memory use low. Pass
`FLATPAK_JOBS=N` to `make flatpak`, or set `MOCKTAIL_FLATPAK_JOBS=N` for
`scripts/build_flatpak.sh`, to change that.
`scripts/build_flatpak.sh`, to change that. To build without installing, run
`make flatpak-build`; the result stays in `build-flatpak/`.

To run the same checks as CI before each commit and push, install
[lefthook](https://github.com/evilmartians/lefthook) and run once:
Expand Down
23 changes: 6 additions & 17 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,15 @@ What Nightcap is working toward, in priority order. Items move to
[Shipped](#shipped) when they land on `main`. Open an issue to propose a
change to this list.

## 1. Settings UI

Nightcap is configured by editing `~/.config/mocktail/config.yaml`. Most
people never open it, so they never find the FPS unlock or the vsync switch.

Goal: a small GTK4 settings window, reachable from the app menu and from a
`--settings` flag, that reads and writes the same `config.yaml`.

- Graphics tab: backend, frame rate limit, vsync, small texture upscale.
- Input tab: the `input.raw_mouse` toggle and controller options.
- Advanced tab: FFlag overrides with a plain text editor for `fflags.json`.
- Discord tab: every field in the `discord_rpc` block, so a friend can see
what you are playing and you can switch it all off in one click.
- Changes apply on next launch. A restart button restarts Nightcap.

Done when every option in `config/mocktail.example.yaml` that a player would
touch can be set without a text editor.
Nothing is queued right now.

## Shipped

- Settings window, from the app menu or `mocktail --settings`: graphics,
input, performance, audio, and Discord options, and a text editor for
`fflags.json`. It edits the same `config.yaml`, keeps your comments, and
does not save a value Nightcap could not start with. Changes apply on the
next launch.
- Lower input latency: frames are presented as soon as they are ready.
- Controllers hot-plug without a restart, and a pad that is unplugged mid-game
releases its held buttons and sticks instead of leaving them stuck.
Expand Down
12 changes: 12 additions & 0 deletions include/mocktail/platform/application_id.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef MOCKTAIL_PLATFORM_APPLICATION_ID_H_
#define MOCKTAIL_PLATFORM_APPLICATION_ID_H_

namespace mocktail {
namespace platform {

inline constexpr char kApplicationId[] = "io.github.CoderDayton.nightcap";

}
}

#endif
4 changes: 2 additions & 2 deletions include/mocktail/platform/sdl_application_metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
#ifndef MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_
#define MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_

#include "mocktail/platform/application_id.h"
#include "mocktail/status.h"

namespace mocktail {
namespace platform {

inline constexpr char kMocktailApplicationName[] = "Nightcap";
inline constexpr char kMocktailApplicationIdentifier[] =
"io.github.CoderDayton.nightcap";
inline constexpr const char* kMocktailApplicationIdentifier = kApplicationId;

// Configures the stable compositor identity before SDL initializes. The
// identifier matches the installed desktop file and icon name so Wayland and
Expand Down
2 changes: 2 additions & 0 deletions include/runtime/command_line.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace runtime {
enum class CommandMode {
kRun,
kHelp,
// Open the settings window in place of the game.
kSettings,
};

enum class WindowMode {
Expand Down
24 changes: 24 additions & 0 deletions include/runtime/helper_program.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef MOCKTAIL_RUNTIME_HELPER_PROGRAM_H_
#define MOCKTAIL_RUNTIME_HELPER_PROGRAM_H_

#include <filesystem>
#include <string_view>

#include "runtime/environment.h"

namespace mocktail {
namespace runtime {

// Path of the helper program `name`, or empty when there is none to run.
// `override_variable` names it outright: it must be an absolute path to a
// regular executable file, and nothing else is tried when it is set.
// Otherwise the program is looked for under MOCKTAIL_PROJECT_ROOT/bin, then
// beside this executable and in its install directories.
std::filesystem::path FindHelperProgram(const Environment& environment,
std::string_view override_variable,
std::string_view name);

} // namespace runtime
} // namespace mocktail

#endif
78 changes: 78 additions & 0 deletions include/runtime/running_instance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#ifndef MOCKTAIL_RUNTIME_RUNNING_INSTANCE_H_
#define MOCKTAIL_RUNTIME_RUNNING_INSTANCE_H_

#include <sys/types.h>

#include <chrono>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "runtime/environment.h"

namespace mocktail {
namespace runtime {

inline constexpr char kInstanceLockVariable[] = "MOCKTAIL_INSTANCE_LOCK";
inline constexpr char kRestartProgramVariable[] = "MOCKTAIL_RESTART_PROGRAM";

// A Flatpak settings window runs in a sandbox of its own and cannot reach
// the game.
bool CanRestartFromSettings(const Environment& environment);

std::optional<pid_t> FindFlockHolder(std::string_view proc_locks,
dev_t device, ino_t inode);

struct RunningInstance {
bool running = false;
// 0 when Nightcap runs but this process cannot see it.
pid_t pid = 0;
};

RunningInstance FindRunningInstance(const std::filesystem::path& lock_file);

// SIGTERM reaches Nightcap as SDL_EVENT_QUIT, the same as closing its window.
bool StopRunningInstance(const std::filesystem::path& lock_file,
std::chrono::milliseconds timeout,
std::string* error);

struct LaunchCommand {
std::vector<std::string> arguments;
// NAME=value entries.
std::vector<std::string> environment;
// Empty keeps this process's own.
std::filesystem::path working_directory;
};

// The arguments in /proc/<pid>/cmdline. Nightcap zeroes a join link after
// reading it, since the link holds a one-time login ticket, so the zeroed
// argument is dropped along with a --launch-uri or --launch-request-json
// left without it.
std::vector<std::string> ParseProcArguments(std::string_view cmdline);
std::vector<std::string> ParseProcEnvironment(std::string_view variables);

// Nothing when /proc cannot be read, as for another user's process.
std::optional<LaunchCommand> ReadLaunchCommand(pid_t pid);

// Starts `program` in a session of its own, so it keeps running after this
// process exits. Empty arguments run it with just its path; an empty
// environment passes this process's own. Signals start out with their
// default handling, whatever this process ignores or blocks.
bool StartDetached(const std::filesystem::path& program, std::string* error,
const LaunchCommand& command = {});

// Closes the running Nightcap and starts `program` with its arguments,
// environment and working directory. Nothing is closed when its launch
// cannot be read, since it could not be started the same way again. A
// Nightcap that does not close within `timeout` may still close later.
bool RestartRunningInstance(const std::filesystem::path& lock_file,
const std::filesystem::path& program,
std::chrono::milliseconds timeout,
std::string* error);

} // namespace runtime
} // namespace mocktail

#endif
Loading