1
1
package main
2
2
3
+ // This example demonstrates how to use a custom Lip Gloss renderer with Wish,
4
+ // a package for building custom SSH servers.
5
+ //
6
+ // The big advantage to using custom renderers here is that we can accurately
7
+ // detect the background color and color profile for each client and render
8
+ // against that accordingly.
9
+ //
10
+ // For details on wish see: https://github.com/charmbracelet/wish/
11
+
3
12
import (
4
13
"fmt"
5
14
"log"
@@ -14,6 +23,41 @@ import (
14
23
"github.com/muesli/termenv"
15
24
)
16
25
26
+ // Available styles.
27
+ type styles struct {
28
+ bold lipgloss.Style
29
+ faint lipgloss.Style
30
+ italic lipgloss.Style
31
+ underline lipgloss.Style
32
+ strikethrough lipgloss.Style
33
+ red lipgloss.Style
34
+ green lipgloss.Style
35
+ yellow lipgloss.Style
36
+ blue lipgloss.Style
37
+ magenta lipgloss.Style
38
+ cyan lipgloss.Style
39
+ gray lipgloss.Style
40
+ }
41
+
42
+ // Create new styles against a given renderer.
43
+ func makeStyles (r * lipgloss.Renderer ) styles {
44
+ return styles {
45
+ bold : r .NewStyle ().SetString ("bold" ).Bold (true ),
46
+ faint : r .NewStyle ().SetString ("faint" ).Faint (true ),
47
+ italic : r .NewStyle ().SetString ("italic" ).Italic (true ),
48
+ underline : r .NewStyle ().SetString ("underline" ).Underline (true ),
49
+ strikethrough : r .NewStyle ().SetString ("strikethrough" ).Strikethrough (true ),
50
+ red : r .NewStyle ().SetString ("red" ).Foreground (lipgloss .Color ("#E88388" )),
51
+ green : r .NewStyle ().SetString ("green" ).Foreground (lipgloss .Color ("#A8CC8C" )),
52
+ yellow : r .NewStyle ().SetString ("yellow" ).Foreground (lipgloss .Color ("#DBAB79" )),
53
+ blue : r .NewStyle ().SetString ("blue" ).Foreground (lipgloss .Color ("#71BEF2" )),
54
+ magenta : r .NewStyle ().SetString ("magenta" ).Foreground (lipgloss .Color ("#D290E4" )),
55
+ cyan : r .NewStyle ().SetString ("cyan" ).Foreground (lipgloss .Color ("#66C2CD" )),
56
+ gray : r .NewStyle ().SetString ("gray" ).Foreground (lipgloss .Color ("#B9BFCA" )),
57
+ }
58
+ }
59
+
60
+ // Bridge Wish and Termenv so we can query for a user's terminal capabilities.
17
61
type sshOutput struct {
18
62
ssh.Session
19
63
tty * os.File
@@ -23,6 +67,10 @@ func (s *sshOutput) Write(p []byte) (int, error) {
23
67
return s .Session .Write (p )
24
68
}
25
69
70
+ func (s * sshOutput ) Read (p []byte ) (int , error ) {
71
+ return s .Session .Read (p )
72
+ }
73
+
26
74
func (s * sshOutput ) Fd () uintptr {
27
75
return s .tty .Fd ()
28
76
}
@@ -44,86 +92,104 @@ func (s *sshEnviron) Environ() []string {
44
92
return s .environ
45
93
}
46
94
47
- func outputFromSession (s ssh.Session ) * termenv.Output {
48
- sshPty , _ , _ := s .Pty ()
95
+ // Create a termenv.Output from the session.
96
+ func outputFromSession (sess ssh.Session ) * termenv.Output {
97
+ sshPty , _ , _ := sess .Pty ()
49
98
_ , tty , err := pty .Open ()
50
99
if err != nil {
51
- panic (err )
100
+ log . Fatal (err )
52
101
}
53
102
o := & sshOutput {
54
- Session : s ,
103
+ Session : sess ,
55
104
tty : tty ,
56
105
}
57
- environ := s .Environ ()
106
+ environ := sess .Environ ()
58
107
environ = append (environ , fmt .Sprintf ("TERM=%s" , sshPty .Term ))
59
- e := & sshEnviron {
60
- environ : environ ,
108
+ e := & sshEnviron {environ : environ }
109
+ // We need to use unsafe mode here because the ssh session is not running
110
+ // locally and we already know that the session is a TTY.
111
+ return termenv .NewOutput (o , termenv .WithUnsafe (), termenv .WithEnvironment (e ))
112
+ }
113
+
114
+ // Handle SSH requests.
115
+ func handler (next ssh.Handler ) ssh.Handler {
116
+ return func (sess ssh.Session ) {
117
+ // Get client's output.
118
+ clientOutput := outputFromSession (sess )
119
+
120
+ pty , _ , active := sess .Pty ()
121
+ if ! active {
122
+ next (sess )
123
+ return
124
+ }
125
+ width := pty .Window .Width
126
+
127
+ // Initialize new renderer for the client.
128
+ renderer := lipgloss .NewRenderer (sess )
129
+ renderer .SetOutput (clientOutput )
130
+
131
+ // Initialize new styles against the renderer.
132
+ styles := makeStyles (renderer )
133
+
134
+ str := strings.Builder {}
135
+
136
+ fmt .Fprintf (& str , "\n \n %s %s %s %s %s" ,
137
+ styles .bold ,
138
+ styles .faint ,
139
+ styles .italic ,
140
+ styles .underline ,
141
+ styles .strikethrough ,
142
+ )
143
+
144
+ fmt .Fprintf (& str , "\n %s %s %s %s %s %s %s" ,
145
+ styles .red ,
146
+ styles .green ,
147
+ styles .yellow ,
148
+ styles .blue ,
149
+ styles .magenta ,
150
+ styles .cyan ,
151
+ styles .gray ,
152
+ )
153
+
154
+ fmt .Fprintf (& str , "\n %s %s %s %s %s %s %s\n \n " ,
155
+ styles .red ,
156
+ styles .green ,
157
+ styles .yellow ,
158
+ styles .blue ,
159
+ styles .magenta ,
160
+ styles .cyan ,
161
+ styles .gray ,
162
+ )
163
+
164
+ fmt .Fprintf (& str , "%s %t %s\n \n " , styles .bold .Copy ().UnsetString ().Render ("Has dark background?" ),
165
+ renderer .HasDarkBackground (),
166
+ renderer .Output ().BackgroundColor ())
167
+
168
+ block := renderer .Place (width ,
169
+ lipgloss .Height (str .String ()), lipgloss .Center , lipgloss .Center , str .String (),
170
+ lipgloss .WithWhitespaceChars ("/" ),
171
+ lipgloss .WithWhitespaceForeground (lipgloss.AdaptiveColor {Light : "250" , Dark : "236" }),
172
+ )
173
+
174
+ // Render to client.
175
+ wish .WriteString (sess , block )
176
+
177
+ next (sess )
61
178
}
62
- return termenv .NewOutput (o , termenv .WithEnvironment (e ))
63
179
}
64
180
65
181
func main () {
66
- addr := ": 3456"
182
+ port := 3456
67
183
s , err := wish .NewServer (
68
- wish .WithAddress (addr ),
184
+ wish .WithAddress (fmt . Sprintf ( ":%d" , port ) ),
69
185
wish .WithHostKeyPath ("ssh_example" ),
70
- wish .WithMiddleware (
71
- func (sh ssh.Handler ) ssh.Handler {
72
- return func (s ssh.Session ) {
73
- output := outputFromSession (s )
74
- pty , _ , active := s .Pty ()
75
- if ! active {
76
- sh (s )
77
- return
78
- }
79
- w , _ := pty .Window .Width , pty .Window .Height
80
-
81
- renderer := lipgloss .NewRenderer (lipgloss .WithTermenvOutput (output ),
82
- lipgloss .WithColorProfile (termenv .TrueColor ))
83
- str := strings.Builder {}
84
- fmt .Fprintf (& str , "\n %s %s %s %s %s" ,
85
- renderer .NewStyle ().SetString ("bold" ).Bold (true ),
86
- renderer .NewStyle ().SetString ("faint" ).Faint (true ),
87
- renderer .NewStyle ().SetString ("italic" ).Italic (true ),
88
- renderer .NewStyle ().SetString ("underline" ).Underline (true ),
89
- renderer .NewStyle ().SetString ("crossout" ).Strikethrough (true ),
90
- )
91
-
92
- fmt .Fprintf (& str , "\n %s %s %s %s %s %s %s" ,
93
- renderer .NewStyle ().SetString ("red" ).Foreground (lipgloss .Color ("#E88388" )),
94
- renderer .NewStyle ().SetString ("green" ).Foreground (lipgloss .Color ("#A8CC8C" )),
95
- renderer .NewStyle ().SetString ("yellow" ).Foreground (lipgloss .Color ("#DBAB79" )),
96
- renderer .NewStyle ().SetString ("blue" ).Foreground (lipgloss .Color ("#71BEF2" )),
97
- renderer .NewStyle ().SetString ("magenta" ).Foreground (lipgloss .Color ("#D290E4" )),
98
- renderer .NewStyle ().SetString ("cyan" ).Foreground (lipgloss .Color ("#66C2CD" )),
99
- renderer .NewStyle ().SetString ("gray" ).Foreground (lipgloss .Color ("#B9BFCA" )),
100
- )
101
-
102
- fmt .Fprintf (& str , "\n %s %s %s %s %s %s %s\n \n " ,
103
- renderer .NewStyle ().SetString ("red" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#E88388" )),
104
- renderer .NewStyle ().SetString ("green" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#A8CC8C" )),
105
- renderer .NewStyle ().SetString ("yellow" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#DBAB79" )),
106
- renderer .NewStyle ().SetString ("blue" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#71BEF2" )),
107
- renderer .NewStyle ().SetString ("magenta" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#D290E4" )),
108
- renderer .NewStyle ().SetString ("cyan" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#66C2CD" )),
109
- renderer .NewStyle ().SetString ("gray" ).Foreground (lipgloss .Color ("0" )).Background (lipgloss .Color ("#B9BFCA" )),
110
- )
111
-
112
- fmt .Fprintf (& str , "%s %t\n " , renderer .NewStyle ().SetString ("Has dark background?" ).Bold (true ), renderer .HasDarkBackground ())
113
- fmt .Fprintln (& str )
114
-
115
- wish .WriteString (s , renderer .Place (w , lipgloss .Height (str .String ()), lipgloss .Center , lipgloss .Center , str .String ()))
116
-
117
- sh (s )
118
- }
119
- },
120
- lm .Middleware (),
121
- ),
186
+ wish .WithMiddleware (handler , lm .Middleware ()),
122
187
)
123
188
if err != nil {
124
189
log .Fatal (err )
125
190
}
126
- log .Printf ("Listening on %s" , addr )
191
+ log .Printf ("SSH server listening on port %d" , port )
192
+ log .Printf ("To connect from your local machine run: ssh localhost -p %d" , port )
127
193
if err := s .ListenAndServe (); err != nil {
128
194
log .Fatal (err )
129
195
}
0 commit comments