source: mainline/uspace/srv/hid/remcons/remcons.c@ 235bd1f

Last change on this file since 235bd1f was ca48672, checked in by Jiri Svoboda <jiri@…>, 8 days ago

loc_service_register() needs to take a port ID argument.

  • Property mode set to 100644
File size: 22.2 KB
RevLine 
[21a9869]1/*
[ca48672]2 * Copyright (c) 2025 Jiri Svoboda
[21a9869]3 * Copyright (c) 2012 Vojtech Horky
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup remcons
31 * @{
32 */
33/** @file
34 */
35
[5132379]36#include <adt/list.h>
[c23a1fe]37#include <as.h>
[21a9869]38#include <async.h>
39#include <errno.h>
[5d94b16c]40#include <io/con_srv.h>
41#include <stdio.h>
42#include <stdlib.h>
[21a9869]43#include <str_error.h>
44#include <loc.h>
45#include <io/keycode.h>
46#include <align.h>
47#include <fibril_synch.h>
48#include <task.h>
[fab2746]49#include <inet/addr.h>
50#include <inet/endpoint.h>
51#include <inet/tcp.h>
[21a9869]52#include <io/console.h>
53#include <inttypes.h>
[1d6dd2a]54#include <str.h>
[d3109ff]55#include <vt/vt100.h>
[3806317]56#include "telnet.h"
[30d4706]57#include "user.h"
[d3109ff]58#include "remcons.h"
[21a9869]59
60#define APP_GETTERM "/app/getterm"
[03e0a244]61#define APP_SHELL "/app/bdsh"
[5576358]62
[6a753a9c]63#define DEF_PORT 2223
64
[3806317]65/** Telnet commands to force character mode
66 * (redundant to be on the safe side).
67 * See
[787b65b]68 * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
[3806317]69 * for discussion.
70 */
71static const telnet_cmd_t telnet_force_character_mode_command[] = {
72 TELNET_IAC, TELNET_WILL, TELNET_ECHO,
73 TELNET_IAC, TELNET_WILL, TELNET_SUPPRESS_GO_AHEAD,
74 TELNET_IAC, TELNET_WONT, TELNET_LINEMODE
75};
[3123d2a]76
[3806317]77static const size_t telnet_force_character_mode_command_count =
78 sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
79
[b7fd2a0]80static errno_t remcons_open(con_srvs_t *, con_srv_t *);
81static errno_t remcons_close(con_srv_t *);
[d3109ff]82static errno_t remcons_read(con_srv_t *, void *, size_t, size_t *);
[b7fd2a0]83static errno_t remcons_write(con_srv_t *, void *, size_t, size_t *);
[5d94b16c]84static void remcons_sync(con_srv_t *);
85static void remcons_clear(con_srv_t *);
86static void remcons_set_pos(con_srv_t *, sysarg_t col, sysarg_t row);
[b7fd2a0]87static errno_t remcons_get_pos(con_srv_t *, sysarg_t *, sysarg_t *);
88static errno_t remcons_get_size(con_srv_t *, sysarg_t *, sysarg_t *);
89static errno_t remcons_get_color_cap(con_srv_t *, console_caps_t *);
[d3109ff]90static void remcons_set_style(con_srv_t *, console_style_t);
91static void remcons_set_color(con_srv_t *, console_color_t,
92 console_color_t, console_color_attr_t);
93static void remcons_set_color(con_srv_t *, console_color_t,
94 console_color_t, console_color_attr_t);
95static void remcons_set_rgb_color(con_srv_t *, pixel_t, pixel_t);
96static void remcons_cursor_visibility(con_srv_t *, bool);
[9d5cea6]97static errno_t remcons_set_caption(con_srv_t *, const char *);
[b7fd2a0]98static errno_t remcons_get_event(con_srv_t *, cons_event_t *);
[c23a1fe]99static errno_t remcons_map(con_srv_t *, sysarg_t, sysarg_t, charfield_t **);
100static void remcons_unmap(con_srv_t *);
101static void remcons_update(con_srv_t *, sysarg_t, sysarg_t, sysarg_t,
102 sysarg_t);
[5d94b16c]103
104static con_ops_t con_ops = {
105 .open = remcons_open,
106 .close = remcons_close,
[d3109ff]107 .read = remcons_read,
[5d94b16c]108 .write = remcons_write,
109 .sync = remcons_sync,
110 .clear = remcons_clear,
111 .set_pos = remcons_set_pos,
112 .get_pos = remcons_get_pos,
113 .get_size = remcons_get_size,
114 .get_color_cap = remcons_get_color_cap,
[d3109ff]115 .set_style = remcons_set_style,
116 .set_color = remcons_set_color,
117 .set_rgb_color = remcons_set_rgb_color,
118 .set_cursor_visibility = remcons_cursor_visibility,
[9d5cea6]119 .set_caption = remcons_set_caption,
[c23a1fe]120 .get_event = remcons_get_event,
121 .map = remcons_map,
122 .unmap = remcons_unmap,
123 .update = remcons_update
[5d94b16c]124};
[21a9869]125
[d31c3ea]126static void remcons_vt_putchar(void *, char32_t);
127static void remcons_vt_cputs(void *, const char *);
128static void remcons_vt_flush(void *);
[6907f26]129static void remcons_vt_key(void *, keymod_t, keycode_t, char);
[5e0acaa]130static void remcons_vt_pos_event(void *, pos_event_t *);
[d31c3ea]131
132static vt100_cb_t remcons_vt_cb = {
133 .putuchar = remcons_vt_putchar,
134 .control_puts = remcons_vt_cputs,
[6907f26]135 .flush = remcons_vt_flush,
[5e0acaa]136 .key = remcons_vt_key,
137 .pos_event = remcons_vt_pos_event
[d31c3ea]138};
139
[fab2746]140static void remcons_new_conn(tcp_listener_t *lst, tcp_conn_t *conn);
141
142static tcp_listen_cb_t listen_cb = {
143 .new_conn = remcons_new_conn
144};
145
146static tcp_cb_t conn_cb = {
147 .connected = NULL
148};
149
[47d060d]150static void remcons_telnet_ws_update(void *, unsigned, unsigned);
151
152static telnet_cb_t remcons_telnet_cb = {
153 .ws_update = remcons_telnet_ws_update
154};
155
[4c6fd56]156static loc_srv_t *remcons_srv;
[6a753a9c]157static bool no_ctl;
158static bool no_rgb;
[4c6fd56]159
[5d94b16c]160static telnet_user_t *srv_to_user(con_srv_t *srv)
[21a9869]161{
[d3109ff]162 remcons_t *remcons = (remcons_t *)srv->srvs->sarg;
163 return remcons->user;
164}
165
166static remcons_t *srv_to_remcons(con_srv_t *srv)
167{
168 remcons_t *remcons = (remcons_t *)srv->srvs->sarg;
169 return remcons;
[5d94b16c]170}
[21a9869]171
[b7fd2a0]172static errno_t remcons_open(con_srvs_t *srvs, con_srv_t *srv)
[5d94b16c]173{
174 telnet_user_t *user = srv_to_user(srv);
[99c2e9f3]175
[5d94b16c]176 telnet_user_log(user, "New client connected (%p).", srv);
[21a9869]177
[5d94b16c]178 /* Force character mode. */
[fab2746]179 (void) tcp_conn_send(user->conn, (void *)telnet_force_character_mode_command,
180 telnet_force_character_mode_command_count);
[21a9869]181
[5d94b16c]182 return EOK;
183}
184
[b7fd2a0]185static errno_t remcons_close(con_srv_t *srv)
[5d94b16c]186{
187 telnet_user_t *user = srv_to_user(srv);
188
189 telnet_user_notify_client_disconnected(user);
190 telnet_user_log(user, "Client disconnected (%p).", srv);
191
192 return EOK;
193}
194
[d3109ff]195static errno_t remcons_read(con_srv_t *srv, void *data, size_t size,
196 size_t *nread)
197{
198 telnet_user_t *user = srv_to_user(srv);
199 errno_t rc;
200
201 rc = telnet_user_recv(user, data, size, nread);
202 if (rc != EOK)
203 return rc;
204
205 return EOK;
206}
207
[b7fd2a0]208static errno_t remcons_write(con_srv_t *srv, void *data, size_t size, size_t *nwritten)
[5d94b16c]209{
[c23a1fe]210 remcons_t *remcons = srv_to_remcons(srv);
[b7fd2a0]211 errno_t rc;
[5d94b16c]212
[c23a1fe]213 rc = telnet_user_send_data(remcons->user, data, size);
214 if (rc != EOK)
215 return rc;
216
217 rc = telnet_user_flush(remcons->user);
[5d94b16c]218 if (rc != EOK)
219 return rc;
220
[c8211849]221 *nwritten = size;
222 return EOK;
[5d94b16c]223}
224
225static void remcons_sync(con_srv_t *srv)
226{
227 (void) srv;
228}
229
230static void remcons_clear(con_srv_t *srv)
231{
[d3109ff]232 remcons_t *remcons = srv_to_remcons(srv);
233
234 if (remcons->enable_ctl) {
235 vt100_cls(remcons->vt);
236 vt100_set_pos(remcons->vt, 0, 0);
237 remcons->user->cursor_x = 0;
238 remcons->user->cursor_y = 0;
239 }
[5d94b16c]240}
241
242static void remcons_set_pos(con_srv_t *srv, sysarg_t col, sysarg_t row)
243{
[d3109ff]244 remcons_t *remcons = srv_to_remcons(srv);
[5d94b16c]245 telnet_user_t *user = srv_to_user(srv);
246
[d3109ff]247 if (remcons->enable_ctl) {
248 vt100_set_pos(remcons->vt, col, row);
249 remcons->user->cursor_x = col;
250 remcons->user->cursor_y = row;
[c23a1fe]251 (void)telnet_user_flush(remcons->user);
[d3109ff]252 } else {
253 telnet_user_update_cursor_x(user, col);
254 }
[5d94b16c]255}
256
[b7fd2a0]257static errno_t remcons_get_pos(con_srv_t *srv, sysarg_t *col, sysarg_t *row)
[5d94b16c]258{
259 telnet_user_t *user = srv_to_user(srv);
260
261 *col = user->cursor_x;
[d3109ff]262 *row = user->cursor_y;
[5d94b16c]263
264 return EOK;
265}
266
[b7fd2a0]267static errno_t remcons_get_size(con_srv_t *srv, sysarg_t *cols, sysarg_t *rows)
[5d94b16c]268{
[d3109ff]269 remcons_t *remcons = srv_to_remcons(srv);
270
271 if (remcons->enable_ctl) {
[47d060d]272 *cols = remcons->vt->cols;
273 *rows = remcons->vt->rows;
[d3109ff]274 } else {
275 *cols = 100;
276 *rows = 1;
277 }
[5d94b16c]278
279 return EOK;
280}
281
[b7fd2a0]282static errno_t remcons_get_color_cap(con_srv_t *srv, console_caps_t *ccaps)
[5d94b16c]283{
[d3109ff]284 remcons_t *remcons = srv_to_remcons(srv);
285
[09f41d3]286 *ccaps = 0;
287
288 if (remcons->enable_ctl) {
289 *ccaps |= CONSOLE_CAP_CURSORCTL | CONSOLE_CAP_STYLE |
290 CONSOLE_CAP_INDEXED;
291 }
292
293 if (remcons->enable_rgb)
294 *ccaps |= CONSOLE_CAP_RGB;
[5d94b16c]295
296 return EOK;
297}
298
[d3109ff]299static void remcons_set_style(con_srv_t *srv, console_style_t style)
300{
301 remcons_t *remcons = srv_to_remcons(srv);
302 char_attrs_t attrs;
303
304 if (remcons->enable_ctl) {
305 attrs.type = CHAR_ATTR_STYLE;
306 attrs.val.style = style;
307 vt100_set_attr(remcons->vt, attrs);
308 }
309}
310
311static void remcons_set_color(con_srv_t *srv, console_color_t bgcolor,
312 console_color_t fgcolor, console_color_attr_t flags)
313{
314 remcons_t *remcons = srv_to_remcons(srv);
315 char_attrs_t attrs;
316
317 if (remcons->enable_ctl) {
318 attrs.type = CHAR_ATTR_INDEX;
319 attrs.val.index.bgcolor = bgcolor;
320 attrs.val.index.fgcolor = fgcolor;
321 attrs.val.index.attr = flags;
322 vt100_set_attr(remcons->vt, attrs);
323 }
324}
325
326static void remcons_set_rgb_color(con_srv_t *srv, pixel_t bgcolor,
327 pixel_t fgcolor)
328{
329 remcons_t *remcons = srv_to_remcons(srv);
330 char_attrs_t attrs;
331
332 if (remcons->enable_ctl) {
333 attrs.type = CHAR_ATTR_RGB;
334 attrs.val.rgb.bgcolor = bgcolor;
335 attrs.val.rgb.fgcolor = fgcolor;
336 vt100_set_attr(remcons->vt, attrs);
337 }
338}
339
340static void remcons_cursor_visibility(con_srv_t *srv, bool visible)
341{
342 remcons_t *remcons = srv_to_remcons(srv);
343
[c23a1fe]344 if (remcons->enable_ctl) {
345 if (!remcons->curs_visible && visible) {
346 vt100_set_pos(remcons->vt, remcons->user->cursor_x,
347 remcons->user->cursor_y);
348 }
[d3109ff]349 vt100_cursor_visibility(remcons->vt, visible);
[c23a1fe]350 }
351
352 remcons->curs_visible = visible;
[d3109ff]353}
354
[9d5cea6]355static errno_t remcons_set_caption(con_srv_t *srv, const char *caption)
356{
357 remcons_t *remcons = srv_to_remcons(srv);
358
359 if (remcons->enable_ctl) {
360 vt100_set_title(remcons->vt, caption);
361 }
362
363 return EOK;
364}
365
[6907f26]366/** Creates new keyboard event from given char.
367 *
368 * @param type Event type (press / release).
369 * @param c Pressed character.
370 */
[b2c9e42c]371static remcons_event_t *new_kbd_event(kbd_event_type_t type, keymod_t mods,
[6907f26]372 keycode_t key, char c)
373{
[b2c9e42c]374 remcons_event_t *event = malloc(sizeof(remcons_event_t));
[47d060d]375 if (event == NULL) {
376 fprintf(stderr, "Out of memory.\n");
377 return NULL;
378 }
379
380 link_initialize(&event->link);
381 event->cev.type = CEV_KEY;
382 event->cev.ev.key.type = type;
383 event->cev.ev.key.mods = mods;
384 event->cev.ev.key.key = key;
385 event->cev.ev.key.c = c;
386
387 return event;
388}
389
[5e0acaa]390/** Creates new position event.
391 *
392 * @param ev Position event.
393 * @param c Pressed character.
394 */
395static remcons_event_t *new_pos_event(pos_event_t *ev)
396{
397 remcons_event_t *event = malloc(sizeof(remcons_event_t));
398 if (event == NULL) {
399 fprintf(stderr, "Out of memory.\n");
400 return NULL;
401 }
402
403 link_initialize(&event->link);
404 event->cev.type = CEV_POS;
405 event->cev.ev.pos = *ev;
406
407 return event;
408}
409
[47d060d]410/** Creates new console resize event.
411 */
412static remcons_event_t *new_resize_event(void)
413{
414 remcons_event_t *event = malloc(sizeof(remcons_event_t));
415 if (event == NULL) {
416 fprintf(stderr, "Out of memory.\n");
417 return NULL;
418 }
[6907f26]419
420 link_initialize(&event->link);
[47d060d]421 event->cev.type = CEV_RESIZE;
[6907f26]422
423 return event;
424}
425
[b7fd2a0]426static errno_t remcons_get_event(con_srv_t *srv, cons_event_t *event)
[5d94b16c]427{
[6907f26]428 remcons_t *remcons = srv_to_remcons(srv);
[5d94b16c]429 telnet_user_t *user = srv_to_user(srv);
[6907f26]430 size_t nread;
[5d94b16c]431
[5132379]432 while (list_empty(&remcons->in_events)) {
[6907f26]433 char next_byte = 0;
434
435 errno_t rc = telnet_user_recv(user, &next_byte, 1,
436 &nread);
437 if (rc != EOK)
438 return rc;
439
440 vt100_rcvd_char(remcons->vt, next_byte);
[21a9869]441 }
[5d94b16c]442
[5132379]443 link_t *link = list_first(&remcons->in_events);
444 list_remove(link);
445
[b2c9e42c]446 remcons_event_t *tmp = list_get_instance(link, remcons_event_t, link);
[6907f26]447
[47d060d]448 *event = tmp->cev;
[6907f26]449 free(tmp);
[902f0906]450
[5d94b16c]451 return EOK;
[21a9869]452}
453
[c23a1fe]454static errno_t remcons_map(con_srv_t *srv, sysarg_t cols, sysarg_t rows,
455 charfield_t **rbuf)
456{
457 remcons_t *remcons = srv_to_remcons(srv);
458 void *buf;
459
460 if (!remcons->enable_ctl)
461 return ENOTSUP;
462
463 if (remcons->ubuf != NULL)
464 return EBUSY;
465
466 buf = as_area_create(AS_AREA_ANY, cols * rows * sizeof(charfield_t),
467 AS_AREA_READ | AS_AREA_WRITE | AS_AREA_CACHEABLE, AS_AREA_UNPAGED);
468 if (buf == AS_MAP_FAILED)
469 return ENOMEM;
470
471 remcons->ucols = cols;
472 remcons->urows = rows;
473 remcons->ubuf = buf;
474
475 *rbuf = buf;
476 return EOK;
477
478}
479
480static void remcons_unmap(con_srv_t *srv)
481{
482 remcons_t *remcons = srv_to_remcons(srv);
483 void *buf;
484
485 buf = remcons->ubuf;
486 remcons->ubuf = NULL;
487
488 if (buf != NULL)
489 as_area_destroy(buf);
490}
491
492static void remcons_update(con_srv_t *srv, sysarg_t c0, sysarg_t r0,
493 sysarg_t c1, sysarg_t r1)
494{
495 remcons_t *remcons = srv_to_remcons(srv);
496 charfield_t *ch;
497 sysarg_t col, row;
498 sysarg_t old_x, old_y;
499
500 if (remcons->ubuf == NULL)
501 return;
502
503 /* Make sure we have meaningful coordinates, within bounds */
504
505 if (c1 > remcons->ucols)
506 c1 = remcons->ucols;
507 if (c1 > remcons->user->cols)
508 c1 = remcons->user->cols;
509 if (c0 >= c1)
510 return;
511
512 if (r1 > remcons->urows)
513 r1 = remcons->urows;
514 if (r1 > remcons->user->rows)
515 r1 = remcons->user->rows;
516 if (r0 >= r1)
517 return;
518
519 /* Update screen from user buffer */
520
521 old_x = remcons->user->cursor_x;
522 old_y = remcons->user->cursor_y;
523
524 if (remcons->curs_visible)
525 vt100_cursor_visibility(remcons->vt, false);
526
527 for (row = r0; row < r1; row++) {
528 for (col = c0; col < c1; col++) {
529 vt100_set_pos(remcons->vt, col, row);
530 ch = &remcons->ubuf[row * remcons->ucols + col];
531 vt100_set_attr(remcons->vt, ch->attrs);
532 vt100_putuchar(remcons->vt, ch->ch);
533 }
534 }
535
536 if (remcons->curs_visible) {
537 old_x = remcons->user->cursor_x = old_x;
538 remcons->user->cursor_y = old_y;
539 vt100_set_pos(remcons->vt, old_x, old_y);
540 vt100_cursor_visibility(remcons->vt, true);
541 }
542
543 /* Flush data */
544 (void)telnet_user_flush(remcons->user);
545}
546
[5923cf82]547/** Callback when client connects to a telnet terminal. */
[984a9ba]548static void client_connection(ipc_call_t *icall, void *arg)
[6f7cd5d]549{
[787b65b]550 /* Find the user. */
[fafb8e5]551 telnet_user_t *user = telnet_user_get_for_client_connection(ipc_get_arg2(icall));
[787b65b]552 if (user == NULL) {
[984a9ba]553 async_answer_0(icall, ENOENT);
[6f7cd5d]554 return;
555 }
556
[99c2e9f3]557 /* Handle messages. */
[984a9ba]558 con_conn(icall, &user->srvs);
[6f7cd5d]559}
560
[5923cf82]561/** Fibril for spawning the task running after user connects.
562 *
563 * @param arg Corresponding @c telnet_user_t structure.
564 */
[b7fd2a0]565static errno_t spawn_task_fibril(void *arg)
[21a9869]566{
[787b65b]567 telnet_user_t *user = arg;
[a35b458]568
[21a9869]569 task_id_t task;
[1c635d6]570 task_wait_t wait;
[b7fd2a0]571 errno_t rc = task_spawnl(&task, &wait, APP_GETTERM, APP_GETTERM, user->service_name,
[593e023]572 "/loc", "--msg", "--", APP_SHELL, NULL);
[21a9869]573 if (rc != EOK) {
[593e023]574 telnet_user_error(user, "Spawning `%s %s /loc --msg -- %s' "
575 "failed: %s.", APP_GETTERM, user->service_name, APP_SHELL,
576 str_error(rc));
[5f5d375]577 fibril_mutex_lock(&user->recv_lock);
[787b65b]578 user->task_finished = true;
[5d94b16c]579 user->srvs.aborted = true;
[787b65b]580 fibril_condvar_signal(&user->refcount_cv);
[5f5d375]581 fibril_mutex_unlock(&user->recv_lock);
[21a9869]582 return EOK;
583 }
584
[5f5d375]585 fibril_mutex_lock(&user->recv_lock);
[787b65b]586 user->task_id = task;
[5f5d375]587 fibril_mutex_unlock(&user->recv_lock);
[1870d81]588
[21a9869]589 task_exit_t task_exit;
590 int task_retval;
[1c635d6]591 task_wait(&wait, &task_exit, &task_retval);
[d545d03]592 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
593 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
594 task_retval);
[21a9869]595
[7c2bb2c]596 /* Announce destruction. */
[5f5d375]597 fibril_mutex_lock(&user->recv_lock);
[787b65b]598 user->task_finished = true;
[5d94b16c]599 user->srvs.aborted = true;
[787b65b]600 fibril_condvar_signal(&user->refcount_cv);
[5f5d375]601 fibril_mutex_unlock(&user->recv_lock);
[7c2bb2c]602
603 return EOK;
604}
605
[5923cf82]606/** Tell whether given user can be destroyed (has no active clients).
607 *
608 * @param user The telnet user in question.
609 */
[787b65b]610static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
[1870d81]611{
[787b65b]612 return user->task_finished && user->socket_closed &&
613 (user->locsrv_connection_count == 0);
[1870d81]614}
[7c2bb2c]615
[d3109ff]616static void remcons_vt_putchar(void *arg, char32_t c)
617{
618 remcons_t *remcons = (remcons_t *)arg;
619 char buf[STR_BOUNDS(1)];
620 size_t off;
621 errno_t rc;
622
623 (void)arg;
624
625 off = 0;
626 rc = chr_encode(c, buf, &off, sizeof(buf));
627 if (rc != EOK)
628 return;
629
630 (void)telnet_user_send_data(remcons->user, buf, off);
631}
632
633static void remcons_vt_cputs(void *arg, const char *str)
634{
635 remcons_t *remcons = (remcons_t *)arg;
636
[89e5c0c7]637 (void)telnet_user_send_raw(remcons->user, str, str_size(str));
[d3109ff]638}
639
640static void remcons_vt_flush(void *arg)
641{
642 remcons_t *remcons = (remcons_t *)arg;
[c23a1fe]643 (void)telnet_user_flush(remcons->user);
[d3109ff]644}
645
[6907f26]646static void remcons_vt_key(void *arg, keymod_t mods, keycode_t key, char c)
647{
648 remcons_t *remcons = (remcons_t *)arg;
649
[b2c9e42c]650 remcons_event_t *down = new_kbd_event(KEY_PRESS, mods, key, c);
[47d060d]651 if (down == NULL)
652 return;
653
[b2c9e42c]654 remcons_event_t *up = new_kbd_event(KEY_RELEASE, mods, key, c);
[47d060d]655 if (up == NULL) {
656 free(down);
657 return;
658 }
659
[5132379]660 list_append(&down->link, &remcons->in_events);
661 list_append(&up->link, &remcons->in_events);
[6907f26]662}
663
[5e0acaa]664static void remcons_vt_pos_event(void *arg, pos_event_t *ev)
665{
666 remcons_t *remcons = (remcons_t *)arg;
667
668 remcons_event_t *cev = new_pos_event(ev);
669 if (cev == NULL)
670 return;
671
672 list_append(&cev->link, &remcons->in_events);
673}
674
[47d060d]675/** Window size update callback.
676 *
677 * @param arg Argument (remcons_t *)
678 * @param cols New number of columns
679 * @param rows New number of rows
680 */
681static void remcons_telnet_ws_update(void *arg, unsigned cols, unsigned rows)
682{
683 remcons_t *remcons = (remcons_t *)arg;
684
685 vt100_resize(remcons->vt, cols, rows);
686 telnet_user_resize(remcons->user, cols, rows);
687
688 remcons_event_t *resize = new_resize_event();
689 if (resize == NULL)
690 return;
691
692 list_append(&resize->link, &remcons->in_events);
693}
694
[d6ff08a0]695/** Handle network connection.
[5923cf82]696 *
[d6ff08a0]697 * @param lst Listener
698 * @param conn Connection
[5923cf82]699 */
[d6ff08a0]700static void remcons_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
[7c2bb2c]701{
[d3109ff]702 char_attrs_t attrs;
[47d060d]703 remcons_t *remcons = NULL;
704 telnet_user_t *user = NULL;
705
706 remcons = calloc(1, sizeof(remcons_t));
707 if (remcons == NULL) {
708 fprintf(stderr, "Out of memory.\n");
709 goto error;
710 }
711
712 user = telnet_user_create(conn, &remcons_telnet_cb,
713 (void *)remcons);
714 if (user == NULL) {
715 fprintf(stderr, "Out of memory.\n");
716 goto error;
717 }
[d6ff08a0]718
[6a753a9c]719 remcons->enable_ctl = !no_ctl;
[09f41d3]720 remcons->enable_rgb = !no_ctl && !no_rgb;
[d3109ff]721 remcons->user = user;
[5132379]722 list_initialize(&remcons->in_events);
[d3109ff]723
724 if (remcons->enable_ctl) {
[c23a1fe]725 user->cols = 80;
[d3109ff]726 user->rows = 25;
727 } else {
[c23a1fe]728 user->cols = 100;
[d3109ff]729 user->rows = 1;
730 }
731
[c23a1fe]732 remcons->curs_visible = true;
733
[d31c3ea]734 remcons->vt = vt100_create((void *)remcons, 80, 25, &remcons_vt_cb);
[47d060d]735 if (remcons->vt == NULL) {
736 fprintf(stderr, "Error creating VT100 driver instance.\n");
737 goto error;
738 }
739
[09f41d3]740 remcons->vt->enable_rgb = remcons->enable_rgb;
[d3109ff]741
742 if (remcons->enable_ctl) {
743 attrs.type = CHAR_ATTR_STYLE;
744 attrs.val.style = STYLE_NORMAL;
745 vt100_set_sgr(remcons->vt, attrs);
746 vt100_cls(remcons->vt);
747 vt100_set_pos(remcons->vt, 0, 0);
[5e0acaa]748 vt100_set_button_reporting(remcons->vt, true);
[d3109ff]749 }
750
[d6ff08a0]751 con_srvs_init(&user->srvs);
752 user->srvs.ops = &con_ops;
[d3109ff]753 user->srvs.sarg = remcons;
[5be5396]754 user->srvs.abort_timeout = 1000000;
[d6ff08a0]755
756 telnet_user_add(user);
[7c2bb2c]757
[4c6fd56]758 errno_t rc = loc_service_register(remcons_srv, user->service_name,
[ca48672]759 fallback_port_id, &user->service_id);
[6f7cd5d]760 if (rc != EOK) {
[d545d03]761 telnet_user_error(user, "Unable to register %s with loc: %s.",
762 user->service_name, str_error(rc));
[47d060d]763 goto error;
[6f7cd5d]764 }
[d545d03]765
766 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
767 user->service_name, user->service_id);
[d6ff08a0]768
[787b65b]769 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
[47d060d]770 if (spawn_fibril == 0) {
771 fprintf(stderr, "Failed creating fibril.\n");
772 goto error;
773 }
[7c2bb2c]774 fibril_add_ready(spawn_fibril);
[d6ff08a0]775
[6f7cd5d]776 /* Wait for all clients to exit. */
[5f5d375]777 fibril_mutex_lock(&user->recv_lock);
[787b65b]778 while (!user_can_be_destroyed_no_lock(user)) {
779 if (user->task_finished) {
780 user->socket_closed = true;
[5d94b16c]781 user->srvs.aborted = true;
[787b65b]782 } else if (user->socket_closed) {
783 if (user->task_id != 0) {
784 task_kill(user->task_id);
[1870d81]785 }
786 }
[5be5396]787 fibril_condvar_wait_timeout(&user->refcount_cv,
788 &user->recv_lock, 1000000);
[6f7cd5d]789 }
[5f5d375]790 fibril_mutex_unlock(&user->recv_lock);
[d6ff08a0]791
[4c6fd56]792 rc = loc_service_unregister(remcons_srv, user->service_id);
[7c2bb2c]793 if (rc != EOK) {
[d545d03]794 telnet_user_error(user,
795 "Unable to unregister %s from loc: %s (ignored).",
796 user->service_name, str_error(rc));
[7c2bb2c]797 }
798
[d545d03]799 telnet_user_log(user, "Destroying...");
[5be5396]800
801 if (remcons->enable_ctl) {
802 /* Disable mouse tracking */
803 vt100_set_button_reporting(remcons->vt, false);
804
805 /* Reset all character attributes and clear screen */
806 vt100_sgr(remcons->vt, 0);
807 vt100_cls(remcons->vt);
808 vt100_set_pos(remcons->vt, 0, 0);
809
810 telnet_user_flush(user);
811 }
812
813 tcp_conn_send_fin(user->conn);
814 user->conn = NULL;
815
[787b65b]816 telnet_user_destroy(user);
[5be5396]817 vt100_destroy(remcons->vt);
818 free(remcons);
[47d060d]819 return;
820error:
821 if (user != NULL && user->service_id != 0)
822 loc_service_unregister(remcons_srv, user->service_id);
823 if (user != NULL)
824 free(user);
825 if (remcons != NULL && remcons->vt != NULL)
826 vt100_destroy(remcons->vt);
827 if (remcons != NULL)
828 free(remcons);
[fab2746]829}
830
[6a753a9c]831static void print_syntax(void)
832{
833 fprintf(stderr, "syntax: remcons [<options>]\n");
834 fprintf(stderr, "\t--no-ctl Disable all terminal control sequences\n");
835 fprintf(stderr, "\t--no-rgb Disable RGB colors\n");
836 fprintf(stderr, "\t--port <port> Listening port (default: %u)\n",
837 DEF_PORT);
838}
839
[21a9869]840int main(int argc, char *argv[])
841{
[b7fd2a0]842 errno_t rc;
[fab2746]843 tcp_listener_t *lst;
844 tcp_t *tcp;
845 inet_ep_t ep;
[6a753a9c]846 uint16_t port;
847 int i;
848
849 port = DEF_PORT;
850
851 i = 1;
852 while (i < argc) {
853 if (argv[i][0] == '-') {
854 if (str_cmp(argv[i], "--no-ctl") == 0) {
855 no_ctl = true;
856 } else if (str_cmp(argv[i], "--no-rgb") == 0) {
857 no_rgb = true;
858 } else if (str_cmp(argv[i], "--port") == 0) {
859 ++i;
860 if (i >= argc) {
861 fprintf(stderr, "Option argument "
862 "missing.\n");
863 print_syntax();
864 return EINVAL;
865 }
866 rc = str_uint16_t(argv[i], NULL, 10, true, &port);
867 if (rc != EOK) {
868 fprintf(stderr, "Invalid port number "
869 "'%s'.\n", argv[i]);
870 print_syntax();
871 return EINVAL;
872 }
873 } else {
874 fprintf(stderr, "Unknown option '%s'.\n",
875 argv[i]);
876 print_syntax();
877 return EINVAL;
878 }
879 } else {
880 fprintf(stderr, "Unexpected argument.\n");
881 print_syntax();
882 return EINVAL;
883 }
884
885 ++i;
886 }
[fab2746]887
[b688fd8]888 async_set_fallback_port_handler(client_connection, NULL);
[4c6fd56]889 rc = loc_server_register(NAME, &remcons_srv);
[3123d2a]890 if (rc != EOK) {
891 fprintf(stderr, "%s: Unable to register server\n", NAME);
892 return rc;
[21a9869]893 }
894
[fab2746]895 rc = tcp_create(&tcp);
[d6ff08a0]896 if (rc != EOK) {
897 fprintf(stderr, "%s: Error initializing TCP.\n", NAME);
[fab2746]898 return rc;
[21a9869]899 }
900
[fab2746]901 inet_ep_init(&ep);
[6a753a9c]902 ep.port = port;
[21a9869]903
[fab2746]904 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
905 &lst);
[21a9869]906 if (rc != EOK) {
[fab2746]907 fprintf(stderr, "%s: Error creating listener.\n", NAME);
908 return rc;
[21a9869]909 }
910
911 printf("%s: HelenOS Remote console service\n", NAME);
[6d5e378]912 task_retval(0);
[fab2746]913 async_manager();
[21a9869]914
[fab2746]915 /* Not reached */
[21a9869]916 return 0;
917}
918
919/** @}
920 */
Note: See TracBrowser for help on using the repository browser.