Repository navigation
custom-client: runtime branding fields are not wired on Windows; app-name is missing from client config; clarify Incoming-only as QuickSupport mode #80
Description
Context
A Windows custom client built by DeskForge is only partially customized.
The executable itself is renamed correctly and its Windows file metadata reflects app_name, but the running RustDesk client still behaves as if its application name is RustDesk.
This has multiple visible consequences:
- About/UI strings still contain
RustDesk. - RustDesk website/privacy links and vendor copyright remain.
company_name,download_url, logo/icon/privacy-related fields configured in the DeskForge UI do not reach the Windows client.Hide Connection Managementcan remain ineffective even when its native settings are otherwise correct.Direction = Incomingalready implements the QuickSupport-style/incoming-only client mode, but the DeskForge UI does not explain this, making it easy to confuse it withHide Connection Management.
The important distinction is that packaging-time app_name branding works, while runtime custom-client app-name is missing.
1. app_name works for packaging, but runtime app-name is never supplied
DeskForge carries app_name as build-record/workflow context.
The active Windows workflow uses it in the L3 branding step to rename/rebrand the produced executable:
- executable/output name;
- Windows
Runner.rcmetadata; - Cargo metadata;
- portable
APP_PREFIX.
Therefore this part works:
DeskForge app_name
↓
workflow RQS_APP_NAME
↓
exe filename / ProductName / FileDescription
However, RustDesk has a separate runtime custom-client application name.
read_custom_client() expects a top-level custom-client field:
if let Some(app_name) = data.remove("app-name") {
if let Some(app_name) = app_name.as_str() {
*config::APP_NAME.write().unwrap() = app_name.to_owned();
}
}DeskForge's generated custom_.txt currently does not contain:
{
"app-name": "..."
}EncodeCustomTxt() only emits top-level values such as conn-type and password, plus default-settings / override-settings.
As a result, at runtime:
APP_NAME == "RustDesk"
even though the executable itself may already be named and branded differently.
This is the main missing link:
app_name
├── packaging / exe branding ✅
└── custom config "app-name" ❌
2. The missing runtime app-name explains why About still says RustDesk
RustDesk already has generic custom-client branding logic in src/lang.rs.
Translated strings are dynamically rewritten when the application is not RustDesk:
if !crate::is_rustdesk() {
if s.contains("RustDesk")
&& !name.starts_with("upgrade_rustdesk_server_pro")
&& name != "powered_by_me"
{
let app_name = crate::get_app_name();
...
s = s.replace("RustDesk", &app_name);
}
}So strings such as:
About RustDesk
do not require a new hard-coded Windows sed patch merely to replace RustDesk.
The mechanism already exists.
But it is gated by:
is_rustdesk()which ultimately depends on runtime APP_NAME.
Because app-name is missing from the custom-client payload, runtime APP_NAME remains:
RustDesk
and the existing branding mechanism never activates.
Therefore the current state is:
exe name / Windows properties → custom name
Flutter translated UI → still RustDesk
The issue is not simply "About branding is unimplemented"; the generic RustDesk branding mechanism exists but DeskForge does not provide the runtime key required to activate it.
3. The same missing app-name also affects Hide Connection Management
Hide Connection Management is unrelated to hiding the outgoing ID-entry UI.
It controls the separate RustDesk Connection Manager (--cm).
The native hide condition itself already exists:
pub fn hide_cm() -> bool {
approve_mode() == ApproveMode::Password
&& verification_method() == VerificationMethod::OnlyUsePermanentPassword
&& crate::config::option2bool(
"allow-hide-cm",
&Config::get_option("allow-hide-cm")
)
}There is also a second gate when Flutter requests the hide_cm config through IPC.
The client only exposes it when it believes it is Pro or a custom client:
if crate::hbbs_http::sync::is_pro() || crate::common::is_custom_client() {
Some(hbb_common::password_security::hide_cm().to_string())
} else {
None
}is_custom_client() is determined from the runtime application name:
pub fn is_custom_client() -> bool {
get_app_name() != "RustDesk"
}Therefore the missing app-name causes another failure:
custom_.txt lacks app-name
↓
APP_NAME remains "RustDesk"
↓
is_custom_client() == false
↓
non-Pro build does not expose hide_cm through IPC
So one missing runtime identity key affects at least two apparently unrelated features:
- generic RustDesk → custom app-name replacement;
- Hide Connection Manager availability for a non-Pro custom build.
There is also a separate Hide CM configuration issue: hide_cm() requires approve-mode=password, while the DeskForge UI default is currently password-click.
That should either be validated/fixed together with Hide CM or tracked separately, but it is not the reason the outgoing Remote-ID UI is visible.
4. company_name is stored but not used by the Windows client
company_name is present in the DeskForge builder UI and BuildSpec.
However it is explicitly marked persistedOnly.
It is stored for restoring the form/preset but is not emitted into custom_.txt and is not supplied to the active Windows branding workflow as a consumer-facing branding value.
Consequently the client still contains RustDesk/Purslane vendor text in places such as About/copyright.
This path currently stops at:
UI
↓
BuildSpec
↓
database
✕
Windows build/client
Historical rdgen workflows contained company/copyright replacements, but the active Windows workflow does not currently perform equivalent branding.
The exact current copyright string must be matched against the selected RustDesk source_sha; old rdgen replacements such as Purslane Ltd must not be blindly reused because current source may contain Purslane Tech Pte. Ltd. or another updated string.
5. download_url / Custom URL is also persisted-only
The DeskForge UI exposes a custom/download URL.
download_url is also persistedOnly.
It is not emitted into the native custom-client payload and is not consumed by the active Windows workflow.
Meanwhile RustDesk About UI still contains RustDesk URLs, including the RustDesk website and privacy link.
Therefore a URL configured by the operator does not replace the visible RustDesk URLs in the resulting Windows client.
This should either be wired into the client branding path or clearly marked unsupported.
6. Privacy URL and privacy_screen_url must not be confused
The current builder has privacy_screen_url, but this is not the same thing as a Privacy Policy URL.
privacy_screen_url is an asset/image-oriented field and is currently also persisted-only with no proven active Windows consumer.
At the same time, the client About UI contains a RustDesk privacy-policy link.
There appears to be no dedicated DeskForge field that represents:
Privacy Policy URL
Therefore the current problem is twofold:
privacy_screen_urlis exposed but not wired;- the RustDesk privacy-policy URL has no corresponding effective customization field.
The issue should not describe privacy_screen_url as if it were intended to replace the About privacy-policy link.
7. Other branding asset fields are also persisted-only
The same applies to fields including:
app_icon_url
app_logo_url
privacy_screen_url
They are presented as custom-client settings but currently have no proven runtime/build consumer in the active Windows path.
For every such field, the builder should either:
- actually wire it into the relevant platform build/runtime path; or
- hide/disable it for unsupported platforms and state that it is not supported.
Persisting a value in the database while silently ignoring it during the build is misleading.
8. Direction = Incoming already is the QuickSupport-style mode
The original report incorrectly used the visible Remote-ID/outgoing UI as evidence that Hide Connection Management was not working.
These are separate features.
DeskForge already emits:
{
"conn-type": "incoming"
}when:
Direction = Incoming
RustDesk loads top-level custom-client values into HARD_SETTINGS.
is_incoming_only() is implemented as:
pub fn is_incoming_only() -> bool {
HARD_SETTINGS
.read()
.unwrap()
.get("conn-type")
.map_or(false, |x| x == "incoming")
}Flutter then uses bind.isIncomingOnly() to remove the outgoing ConnectionPage.
That removes the normal UI used to enter another device ID / initiate outgoing connections.
The Rust client also rejects ordinary outgoing connections while in incoming-only mode.
Therefore:
Direction = Incoming
already means, effectively:
QuickSupport-style / incoming-only client
This functionality does not need to be invented.
9. DeskForge UI should explicitly call Incoming mode "QuickSupport"
The current Direction wording is too implementation-oriented.
An operator should not need to know RustDesk internals to discover that Incoming is the mode intended for a support client that only receives connections.
Suggested label:
Incoming only (QuickSupport)
or:
Incoming only — QuickSupport mode
Suggested description/tooltip:
QuickSupport-style client. Allows incoming remote support connections while hiding and disabling normal outgoing connections to other devices.
The UI should make the difference from Hide Connection Management explicit.
For example:
Incoming only (QuickSupport)
Controls whether this client can initiate outgoing remote connections.
Hide Connection Management
Hides the separate local Connection Manager UI for incoming sessions.
Requires unattended/password-only configuration.
This would have prevented the two options from being confused.
Impact
The current builder produces a client with inconsistent branding and misleading configuration behavior:
app_namechanges the exe/output branding but does not set RustDesk's runtimeapp-name;- the existing generic RustDesk string-branding logic therefore remains disabled;
- the same missing runtime identity prevents the non-Pro build from being recognized as a custom client for features such as Hide CM;
company_name,download_url,app_icon_url,app_logo_url, andprivacy_screen_urlare stored but do not affect the active Windows build;- RustDesk URLs/vendor information remain visible despite branding fields being present in the builder;
Incomingalready provides the desired QuickSupport behavior, but the UI does not communicate that.
Proposed direction
Runtime custom-client identity
Include the build's application name in the native custom-client payload using the key RustDesk already consumes:
{
"app-name": "<app_name>"
}Then verify at runtime that:
get_app_name() == configured app_name
is_custom_client() == true
This should activate existing RustDesk custom-client behavior instead of reproducing it through additional ad-hoc patches.
Branding fields
Audit each UI branding field and give it an explicit Windows consumer:
company_namedownload_urlapp_icon_urlapp_logo_urlprivacy_screen_url
Where RustDesk already provides a native/custom-client mechanism, use it.
Where it does not, perform a source/build branding step against the exact selected source_sha.
Fields that are not supported should not silently remain editable as though they affect the artifact.
About / copyright / URLs
After runtime app-name is fixed, re-test About before adding redundant RustDesk-name replacements.
Handle separately the elements not covered by RustDesk's generic app-name replacement:
- company/vendor name;
- copyright;
- website URL;
- privacy-policy URL.
Incoming-only UX
Rename or annotate:
Incoming
as:
Incoming only (QuickSupport)
and explain that it removes/disables normal outgoing connections.
Hide CM
Do not use Hide CM to implement QuickSupport/incoming-only behavior.
Keep it as its actual Connection Manager setting and separately validate its required combination:
allow-hide-cm = Y
verification-method = use-permanent-password
approve-mode = password
Acceptance criteria
A Windows custom client built with:
app_name = Acme Support
company_name = Acme Ltd
download_url = https://example.com
direction = incoming
should satisfy all applicable conditions:
- executable/file metadata uses
Acme Support; - runtime
get_app_name()returnsAcme Support; is_custom_client()is true;- generic UI strings such as
About RustDeskare rendered with the custom app name; - company/vendor/copyright text no longer shows the RustDesk/Purslane defaults when corresponding branding is configured;
- configured website/privacy branding is reflected in the UI where supported;
Direction = Incomingproduces the existing incoming-only/QuickSupport UI and blocks normal outgoing connections;- DeskForge labels/describes Incoming mode clearly as QuickSupport-style;
- Hide CM is tested separately against its actual password/verification requirements.
Activity
[-]custom-client: Windows build options do not reach the client — Hide Connection Management, About branding, privacy/copyright[/-][+]custom-client: Windows build — hide-cm effect gated off, About/privacy branding not applied, dead form fields[/+][-]custom-client: Windows build — hide-cm effect gated off, About/privacy branding not applied, dead form fields[/-][+]custom-client: runtime branding fields are not wired on Windows; app-name is missing from client config; clarify Incoming-only as QuickSupport mode[/+][-]custom-client: runtime branding fields are not wired on Windows; app-name is missing from client config; clarify Incoming-only as QuickSupport mode[/-][+]custom-client: runtime branding fields are not wired on Windows; `app-name` is missing from client config; clarify Incoming-only as QuickSupport mode[/+]docs: correct retracted return_run_details claims in README/PLAN/CHAN…
bashrusakh commentedon Oct 14, 2026
Implementation landed; closing on the task owner's decision, with the live runtime check deliberately deferred.
bf1506c26a30c3eee09d34f8d09781ac5abd6e07:app-nameemitted top-level incustom_.txt,hide_cmrequiring a permanent password and password approval, the branding URL/image contract (relative/upload/...resolved againstpublic-api-serveronly, no fallback, structured field errors before dispatch, ASCII wire form with non-transitional IDNA and a canonicalized terminal root dot, ports bounded to 1..65535), the literal public-address policy bounded to IANA allocations, and the schema-285 stored-data preflight.4148a8595ae3f2fec386d39f2cbcdba3b0f2d597: destination-pinned branding fetch (resolve once, validate every resolved address, connect to the validated address, per-hop scheme re-check, TLS verified against the URL hostname), a per-attempt wall-clock bound covering DNS, connect, TLS, headers, body and every redirect, wire-input and port fail-fast, and the IANA-bounded IPv6 allow list.tests/test_branding_contract.pyinto CI and keep the IANA comparison as an offline fixture; correct the stale 283→284 header comment inapi/cmd/apimain.go; add the 4096 px upper bound (and logo/privacy dimension checks) to the admin-ui upload guard; fill in theCompanyName/CustomUrllabels missing inru/zh_CN.