Skip to content

Commit b6613b5

Browse files
committed
Fix missing auth banner in tailssh
1 parent 90f2460 commit b6613b5

3 files changed

Lines changed: 60 additions & 0 deletions

File tree

app/src/main/java/io/nekohasekai/sfa/compose/screen/tools/TailscaleSSHTerminalScreen.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize
1212
import androidx.compose.foundation.layout.fillMaxWidth
1313
import androidx.compose.foundation.layout.imePadding
1414
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.widthIn
16+
import androidx.compose.foundation.shape.RoundedCornerShape
1517
import androidx.compose.material.icons.Icons
1618
import androidx.compose.material.icons.automirrored.filled.ArrowBack
1719
import androidx.compose.material.icons.filled.Add
@@ -27,6 +29,7 @@ import androidx.compose.material3.Icon
2729
import androidx.compose.material3.IconButton
2830
import androidx.compose.material3.MaterialTheme
2931
import androidx.compose.material3.RadioButton
32+
import androidx.compose.material3.Surface
3033
import androidx.compose.material3.Text
3134
import androidx.compose.material3.TopAppBar
3235
import androidx.compose.runtime.Composable
@@ -39,9 +42,16 @@ import androidx.compose.runtime.remember
3942
import androidx.compose.runtime.setValue
4043
import androidx.compose.ui.Alignment
4144
import androidx.compose.ui.Modifier
45+
import androidx.compose.ui.graphics.Color
4246
import androidx.compose.ui.platform.LocalContext
4347
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
4448
import androidx.compose.ui.res.stringResource
49+
import androidx.compose.ui.text.LinkAnnotation
50+
import androidx.compose.ui.text.SpanStyle
51+
import androidx.compose.ui.text.TextLinkStyles
52+
import androidx.compose.ui.text.buildAnnotatedString
53+
import androidx.compose.ui.text.style.TextDecoration
54+
import androidx.compose.ui.text.withLink
4555
import androidx.compose.ui.unit.dp
4656
import androidx.compose.ui.viewinterop.AndroidView
4757
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -298,6 +308,24 @@ fun TailscaleSSHTerminalScreen(
298308
color = MaterialTheme.colorScheme.onSurface,
299309
modifier = Modifier.padding(top = 16.dp),
300310
)
311+
val banner = activeSession.terminalSession.authBanner
312+
if (!banner.isNullOrBlank()) {
313+
val linkColor = MaterialTheme.colorScheme.primary
314+
Surface(
315+
color = MaterialTheme.colorScheme.surfaceVariant,
316+
shape = RoundedCornerShape(10.dp),
317+
modifier = Modifier
318+
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
319+
.widthIn(max = 480.dp),
320+
) {
321+
Text(
322+
remember(banner, linkColor) { bannerAnnotatedString(banner, linkColor) },
323+
style = MaterialTheme.typography.bodyMedium,
324+
color = MaterialTheme.colorScheme.onSurfaceVariant,
325+
modifier = Modifier.padding(12.dp),
326+
)
327+
}
328+
}
301329
}
302330
}
303331
}
@@ -331,6 +359,30 @@ private class TerminalViewRef {
331359
var view: TerminalView? = null
332360
}
333361

362+
private val bannerUrlRegex = Regex("""https?://\S+""")
363+
364+
private fun bannerAnnotatedString(text: String, linkColor: Color) = buildAnnotatedString {
365+
var lastIndex = 0
366+
for (match in bannerUrlRegex.findAll(text)) {
367+
if (match.range.first > lastIndex) {
368+
append(text.substring(lastIndex, match.range.first))
369+
}
370+
val url = match.value
371+
withLink(
372+
LinkAnnotation.Url(
373+
url,
374+
TextLinkStyles(SpanStyle(color = linkColor, textDecoration = TextDecoration.Underline)),
375+
),
376+
) {
377+
append(url)
378+
}
379+
lastIndex = match.range.last + 1
380+
}
381+
if (lastIndex < text.length) {
382+
append(text.substring(lastIndex))
383+
}
384+
}
385+
334386
private fun resolveTypeface(fontFamily: String, customFontPath: String): Typeface? {
335387
if (customFontPath.isNotBlank()) {
336388
val typeface = ImportedFontStore.loadTypeface(customFontPath)

app/src/main/java/io/nekohasekai/sfa/compose/screen/tools/TailscaleSSHTerminalViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class TailscaleSSHTerminalViewModel : BaseViewModel<TailscaleSSHTerminalState, N
4545
}
4646

4747
override fun onAuthBanner(message: String) {
48+
updateState { copy(sessions = sessions.toList(), version = version + 1) }
4849
}
4950
})
5051

app/src/main/java/io/nekohasekai/sfa/terminal/TailscaleSSHTerminalSession.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public interface PhaseCallback {
2626
private TailscaleSSHSession sshSession;
2727
private PhaseCallback phaseCallback;
2828
private volatile Phase phase = Phase.CONNECTING;
29+
private volatile String authBanner;
2930
private int exitCode;
3031
private String exitSignal;
3132
private String exitErrorMessage;
@@ -56,6 +57,10 @@ public Phase getPhase() {
5657
return phase;
5758
}
5859

60+
public String getAuthBanner() {
61+
return authBanner;
62+
}
63+
5964
public int getSSHExitCode() {
6065
return exitCode;
6166
}
@@ -125,6 +130,7 @@ public void feedOutput(byte[] data) {
125130

126131
public void onReady() {
127132
phase = Phase.RUNNING;
133+
authBanner = null;
128134
mainHandler.post(
129135
() -> {
130136
if (phaseCallback != null) {
@@ -134,6 +140,7 @@ public void onReady() {
134140
}
135141

136142
public void onAuthBanner(String message) {
143+
authBanner = message;
137144
mainHandler.post(
138145
() -> {
139146
if (phaseCallback != null) {

0 commit comments

Comments
 (0)