This page documents the server-side UI that appears on the controlled host (the machine being accessed). It covers the ServerModel data model, the desktop DesktopServerPage / ConnectionManager widgets, the per-client Client model, the permission control UI, chat and voice-call integration, and the mobile ServerPage variant.
This is specifically the UI layer for managing incoming connections. For the backend Rust-side connection handler that produces the events consumed here, see 3.1. For IPC data flow between the server process and the CM process, see 5.6. For the Sciter-based legacy CM, see 4.2.
When a remote client connects to this machine, the RustDesk CM (Connection Manager) process is launched or signaled. The Flutter CM window is driven by ServerModel, which receives connection events via FFI and IPC. Each inbound connection is represented as a Client object and displayed as a tab in DesktopServerPage.
Widget and model hierarchy:
Sources: flutter/lib/desktop/pages/server_page.dart25-106 flutter/lib/desktop/pages/server_page.dart108-266 flutter/lib/models/server_model.dart26-52
ServerModelServerModel is a ChangeNotifier that holds all state for the host-side server UI. It is accessed via gFFI.serverModel.
| Field | Type | Purpose |
|---|---|---|
_clients | List<Client> | All current inbound connections |
tabController | DesktopTabController | Tab bar for CM window |
_connectStatus | int | Rendezvous server status (-1/0/positive) |
_verificationMethod | String | use-temporary-password / use-permanent-password / use-both-passwords |
_approveMode | String | password / click / both |
hideCm | bool | Whether the CM window is hidden |
cmHiddenTimer | Timer? | Auto-minimize timer after authorization |
showElevation | bool | Whether to show the Elevate button |
Sources: flutter/lib/models/server_model.dart26-52 flutter/lib/models/server_model.dart55-84
ServerModel runs a 500ms periodic timer (started in the constructor) that performs:
bind.mainGetConnectStatus() and updates _connectStatus.bind.cmCheckClientsLength() to detect drift between Flutter's client list and Rust's. If the lists diverge, calls updateClientState() to resync.updatePasswordModel() to sync password/verification settings.Sources: flutter/lib/models/server_model.dart150-196
Sources: flutter/lib/models/server_model.dart554-610 flutter/lib/models/server_model.dart716-749
Key methods:
| Method | Triggered by | Effect |
|---|---|---|
addConnection(evt) | FFI event | Creates Client, adds tab, shows CM window |
onClientRemove(evt) | FFI event | Removes or marks client as disconnected |
updateClientState([json]) | Timer / startup | Full resync of client list from Rust |
sendLoginResponse(client, res) | Accept/Deny button | Calls bind.cmLoginRes, starts capture on accept |
closeAll() | Window close | Calls bind.cmCloseConnection for each client |
jumpTo(id) | Chat message arrival | Selects the tab for the given connection ID |
setShowElevation(show) | FFI event | Shows/hides Elevate button |
updateVoiceCallState(evt) | FFI event | Updates inVoiceCall / incomingVoiceCall on client |
Sources: flutter/lib/models/server_model.dart517-610 flutter/lib/models/server_model.dart629-785
Client Data ModelClient represents a single inbound connection. It is serialized/deserialized via JSON from the Rust side.
type_() returns the connection type based on the boolean flags; the order of precedence is: isFileTransfer > isViewCamera > isTerminal > portForward.isNotEmpty > remote.
Sources: flutter/lib/models/server_model.dart807-901
DesktopServerPage and ConnectionManagerDesktopServerPageDesktopServerPage is the root widget for the CM window. It:
ServerModel and ChatModel via MultiProvider.WindowListener.onWindowClose to call serverModel.closeAll() and gFFI.close() before terminating.RdPlatformChannel.instance.terminate(); on other platforms, calls windowManager.close().wantKeepAlive = true to prevent disposal while the tab is not selected.Sources: flutter/lib/desktop/pages/server_page.dart25-106
ConnectionManagerConnectionManagerState wraps the tab display and the side panel. Its constructor sets up two callbacks on serverModel.tabController.onSelected:
chatModel.currentKey to the selected client.windowManager.setTitle(getWindowNameWithId(...)).gFFI.cmFileModel.updateCurrentClientId(...).Layout when no clients: Shows a centered "Waiting" text with a title bar.
Layout with clients: Shows a DesktopTab with a pageViewBuilder that uses LayoutBuilder to split the window into a main control panel and an optional side panel (chat or file transfer log). The side panel appears when the window width exceeds kConnectionManagerWindowSizeClosedChat.width.
The side page is determined by buildSidePage():
ClientType.file → _FileTransferLogPageChatPage(type: ChatPageType.desktopCM)Sources: flutter/lib/desktop/pages/server_page.dart108-346
Window size constants used:
| Constant | Role |
|---|---|
kConnectionManagerWindowSizeClosedChat | Width of CM window without chat panel |
kConnectionManagerWindowSizeOpenChat | Width of CM window with chat panel open |
kDesktopRemoteTabBarHeight | Height of the tab bar row |
buildConnectionCardEach tab's page is produced by buildConnectionCard(client), which stacks three sub-widgets:
Sources: flutter/lib/desktop/pages/server_page.dart348-371
_CmHeaderDisplays a gradient card containing:
str2color(client.name) with the first character of the name.Timer (only increments when authorized && !disconnected).remote, file, camera client types when authorized) that calls chatModel.toggleCMChatPage() or chatModel.toggleCMFilePage().Sources: flutter/lib/desktop/pages/server_page.dart404-585
_PrivilegeBoardDisplays a 4-column grid of toggle icons for permissions. Hidden for file transfer, port forward, terminal, and disconnected clients.
| Permission | Icon | FFI Call |
|---|---|---|
| Keyboard/mouse | Icons.keyboard | bind.cmSwitchPermission(connId, "keyboard", enabled) |
| Clipboard | Icons.assignment_rounded | bind.cmSwitchPermission(connId, "clipboard", enabled) |
| Audio | Icons.volume_up_rounded | bind.cmSwitchPermission(connId, "audio", enabled) |
| File | Icons.upload_file_rounded | bind.cmSwitchPermission(connId, "file", enabled) |
| Remote restart | Icons.restart_alt_rounded | bind.cmSwitchPermission(connId, "restart", enabled) |
| Recording | Icons.videocam_rounded | bind.cmSwitchPermission(connId, "recording", enabled) |
| Block input | Icons.block | bind.cmSwitchPermission(connId, "block_input", enabled) (Windows only) |
For ClientType.camera, only audio and recording permissions are shown.
Each icon is rendered by buildPermissionIcon(enabled, iconData, onTap, tooltipText), which shows accent color when enabled and grey when disabled, and displays a tooltip with the current state.
Sources: flutter/lib/desktop/pages/server_page.dart587-802
_CmControlPanelDisplays action buttons at the bottom of the connection card. The content varies by client state:
Unauthorized (buildUnAuthorized):
canElevate && showElevation && approveMode != 'password')approveMode != 'password')Authorized, connected (buildAuthorized):
client.inVoiceCall): "Audio input" (device picker popup) + "Stop voice call"client.incomingVoiceCall): "Accept" + "Dismiss"client.fromSwitch)Authorized, disconnected (buildDisconnected):
All buttons call checkClickTime(client.id, callback) to prevent double-clicks.
Sources: flutter/lib/desktop/pages/server_page.dart806-1069
handleAccept calls bind.cmLoginRes(connId, res: true). handleDisconnect calls bind.cmCloseConnection(connId). Elevation calls bind.cmElevatePortable(connId) and minimizes the CM window.
Sources: flutter/lib/desktop/pages/server_page.dart1069-1200
ChatModelChatModel is shared between the client-mode (outgoing) and CM (incoming) contexts. When used in the CM, isConnManager = true.
Messages are indexed by MessageKey(peerId, connId). The CM uses connId from the Client.id to route messages to the correct conversation.
Key methods relevant to CM:
| Method | Effect |
|---|---|
toggleCMChatPage(key) | Changes current key and calls toggleCMSidePage() |
toggleCMFilePage() | Calls toggleCMSidePage() without changing key |
toggleCMSidePage() | Resizes window between kConnectionManagerWindowSizeClosedChat and kConnectionManagerWindowSizeOpenChat via windowManager.setSizeAlignment |
changeCurrentKey(key) | Updates _currentKey, initializes MessageBody if needed |
receive(id, text) | Handles incoming message: brings CM window to top, increments unreadChatMessageCount if not focused |
send(message) | Calls bind.cmSendChat(connId, msg) for CM context |
Sources: flutter/lib/models/chat_model.dart56-100 flutter/lib/models/chat_model.dart282-320 flutter/lib/models/chat_model.dart444-459
DraggableChatWindowUsed in non-CM contexts (remote session overlay). In the CM, chat is embedded as a side panel rather than an overlay. DraggableChatWindow is still used in the mobile remote session context.
It wraps a ChatPage inside a Draggable widget that persists its screen position using DraggableKeyPosition (stored via bind.setLocalFlutterOption(k: 'draggablePositionChat', ...)).
Sources: flutter/lib/common/widgets/overlay.dart15-153 flutter/lib/common/widgets/overlay.dart250-334
Voice call state is tracked on the Client object with two booleans: inVoiceCall and incomingVoiceCall. These are updated via ServerModel.updateVoiceCallState(evt), which parses the client JSON from the FFI event.
On desktop, an incoming voice call brings the CM window to the foreground via windowOnTop(null). On Android, showVoiceCallDialog(client) shows a dialog.
The _CmControlPanel responds to client.incomingVoiceCall by showing Accept/Dismiss buttons that call handleVoiceCall(true/false), which ultimately calls bind.cmHandleIncomingVoiceCall(id, accept).
Sources: flutter/lib/models/server_model.dart629-643 flutter/lib/models/server_model.dart763-785 flutter/lib/desktop/pages/server_page.dart829-947
On Android, ServerPage is a tab in the main HomePage bottom navigation bar. It differs from the desktop CM window in several ways:
ServerPage structure:
Sources: flutter/lib/mobile/pages/server_page.dart181-222
ServerInfoShows the device's ID (from serverModel.serverId.value.text) and one-time password (from serverModel.serverPasswd.value.text) with copy and refresh buttons. Also shows a ConnectionStateNotification (connecting spinner / ready checkmark / error icon) based on connectStatus.
Sources: flutter/lib/mobile/pages/server_page.dart462-570
PermissionCheckerRenders SwitchListTile rows for each Android permission (screen capture, input control, file transfer, audio capture, clipboard), bound to serverModel.toggleXxx() methods. Also shows a "Stop service" button when mediaOk is true.
Sources: flutter/lib/mobile/pages/server_page.dart573-625
ConnectionManagerLists all serverModel.clients as PaddingCard widgets. Each card shows ClientInfo, a chat icon button (navigates to the Chat tab), and either Accept/Deny buttons (unauthorized) or a Disconnect button (authorized). Voice call Accept/Dismiss buttons appear when incomingVoiceCall && !inVoiceCall.
Sources: flutter/lib/mobile/pages/server_page.dart648-697
The _DropDownAction menu (top-right of ServerPage) lets users set:
password / click / both (via serverModel.setApproveMode)use-temporary-password / use-permanent-password / use-both-passwordsserverModel.switchAllowNumericOneTimePassword()Sources: flutter/lib/mobile/pages/server_page.dart37-173 flutter/lib/models/server_model.dart85-121
The Sciter UI uses SciterConnectionManager (src/ui/cm.rs) which implements InvokeUiCM by forwarding events to TIScript callbacks via SciterHandler.call(funcName, args).
The Sciter CM starts IPC in a background thread via start_ipc(cloned_cm) and exposes methods (authorize, close, switch_permission, send_msg, elevate_portable, etc.) as script-callable functions via sciter::dispatch_script_call!.
Sources: src/ui/cm.rs1-185 src/ui/cm.tis1-100
The following bind.* calls are used by the CM UI to control connections:
bind function | Purpose |
|---|---|
bind.cmGetClientsState() | Returns JSON array of all current clients |
bind.cmCheckClientsLength(length) | Compares Flutter's count to Rust's; returns diff JSON or null |
bind.cmLoginRes(connId, res) | Accept or deny a connection |
bind.cmCloseConnection(connId) | Disconnect a client |
bind.cmSwitchPermission(connId, name, enabled) | Toggle a permission flag |
bind.cmCanElevate() | Returns whether elevation is possible |
bind.cmElevatePortable(connId) | Request privilege elevation |
bind.cmSendChat(connId, msg) | Send a chat message to a client |
bind.cmHandleIncomingVoiceCall(id, accept) | Accept or reject a voice call |
bind.cmCloseVoiceCall(id) | End an active voice call |
bind.mainGetConnectStatus() | Get rendezvous server status JSON |
bind.mainGetTemporaryPassword() | Get the current one-time password |
bind.cmSwitchPermission(connId, name, enabled) | Toggle permission |
Sources: flutter/lib/models/server_model.dart150-196 flutter/lib/desktop/pages/server_page.dart699-795 flutter/lib/models/server_model.dart697-713
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.