// What this machine offers to other machines on the network. // // Every row states what is true right now, including "the software for this is // not installed" -- which is the honest answer for file sharing here, and is // what the panel this replaces hides behind a switch that silently does // nothing. // // Turning remote login on changes the whole system and prompts. Remote desktop // is a user service and does not. import Quickshell import QtQuick import qs.config import qs.services SettingsPage { id: root objectName: "sharing" title: "Sharing" lede: "What this machine offers to other machines on the network." Component.onCompleted: if (!Sharing.scanned) Sharing.refresh() SettingsCard { title: "This machine" subtitle: "The name other machines see." // The error belongs to the machine, not to a floating banner above the // title: a bare row above the first card read as part of the page // furniture, which is exactly what an error must not do. ErrorRow { width: parent.width label: "Sharing needs attention" message: Sharing.lastError divider: true } TextFieldRow { label: "Network name" detail: "Used for ssh and for anything else that finds this machine by name" text: Sharing.hostname placeholder: "desktop" enabled: !Sharing.busy divider: false onAccepted: value => Sharing.setHostname(value) } } SettingsCard { title: "Remote login" subtitle: Sharing.remoteLoginOn ? "Sign in to a terminal on this machine with: ssh " + Sharing.networkName : "Sign in to a terminal on this machine over SSH." // Port and the password-sign-in claim used to be two rows of their own // below this one. They describe this switch rather than standing beside // it, so they read as its detail line -- and the card is three rows // shorter for saying the same things. // // "Keys only" is reported from sshd's configuration rather than assumed: // claiming it on a machine that actually accepts passwords would be a // security claim this page cannot back up. SwitchRow { label: "Allow remote login" detail: Sharing.remoteLogin?.installed === true ? "OpenSSH · port " + String(Sharing.remoteLogin?.port ?? "22") + " · password sign-in: " + Sharing.passwordLoginSummary().toLowerCase() + (Sharing.remoteLoginOn ? " · starts at boot" : "") : "OpenSSH server is not installed" checked: Sharing.remoteLoginOn enabled: !Sharing.busy && Sharing.remoteLogin?.installed === true divider: Sharing.remoteLoginOn onToggled: value => Sharing.setRemoteLogin(value) } TextRow { visible: Sharing.remoteLoginOn && Sharing.remoteSessions.length === 0 label: "Nobody is signed in remotely" detail: "Remote login is on, and no one is connected from another machine" value: "" divider: false } Repeater { model: Sharing.remoteSessions delegate: TextRow { required property var modelData required property int index width: parent.width label: String(modelData.user ?? "") + " is signed in from " + String(modelData.from ?? "") detail: "Since " + String(modelData.since ?? "") + " · " + String(modelData.line ?? "") value: "" divider: index < Sharing.remoteSessions.length - 1 } } } SettingsCard { title: "Remote desktop" subtitle: "See and control this desktop from another machine." SwitchRow { label: "Allow remote desktop" detail: Sharing.remoteDesktop?.available === true ? "RDP · port " + String(Sharing.remoteDesktop?.port ?? "3389") + " · " + (Sharing.remoteDesktopOn ? "running for your session" : (Sharing.remoteDesktop?.hasCredentials === true ? "not running" : "set a username and password before turning this on")) : "Remote desktop support is not installed" checked: Sharing.remoteDesktopOn enabled: !Sharing.busy && Sharing.remoteDesktop?.available === true && Sharing.remoteDesktop?.hasCredentials === true onToggled: value => Sharing.setRemoteDesktop(value) } TextFieldRow { visible: Sharing.remoteDesktop?.available === true label: "Port" detail: "The RDP port other machines connect to" text: String(Sharing.remoteDesktop?.port ?? "") placeholder: "3389" enabled: !Sharing.busy onAccepted: value => Sharing.setRdpPort(value) } SwitchRow { visible: Sharing.remoteDesktop?.available === true label: "View only" detail: "Let someone watch this desktop without controlling the pointer or keyboard" checked: Sharing.remoteDesktop?.viewOnly === true enabled: !Sharing.busy onToggled: value => Sharing.setRdpViewOnly(value) } // The password is typed into gnome-remote-desktop's own tool in a // terminal, never into this page. grdctl prompts for it on a terminal // and crashes without one, and passing it as an argument would publish // it through /proc to every process on this machine. // // The row says why rather than naming the mechanism: "opens kitty" is // an implementation detail, and the reason -- the password never passes // through Panama -- is the part worth reading. ActionRow { visible: Sharing.remoteDesktop?.available === true label: "Credentials" detail: Sharing.remoteDesktop?.hasCredentials === true ? "Stored in the login keyring · set in a terminal so the password never passes through Panama" : "None stored yet · set in a terminal so the password never passes through Panama" action: "Set…" enabled: !Sharing.busy onTriggered: Sharing.setRdpCredentials(Quickshell.env("USER") || "") } SettingRow { id: forgetCredentials width: parent.width visible: Sharing.remoteDesktop?.available === true && Sharing.remoteDesktop?.hasCredentials === true label: "Forget the stored credentials" // Armed, the row names the loss rather than the state: the keyring // entry goes, and the only way back is the terminal flow above. detail: forgetCredentialsConfirm.armed ? "The user name and password are deleted from the login keyring. Remote desktop stays off until you set new ones in a terminal." : "Remote desktop cannot be turned on again until new ones are set" controlWidth: 200 divider: false ConfirmAction { id: forgetCredentialsConfirm anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter actionId: "forget-rdp-credentials" armText: "Forget…" confirmText: "Forget them" enabled: !Sharing.busy onConfirmed: Sharing.clearRdpCredentials() } } } SettingsCard { title: "File and media sharing" subtitle: "Sharing folders and media needs software this machine does not necessarily have." // Not a switch, because there is nothing behind it. Naming what // installing Samba would unlock is the difference between a dead row // and an answer -- the panel this replaces shows a switch that silently // does nothing. TextRow { label: "Share folders on the network" detail: Sharing.fileSharing?.installed === true ? "Samba is installed, so folders can be published to Windows, macOS and Linux machines alike" : "Samba is not installed — install it and this becomes a switch. Settings does not install software." value: Sharing.fileSharing?.installed === true ? "Available" : "Not installed" } SwitchRow { visible: Sharing.mediaSharing?.installed === true label: "Share music and video to devices" // Said before it happens, not after: this advertises on the network // to anything that speaks DLNA, with no password in front of it. detail: Sharing.mediaSharing?.active === true ? "Rygel is serving your media to devices on the network" : "Publishes your media folders to every device on the network. No password is asked for." checked: Sharing.mediaSharing?.active === true enabled: !Sharing.busy divider: false onToggled: value => Sharing.setMediaSharing(value) } TextRow { visible: Sharing.mediaSharing?.installed !== true label: "Share music and video to devices" detail: "Needs Rygel, which is not installed" value: "Not installed" divider: false } } }