source: mainline/uspace/srv/hid/console/console.c@ 6d5e378

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 6d5e378 was 6d5e378, checked in by Martin Decky <martin@…>, 13 years ago

cherrypick GUI implementation (originally by Petr Koupy), with several major changes

  • for character-oriented devices a new output server and output protocol was created based on the original fb server
  • DDF visualizer drivers are pixel-oriented only
  • console and compositor can coexist in the same build
  • terminal widget is self-sufficient, no strange console nesting is needed
  • Property mode set to 100644
File size: 16.3 KB
Line 
1/*
2 * Copyright (c) 2011 Martin Decky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup console
30 * @{
31 */
32/** @file
33 */
34
35#include <async.h>
36#include <stdio.h>
37#include <adt/prodcons.h>
38#include <ipc/input.h>
39#include <ipc/console.h>
40#include <ipc/vfs.h>
41#include <errno.h>
42#include <str_error.h>
43#include <loc.h>
44#include <event.h>
45#include <io/keycode.h>
46#include <io/chargrid.h>
47#include <io/console.h>
48#include <io/output.h>
49#include <align.h>
50#include <malloc.h>
51#include <as.h>
52#include <fibril_synch.h>
53#include "console.h"
54
55#define NAME "console"
56#define NAMESPACE "term"
57
58#define UTF8_CHAR_BUFFER_SIZE (STR_BOUNDS(1) + 1)
59
60typedef struct {
61 atomic_t refcnt; /**< Connection reference count */
62 prodcons_t input_pc; /**< Incoming keyboard events */
63
64 /**
65 * Not yet sent bytes of last char event.
66 */
67 char char_remains[UTF8_CHAR_BUFFER_SIZE];
68 size_t char_remains_len; /**< Number of not yet sent bytes. */
69
70 fibril_mutex_t mtx; /**< Lock protecting mutable fields */
71
72 size_t index; /**< Console index */
73 service_id_t dsid; /**< Service handle */
74
75 sysarg_t cols; /**< Number of columns */
76 sysarg_t rows; /**< Number of rows */
77 console_caps_t ccaps; /**< Console capabilities */
78
79 chargrid_t *frontbuf; /**< Front buffer */
80 frontbuf_handle_t fbid; /**< Front buffer handle */
81} console_t;
82
83/** Session to the input server */
84static async_sess_t *input_sess;
85
86/** Session to the output server */
87static async_sess_t *output_sess;
88
89/** Output dimensions */
90static sysarg_t cols;
91static sysarg_t rows;
92
93/** Array of data for virtual consoles */
94static console_t consoles[CONSOLE_COUNT];
95
96/** Mutex for console switching */
97static FIBRIL_MUTEX_INITIALIZE(switch_mtx);
98
99static console_t *prev_console = &consoles[0];
100static console_t *active_console = &consoles[0];
101static console_t *kernel_console = &consoles[KERNEL_CONSOLE];
102
103static void cons_update(console_t *cons)
104{
105 fibril_mutex_lock(&switch_mtx);
106 fibril_mutex_lock(&cons->mtx);
107
108 if ((cons == active_console) && (active_console != kernel_console)) {
109 output_update(output_sess, cons->fbid);
110 output_cursor_update(output_sess, cons->fbid);
111 }
112
113 fibril_mutex_unlock(&cons->mtx);
114 fibril_mutex_unlock(&switch_mtx);
115}
116
117static void cons_update_cursor(console_t *cons)
118{
119 fibril_mutex_lock(&switch_mtx);
120 fibril_mutex_lock(&cons->mtx);
121
122 if ((cons == active_console) && (active_console != kernel_console))
123 output_cursor_update(output_sess, cons->fbid);
124
125 fibril_mutex_unlock(&cons->mtx);
126 fibril_mutex_unlock(&switch_mtx);
127}
128
129static void cons_clear(console_t *cons)
130{
131 fibril_mutex_lock(&cons->mtx);
132 chargrid_clear(cons->frontbuf);
133 fibril_mutex_unlock(&cons->mtx);
134
135 cons_update(cons);
136}
137
138static void cons_damage(console_t *cons)
139{
140 fibril_mutex_lock(&switch_mtx);
141 fibril_mutex_lock(&cons->mtx);
142
143 if ((cons == active_console) && (active_console != kernel_console)) {
144 output_damage(output_sess, cons->fbid, 0, 0, cons->cols,
145 cons->rows);
146 output_cursor_update(output_sess, cons->fbid);
147 }
148
149 fibril_mutex_unlock(&cons->mtx);
150 fibril_mutex_unlock(&switch_mtx);
151}
152
153static void cons_switch(console_t *cons)
154{
155 fibril_mutex_lock(&switch_mtx);
156
157 if (cons == active_console) {
158 fibril_mutex_unlock(&switch_mtx);
159 return;
160 }
161
162 if (cons == kernel_console) {
163 output_yield(output_sess);
164 if (!console_kcon()) {
165 output_claim(output_sess);
166 fibril_mutex_unlock(&switch_mtx);
167 return;
168 }
169 }
170
171 if (active_console == kernel_console)
172 output_claim(output_sess);
173
174 prev_console = active_console;
175 active_console = cons;
176
177 fibril_mutex_unlock(&switch_mtx);
178
179 cons_damage(cons);
180}
181
182static console_t *cons_get_active_uspace(void)
183{
184 fibril_mutex_lock(&switch_mtx);
185
186 console_t *active_uspace = active_console;
187 if (active_uspace == kernel_console)
188 active_uspace = prev_console;
189
190 assert(active_uspace != kernel_console);
191
192 fibril_mutex_unlock(&switch_mtx);
193
194 return active_uspace;
195}
196
197static void input_events(ipc_callid_t iid, ipc_call_t *icall, void *arg)
198{
199 /* Ignore parameters, the connection is already opened */
200 while (true) {
201 ipc_call_t call;
202 ipc_callid_t callid = async_get_call(&call);
203
204 if (!IPC_GET_IMETHOD(call)) {
205 /* TODO: Handle hangup */
206 async_hangup(input_sess);
207 return;
208 }
209
210 kbd_event_type_t type;
211 keycode_t key;
212 keymod_t mods;
213 wchar_t c;
214
215 switch (IPC_GET_IMETHOD(call)) {
216 case INPUT_EVENT_KEY:
217 type = IPC_GET_ARG1(call);
218 key = IPC_GET_ARG2(call);
219 mods = IPC_GET_ARG3(call);
220 c = IPC_GET_ARG4(call);
221
222 if ((key >= KC_F1) && (key < KC_F1 + CONSOLE_COUNT) &&
223 ((mods & KM_CTRL) == 0))
224 cons_switch(&consoles[key - KC_F1]);
225 else {
226 /* Got key press/release event */
227 kbd_event_t *event =
228 (kbd_event_t *) malloc(sizeof(kbd_event_t));
229 if (event == NULL) {
230 async_answer_0(callid, ENOMEM);
231 break;
232 }
233
234 link_initialize(&event->link);
235 event->type = type;
236 event->key = key;
237 event->mods = mods;
238 event->c = c;
239
240 /*
241 * Kernel console does not read events
242 * from us, so we will redirect them
243 * to the (last) active userspace console
244 * if necessary.
245 */
246 console_t *target_console = cons_get_active_uspace();
247
248 prodcons_produce(&target_console->input_pc,
249 &event->link);
250 }
251
252 async_answer_0(callid, EOK);
253 break;
254 case INPUT_EVENT_MOVE:
255 async_answer_0(callid, EOK);
256 break;
257 case INPUT_EVENT_BUTTON:
258 async_answer_0(callid, EOK);
259 break;
260 default:
261 async_answer_0(callid, EINVAL);
262 }
263 }
264}
265
266/** Process a character from the client (TTY emulation). */
267static void cons_write_char(console_t *cons, wchar_t ch)
268{
269 sysarg_t updated = 0;
270
271 fibril_mutex_lock(&cons->mtx);
272
273 switch (ch) {
274 case '\n':
275 updated = chargrid_newline(cons->frontbuf);
276 break;
277 case '\r':
278 break;
279 case '\t':
280 updated = chargrid_tabstop(cons->frontbuf, 8);
281 break;
282 case '\b':
283 updated = chargrid_backspace(cons->frontbuf);
284 break;
285 default:
286 updated = chargrid_putchar(cons->frontbuf, ch, true);
287 }
288
289 fibril_mutex_unlock(&cons->mtx);
290
291 if (updated > 1)
292 cons_update(cons);
293}
294
295static void cons_set_cursor(console_t *cons, sysarg_t col, sysarg_t row)
296{
297 fibril_mutex_lock(&cons->mtx);
298 chargrid_set_cursor(cons->frontbuf, col, row);
299 fibril_mutex_unlock(&cons->mtx);
300
301 cons_update_cursor(cons);
302}
303
304static void cons_set_cursor_visibility(console_t *cons, bool visible)
305{
306 fibril_mutex_lock(&cons->mtx);
307 chargrid_set_cursor_visibility(cons->frontbuf, visible);
308 fibril_mutex_unlock(&cons->mtx);
309
310 cons_update_cursor(cons);
311}
312
313static void cons_get_cursor(console_t *cons, ipc_callid_t iid, ipc_call_t *icall)
314{
315 sysarg_t col;
316 sysarg_t row;
317
318 fibril_mutex_lock(&cons->mtx);
319 chargrid_get_cursor(cons->frontbuf, &col, &row);
320 fibril_mutex_unlock(&cons->mtx);
321
322 async_answer_2(iid, EOK, col, row);
323}
324
325static void cons_write(console_t *cons, ipc_callid_t iid, ipc_call_t *icall)
326{
327 void *buf;
328 size_t size;
329 int rc = async_data_write_accept(&buf, false, 0, 0, 0, &size);
330
331 if (rc != EOK) {
332 async_answer_0(iid, rc);
333 return;
334 }
335
336 size_t off = 0;
337 while (off < size)
338 cons_write_char(cons, str_decode(buf, &off, size));
339
340 async_answer_1(iid, EOK, size);
341 free(buf);
342}
343
344static void cons_read(console_t *cons, ipc_callid_t iid, ipc_call_t *icall)
345{
346 ipc_callid_t callid;
347 size_t size;
348 if (!async_data_read_receive(&callid, &size)) {
349 async_answer_0(callid, EINVAL);
350 async_answer_0(iid, EINVAL);
351 return;
352 }
353
354 char *buf = (char *) malloc(size);
355 if (buf == NULL) {
356 async_answer_0(callid, ENOMEM);
357 async_answer_0(iid, ENOMEM);
358 return;
359 }
360
361 size_t pos = 0;
362
363 /*
364 * Read input from keyboard and copy it to the buffer.
365 * We need to handle situation when wchar is split by 2 following
366 * reads.
367 */
368 while (pos < size) {
369 /* Copy to the buffer remaining characters. */
370 while ((pos < size) && (cons->char_remains_len > 0)) {
371 buf[pos] = cons->char_remains[0];
372 pos++;
373
374 /* Unshift the array. */
375 for (size_t i = 1; i < cons->char_remains_len; i++)
376 cons->char_remains[i - 1] = cons->char_remains[i];
377
378 cons->char_remains_len--;
379 }
380
381 /* Still not enough? Then get another key from the queue. */
382 if (pos < size) {
383 link_t *link = prodcons_consume(&cons->input_pc);
384 kbd_event_t *event = list_get_instance(link, kbd_event_t, link);
385
386 /* Accept key presses of printable chars only. */
387 if ((event->type == KEY_PRESS) && (event->c != 0)) {
388 wchar_t tmp[2] = { event->c, 0 };
389 wstr_to_str(cons->char_remains, UTF8_CHAR_BUFFER_SIZE, tmp);
390 cons->char_remains_len = str_size(cons->char_remains);
391 }
392
393 free(event);
394 }
395 }
396
397 (void) async_data_read_finalize(callid, buf, size);
398 async_answer_1(iid, EOK, size);
399 free(buf);
400}
401
402static void cons_set_style(console_t *cons, console_style_t style)
403{
404 fibril_mutex_lock(&cons->mtx);
405 chargrid_set_style(cons->frontbuf, style);
406 fibril_mutex_unlock(&cons->mtx);
407}
408
409static void cons_set_color(console_t *cons, console_color_t bgcolor,
410 console_color_t fgcolor, console_color_attr_t attr)
411{
412 fibril_mutex_lock(&cons->mtx);
413 chargrid_set_color(cons->frontbuf, bgcolor, fgcolor, attr);
414 fibril_mutex_unlock(&cons->mtx);
415}
416
417static void cons_set_rgb_color(console_t *cons, pixel_t bgcolor,
418 pixel_t fgcolor)
419{
420 fibril_mutex_lock(&cons->mtx);
421 chargrid_set_rgb_color(cons->frontbuf, bgcolor, fgcolor);
422 fibril_mutex_unlock(&cons->mtx);
423}
424
425static void cons_get_event(console_t *cons, ipc_callid_t iid, ipc_call_t *icall)
426{
427 link_t *link = prodcons_consume(&cons->input_pc);
428 kbd_event_t *event = list_get_instance(link, kbd_event_t, link);
429
430 async_answer_4(iid, EOK, event->type, event->key, event->mods, event->c);
431 free(event);
432}
433
434static void client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
435{
436 console_t *cons = NULL;
437
438 for (size_t i = 0; i < CONSOLE_COUNT; i++) {
439 if (i == KERNEL_CONSOLE)
440 continue;
441
442 if (consoles[i].dsid == (service_id_t) IPC_GET_ARG1(*icall)) {
443 cons = &consoles[i];
444 break;
445 }
446 }
447
448 if (cons == NULL) {
449 async_answer_0(iid, ENOENT);
450 return;
451 }
452
453 if (atomic_postinc(&cons->refcnt) == 0)
454 cons_set_cursor_visibility(cons, true);
455
456 /* Accept the connection */
457 async_answer_0(iid, EOK);
458
459 while (true) {
460 ipc_call_t call;
461 ipc_callid_t callid = async_get_call(&call);
462
463 if (!IPC_GET_IMETHOD(call))
464 return;
465
466 switch (IPC_GET_IMETHOD(call)) {
467 case VFS_OUT_READ:
468 cons_read(cons, callid, &call);
469 break;
470 case VFS_OUT_WRITE:
471 cons_write(cons, callid, &call);
472 break;
473 case VFS_OUT_SYNC:
474 cons_update(cons);
475 async_answer_0(callid, EOK);
476 break;
477 case CONSOLE_CLEAR:
478 cons_clear(cons);
479 async_answer_0(callid, EOK);
480 break;
481 case CONSOLE_GOTO:
482 cons_set_cursor(cons, IPC_GET_ARG1(call), IPC_GET_ARG2(call));
483 async_answer_0(callid, EOK);
484 break;
485 case CONSOLE_GET_POS:
486 cons_get_cursor(cons, callid, &call);
487 break;
488 case CONSOLE_GET_SIZE:
489 async_answer_2(callid, EOK, cons->cols, cons->rows);
490 break;
491 case CONSOLE_GET_COLOR_CAP:
492 async_answer_1(callid, EOK, cons->ccaps);
493 break;
494 case CONSOLE_SET_STYLE:
495 cons_set_style(cons, IPC_GET_ARG1(call));
496 async_answer_0(callid, EOK);
497 break;
498 case CONSOLE_SET_COLOR:
499 cons_set_color(cons, IPC_GET_ARG1(call), IPC_GET_ARG2(call),
500 IPC_GET_ARG3(call));
501 async_answer_0(callid, EOK);
502 break;
503 case CONSOLE_SET_RGB_COLOR:
504 cons_set_rgb_color(cons, IPC_GET_ARG1(call), IPC_GET_ARG2(call));
505 async_answer_0(callid, EOK);
506 break;
507 case CONSOLE_CURSOR_VISIBILITY:
508 cons_set_cursor_visibility(cons, IPC_GET_ARG1(call));
509 async_answer_0(callid, EOK);
510 break;
511 case CONSOLE_GET_EVENT:
512 cons_get_event(cons, callid, &call);
513 break;
514 default:
515 async_answer_0(callid, EINVAL);
516 }
517 }
518}
519
520static async_sess_t *input_connect(const char *svc)
521{
522 async_sess_t *sess;
523 service_id_t dsid;
524
525 int rc = loc_service_get_id(svc, &dsid, 0);
526 if (rc == EOK) {
527 sess = loc_service_connect(EXCHANGE_ATOMIC, dsid, 0);
528 if (sess == NULL) {
529 printf("%s: Unable to connect to input service %s\n", NAME,
530 svc);
531 return NULL;
532 }
533 } else
534 return NULL;
535
536 async_exch_t *exch = async_exchange_begin(sess);
537 rc = async_connect_to_me(exch, 0, 0, 0, input_events, NULL);
538 async_exchange_end(exch);
539
540 if (rc != EOK) {
541 async_hangup(sess);
542 printf("%s: Unable to create callback connection to service %s (%s)\n",
543 NAME, svc, str_error(rc));
544 return NULL;
545 }
546
547 return sess;
548}
549
550static void interrupt_received(ipc_callid_t callid, ipc_call_t *call)
551{
552 cons_switch(prev_console);
553}
554
555static async_sess_t *output_connect(const char *svc)
556{
557 async_sess_t *sess;
558 service_id_t dsid;
559
560 int rc = loc_service_get_id(svc, &dsid, 0);
561 if (rc == EOK) {
562 sess = loc_service_connect(EXCHANGE_SERIALIZE, dsid, 0);
563 if (sess == NULL) {
564 printf("%s: Unable to connect to output service %s\n",
565 NAME, svc);
566 return NULL;
567 }
568 } else
569 return NULL;
570
571 return sess;
572}
573
574static bool console_srv_init(char *input_svc, char *output_svc)
575{
576 /* Connect to input service */
577 input_sess = input_connect(input_svc);
578 if (input_sess == NULL)
579 return false;
580
581 /* Connect to output service */
582 output_sess = output_connect(output_svc);
583 if (output_sess == NULL)
584 return false;
585
586 /* Register server */
587 async_set_client_connection(client_connection);
588 int rc = loc_server_register(NAME);
589 if (rc != EOK) {
590 printf("%s: Unable to register server (%s)\n", NAME,
591 str_error(rc));
592 return false;
593 }
594
595 output_get_dimensions(output_sess, &cols, &rows);
596 output_set_style(output_sess, STYLE_NORMAL);
597
598 console_caps_t ccaps;
599 output_get_caps(output_sess, &ccaps);
600
601 /* Inititalize consoles */
602 for (size_t i = 0; i < CONSOLE_COUNT; i++) {
603 consoles[i].index = i;
604 atomic_set(&consoles[i].refcnt, 0);
605 fibril_mutex_initialize(&consoles[i].mtx);
606 prodcons_initialize(&consoles[i].input_pc);
607 consoles[i].char_remains_len = 0;
608
609 if (i == KERNEL_CONSOLE)
610 continue;
611
612 consoles[i].cols = cols;
613 consoles[i].rows = rows;
614 consoles[i].ccaps = ccaps;
615 consoles[i].frontbuf =
616 chargrid_create(cols, rows, CHARGRID_FLAG_SHARED);
617
618 if (consoles[i].frontbuf == NULL) {
619 printf("%s: Unable to allocate frontbuffer %zu\n", NAME, i);
620 return false;
621 }
622
623 consoles[i].fbid = output_frontbuf_create(output_sess,
624 consoles[i].frontbuf);
625 if (consoles[i].fbid == 0) {
626 printf("%s: Unable to create frontbuffer %zu\n", NAME, i);
627 return false;
628 }
629
630 char vc[LOC_NAME_MAXLEN + 1];
631 snprintf(vc, LOC_NAME_MAXLEN, "%s/vc%zu", NAMESPACE, i);
632
633 if (loc_service_register(vc, &consoles[i].dsid) != EOK) {
634 printf("%s: Unable to register device %s\n", NAME, vc);
635 return false;
636 }
637 }
638
639 cons_damage(active_console);
640
641 /* Receive kernel notifications */
642 async_set_interrupt_received(interrupt_received);
643 rc = event_subscribe(EVENT_KCONSOLE, 0);
644 if (rc != EOK)
645 printf("%s: Failed to register kconsole notifications (%s)\n",
646 NAME, str_error(rc));
647
648 return true;
649}
650
651static void usage(char *name)
652{
653 printf("Usage: %s <input_dev> <output_dev>\n", name);
654}
655
656int main(int argc, char *argv[])
657{
658 if (argc < 3) {
659 usage(argv[0]);
660 return -1;
661 }
662
663 printf("%s: HelenOS Console service\n", NAME);
664
665 if (!console_srv_init(argv[1], argv[2]))
666 return -1;
667
668 printf("%s: Accepting connections\n", NAME);
669 task_retval(0);
670 async_manager();
671
672 /* Never reached */
673 return 0;
674}
675
676/** @}
677 */
Note: See TracBrowser for help on using the repository browser.