Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 cmake/prep/init.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ elseif (UNIX)
set(SUNSHINE_EXECUTABLE_PATH "sunshine")
endif()

# Auto-detect init system for desktop file
find_package(Systemd QUIET)
if(SYSTEMD_FOUND)
set(SUNSHINE_DESKTOP_EXEC "/usr/bin/env systemctl start --u app-${PROJECT_FQDN}")
else()
set(SUNSHINE_DESKTOP_EXEC "${CMAKE_INSTALL_FULL_BINDIR}/${SUNSHINE_EXECUTABLE_PATH}")
endif()

if(SUNSHINE_BUILD_FLATPAK)
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=flatpak run --command=sunshine ${PROJECT_FQDN}")
set(SUNSHINE_SERVICE_STOP_COMMAND "ExecStop=flatpak kill ${PROJECT_FQDN}")
Expand Down
2 changes: 1 addition & 1 deletion packaging/linux/dev.lizardbyte.app.Sunshine.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Actions=RunInTerminal;
Categories=RemoteAccess;Network;
Comment=@PROJECT_DESCRIPTION@
Exec=/usr/bin/env systemctl start --u app-@PROJECT_FQDN@
Exec=@SUNSHINE_DESKTOP_EXEC@
Icon=@SUNSHINE_DESKTOP_ICON@
StartupWMClass=@PROJECT_FQDN@
Keywords=gamestream;stream;moonlight;remote play;
Expand Down
24 changes: 24 additions & 0 deletions src/nvhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ namespace nvhttp {
// Set by TLS verify callback, read by launch/resume handler (single-threaded HTTPS server)
std::string last_verified_client_cert; ///< Last client certificate accepted by the TLS verify callback. // NOSONAR(cpp:S5421) - intentionally mutable global

// Saved originals for per-app config overrides
std::string saved_output_name; ///< Original config::video.output_name before per-app override. // NOSONAR(cpp:S5421) - intentionally mutable global
bool saved_stream_audio {true}; ///< Original config::audio.stream before per-app override. // NOSONAR(cpp:S5421) - intentionally mutable global

/**
* @brief Case-insensitive map used for HTTP headers and query parameters.
*/
Expand Down Expand Up @@ -1037,6 +1041,22 @@ namespace nvhttp {
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
auto launch_session = make_launch_session(host_audio, args);

// Save originals before per-app config overrides
saved_output_name = config::video.output_name;
saved_stream_audio = config::audio.stream;

for (auto &app : proc::proc.get_apps()) {
if (app.id == std::to_string(appid)) {
if (!app.output_name.empty()) {
config::video.output_name = app.output_name;
}
if (app.stream_audio.has_value()) {
config::audio.stream = app.stream_audio.value();
}
break;
}
}

if (rtsp_stream::session_count() == 0) {
// The display should be restored in case something fails as there are no other sessions.
revert_display_configuration = true;
Expand Down Expand Up @@ -1226,6 +1246,10 @@ namespace nvhttp {

// The config needs to be reverted regardless of whether "proc::proc.terminate()" was called or not.
display_device::revert_configuration();

// Restore per-app config overrides to global defaults
config::video.output_name = saved_output_name;
config::audio.stream = saved_stream_audio;
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -690,11 +690,13 @@ namespace proc {
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
auto image_path = app_node.get_optional<std::string>("image-path"s);
auto output_name = app_node.get_optional<std::string>("output-name"s);
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
auto elevated = app_node.get_optional<bool>("elevated"s);
auto auto_detach = app_node.get_optional<bool>("auto-detach"s);
auto wait_all = app_node.get_optional<bool>("wait-all"s);
auto exit_timeout = app_node.get_optional<int>("exit-timeout"s);
auto stream_audio = app_node.get_optional<bool>("stream-audio"s);

std::vector<proc::cmd_t> prep_cmds;
if (!exclude_global_prep.value_or(false)) {
Expand Down Expand Up @@ -760,6 +762,14 @@ namespace proc {
ctx.image_path = parse_env_val(this_env, *image_path);
}

if (output_name) {
ctx.output_name = parse_env_val(this_env, *output_name);
}

if (stream_audio) {
ctx.stream_audio = *stream_audio;
}

ctx.elevated = elevated.value_or(false);
ctx.auto_detach = auto_detach.value_or(true);
ctx.wait_all = wait_all.value_or(true);
Expand Down
2 changes: 2 additions & 0 deletions src/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ namespace proc {
std::string cmd; ///< Command line used to launch the application.
std::string working_dir; ///< Working dir.
std::string output; ///< Captured output from the launched process.
std::string output_name; ///< Display output name override for this app.
std::optional<bool> stream_audio; ///< Audio streaming override for this app (std::nullopt = use global config).
std::string image_path; ///< Image path.
std::string id; ///< Stable identifier for the configured application.
bool elevated; ///< Whether the process should be launched elevated.
Expand Down
23 changes: 22 additions & 1 deletion src_assets/common/assets/web/apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ <h5 class="modal-title" id="appEditModalLabel">
</div>
<div id="appImagePathHelp" class="form-text">{{ $t('apps.image_desc') }}</div>
</div>
<!-- output-name -->
<div class="mb-3">
<label for="appOutputName" class="form-label">{{ $t('apps.output_display') }}</label>
<input type="text" class="form-control monospace" id="appOutputName"
aria-describedby="appOutputNameHelp" v-model="editForm['output-name']"
placeholder="e.g. virtmon" />
<div id="appOutputNameHelp" class="form-text">{{ $t('apps.output_display_desc') }}</div>
</div>
<!-- stream-audio -->
<Checkbox class="mb-3"
id="streamAudio"
label="apps.stream_audio"
desc="apps.stream_audio_desc"
v-model="editForm['stream-audio']"
default="true"
></Checkbox>
<div class="env-hint alert alert-info">
<div class="form-text">
<h4>{{ $t('apps.env_vars_about') }}</h4>
Expand Down Expand Up @@ -739,7 +755,9 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
"exit-timeout": 5,
"prep-cmd": [],
detached: [],
"image-path": ""
"image-path": "",
"output-name": "",
"stream-audio": true
};
this.openEditModal();
},
Expand All @@ -764,6 +782,9 @@ <h5 class="modal-title">{{ fileBrowserTitle || $t('file_browser.title') }}</h5>
if (this.editForm["exit-timeout"] === undefined) {
this.editForm["exit-timeout"] = 5;
}
if (this.editForm["stream-audio"] === undefined) {
this.editForm["stream-audio"] = true;
}
this.openEditModal();
},
showDeleteModal(id) {
Expand Down
4 changes: 4 additions & 0 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,13 @@
"no_covers_found": "No covers found",
"no_search_results": "No applications match your search",
"output_desc": "The file where the output of the command is stored, if it is not specified, the output is ignored",
"output_display": "Display Id",
"output_display_desc": "Override the display output for this application. Leave empty to use the global setting.",
"output_name": "Output",
"run_as_desc": "This can be necessary for some applications that require administrator permissions to run properly.",
"search_placeholder": "Search applications...",
"stream_audio": "Stream Audio",
"stream_audio_desc": "Override audio streaming for this application. When unchecked, audio will not be streamed to the client.",
"searching_covers": "Searching for covers...",
"sort_ascending": "Ascending",
"sort_by_name": "Sort by Name",
Expand Down