Internal
Public Access
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34c16cbfa8 | ||
|
|
1506d87719 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
project(OrbitHub VERSION 2026.9.16.5 LANGUAGES CXX)
|
project(OrbitHub VERSION 2026.9.16.7 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|||||||
@@ -15,16 +15,23 @@ OrbitHub is in active development.
|
|||||||
|
|
||||||
- Milestones completed: M0-M9
|
- Milestones completed: M0-M9
|
||||||
- Current milestone: Milestone 10 (v1.0 Stabilization)
|
- Current milestone: Milestone 10 (v1.0 Stabilization)
|
||||||
- Latest checkpoint tag: `v2026.9.15`
|
- Latest published release: `v2026.9.16`; a `v2026.9.16.6` release is
|
||||||
- VNC (M6) covers standard VNC Authentication and no-auth servers; see
|
drafted with several post-`.16` fixes and is pending a macOS installer
|
||||||
[docs/PROGRESS.md](docs/PROGRESS.md) for known gaps (Apple Screen
|
before publishing
|
||||||
Sharing auth, compression encodings, resize, cursor sync, clipboard)
|
- VNC (M6) is fully built out: Raw/CopyRect/Hextile/ZRLE/Tight encodings,
|
||||||
|
bidirectional clipboard sync, remote cursor shape sync, and both
|
||||||
|
scale-to-fit and actual-size display modes. Standard VNC Authentication
|
||||||
|
and no-auth servers (TigerVNC, x11vnc, TightVNC, etc.) work end to end.
|
||||||
|
macOS's built-in Screen Sharing uses two undocumented, reverse-engineered
|
||||||
|
authentication schemes that are implemented but not yet confirmed working
|
||||||
|
against a real macOS server — see
|
||||||
|
[docs/PROGRESS.md](docs/PROGRESS.md) for details.
|
||||||
|
|
||||||
Progress and milestone details:
|
Progress and milestone details:
|
||||||
- [docs/PROGRESS.md](docs/PROGRESS.md)
|
- [docs/PROGRESS.md](docs/PROGRESS.md)
|
||||||
|
|
||||||
Latest release (installers for Windows, Linux, and macOS):
|
Latest published release (installers for Windows, Linux, and macOS):
|
||||||
- [v2026.9.15](https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.15)
|
- [v2026.9.16](https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.16)
|
||||||
|
|
||||||
User Guide:
|
User Guide:
|
||||||
- [docs/USER_GUIDE.md](docs/USER_GUIDE.md) (also available as a PDF attached to each release, and in-app via `Help -> User Guide`)
|
- [docs/USER_GUIDE.md](docs/USER_GUIDE.md) (also available as a PDF attached to each release, and in-app via `Help -> User Guide`)
|
||||||
@@ -50,6 +57,8 @@ An embedded RDP session in a tab.
|
|||||||
- SQLite-backed profile storage
|
- SQLite-backed profile storage
|
||||||
- Create, edit, delete profiles
|
- Create, edit, delete profiles
|
||||||
- Protocol-aware profile validation (SSH/RDP/VNC)
|
- Protocol-aware profile validation (SSH/RDP/VNC)
|
||||||
|
- Username is optional on any protocol's profile — if left blank, OrbitHub
|
||||||
|
asks for one inline at connect time instead of requiring it up front
|
||||||
- Profile search and sorting
|
- Profile search and sorting
|
||||||
- Tags support
|
- Tags support
|
||||||
- Folder/subfolder support
|
- Folder/subfolder support
|
||||||
@@ -58,13 +67,20 @@ An embedded RDP session in a tab.
|
|||||||
- New Folder
|
- New Folder
|
||||||
- New Connection
|
- New Connection
|
||||||
- Drag-and-drop profile moves between folders with persistence
|
- Drag-and-drop profile moves between folders with persistence
|
||||||
|
- Import/Export of the whole profile list as JSON, and one-way import from
|
||||||
|
mRemoteNG connection XML files (passwords are never imported)
|
||||||
|
|
||||||
### Session Experience
|
### Session Experience
|
||||||
|
|
||||||
- Multi-tab session window
|
- Multi-tab session window
|
||||||
- Auto-connect on tab open
|
- Auto-connect on tab open
|
||||||
- Disconnect on tab close
|
- Disconnect on tab close
|
||||||
- Session state indicators on tabs
|
- Session state indicators on tabs, colored distinctly per state
|
||||||
|
(connecting/connected/disconnected/failed)
|
||||||
|
- A tab awaiting a username/password prompt is clearly marked — its title
|
||||||
|
gets a "(Needs input)" suffix and its tab color changes — even when it
|
||||||
|
isn't the tab currently in view, and the inline prompt itself uses a
|
||||||
|
solid highlighted banner rather than blending into the rest of the tab
|
||||||
- Timestamped event log with filtering and export
|
- Timestamped event log with filtering and export
|
||||||
|
|
||||||
### SSH
|
### SSH
|
||||||
@@ -82,14 +98,36 @@ An embedded RDP session in a tab.
|
|||||||
- Domain-aware authentication support
|
- Domain-aware authentication support
|
||||||
- RDP security/performance profile options
|
- RDP security/performance profile options
|
||||||
|
|
||||||
|
### VNC
|
||||||
|
|
||||||
|
- Embedded in-window VNC rendering surface (no external launcher)
|
||||||
|
- Raw, CopyRect, Hextile, ZRLE, and Tight rectangle encodings
|
||||||
|
- Bidirectional clipboard sync
|
||||||
|
- Remote cursor shape sync
|
||||||
|
- Scale-to-fit and actual-size (scrollable) display modes, toggled
|
||||||
|
per-tab and remembered across sessions
|
||||||
|
- Standard VNC Authentication and no-auth servers (TigerVNC, x11vnc,
|
||||||
|
TightVNC, etc.)
|
||||||
|
- No dynamic remote-desktop resizing (VNC has no real equivalent of RDP's
|
||||||
|
MS-RDPEDISP)
|
||||||
|
- macOS Screen Sharing's two undocumented Apple auth schemes are
|
||||||
|
implemented but not yet confirmed working against a real macOS server —
|
||||||
|
see [docs/PROGRESS.md](docs/PROGRESS.md)
|
||||||
|
|
||||||
### App UX
|
### App UX
|
||||||
|
|
||||||
- App icon and themed About dialog
|
- App icon and themed About dialog
|
||||||
|
- In-app User Guide (`Help -> User Guide`), matching
|
||||||
|
[docs/USER_GUIDE.md](docs/USER_GUIDE.md)
|
||||||
- `File` menu:
|
- `File` menu:
|
||||||
- New Profile
|
- New Profile
|
||||||
- New Folder
|
- New Folder
|
||||||
|
- Import Profiles...
|
||||||
|
- Export Profiles...
|
||||||
|
- Import from mRemoteNG...
|
||||||
- Quit
|
- Quit
|
||||||
- `Help` menu:
|
- `Help` menu:
|
||||||
|
- User Guide
|
||||||
- About OrbitHub
|
- About OrbitHub
|
||||||
|
|
||||||
## Build and Run
|
## Build and Run
|
||||||
@@ -140,6 +178,9 @@ Core dependencies:
|
|||||||
- Qt 6 (Widgets, SQL)
|
- Qt 6 (Widgets, SQL)
|
||||||
- CMake 3.21+
|
- CMake 3.21+
|
||||||
- C++17 toolchain
|
- C++17 toolchain
|
||||||
|
- OpenSSL (RDP/VNC transport security and Apple VNC auth)
|
||||||
|
- zlib (VNC's ZRLE and Tight encodings)
|
||||||
|
- libjpeg-turbo (VNC's Tight encoding's JPEG sub-mode)
|
||||||
|
|
||||||
Protocol/runtime dependencies:
|
Protocol/runtime dependencies:
|
||||||
- SSH client (`ssh`) available on `PATH` for SSH sessions
|
- SSH client (`ssh`) available on `PATH` for SSH sessions
|
||||||
@@ -176,12 +217,19 @@ See in-app `Help -> About OrbitHub` for license links and third-party inventory.
|
|||||||
|
|
||||||
- `src/` - application source code
|
- `src/` - application source code
|
||||||
- `docs/` - build guide, spec, and progress tracking
|
- `docs/` - build guide, spec, and progress tracking
|
||||||
|
- `packaging/` - per-platform installer/package build scripts
|
||||||
|
- `tests/` - CTest unit/integration tests and fixtures
|
||||||
|
- `tools/` - standalone build-time tools (e.g. the User Guide PDF generator)
|
||||||
- `third_party/` - vendored third-party dependencies
|
- `third_party/` - vendored third-party dependencies
|
||||||
- `build/` - local build output (generated)
|
- `build/` - local build output (generated)
|
||||||
|
- `dist/` - packaged installer/deb/flatpak/dmg output (generated)
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Passwords are requested at connect time and are not stored in the profile database.
|
- Passwords are requested at connect time and are not stored in the profile database.
|
||||||
|
- A profile's username is optional for every protocol; if left blank,
|
||||||
|
OrbitHub asks for one inline the first time you connect that profile.
|
||||||
- VNC support covers standard VNC Authentication and no-auth servers (e.g. TigerVNC, x11vnc,
|
- VNC support covers standard VNC Authentication and no-auth servers (e.g. TigerVNC, x11vnc,
|
||||||
TightVNC); it doesn't yet reach macOS's built-in Screen Sharing server, which uses a different
|
TightVNC). macOS's built-in Screen Sharing server uses two undocumented, reverse-engineered
|
||||||
authentication scheme (see docs/PROGRESS.md, Milestone 6).
|
authentication schemes that are implemented but not yet confirmed working against a real
|
||||||
|
macOS server (see docs/PROGRESS.md, Milestone 6).
|
||||||
|
|||||||
@@ -209,6 +209,23 @@ Delivered:
|
|||||||
option, not the stale profile copy, is actually used) -- RDP has no
|
option, not the stale profile copy, is actually used) -- RDP has no
|
||||||
equivalent fake-server test harness, so that side relies on mirroring
|
equivalent fake-server test harness, so that side relies on mirroring
|
||||||
the already-tested `m_activeOptions.password` pattern exactly
|
the already-tested `m_activeOptions.password` pattern exactly
|
||||||
|
- Issue #22: the inline username/password prompt bar used to just be a
|
||||||
|
plain `QWidget` with `setAutoFillBackground(true)` and no explicit
|
||||||
|
color, which meant it rendered in the same color as everything else
|
||||||
|
around it and was easy to miss -- especially on a tab that wasn't the
|
||||||
|
active one, where there was previously no indication anything needed
|
||||||
|
attention at all. Now uses a solid `QPalette::Highlight` fill with
|
||||||
|
`QPalette::HighlightedText` for the label (the OS theme's own
|
||||||
|
guaranteed-contrasting pair, so it stays correct under both light and
|
||||||
|
dark themes without a hardcoded color) plus a hand-drawn "?" badge
|
||||||
|
(not a themed `QStyle` icon, whose own colors are outside our control
|
||||||
|
and could land close in hue to the bar's background); a background
|
||||||
|
tab showing the prompt gets its title suffixed "(Needs input)" and its
|
||||||
|
tab-bar text colored distinctly from the four connection-state colors.
|
||||||
|
A first pass at the tab color (`#6a1b9a`) was reported unreadable in
|
||||||
|
dark mode -- its perceived luminance was well below the four existing
|
||||||
|
state colors -- and was replaced with `#ab47bc`, tuned to roughly
|
||||||
|
match their visibility
|
||||||
- Robustness fix: an unrecognized `FramebufferUpdate` rectangle encoding
|
- Robustness fix: an unrecognized `FramebufferUpdate` rectangle encoding
|
||||||
used to abort the connection generically; `kAnnouncedEncodings` is now
|
used to abort the connection generically; `kAnnouncedEncodings` is now
|
||||||
the single source of truth for what `SetEncodings` announces and what
|
the single source of truth for what `SetEncodings` announces and what
|
||||||
|
|||||||
+113
-15
@@ -1,16 +1,19 @@
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
OrbitHub is a native desktop application for organizing connection profiles
|
OrbitHub is a native desktop application for organizing connection profiles
|
||||||
and launching SSH and RDP sessions from one place, in a single tabbed
|
and launching SSH, RDP, and VNC sessions from one place, in a single tabbed
|
||||||
window. It runs on Windows, Linux, and macOS.
|
window. It runs on Windows, Linux, and macOS.
|
||||||
|
|
||||||
This guide covers everyday use: creating and organizing profiles,
|
This guide covers everyday use: creating and organizing profiles,
|
||||||
connecting over SSH and RDP, managing active sessions, and what to do when
|
connecting over SSH, RDP, and VNC, managing active sessions, and what to do
|
||||||
something goes wrong. It does not cover installation or building from
|
when something goes wrong. It does not cover installation or building from
|
||||||
source — see the project's `README.md` and `docs/BUILDING.md` for that.
|
source — see the project's `README.md` and `docs/BUILDING.md` for that.
|
||||||
|
|
||||||
VNC support is planned but not yet implemented; profiles can be tagged for
|
VNC support covers standard VNC Authentication and no-auth servers
|
||||||
it, but connecting will show an "unsupported protocol" message for now.
|
(TigerVNC, x11vnc, TightVNC, and similar). macOS's built-in Screen Sharing
|
||||||
|
server uses two undocumented, reverse-engineered authentication schemes
|
||||||
|
that are implemented but not yet confirmed working end to end against a
|
||||||
|
real macOS server — see [Connecting via VNC](#connecting-via-vnc).
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -43,15 +46,19 @@ A profile stores everything needed to reach one remote host. Open **New**
|
|||||||
|
|
||||||
- **Name** — a label for the profile; shown on its tab and in the list.
|
- **Name** — a label for the profile; shown on its tab and in the list.
|
||||||
- **Host** — hostname or IP address.
|
- **Host** — hostname or IP address.
|
||||||
- **Port** — defaults to `22` for SSH, `3389` for RDP.
|
- **Port** — defaults to `22` for SSH, `3389` for RDP, `5900` for VNC.
|
||||||
- **Username** — the account to log in as.
|
- **Username** — the account to log in as. Optional for every protocol —
|
||||||
|
leave it blank and OrbitHub asks for one inline the first time you
|
||||||
|
connect that profile, the same way it already asks for a password. (VNC
|
||||||
|
servers mostly ignore username entirely; macOS Screen Sharing is the
|
||||||
|
exception and requires one — see
|
||||||
|
[Connecting via VNC](#connecting-via-vnc).)
|
||||||
- **Domain** — Windows domain for RDP logins (leave blank for local/
|
- **Domain** — Windows domain for RDP logins (leave blank for local/
|
||||||
workgroup accounts or SSH profiles).
|
workgroup accounts, SSH, or VNC profiles).
|
||||||
- **Tags** — free-form, comma-separated labels for filtering and grouping
|
- **Tags** — free-form, comma-separated labels for filtering and grouping
|
||||||
(e.g. `prod, linux, db`).
|
(e.g. `prod, linux, db`).
|
||||||
- **Folder** — where the profile lives in the Folders view.
|
- **Folder** — where the profile lives in the Folders view.
|
||||||
- **Protocol** — `SSH`, `RDP`, or `VNC` (VNC is accepted but not yet
|
- **Protocol** — `SSH`, `RDP`, or `VNC`.
|
||||||
connectable — see Introduction).
|
|
||||||
|
|
||||||
Fields below Protocol change based on what you pick:
|
Fields below Protocol change based on what you pick:
|
||||||
|
|
||||||
@@ -63,6 +70,9 @@ policy means.
|
|||||||
**RDP**: **RDP Security** and **RDP Performance** — see
|
**RDP**: **RDP Security** and **RDP Performance** — see
|
||||||
[Connecting via RDP](#connecting-via-rdp).
|
[Connecting via RDP](#connecting-via-rdp).
|
||||||
|
|
||||||
|
**VNC** has no protocol-specific fields beyond Host/Port/Username above —
|
||||||
|
see [Connecting via VNC](#connecting-via-vnc).
|
||||||
|
|
||||||
Passwords are never saved in the profile — OrbitHub asks for them each
|
Passwords are never saved in the profile — OrbitHub asks for them each
|
||||||
time you connect (unless you've set up private-key SSH auth, which needs
|
time you connect (unless you've set up private-key SSH auth, which needs
|
||||||
no password to be entered per-connection if the key itself has none).
|
no password to be entered per-connection if the key itself has none).
|
||||||
@@ -85,6 +95,24 @@ Combine both with the **Search** box (matches name, host, folder path, or
|
|||||||
tags) and the **Sort** control (Name / Protocol / Host) to find what you
|
tags) and the **Sort** control (Name / Protocol / Host) to find what you
|
||||||
need quickly even with a large profile list.
|
need quickly even with a large profile list.
|
||||||
|
|
||||||
|
## Importing and Exporting Profiles
|
||||||
|
|
||||||
|
The **File** menu has three options for moving profiles in or out of
|
||||||
|
OrbitHub:
|
||||||
|
|
||||||
|
- **Export Profiles...** — saves your entire profile list, including
|
||||||
|
folder structure and tags, to a JSON file. Passwords are never included,
|
||||||
|
since OrbitHub never stores them in the first place.
|
||||||
|
- **Import Profiles...** — reads that same JSON format back in, recreating
|
||||||
|
folders and profiles. Existing profiles aren't touched; entries missing
|
||||||
|
a name or host are skipped and reported in the summary.
|
||||||
|
- **Import from mRemoteNG...** — reads an mRemoteNG connections `.xml`
|
||||||
|
file and creates equivalent OrbitHub profiles. Passwords are never
|
||||||
|
imported — you'll be prompted the first time you connect each imported
|
||||||
|
profile, same as a new one. Connections using a protocol OrbitHub
|
||||||
|
doesn't support are skipped and listed in the import summary. This is a
|
||||||
|
one-way conversion; there's no export back to mRemoteNG's format.
|
||||||
|
|
||||||
## Connecting via SSH
|
## Connecting via SSH
|
||||||
|
|
||||||
Double-clicking an SSH profile opens a new tab with an embedded, fully
|
Double-clicking an SSH profile opens a new tab with an embedded, fully
|
||||||
@@ -93,6 +121,8 @@ emulator. A **theme** selector lets you switch between `Dark`, `Light`,
|
|||||||
and `Solarized Dark`; your choice is remembered for future sessions.
|
and `Solarized Dark`; your choice is remembered for future sessions.
|
||||||
|
|
||||||
**Authentication.** Set in the profile itself:
|
**Authentication.** Set in the profile itself:
|
||||||
|
- **Username** — if left blank in the profile, OrbitHub asks for one
|
||||||
|
inline the first time you connect, right alongside the password prompt.
|
||||||
- **Password** — OrbitHub prompts for a password each time you connect.
|
- **Password** — OrbitHub prompts for a password each time you connect.
|
||||||
It is never stored.
|
It is never stored.
|
||||||
- **Private Key** — point at a key file (via the profile's Browse button);
|
- **Private Key** — point at a key file (via the profile's Browse button);
|
||||||
@@ -105,7 +135,7 @@ silently impersonating a host you've connected to before:
|
|||||||
| Policy | Behavior |
|
| Policy | Behavior |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `Ask` | Prompts you to confirm trust the first time a host is seen, and on any later change. Recommended default. |
|
| `Ask` | Prompts you to confirm trust the first time a host is seen, and on any later change. Recommended default. |
|
||||||
| `Accept-new` | Silently trusts a host the first time it's seen, but still stops and warns if a previously-trusted host's key later changes. |
|
| `Accept New` | Silently trusts a host the first time it's seen, but still stops and warns if a previously-trusted host's key later changes. |
|
||||||
| `Strict` | Never trusts an unknown host automatically — the connection fails until you've manually confirmed the host key some other way. |
|
| `Strict` | Never trusts an unknown host automatically — the connection fails until you've manually confirmed the host key some other way. |
|
||||||
| `Ignore` | Skips host-key checking entirely. Only use this for throwaway/test environments — it removes protection against on-path attacks. |
|
| `Ignore` | Skips host-key checking entirely. Only use this for throwaway/test environments — it removes protection against on-path attacks. |
|
||||||
|
|
||||||
@@ -123,7 +153,8 @@ window renegotiates the remote resolution to match. Clipboard content
|
|||||||
syncs between your machine and the remote session automatically.
|
syncs between your machine and the remote session automatically.
|
||||||
|
|
||||||
**Authentication** uses the profile's Username/Domain fields; OrbitHub
|
**Authentication** uses the profile's Username/Domain fields; OrbitHub
|
||||||
prompts for the password at connect time.
|
prompts for the password at connect time, and for a username too if the
|
||||||
|
profile's own Username field was left blank.
|
||||||
|
|
||||||
**RDP Security** controls which transport-security layer is used to
|
**RDP Security** controls which transport-security layer is used to
|
||||||
negotiate the connection:
|
negotiate the connection:
|
||||||
@@ -151,6 +182,51 @@ expected — you rotated the server's certificate yourself — reconnecting
|
|||||||
after clearing the old entry from FreeRDP's certificate store will trust
|
after clearing the old entry from FreeRDP's certificate store will trust
|
||||||
the new one.
|
the new one.
|
||||||
|
|
||||||
|
## Connecting via VNC
|
||||||
|
|
||||||
|
VNC sessions render in an embedded display surface inside the tab, like
|
||||||
|
RDP — no external VNC client window opens.
|
||||||
|
|
||||||
|
**Authentication.** VNC servers don't have a fixed username/password
|
||||||
|
convention the way SSH and RDP do, so OrbitHub always prompts for a
|
||||||
|
password at connect time (leave it blank if the server doesn't require
|
||||||
|
one — no-auth servers exist and are supported). Most VNC servers ignore
|
||||||
|
username entirely; the one common exception is **macOS's built-in Screen
|
||||||
|
Sharing**, which does require one — see the Apple Screen Sharing note
|
||||||
|
below if you're connecting to a Mac.
|
||||||
|
|
||||||
|
**Display mode.** Right-click a VNC session's tab to choose:
|
||||||
|
- **Scale to Fit** (default) — the whole remote screen is scaled to fit
|
||||||
|
the tab, like RDP.
|
||||||
|
- **Actual Size (Scrollbars)** — renders at the remote's native pixel
|
||||||
|
size, with scrollbars for panning. Useful when scaling would make small
|
||||||
|
text illegible.
|
||||||
|
|
||||||
|
Your choice is remembered across sessions. Unlike RDP, VNC has no way to
|
||||||
|
renegotiate the remote screen's resolution to match your window size —
|
||||||
|
resizing the OrbitHub window changes how much of a scaled-down remote
|
||||||
|
screen you can see, not the remote resolution itself.
|
||||||
|
|
||||||
|
**Clipboard and cursor.** Clipboard content syncs between your machine and
|
||||||
|
the remote session in both directions, and the remote's cursor shape (not
|
||||||
|
just position) is mirrored locally when the server supports it.
|
||||||
|
|
||||||
|
**Encodings.** OrbitHub negotiates whichever of Raw, CopyRect, Hextile,
|
||||||
|
ZRLE, or Tight the server prefers — this is automatic and requires no
|
||||||
|
configuration; more efficient encodings (Tight, ZRLE) simply mean better
|
||||||
|
performance over slower links.
|
||||||
|
|
||||||
|
**Apple Screen Sharing (macOS).** macOS's built-in Screen Sharing server
|
||||||
|
doesn't use a standard, documented VNC authentication method — it uses two
|
||||||
|
undocumented schemes that OrbitHub implements based on reverse-engineering
|
||||||
|
work, but neither is yet confirmed to authenticate successfully against a
|
||||||
|
real macOS server. If you're connecting to a Mac's Screen Sharing and see
|
||||||
|
an authentication failure, this is the likely cause rather than a wrong
|
||||||
|
username or password; check
|
||||||
|
[docs/PROGRESS.md](https://git.darksingularity.org/DarkSingularity/orbithub/blob/main/docs/PROGRESS.md)
|
||||||
|
(Milestone 6) for the current status, or a VNC server you install yourself
|
||||||
|
on the Mac (e.g. TigerVNC) as a workaround in the meantime.
|
||||||
|
|
||||||
## Managing Sessions
|
## Managing Sessions
|
||||||
|
|
||||||
Every open connection lives in its own tab in the same window, alongside
|
Every open connection lives in its own tab in the same window, alongside
|
||||||
@@ -158,6 +234,14 @@ the Profiles tab. A tab's title and a colored state indicator show
|
|||||||
whether it's connecting, connected, disconnected, or failed. Closing a
|
whether it's connecting, connected, disconnected, or failed. Closing a
|
||||||
tab disconnects that session; opening a profile again starts a fresh one.
|
tab disconnects that session; opening a profile again starts a fresh one.
|
||||||
|
|
||||||
|
**When OrbitHub needs a username or password from you**, the tab shows a
|
||||||
|
highlighted prompt bar with a Connect/Cancel pair — this happens for a
|
||||||
|
blank-username SSH/RDP profile, any RDP or SSH password prompt, and any
|
||||||
|
VNC connection (VNC always asks for a password, even if blank). If that
|
||||||
|
prompt appears on a tab you're not currently viewing, the tab's title
|
||||||
|
gets a `(Needs input)` suffix and its color changes so it's easy to spot
|
||||||
|
among several open sessions.
|
||||||
|
|
||||||
Each session tab includes a collapsible **event log** beneath the
|
Each session tab includes a collapsible **event log** beneath the
|
||||||
connection surface — a timestamped record of connection state changes,
|
connection surface — a timestamped record of connection state changes,
|
||||||
warnings, and errors for that session. Controls above the log let you:
|
warnings, and errors for that session. Controls above the log let you:
|
||||||
@@ -176,8 +260,9 @@ settings screen — they're saved automatically as you use the app:
|
|||||||
|
|
||||||
- Profile list: search text, view mode (List/Folders), Protocol/Tag
|
- Profile list: search text, view mode (List/Folders), Protocol/Tag
|
||||||
filters, and sort order.
|
filters, and sort order.
|
||||||
- Session tabs: terminal theme choice, and whether the events panel is
|
- Session tabs: terminal theme choice, whether the events panel is shown
|
||||||
shown or hidden for new tabs.
|
or hidden for new tabs, and VNC display mode (Scale to Fit / Actual
|
||||||
|
Size).
|
||||||
|
|
||||||
Profile data itself (names, hosts, tags, folder structure, and so on) is
|
Profile data itself (names, hosts, tags, folder structure, and so on) is
|
||||||
stored in a local SQLite database — see the README for its exact path on
|
stored in a local SQLite database — see the README for its exact path on
|
||||||
@@ -209,10 +294,23 @@ certificate is replaced.
|
|||||||
|
|
||||||
**SSH: connection hangs at "Ask" waiting for host-key trust** — check
|
**SSH: connection hangs at "Ask" waiting for host-key trust** — check
|
||||||
that policy's behavior under
|
that policy's behavior under
|
||||||
[Connecting via SSH](#connecting-via-ssh); switching to `Accept-new` avoids
|
[Connecting via SSH](#connecting-via-ssh); switching to `Accept New` avoids
|
||||||
the prompt for genuinely new hosts while still protecting against a later
|
the prompt for genuinely new hosts while still protecting against a later
|
||||||
key change.
|
key change.
|
||||||
|
|
||||||
|
**VNC: "Authentication or authorization failure"** — for most VNC
|
||||||
|
servers this means a wrong password. If you're connecting to **macOS
|
||||||
|
Screen Sharing** specifically, this is the expected result right now — see
|
||||||
|
the Apple Screen Sharing note under
|
||||||
|
[Connecting via VNC](#connecting-via-vnc).
|
||||||
|
|
||||||
|
**VNC: connection closes immediately with no prompt** — some servers
|
||||||
|
(including macOS Screen Sharing in some configurations) close the
|
||||||
|
connection outright rather than negotiating; confirm the server is
|
||||||
|
actually running and reachable on the profile's port (default `5900`),
|
||||||
|
and that no allowlist on the server side is blocking your account or
|
||||||
|
machine.
|
||||||
|
|
||||||
If none of this covers what you're seeing, a session's exported event log
|
If none of this covers what you're seeing, a session's exported event log
|
||||||
(see Managing Sessions) is the most useful thing to include when asking
|
(see Managing Sessions) is the most useful thing to include when asking
|
||||||
for help or filing an issue.
|
for help or filing an issue.
|
||||||
|
|||||||
@@ -17,10 +17,14 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QPixmap>
|
||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QColor>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
@@ -59,6 +63,34 @@ TerminalTheme themeForName(const QString& themeName)
|
|||||||
return TerminalTheme::loadKonsoleTheme(
|
return TerminalTheme::loadKonsoleTheme(
|
||||||
QStringLiteral(":/KodoTermThemes/konsole/Breeze.colorscheme"));
|
QStringLiteral(":/KodoTermThemes/konsole/Breeze.colorscheme"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A filled circle with a bold "?", used on the username/password prompt
|
||||||
|
// bar (issue #22). Drawn by hand rather than pulled from a QStyle standard
|
||||||
|
// icon because a themed icon's own internal colors are outside our
|
||||||
|
// control and could end up close in hue to the bar's own background,
|
||||||
|
// undermining the contrast the bar is trying to achieve; painting it
|
||||||
|
// ourselves guarantees fillColor/textColor are exactly the same
|
||||||
|
// guaranteed-contrasting pair used for the rest of the bar.
|
||||||
|
QPixmap questionMarkBadgePixmap(const QColor& fillColor, const QColor& textColor, int diameter)
|
||||||
|
{
|
||||||
|
QPixmap pixmap(diameter, diameter);
|
||||||
|
pixmap.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&pixmap);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.setBrush(fillColor);
|
||||||
|
painter.drawEllipse(0, 0, diameter, diameter);
|
||||||
|
|
||||||
|
QFont font = painter.font();
|
||||||
|
font.setBold(true);
|
||||||
|
font.setPixelSize(static_cast<int>(diameter * 0.65));
|
||||||
|
painter.setFont(font);
|
||||||
|
painter.setPen(textColor);
|
||||||
|
painter.drawText(QRect(0, 0, diameter, diameter), Qt::AlignCenter, QStringLiteral("?"));
|
||||||
|
|
||||||
|
return pixmap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionTab::SessionTab(const Profile& profile,
|
SessionTab::SessionTab(const Profile& profile,
|
||||||
@@ -93,10 +125,12 @@ SessionTab::SessionTab(const Profile& profile,
|
|||||||
m_exportEventsButton(nullptr),
|
m_exportEventsButton(nullptr),
|
||||||
m_eventsPanel(nullptr),
|
m_eventsPanel(nullptr),
|
||||||
m_passwordPromptBar(nullptr),
|
m_passwordPromptBar(nullptr),
|
||||||
|
m_passwordPromptIcon(nullptr),
|
||||||
m_passwordPromptLabel(nullptr),
|
m_passwordPromptLabel(nullptr),
|
||||||
m_passwordPromptInput(nullptr),
|
m_passwordPromptInput(nullptr),
|
||||||
m_passwordPromptConnectButton(nullptr),
|
m_passwordPromptConnectButton(nullptr),
|
||||||
m_passwordPromptCancelButton(nullptr),
|
m_passwordPromptCancelButton(nullptr),
|
||||||
|
m_awaitingUserInput(false),
|
||||||
m_eventSeverityFilter(EventSeverity::Info),
|
m_eventSeverityFilter(EventSeverity::Info),
|
||||||
m_eventsPanelExpanded(preferences.eventsPanelExpanded)
|
m_eventsPanelExpanded(preferences.eventsPanelExpanded)
|
||||||
{
|
{
|
||||||
@@ -341,9 +375,17 @@ SessionTab::~SessionTab()
|
|||||||
|
|
||||||
QString SessionTab::tabTitle() const
|
QString SessionTab::tabTitle() const
|
||||||
{
|
{
|
||||||
|
if (m_awaitingUserInput) {
|
||||||
|
return QStringLiteral("%1 (Needs input)").arg(m_profile.name);
|
||||||
|
}
|
||||||
return QStringLiteral("%1 (%2)").arg(m_profile.name, stateSuffix());
|
return QStringLiteral("%1 (%2)").arg(m_profile.name, stateSuffix());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SessionTab::awaitingUserInput() const
|
||||||
|
{
|
||||||
|
return m_awaitingUserInput;
|
||||||
|
}
|
||||||
|
|
||||||
void SessionTab::connectSession()
|
void SessionTab::connectSession()
|
||||||
{
|
{
|
||||||
if (m_state == SessionState::Connecting || m_state == SessionState::Connected) {
|
if (m_state == SessionState::Connecting || m_state == SessionState::Connected) {
|
||||||
@@ -806,21 +848,42 @@ void SessionTab::setupUi()
|
|||||||
|
|
||||||
applyTerminalTheme(m_terminalThemeName);
|
applyTerminalTheme(m_terminalThemeName);
|
||||||
|
|
||||||
|
// Styled distinctly (issue #22: this bar used to blend straight into
|
||||||
|
// the plain window background and was easy to miss, especially on a
|
||||||
|
// tab you weren't actively looking at). A solid QPalette::Highlight
|
||||||
|
// fill with QPalette::HighlightedText for the label is used rather
|
||||||
|
// than a subtle tint -- a faint tint proved to still be easy to miss,
|
||||||
|
// and Highlight/HighlightedText are the OS theme's own guaranteed-
|
||||||
|
// contrasting pair, so this stays readable under both light and dark
|
||||||
|
// themes without hardcoding a color.
|
||||||
auto* passwordPromptLayout = new QHBoxLayout();
|
auto* passwordPromptLayout = new QHBoxLayout();
|
||||||
|
m_passwordPromptIcon = new QLabel(this);
|
||||||
|
const QColor highlight = palette().color(QPalette::Highlight);
|
||||||
|
const QColor highlightedText = palette().color(QPalette::HighlightedText);
|
||||||
|
m_passwordPromptIcon->setPixmap(questionMarkBadgePixmap(highlightedText, highlight, 22));
|
||||||
m_passwordPromptLabel = new QLabel(this);
|
m_passwordPromptLabel = new QLabel(this);
|
||||||
|
m_passwordPromptLabel->setObjectName(QStringLiteral("passwordPromptLabel"));
|
||||||
m_passwordPromptInput = new QLineEdit(this);
|
m_passwordPromptInput = new QLineEdit(this);
|
||||||
m_passwordPromptInput->setEchoMode(QLineEdit::Password);
|
m_passwordPromptInput->setEchoMode(QLineEdit::Password);
|
||||||
m_passwordPromptConnectButton = new QPushButton(QStringLiteral("Connect"), this);
|
m_passwordPromptConnectButton = new QPushButton(QStringLiteral("Connect"), this);
|
||||||
m_passwordPromptCancelButton = new QPushButton(QStringLiteral("Cancel"), this);
|
m_passwordPromptCancelButton = new QPushButton(QStringLiteral("Cancel"), this);
|
||||||
|
passwordPromptLayout->addWidget(m_passwordPromptIcon);
|
||||||
passwordPromptLayout->addWidget(m_passwordPromptLabel);
|
passwordPromptLayout->addWidget(m_passwordPromptLabel);
|
||||||
passwordPromptLayout->addWidget(m_passwordPromptInput, 1);
|
passwordPromptLayout->addWidget(m_passwordPromptInput, 1);
|
||||||
passwordPromptLayout->addWidget(m_passwordPromptConnectButton);
|
passwordPromptLayout->addWidget(m_passwordPromptConnectButton);
|
||||||
passwordPromptLayout->addWidget(m_passwordPromptCancelButton);
|
passwordPromptLayout->addWidget(m_passwordPromptCancelButton);
|
||||||
|
passwordPromptLayout->setContentsMargins(10, 8, 10, 8);
|
||||||
|
|
||||||
m_passwordPromptBar = new QWidget(this);
|
m_passwordPromptBar = new QWidget(this);
|
||||||
|
m_passwordPromptBar->setObjectName(QStringLiteral("passwordPromptBar"));
|
||||||
m_passwordPromptBar->setLayout(passwordPromptLayout);
|
m_passwordPromptBar->setLayout(passwordPromptLayout);
|
||||||
m_passwordPromptBar->setAutoFillBackground(true);
|
m_passwordPromptBar->setAutoFillBackground(true);
|
||||||
m_passwordPromptBar->setVisible(false);
|
m_passwordPromptBar->setVisible(false);
|
||||||
|
m_passwordPromptBar->setStyleSheet(
|
||||||
|
QStringLiteral("QWidget#passwordPromptBar { background-color: %1; }"
|
||||||
|
"QWidget#passwordPromptBar QLabel#passwordPromptLabel "
|
||||||
|
"{ color: %2; font-weight: bold; font-size: 11pt; }")
|
||||||
|
.arg(highlight.name(), highlightedText.name()));
|
||||||
rootLayout->addWidget(m_passwordPromptBar);
|
rootLayout->addWidget(m_passwordPromptBar);
|
||||||
|
|
||||||
connect(m_passwordPromptConnectButton, &QPushButton::clicked, this, [this]() {
|
connect(m_passwordPromptConnectButton, &QPushButton::clicked, this, [this]() {
|
||||||
@@ -1161,12 +1224,24 @@ void SessionTab::showPasswordPrompt(const QString& labelText,
|
|||||||
m_passwordPromptInput->setEchoMode(maskInput ? QLineEdit::Password : QLineEdit::Normal);
|
m_passwordPromptInput->setEchoMode(maskInput ? QLineEdit::Password : QLineEdit::Normal);
|
||||||
m_passwordPromptBar->setVisible(true);
|
m_passwordPromptBar->setVisible(true);
|
||||||
m_passwordPromptInput->setFocus();
|
m_passwordPromptInput->setFocus();
|
||||||
|
|
||||||
|
if (!m_awaitingUserInput) {
|
||||||
|
m_awaitingUserInput = true;
|
||||||
|
emit awaitingUserInputChanged(true);
|
||||||
|
emit tabTitleChanged(tabTitle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionTab::hidePasswordPrompt()
|
void SessionTab::hidePasswordPrompt()
|
||||||
{
|
{
|
||||||
m_passwordPromptBar->setVisible(false);
|
m_passwordPromptBar->setVisible(false);
|
||||||
m_passwordPromptCallback = nullptr;
|
m_passwordPromptCallback = nullptr;
|
||||||
|
|
||||||
|
if (m_awaitingUserInput) {
|
||||||
|
m_awaitingUserInput = false;
|
||||||
|
emit awaitingUserInputChanged(false);
|
||||||
|
emit tabTitleChanged(tabTitle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SessionTab::validateProfileForConnect()
|
bool SessionTab::validateProfileForConnect()
|
||||||
|
|||||||
@@ -67,10 +67,16 @@ public:
|
|||||||
bool supportsVncScaleToggle() const;
|
bool supportsVncScaleToggle() const;
|
||||||
void setVncScaleToFit(bool scaleToFit);
|
void setVncScaleToFit(bool scaleToFit);
|
||||||
bool vncScaleToFit() const;
|
bool vncScaleToFit() const;
|
||||||
|
bool awaitingUserInput() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tabTitleChanged(const QString& title);
|
void tabTitleChanged(const QString& title);
|
||||||
void tabStateChanged(SessionState state);
|
void tabStateChanged(SessionState state);
|
||||||
|
// Fires whenever the inline username/password prompt bar is shown or
|
||||||
|
// hidden -- independent of tabStateChanged(), since the backend is
|
||||||
|
// still just "Connecting" while it's up. Lets SessionWindow mark a
|
||||||
|
// background tab that needs the user's attention (issue #22).
|
||||||
|
void awaitingUserInputChanged(bool waiting);
|
||||||
void terminalThemeChanged(const QString& themeName);
|
void terminalThemeChanged(const QString& themeName);
|
||||||
void terminalFontSizeChanged(int pointSize);
|
void terminalFontSizeChanged(int pointSize);
|
||||||
void eventsPanelVisibilityChanged(bool expanded);
|
void eventsPanelVisibilityChanged(bool expanded);
|
||||||
@@ -129,11 +135,13 @@ private:
|
|||||||
QToolButton* m_exportEventsButton;
|
QToolButton* m_exportEventsButton;
|
||||||
QWidget* m_eventsPanel;
|
QWidget* m_eventsPanel;
|
||||||
QWidget* m_passwordPromptBar;
|
QWidget* m_passwordPromptBar;
|
||||||
|
QLabel* m_passwordPromptIcon;
|
||||||
QLabel* m_passwordPromptLabel;
|
QLabel* m_passwordPromptLabel;
|
||||||
QLineEdit* m_passwordPromptInput;
|
QLineEdit* m_passwordPromptInput;
|
||||||
QPushButton* m_passwordPromptConnectButton;
|
QPushButton* m_passwordPromptConnectButton;
|
||||||
QPushButton* m_passwordPromptCancelButton;
|
QPushButton* m_passwordPromptCancelButton;
|
||||||
std::function<void(std::optional<QString>)> m_passwordPromptCallback;
|
std::function<void(std::optional<QString>)> m_passwordPromptCallback;
|
||||||
|
bool m_awaitingUserInput;
|
||||||
enum class EventSeverity {
|
enum class EventSeverity {
|
||||||
Info,
|
Info,
|
||||||
Warning,
|
Warning,
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ QColor tabColorForState(SessionState state, const QPalette& palette)
|
|||||||
return palette.color(QPalette::WindowText);
|
return palette.color(QPalette::WindowText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Distinct from all four tabColorForState() colors -- a tab awaiting a
|
||||||
|
// username/password prompt response needs to stand out even from a
|
||||||
|
// tab that's merely "Connecting" (issue #22), including when it isn't
|
||||||
|
// the one currently in view. #6a1b9a (a much darker violet) was tried
|
||||||
|
// first and reported unreadable against a dark-theme tab bar -- its
|
||||||
|
// perceived luminance is well below the other three colors above, which
|
||||||
|
// this one is tuned to roughly match so it reads about as well as they
|
||||||
|
// do in both light and dark themes.
|
||||||
|
QColor awaitingInputTabColor()
|
||||||
|
{
|
||||||
|
return QColor(QStringLiteral("#ab47bc"));
|
||||||
|
}
|
||||||
|
|
||||||
QStringList terminalThemeNames()
|
QStringList terminalThemeNames()
|
||||||
{
|
{
|
||||||
return {QStringLiteral("Dark"), QStringLiteral("Light"), QStringLiteral("Solarized Dark")};
|
return {QStringLiteral("Dark"), QStringLiteral("Light"), QStringLiteral("Solarized Dark")};
|
||||||
@@ -57,6 +70,7 @@ SessionWindow::SessionWindow(QWidget* parent)
|
|||||||
QWidget* tab = m_tabs->widget(index);
|
QWidget* tab = m_tabs->widget(index);
|
||||||
if (auto* sessionTab = qobject_cast<SessionTab*>(tab)) {
|
if (auto* sessionTab = qobject_cast<SessionTab*>(tab)) {
|
||||||
sessionTab->disconnectSession();
|
sessionTab->disconnectSession();
|
||||||
|
m_tabStates.remove(sessionTab);
|
||||||
}
|
}
|
||||||
m_tabs->removeTab(index);
|
m_tabs->removeTab(index);
|
||||||
delete tab;
|
delete tab;
|
||||||
@@ -261,6 +275,7 @@ void SessionWindow::addSessionTab(const Profile& profile)
|
|||||||
} else {
|
} else {
|
||||||
setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name));
|
setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name));
|
||||||
}
|
}
|
||||||
|
m_tabStates.insert(tab, SessionState::Disconnected);
|
||||||
m_tabs->tabBar()->setTabTextColor(
|
m_tabs->tabBar()->setTabTextColor(
|
||||||
index, tabColorForState(SessionState::Disconnected, m_tabs->palette()));
|
index, tabColorForState(SessionState::Disconnected, m_tabs->palette()));
|
||||||
|
|
||||||
@@ -272,6 +287,13 @@ void SessionWindow::addSessionTab(const Profile& profile)
|
|||||||
&SessionTab::tabStateChanged,
|
&SessionTab::tabStateChanged,
|
||||||
this,
|
this,
|
||||||
[this, tab](SessionState state) {
|
[this, tab](SessionState state) {
|
||||||
|
m_tabStates.insert(tab, state);
|
||||||
|
if (tab->awaitingUserInput()) {
|
||||||
|
// Keep the "needs input" color on top -- it'll be
|
||||||
|
// restored to reflect this state once the prompt
|
||||||
|
// resolves (see awaitingUserInputChanged below).
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int i = 0; i < m_tabs->count(); ++i) {
|
for (int i = 0; i < m_tabs->count(); ++i) {
|
||||||
if (m_tabs->widget(i) == tab) {
|
if (m_tabs->widget(i) == tab) {
|
||||||
m_tabs->tabBar()->setTabTextColor(
|
m_tabs->tabBar()->setTabTextColor(
|
||||||
@@ -280,6 +302,22 @@ void SessionWindow::addSessionTab(const Profile& profile)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
connect(tab,
|
||||||
|
&SessionTab::awaitingUserInputChanged,
|
||||||
|
this,
|
||||||
|
[this, tab](bool waiting) {
|
||||||
|
for (int i = 0; i < m_tabs->count(); ++i) {
|
||||||
|
if (m_tabs->widget(i) != tab) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QColor color = waiting
|
||||||
|
? awaitingInputTabColor()
|
||||||
|
: tabColorForState(m_tabStates.value(tab, SessionState::Disconnected),
|
||||||
|
m_tabs->palette());
|
||||||
|
m_tabs->tabBar()->setTabTextColor(i, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
connect(tab,
|
connect(tab,
|
||||||
&SessionTab::terminalThemeChanged,
|
&SessionTab::terminalThemeChanged,
|
||||||
this,
|
this,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "profile_repository.h"
|
#include "profile_repository.h"
|
||||||
#include "session_tab.h"
|
#include "session_tab.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
||||||
class QTabWidget;
|
class QTabWidget;
|
||||||
@@ -21,6 +22,10 @@ private:
|
|||||||
QTabWidget* m_tabs;
|
QTabWidget* m_tabs;
|
||||||
ProfilesWindow* m_profilesWidget;
|
ProfilesWindow* m_profilesWidget;
|
||||||
SessionUiPreferences m_preferences;
|
SessionUiPreferences m_preferences;
|
||||||
|
// Last known connection state per tab, so the tab color can be
|
||||||
|
// restored correctly once an awaitingUserInputChanged(false) fires
|
||||||
|
// (that signal is orthogonal to SessionState -- see session_tab.h).
|
||||||
|
QHash<SessionTab*, SessionState> m_tabStates;
|
||||||
|
|
||||||
void addSessionTab(const Profile& profile);
|
void addSessionTab(const Profile& profile);
|
||||||
void updateTabTitle(SessionTab* tab, const QString& title);
|
void updateTabTitle(SessionTab* tab, const QString& title);
|
||||||
|
|||||||
Reference in New Issue
Block a user