/* display.c - The display component of a virtual console. Copyright (C) 2002 Free Software Foundation, Inc. Written by Marcus Brinkmann and Kalle Olavi Niemitalo. This file is part of the GNU Hurd. The GNU Hurd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. The GNU Hurd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __STDC_ISO_10646__ #error It is required that wchar_t is UCS-4. #endif #include "console.h" #include "display.h" struct changes { struct { uint32_t col; uint32_t row; uint32_t status; } cursor; struct { uint32_t cur_line; uint32_t scr_lines; } screen; uint32_t bell_audible; uint32_t bell_visible; off_t start; off_t end; #define DISPLAY_CHANGE_CURSOR_POS 1 #define DISPLAY_CHANGE_CURSOR_STATUS 2 #define DISPLAY_CHANGE_SCREEN_CUR_LINE 4 #define DISPLAY_CHANGE_SCREEN_SCR_LINES 8 #define DISPLAY_CHANGE_BELL_AUDIBLE 16 #define DISPLAY_CHANGE_BELL_VISIBLE 32 #define DISPLAY_CHANGE_MATRIX 64 unsigned int which; }; struct cursor { uint32_t saved_x; uint32_t saved_y; }; typedef struct cursor *cursor_t; struct parse { /* The parsing state of output characters, needed to handle escape character sequences. */ enum { STATE_NORMAL = 0, /* An escape character has just been parsed. */ STATE_ESC, STATE_ESC_BRACKET_INIT, STATE_ESC_BRACKET, STATE_ESC_BRACKET_QUESTION } state; /* How many parameters an escape sequence may have. */ #define PARSE_MAX_PARAMS 10 int params[PARSE_MAX_PARAMS]; int nparams; }; typedef struct parse *parse_t; struct output { /* The state of the conversion of output characters. */ iconv_t cd; /* The output queue holds the characters that are to be outputted. The conversion routine might refuse to handle some incomplete multi-byte or composed character at the end of the buffer, so we have to keep them around. */ int stopped; struct condition resumed; char *buffer; size_t allocated; size_t size; /* The parsing state of output characters. */ struct parse parse; }; typedef struct output *output_t; struct attr { unsigned int bgcol_def; unsigned int fgcol_def; conchar_attr_t current; /* True if in alternate character set (ASCII graphic) mode. */ unsigned int altchar; }; typedef struct attr *attr_t; struct user_pager_info { display_t display; struct pager *p; size_t memobj_npages; vm_address_t memobj_pages[0]; }; /* Pending directory and file modification requests. */ struct modreq { mach_port_t port; struct modreq *next; /* If the port should have been notified, but it was blocking, we set this. */ int pending; }; /* For each display, a notification port is created to which the kernel sends message accepted notifications. */ struct notify { struct port_info pi; struct display *display; }; struct display { /* The lock for the virtual console display structure. */ struct mutex lock; /* Indicates if OWNER_ID is initialized. */ int has_owner; /* Specifies the ID of the process that should receive the WINCH signal for this virtual console. */ int owner_id; struct changes changes; struct cursor cursor; struct output output; struct attr attr; struct cons_display *user; struct user_pager_info *upi; memory_object_t memobj; /* A list of ports to send file change notifications to. */ struct modreq *filemod_reqs; /* Those ports which currently have a pending notification. */ struct modreq *filemod_reqs_pending; /* The notify port. */ struct notify *notify_port; }; /* We need a separate bucket for the pager ports. */ struct port_bucket *pager_bucket; mach_port_t display_get_filemap (display_t display, vm_prot_t prot) { error_t err; /* Add a reference for each call, the caller will deallocate it. */ err = mach_port_mod_refs (mach_task_self (), display->memobj, MACH_PORT_RIGHT_SEND, +1); assert_perror (err); return display->memobj; } /* Implement the pager_clear_user_data callback from the pager library. */ void pager_clear_user_data (struct user_pager_info *upi) { int idx; for (idx = 0; idx < upi->memobj_npages; idx++) if (upi->memobj_pages[idx]) vm_deallocate (mach_task_self (), upi->memobj_pages[idx], vm_page_size); free (upi); } error_t pager_read_page (struct user_pager_info *upi, vm_offset_t page, vm_address_t *buf, int *writelock) { /* XXX clients should get a read only object. */ *writelock = 0; if (upi->memobj_pages[page / vm_page_size] != (vm_address_t) NULL) { *buf = upi->memobj_pages[page / vm_page_size]; upi->memobj_pages[page / vm_page_size] = (vm_address_t) NULL; } else *buf = (vm_address_t) mmap (0, vm_page_size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); return 0; } error_t pager_write_page (struct user_pager_info *upi, vm_offset_t page, vm_address_t buf) { assert (upi->memobj_pages[page / vm_page_size] == (vm_address_t) NULL); upi->memobj_pages[page / vm_page_size] = buf; return 0; } error_t pager_unlock_page (struct user_pager_info *pager, vm_offset_t address) { assert (!"unlocking requested on unlocked page"); return 0; } /* Tell how big the file is. */ error_t pager_report_extent (struct user_pager_info *upi, vm_address_t *offset, vm_size_t *size) { display_t display = upi->display; *offset = 0; *size = display->upi->memobj_npages * vm_page_size; return 0; } void pager_dropweak (struct user_pager_info *upi) { } /* A top-level function for the paging thread that just services paging requests. */ static void service_paging_requests (any_t arg) { struct port_bucket *pager_bucket = arg; for (;;) ports_manage_port_operations_multithread (pager_bucket, pager_demuxer, 1000 * 60 * 2, 1000 * 60 * 10, 0); } /* The bucket and class for notification messages. */ static struct port_bucket *notify_bucket; static struct port_class *notify_class; #define msgh_request_port msgh_remote_port #define msgh_reply_port msgh_local_port /* SimpleRoutine file_changed */ kern_return_t nowait_file_changed (mach_port_t notify_port, file_changed_type_t change, off_t start, off_t end, mach_port_t notify) { typedef struct { mach_msg_header_t Head; mach_msg_type_t changeType; file_changed_type_t change; mach_msg_type_t startType; off_t start; mach_msg_type_t endType; off_t end; } Request; union { Request In; } Mess; register Request *InP = &Mess.In; static const mach_msg_type_t changeType = { /* msgt_name = */ 2, /* msgt_size = */ 32, /* msgt_number = */ 1, /* msgt_inline = */ TRUE, /* msgt_longform = */ FALSE, /* msgt_deallocate = */ FALSE, /* msgt_unused = */ 0 }; static const mach_msg_type_t startType = { /* msgt_name = */ 2, /* msgt_size = */ 32, /* msgt_number = */ 1, /* msgt_inline = */ TRUE, /* msgt_longform = */ FALSE, /* msgt_deallocate = */ FALSE, /* msgt_unused = */ 0 }; static const mach_msg_type_t endType = { /* msgt_name = */ 2, /* msgt_size = */ 32, /* msgt_number = */ 1, /* msgt_inline = */ TRUE, /* msgt_longform = */ FALSE, /* msgt_deallocate = */ FALSE, /* msgt_unused = */ 0 }; InP->changeType = changeType; InP->change = change; InP->startType = startType; InP->start = start; InP->endType = endType; InP->end = end; InP->Head.msgh_bits = MACH_MSGH_BITS(19, 0); /* msgh_size passed as argument. */ InP->Head.msgh_request_port = notify_port; InP->Head.msgh_reply_port = MACH_PORT_NULL; InP->Head.msgh_seqno = 0; InP->Head.msgh_id = 20501; if (notify == MACH_PORT_NULL) return mach_msg (&InP->Head, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, 48, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); else return mach_msg (&InP->Head, MACH_SEND_MSG | MACH_SEND_NOTIFY, 48, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, notify); } /* Free the list of modification requests MR */ static void free_modreqs (struct modreq *mr) { struct modreq *tmp; for (; mr; mr = tmp) { mach_port_deallocate (mach_task_self (), mr->port); tmp = mr->next; free (mr); } } void do_mach_notify_port_deleted (void) { assert (0); } void do_mach_notify_port_destroyed (void) { assert (0); } void do_mach_notify_no_senders (void) { assert (0); } void do_mach_notify_dead_name (void) { assert (0); } kern_return_t do_mach_notify_send_once (mach_port_t notify) { /* XXX Not sure when this is called. */ assert (0); } kern_return_t do_mach_notify_msg_accepted (mach_port_t notify, mach_port_t send) { struct notify *notify_port = ports_lookup_port (notify_bucket, notify, notify_class); struct display *display; struct modreq **preq; struct modreq *req; if (!notify_port) return EOPNOTSUPP; /* If we deallocated the send right in display_destroy before the notification was created. We have nothing to do in this case. */ if (!send) { assert(0); ports_port_deref (notify_port); return 0; } display = notify_port->display; mutex_lock (&display->lock); /* Find request in pending queue. */ preq = &display->filemod_reqs_pending; while (*preq && (*preq)->port != send) preq = &(*preq)->next; /* If we don't find the request, it was destroyed in display_destroy. In this case, there is nothing left to do here. */ if (! *preq) { assert(0); mutex_unlock (&display->lock); ports_port_deref (notify_port); return 0; } req = *preq; if (req->pending) { error_t err; /* A request was desired while we were blocking. Send it now and stay in pending queue. */ req->pending = 0; err = nowait_file_changed (req->port, FILE_CHANGED_WRITE, -1, -1, notify); if (err && err != MACH_SEND_WILL_NOTIFY) { *preq = req->next; mutex_unlock (&display->lock); mach_port_deallocate (mach_task_self (), req->port); free (req); ports_port_deref (notify_port); return err; } if (err == MACH_SEND_WILL_NOTIFY) { mutex_unlock (&display->lock); return 0; } /* The message was succesfully queued, fall through. */ } /* Remove request from pending queue. */ *preq = req->next; /* Insert request into active queue. */ req->next = display->filemod_reqs; display->filemod_reqs = req; mutex_unlock (&display->lock); ports_port_deref (notify_port); return 0; } /* A top-level function for the notification thread that just services notification messages. */ static void service_notifications (any_t arg) { struct port_bucket *notify_bucket = arg; extern int notify_server (mach_msg_header_t *inp, mach_msg_header_t *outp); for (;;) ports_manage_port_operations_one_thread (notify_bucket, notify_server, 1000 * 60 * 10); } error_t display_notice_changes (display_t display, mach_port_t notify) { error_t err; struct modreq *req; mutex_lock (&display->lock); err = nowait_file_changed (notify, FILE_CHANGED_NULL, 0, 0, MACH_PORT_NULL); if (err) { mutex_unlock (&display->lock); return err; } req = malloc (sizeof (struct modreq)); if (!req) { mutex_unlock (&display->lock); return errno; } req->port = notify; req->pending = 0; req->next = display->filemod_reqs; display->filemod_reqs = req; mutex_unlock (&display->lock); return 0; } /* Requires DISPLAY to be locked. */ static void display_notice_filechange (display_t display) { error_t err; struct modreq *req = display->filemod_reqs_pending; struct modreq **preq = &display->filemod_reqs; mach_port_t notify_port = ports_get_right (display->notify_port); while (req) { req->pending = 1; req = req->next; } while (*preq) { req = *preq; err = nowait_file_changed (req->port, FILE_CHANGED_WRITE, -1, -1, notify_port); if (err) { /* Remove notify port. */ *preq = req->next; if (err == MACH_SEND_WILL_NOTIFY) { req->next = display->filemod_reqs_pending; display->filemod_reqs_pending = req; } else { mach_port_deallocate (mach_task_self (), req->port); free (req); } } else preq = &req->next; } } static void display_flush_filechange (display_t display, unsigned int type) { struct cons_display *user = display->user; cons_change_t *next = &user->changes._buffer[user->changes.written % _CONS_CHANGES_LENGTH]; int notify = 0; int bump_written = 0; if (type & DISPLAY_CHANGE_MATRIX && display->changes.which & DISPLAY_CHANGE_MATRIX) { notify = 1; next->matrix.start = display->changes.start; next->matrix.end = display->changes.end; user->changes.written++; next = &user->changes._buffer[user->changes.written % _CONS_CHANGES_LENGTH]; display->changes.which &= ~DISPLAY_CHANGE_MATRIX; } memset (next, 0, sizeof (cons_change_t)); next->what.not_matrix = 1; if (type & DISPLAY_CHANGE_CURSOR_POS && display->changes.which & DISPLAY_CHANGE_CURSOR_POS && (display->changes.cursor.col != user->cursor.col || display->changes.cursor.row != user->cursor.row)) { notify = 1; next->what.cursor_pos = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_CURSOR_POS; } if (type & DISPLAY_CHANGE_CURSOR_STATUS && display->changes.which & DISPLAY_CHANGE_CURSOR_STATUS && display->changes.cursor.status != user->cursor.status) { notify = 1; next->what.cursor_status = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_CURSOR_STATUS; } if (type & DISPLAY_CHANGE_SCREEN_CUR_LINE && display->changes.which & DISPLAY_CHANGE_SCREEN_CUR_LINE && display->changes.screen.cur_line != user->screen.cur_line) { notify = 1; next->what.screen_cur_line = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_SCREEN_CUR_LINE; } if (type & DISPLAY_CHANGE_SCREEN_SCR_LINES && display->changes.which & DISPLAY_CHANGE_SCREEN_SCR_LINES && display->changes.screen.scr_lines != user->screen.scr_lines) { notify = 1; next->what.screen_scr_lines = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_SCREEN_SCR_LINES; } if (type & DISPLAY_CHANGE_BELL_AUDIBLE && display->changes.which & DISPLAY_CHANGE_BELL_AUDIBLE && display->changes.bell_audible != user->bell.audible) { notify = 1; next->what.bell_audible = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_BELL_AUDIBLE; } if (type & DISPLAY_CHANGE_BELL_VISIBLE && display->changes.which & DISPLAY_CHANGE_BELL_VISIBLE && display->changes.bell_visible != user->bell.visible) { notify = 1; next->what.bell_visible = 1; bump_written = 1; display->changes.which &= ~DISPLAY_CHANGE_BELL_VISIBLE; } if (bump_written) user->changes.written++; if (notify) display_notice_filechange (display); } /* Record a change in the matrix ringbuffer. */ static void display_record_filechange (display_t display, off_t start, off_t end) { if (!(display->changes.which & DISPLAY_CHANGE_MATRIX)) { display->changes.start = start; display->changes.end = end; display->changes.which |= DISPLAY_CHANGE_MATRIX; } else { off_t size = display->user->screen.width * display->user->screen.lines; off_t rotate = display->changes.start; off_t old_end = display->changes.end; int disjunct = 0; /* First rotate the buffer to reduce the number of cases. */ old_end -= rotate; if (old_end < 0) old_end += size; start -= rotate; if (start < 0) start += size; end -= rotate; if (end < 0) end += size; /* Now the old region starts at 0 and ends at OLD_END. Try to merge in the new region if it overlaps or touches the old one. */ if (start <= end) { if (start <= old_end + 1) { start = 0; if (old_end > end) end = old_end; } else { if (end == size - 1) end = old_end; else disjunct = 1; } } else { if (start <= old_end + 1) { start = 0; end = size - 1; } else { if (old_end > end) end = old_end; } } /* Now reverse the rotation. */ start += rotate; if (start >= size) start -= size; end += rotate; if (end >= size) end -= size; if (disjunct) { /* The regions are disjunct, so we have to flush the old changes. */ display_flush_filechange (display, DISPLAY_CHANGE_MATRIX); display->changes.which |= DISPLAY_CHANGE_MATRIX; } display->changes.start = start; display->changes.end = end; } } static void conchar_memset (conchar_t *conchar, wchar_t chr, conchar_attr_t attr, size_t size) { int i; for (i = 0; i < size; i++) { conchar->chr = chr; conchar->attr = attr; conchar++; } } static error_t user_create (display_t display, uint32_t width, uint32_t height, uint32_t lines, wchar_t chr, conchar_attr_t attr) { error_t err; struct cons_display *user; int npages = (round_page (sizeof (struct cons_display) + sizeof (conchar_t) * width * lines)) / vm_page_size; display->upi = calloc (1, sizeof (struct user_pager_info) + sizeof (vm_address_t) * npages); if (!display->upi) return MACH_PORT_NULL; display->upi->display = display; display->upi->memobj_npages = npages; /* 1 & MOCD correct? */ display->upi->p = pager_create (display->upi, pager_bucket, 1, MEMORY_OBJECT_COPY_DELAY); if (display->upi->p == 0) { free (display->upi); return errno; } display->memobj = pager_get_port (display->upi->p); ports_port_deref (display->upi->p); mach_port_insert_right (mach_task_self (), display->memobj, display->memobj, MACH_MSG_TYPE_MAKE_SEND); err = vm_map (mach_task_self (), (vm_address_t *) &user, (vm_size_t) display->upi->memobj_npages * vm_page_size, (vm_address_t) 0, 1 /* ! (flags & MAP_FIXED) */, display->memobj, 0 /* (vm_offset_t) offset */, 0 /* ! (flags & MAP_SHARED) */, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE); if (err) { /* UPI will be cleaned up by libpager. */ mach_port_deallocate (mach_task_self (), display->memobj); return err; } user->magic = CONS_MAGIC; user->version = CONS_VERSION_MAJ << 16 | CONS_VERSION_AGE; user->changes.buffer = offsetof (struct cons_display, changes._buffer) / sizeof (uint32_t); user->changes.length = _CONS_CHANGES_LENGTH; user->screen.width = width; user->screen.height = height; user->screen.lines = lines; user->screen.cur_line = 0; user->screen.scr_lines = 0; user->screen.matrix = sizeof (struct cons_display) / sizeof (uint32_t); user->cursor.col = 0; user->cursor.row = 0; user->cursor.status = CONS_CURSOR_NORMAL; conchar_memset (user->_matrix, chr, attr, user->screen.width * user->screen.lines); display->user = user; return 0; } static void user_destroy (display_t display) { /* The pager will be deallocated by libpager. */ mach_port_deallocate (mach_task_self (), display->memobj); } static void screen_fill (display_t display, size_t col1, size_t row1, size_t col2, size_t row2, wchar_t chr, conchar_attr_t attr) { struct cons_display *user = display->user; off_t start = (user->screen.cur_line + row1) * user->screen.width + col1; off_t end = (user->screen.cur_line + row2) * user->screen.width + col2; off_t size = user->screen.width * user->screen.lines; if (start >= size && end >= size) { start -= size; end -= size; } if (end < size) { conchar_memset (user->_matrix + start, chr, attr, end - start + 1); display_record_filechange (display, start, end); } else { conchar_memset (user->_matrix + start, chr, attr, size - start); conchar_memset (user->_matrix, chr, attr, end - size + 1); display_record_filechange (display, start, end - size); } } static void screen_shift_left (display_t display, size_t col1, size_t row1, size_t col2, size_t row2, size_t shift, wchar_t chr, conchar_attr_t attr) { struct cons_display *user = display->user; off_t start = (user->screen.cur_line + row1) * user->screen.width + col1; off_t end = (user->screen.cur_line + row2) * user->screen.width + col2; off_t size = user->screen.width * user->screen.lines; if (start >= size && end >= size) { start -= size; end -= size; } if (start + shift <= end) { /* Use a loop to copy the data. Using wmemmove and wmemset on the chunks is tiresome, as there are many cases. */ off_t src = start + shift; off_t dst = start; while (src <= end) user->_matrix[dst++ % size] = user->_matrix[src++ % size]; while (dst <= end) { user->_matrix[dst++ % size].chr = chr; user->_matrix[dst++ % size].attr = attr; } display_record_filechange (display, start, end); } else screen_fill (display, col1, row1, col2, row2, chr, attr); } static void screen_shift_right (display_t display, size_t col1, size_t row1, size_t col2, size_t row2, size_t shift, wchar_t chr, conchar_attr_t attr) { struct cons_display *user = display->user; off_t start = (user->screen.cur_line + row1) * user->screen.width + col1; off_t end = (user->screen.cur_line + row2) * user->screen.width + col2; off_t size = user->screen.width * user->screen.lines; if (start >= size && end >= size) { start -= size; end -= size; } if (start + shift <= end) { /* Use a loop to copy the data. Using wmemmove and wmemset on the chunks is tiresome, as there are many cases. */ off_t src = end - shift; off_t dst = end; while (src >= start) user->_matrix[dst-- % size] = user->_matrix[src-- % size]; while (dst >= start) { user->_matrix[dst-- % size].chr = chr; user->_matrix[dst-- % size].attr = attr; } display_record_filechange (display, start, end); } else screen_fill (display, col1, row1, col2, row2, chr, attr); } static error_t output_init (output_t output, const char *encoding) { condition_init (&output->resumed); output->stopped = 0; output->buffer = NULL; output->allocated = 0; output->size = 0; /* WCHAR_T happens to be UCS-4 on the GNU system. */ output->cd = iconv_open ("WCHAR_T", encoding); if (output->cd == (iconv_t) -1) return errno; return 0; } static void output_deinit (output_t output) { iconv_close (output->cd); } static void handle_esc_bracket_hl (display_t display, int code, int flag) { switch (code) { case 34: /* Cursor standout: , . */ if (flag) display->user->cursor.status = CONS_CURSOR_VERY_VISIBLE; else display->user->cursor.status = CONS_CURSOR_NORMAL; /* XXX Flag cursor status change. */ break; } } static void handle_esc_bracket_m (attr_t attr, int code) { switch (code) { case 0: /* All attributes off: . */ memset (&attr->current, 0, sizeof (conchar_attr_t)); attr->current.fgcol = attr->fgcol_def; attr->current.bgcol = attr->bgcol_def; attr->altchar = 0; break; case 1: /* Bold on: . */ attr->current.intensity = CONS_ATTR_INTENSITY_BOLD; break; case 2: /* Dim on: . */ attr->current.intensity = CONS_ATTR_INTENSITY_DIM; break; case 4: /* Underline on: . */ attr->current.underlined = 1; break; case 5: /* (Slow) blink on: . */ attr->current.blinking = 1; break; case 7: /* Reverse video on: , . */ attr->current.reversed = 1; break; case 8: /* Concealed on: . */ attr->current.concealed = 1; break; case 10: /* Alternate character set mode off: . */ attr->altchar = 0; break; case 11: /* Alternate character set mode on: . */ attr->altchar = 1; break; case 22: /* Normal intensity. */ attr->current.intensity = CONS_ATTR_INTENSITY_NORMAL; break; case 24: /* Underline off: . */ attr->current.underlined = 0; break; case 25: /* Blink off. */ attr->current.blinking = 0; break; case 27: /* Reverse video off: . */ attr->current.reversed = 0; break; case 28: /* Concealed off. */ attr->current.concealed = 0; break; case 30 ... 37: /* Set foreground color: . */ attr->current.fgcol = code - 30; break; case 39: /* Default foreground color; ANSI?. */ attr->current.fgcol = attr->fgcol_def; break; case 40 ... 47: /* Set background color: . */ attr->current.bgcol = code - 40; break; case 49: /* Default background color; ANSI?. */ attr->current.bgcol = attr->bgcol_def; break; } } static void limit_cursor (display_t display) { struct cons_display *user = display->user; if (user->cursor.col >= user->screen.width) user->cursor.col = user->screen.width - 1; else if (user->cursor.col < 0) user->cursor.col = 0; if (user->cursor.row >= user->screen.height) user->cursor.row = user->screen.height - 1; else if (user->cursor.row < 0) user->cursor.row = 0; /* XXX Flag cursor change. */ } static void linefeed (display_t display) { struct cons_display *user = display->user; if (user->cursor.row < user->screen.height - 1) { user->cursor.row++; /* XXX Flag cursor update. */ } else { user->screen.cur_line++; user->screen.cur_line %= user->screen.lines; screen_fill (display, 0, user->screen.height - 1, user->screen.width - 1, user->screen.height - 1, L' ', display->attr.current); if (user->screen.scr_lines < user->screen.lines - user->screen.height) user->screen.scr_lines++; /* XXX Flag current line change. */ /* XXX Possibly flag change of length of scroll back buffer. */ } } static void horizontal_tab (display_t display) { struct cons_display *user = display->user; user->cursor.col = (user->cursor.col | 7) + 1; if (user->cursor.col >= user->screen.width) { user->cursor.col = 0; linefeed (display); } /* XXX Flag cursor update. */ } static void handle_esc_bracket (display_t display, char op) { struct cons_display *user = display->user; parse_t parse = &display->output.parse; int i; switch (op) { case 'H': /* ECMA-48 . */ case 'f': /* ECMA-48 . */ /* Cursor position: . */ user->cursor.col = (parse->params[1] ?: 1) - 1; user->cursor.row = (parse->params[0] ?: 1) - 1; limit_cursor (display); break; case 'G': /* ECMA-48 . */ case '`': /* ECMA-48 . */ case '\'': /* VT100. */ /* Horizontal cursor position: . */ user->cursor.col = (parse->params[0] ?: 1) - 1; limit_cursor (display); break; case 'a': /* ECMA-48 . */ /* Horizontal cursor position relative. */ user->cursor.col += (parse->params[1] ?: 1) - 1; limit_cursor (display); break; case 'd': /* ECMA-48 . */ /* Vertical cursor position: . */ user->cursor.row = (parse->params[0] ?: 1) - 1; limit_cursor (display); break; case 'F': /* ECMA-48 . */ /* Beginning of previous line. */ user->cursor.col = 0; /* Fall through. */ case 'A': /* ECMA-48 . */ case 'k': /* ECMA-48 . */ /* Cursor up: , . */ user->cursor.row -= (parse->params[0] ?: 1); limit_cursor (display); break; case 'E': /* ECMA-48 . */ /* Beginning of next line. */ user->cursor.col = 0; /* Fall through. */ case 'B': /* ECMA-48 . */ case 'e': /* ECMA-48 . */ /* Cursor down: , . */ user->cursor.row += (parse->params[0] ?: 1); limit_cursor (display); break; case 'C': /* ECMA-48 . */ /* Cursor right: , . */ user->cursor.col += (parse->params[0] ?: 1); limit_cursor (display); break; case 'D': /* ECMA-48 . */ /* Cursor left: , . */ user->cursor.col -= (parse->params[0] ?: 1); limit_cursor (display); break; case 'h': /* Reset mode. */ for (i = 0; i < parse->nparams; i++) handle_esc_bracket_hl (display, parse->params[i], 0); break; case 'l': /* Set mode. */ for (i = 0; i < parse->nparams; i++) handle_esc_bracket_hl (display, parse->params[i], 1); break; case 'm': /* ECMA-48 . */ for (i = 0; i < parse->nparams; i++) handle_esc_bracket_m (&display->attr, parse->params[i]); break; case 'J': /* ECMA-48 . */ switch (parse->params[0]) { case 0: /* Clear to end of screen: . */ screen_fill (display, user->cursor.col, user->cursor.row, user->screen.width - 1, user->screen.height - 1, L' ', display->attr.current); break; case 1: /* Clear to beginning of screen. */ screen_fill (display, 0, 0, user->cursor.col, user->cursor.row, L' ', display->attr.current); break; case 2: /* Clear entire screen. */ screen_fill (display, 0, 0, user->screen.width - 1, user->screen.height - 1, L' ', display->attr.current); break; } break; case 'K': /* ECMA-48 . */ switch (parse->params[0]) { case 0: /* Clear to end of line: . */ screen_fill (display, user->cursor.col, user->cursor.row, user->screen.width - 1, user->cursor.row, L' ', display->attr.current); break; case 1: /* Clear to beginning of line: . */ screen_fill (display, 0, user->cursor.row, user->cursor.col, user->cursor.row, L' ', display->attr.current); break; case 2: /* Clear entire line. */ screen_fill (display, 0, user->cursor.row, user->screen.width - 1, user->cursor.row, L' ', display->attr.current); break; } break; case 'L': /* ECMA-48 . */ /* Insert line(s): , . */ screen_shift_right (display, 0, user->cursor.row, user->screen.width - 1, user->screen.height - 1, (parse->params[0] ?: 1) * user->screen.width, L' ', display->attr.current); break; case 'M': /* ECMA-48
. */ /* Delete line(s): ,
. */ screen_shift_left (display, 0, user->cursor.row, user->screen.width - 1, user->screen.height - 1, (parse->params[0] ?: 1) * user->screen.width, L' ', display->attr.current); break; case '@': /* ECMA-48 . */ /* Insert character(s): , . */ screen_shift_right (display, user->cursor.col, user->cursor.row, user->screen.width - 1, user->cursor.row, parse->params[0] ?: 1, L' ', display->attr.current); break; case 'P': /* ECMA-48 . */ /* Delete character(s): , . */ screen_shift_left (display, user->cursor.col, user->cursor.row, user->screen.width - 1, user->cursor.row, parse->params[0] ?: 1, L' ', display->attr.current); break; case 'S': /* ECMA-48 . */ /* Scroll up: , . */ screen_shift_left (display, 0, 0, user->screen.width - 1, user->screen.height - 1, (parse->params[0] ?: 1) * user->screen.width, L' ', display->attr.current); break; case 'T': /* ECMA-48 . */ /* Scroll down: , . */ screen_shift_right (display, 0, 0, user->screen.width, user->screen.height, (parse->params[0] ?: 1) * user->screen.width, L' ', display->attr.current); break; case 'X': /* ECMA-48 . */ /* Erase character(s): . */ screen_fill (display, user->cursor.col, user->cursor.row, /* XXX limit ? */user->cursor.col + (parse->params[0] ?: 1), user->cursor.row, L' ', display->attr.current); break; case 'I': /* ECMA-48 . */ /* Horizontal tab. */ if (!parse->params[0]) parse->params[0] = 1; while (parse->params[0]--) horizontal_tab (display); break; case 'Z': /* ECMA-48 . */ /* Cursor backward tabulation: . */ if (parse->params[0] > user->screen.height * (user->screen.width / 8)) { user->cursor.col = 0; user->cursor.row = 0; } else { int i = parse->params[0] ?: 1; while (i--) { if (user->cursor.col == 0) { if (user->cursor.row == 0) break; else { user->cursor.col = user->screen.width - 1; user->cursor.row--; } } else user->cursor.col--; user->cursor.col &= ~7; } } } } static void handle_esc_bracket_question_hl (display_t display, int code, int flag) { switch (code) { case 25: /* Cursor invisibility: , . */ if (flag) display->user->cursor.status = CONS_CURSOR_INVISIBLE; else display->user->cursor.status = CONS_CURSOR_NORMAL; /* XXX Flag cursor status change. */ break; } } static void handle_esc_bracket_question (display_t display, char op) { parse_t parse = &display->output.parse; int i; switch (op) { case 'h': /* Reset mode. */ for (i = 0; i < parse->nparams; ++i) handle_esc_bracket_question_hl (display, parse->params[i], 0); break; case 'l': /* Set mode. */ for (i = 0; i < parse->nparams; ++i) handle_esc_bracket_question_hl (display, parse->params[i], 1); break; } } static wchar_t altchar_to_ucs4 (wchar_t chr) { /* Alternative character set frobbing. */ switch (chr) { case L'+': return CONS_CHAR_RARROW; case L',': return CONS_CHAR_LARROW; case L'-': return CONS_CHAR_UARROW; case L'.': return CONS_CHAR_DARROW; case L'0': return CONS_CHAR_BLOCK; case L'I': return CONS_CHAR_LANTERN; case L'`': return CONS_CHAR_DIAMOND; case L'a': return CONS_CHAR_CKBOARD; case L'f': return CONS_CHAR_DEGREE; case L'g': return CONS_CHAR_PLMINUS; case L'h': return CONS_CHAR_BOARD; case L'j': return CONS_CHAR_LRCORNER; case L'k': return CONS_CHAR_URCORNER; case L'l': return CONS_CHAR_ULCORNER; case L'm': return CONS_CHAR_LLCORNER; case L'n': return CONS_CHAR_PLUS; case L'o': return CONS_CHAR_S1; case L'p': return CONS_CHAR_S3; case L'q': return CONS_CHAR_HLINE; case L'r': return CONS_CHAR_S7; case L's': return CONS_CHAR_S9; case L't': return CONS_CHAR_LTEE; case L'u': return CONS_CHAR_RTEE; case L'v': return CONS_CHAR_BTEE; case L'w': return CONS_CHAR_TTEE; case L'x': return CONS_CHAR_VLINE; case L'y': return CONS_CHAR_LEQUAL; case L'z': return CONS_CHAR_GEQUAL; case L'{': return CONS_CHAR_PI; case L'|': return CONS_CHAR_NEQUAL; case L'}': return CONS_CHAR_STERLING; case L'~': return CONS_CHAR_BULLET; default: return chr; } } /* Display must be locked. */ static void display_output_one (display_t display, wchar_t chr) { struct cons_display *user = display->user; parse_t parse = &display->output.parse; switch (parse->state) { case STATE_NORMAL: switch (chr) { case L'\r': /* Carriage return: . */ if (user->cursor.col) { user->cursor.col = 0; /* XXX Flag cursor update. */ } break; case L'\n': /* Line feed. */ linefeed (display); break; case L'\b': /* Backspace. */ if (user->cursor.col > 0 || user->cursor.row > 0) { if (user->cursor.col > 0) user->cursor.col--; else { /* XXX This implements the functionality. The alternative is to cut off and set x to 0. */ user->cursor.col = user->screen.width - 1; user->cursor.row--; } /* XXX Flag cursor update. */ } break; case L'\t': /* Horizontal tab: */ horizontal_tab (display); break; case L'\033': parse->state = STATE_ESC; break; case L'\0': /* Padding character: . */ break; case L'\a': /* Audible bell. */ user->bell.audible++; break; default: { int line = (user->screen.cur_line + user->cursor.row) % user->screen.lines; int idx = line * user->screen.width + user->cursor.col; user->_matrix[idx].chr = display->attr.altchar ? altchar_to_ucs4 (chr) : chr; user->_matrix[idx].attr = display->attr.current; display_record_filechange (display, idx, idx); user->cursor.col++; if (user->cursor.col == user->screen.width) { user->cursor.col = 0; linefeed (display); } } break; } break; case STATE_ESC: switch (chr) { case L'[': parse->state = STATE_ESC_BRACKET_INIT; break; case L'M': /* ECMA-48 . */ /* Reset: . */ * (uint32_t *) &display->attr.current = 0; display->attr.current.bgcol = display->attr.bgcol_def; display->attr.current.fgcol = display->attr.fgcol_def; display->attr.altchar = 0; /* Fall through. */ case L'c': /* Clear screen and home cursor: . */ screen_fill (display, 0, 0, user->screen.width - 1, user->screen.height - 1, L' ', display->attr.current); user->cursor.col = user->cursor.row = 0; /* XXX Flag cursor change. */ parse->state = STATE_NORMAL; break; case L'E': /* ECMA-48 . */ /* Newline. */ user->cursor.col = 0; linefeed (display); break; case L'7': /* VT100: Save cursor and attributes. */ /* Save cursor position: . */ display->cursor.saved_x = user->cursor.col; display->cursor.saved_y = user->cursor.row; break; case L'8': /* VT100: Restore cursor and attributes. */ /* Restore cursor position: . */ user->cursor.col = display->cursor.saved_x; user->cursor.row = display->cursor.saved_y; /* In case the screen was larger before: */ limit_cursor (display); break; case L'g': /* Visible bell. */ user->bell.visible++; break; default: /* Unsupported escape sequence. */ parse->state = STATE_NORMAL; break; } break; case STATE_ESC_BRACKET_INIT: memset (&parse->params, 0, sizeof parse->params); parse->nparams = 0; if (chr == '?') { parse->state = STATE_ESC_BRACKET_QUESTION; break; /* Consume the question mark. */ } else parse->state = STATE_ESC_BRACKET; /* Fall through. */ case STATE_ESC_BRACKET: case STATE_ESC_BRACKET_QUESTION: if (chr >= '0' && chr <= '9') parse->params[parse->nparams] = parse->params[parse->nparams]*10 + chr - '0'; else if (chr == ';') { if (++(parse->nparams) >= PARSE_MAX_PARAMS) parse->state = STATE_NORMAL; /* too many */ } else { parse->nparams++; if (parse->state == STATE_ESC_BRACKET) handle_esc_bracket (display, chr); else handle_esc_bracket_question (display, chr); parse->state = STATE_NORMAL; } break; default: abort (); } } /* Output LENGTH bytes starting from BUFFER in the system encoding. Set BUFFER and LENGTH to the new values. The exact semantics are just as in the iconv interface. */ static error_t display_output_some (display_t display, char **buffer, size_t *length) { #define CONV_OUTBUF_SIZE 256 error_t err = 0; display->changes.cursor.col = display->user->cursor.col; display->changes.cursor.row = display->user->cursor.row; display->changes.cursor.status = display->user->cursor.status; display->changes.screen.cur_line = display->user->screen.cur_line; display->changes.screen.scr_lines = display->user->screen.scr_lines; display->changes.bell_audible = display->user->bell.audible; display->changes.bell_visible = display->user->bell.visible; display->changes.which = ~DISPLAY_CHANGE_MATRIX; while (!err && *length > 0) { size_t nconv; wchar_t outbuf[CONV_OUTBUF_SIZE]; char *outptr = (char *) outbuf; size_t outsize = CONV_OUTBUF_SIZE * sizeof (wchar_t); error_t saved_err; int i; nconv = iconv (display->output.cd, buffer, length, &outptr, &outsize); saved_err = errno; /* First process all successfully converted characters. */ for (i = 0; i < CONV_OUTBUF_SIZE - outsize / sizeof (wchar_t); i++) display_output_one (display, outbuf[i]); if (nconv == (size_t) -1) { /* Conversion didn't work out. */ if (saved_err == EINVAL) /* This is only an unfinished byte sequence at the end of the input buffer. */ break; else if (saved_err != E2BIG) err = saved_err; } } display_flush_filechange (display, ~0); return err; } /* Forward declaration. */ void display_destroy_complete (void *pi); void display_init (void) { /* Create the pager bucket, and start to serve paging requests. */ pager_bucket = ports_create_bucket (); if (! pager_bucket) error (5, errno, "Cannot create pager bucket"); /* Make a thread to service paging requests. */ cthread_detach (cthread_fork ((cthread_fn_t) service_paging_requests, (any_t) pager_bucket)); /* Create the notify bucket, and start to serve notifications. */ notify_bucket = ports_create_bucket (); if (! notify_bucket) error (5, errno, "Cannot create notify bucket"); notify_class = ports_create_class (display_destroy_complete, NULL); if (! notify_class) error (5, errno, "Cannot create notify class"); cthread_detach (cthread_fork ((cthread_fn_t) service_notifications, (any_t) notify_bucket)); } /* Create a new virtual console display, with the system encoding being ENCODING. */ error_t display_create (display_t *r_display, const char *encoding, int foreground, int background) { error_t err = 0; display_t display; int width = 80; int height = 25; int lines = 50; /* XXX For now. */ *r_display = NULL; display = calloc (1, sizeof *display); if (!display) return ENOMEM; err = ports_create_port (notify_class, notify_bucket, sizeof (struct notify), &display->notify_port); if (err) { free (display); return err; } display->notify_port->display = display; mutex_init (&display->lock); display->attr.bgcol_def = background; display->attr.fgcol_def = foreground; display->attr.current.bgcol = display->attr.bgcol_def; display->attr.current.fgcol = display->attr.fgcol_def; err = user_create (display, width, height, lines, L' ', display->attr.current); if (err) { ports_destroy_right (display->notify_port); free (display); return err; } err = output_init (&display->output, encoding); if (err) { user_destroy (display); ports_destroy_right (display->notify_port); free (display); } *r_display = display; return err; } /* Destroy the display DISPLAY. */ void display_destroy (display_t display) { if (display->filemod_reqs_pending) free_modreqs (display->filemod_reqs_pending); if (display->filemod_reqs) free_modreqs (display->filemod_reqs); ports_destroy_right (display->notify_port); output_deinit (&display->output); user_destroy (display); /* We can not free the display structure here, because it might still be needed by pending modification requests when msg accepted notifications are handled. So we have to wait until all notifications have arrived and the notify port is completely deallocated, which will invoke display_destroy_complete below. */ } /* Complete destruction of the display DISPLAY. */ void display_destroy_complete (void *pi) { struct display *display = ((struct notify *) pi)->display; free (display); } /* Return the dimensions of the display DISPLAY in *WINSIZE. */ void display_getsize (display_t display, struct winsize *winsize) { mutex_lock (&display->lock); winsize->ws_row = display->user->screen.height; winsize->ws_col = display->user->screen.width; winsize->ws_xpixel = 0; winsize->ws_ypixel = 0; mutex_unlock (&display->lock); } /* Set the owner of the display DISPLAY to PID. The owner receives the SIGWINCH signal when the terminal size changes. */ error_t display_set_owner (display_t display, pid_t pid) { mutex_lock (&display->lock); display->has_owner = 1; display->owner_id = pid; mutex_unlock (&display->lock); return 0; } /* Return the owner of the display DISPLAY in PID. If there is no owner, return ENOTTY. */ error_t display_get_owner (display_t display, pid_t *pid) { error_t err = 0; mutex_lock (&display->lock); if (!display->has_owner) err = ENOTTY; else *pid = display->owner_id; mutex_unlock (&display->lock); return err; } /* Output DATALEN characters from the buffer DATA on display DISPLAY. The DATA must be supplied in the system encoding configured for DISPLAY. The function returns the amount of bytes written (might be smaller than DATALEN) or -1 and the error number in errno. If NONBLOCK is not zero, return with -1 and set errno to EWOULDBLOCK if operation would block for a long time. */ ssize_t display_output (display_t display, int nonblock, char *data, size_t datalen) { output_t output = &display->output; error_t err; char *buffer; size_t buffer_size; ssize_t amount; error_t ensure_output_buffer_size (size_t new_size) { /* Must be a power of two. */ #define OUTPUT_ALLOCSIZE 32 if (output->allocated < new_size) { char *new_buffer; new_size = (new_size + OUTPUT_ALLOCSIZE - 1) & ~(OUTPUT_ALLOCSIZE - 1); new_buffer = realloc (output->buffer, new_size); if (!new_buffer) return ENOMEM; output->buffer = new_buffer; output->allocated = new_size; } return 0; } mutex_lock (&display->lock); while (output->stopped) { if (nonblock) { mutex_unlock (&display->lock); errno = EWOULDBLOCK; return -1; } if (hurd_condition_wait (&output->resumed, &display->lock)) { mutex_unlock (&display->lock); errno = EINTR; return -1; } } if (output->size) { err = ensure_output_buffer_size (output->size + datalen); if (err) { mutex_unlock (&display->lock); errno = ENOMEM; return -1; } buffer = output->buffer; buffer_size = output->size; memcpy (buffer + buffer_size, data, datalen); buffer_size += datalen; } else { buffer = data; buffer_size = datalen; } amount = buffer_size; err = display_output_some (display, &buffer, &buffer_size); amount -= buffer_size; if (err && !amount) { mutex_unlock (&display->lock); errno = err; return err; } /* XXX What should be done with invalid characters etc? */ if (buffer_size) { /* If we used the caller's buffer DATA, the remaining bytes might not fit in our internal output buffer. In this case we can reallocate the buffer in VCONS without needing to update OUTPUT (as it points into DATA). */ err = ensure_output_buffer_size (buffer_size); if (err) { mutex_unlock (&display->lock); return err; } memmove (output->buffer, buffer, buffer_size); } output->size = buffer_size; amount += buffer_size; mutex_unlock (&display->lock); return amount; } ssize_t display_read (display_t display, int nonblock, off_t off, char *data, size_t len) { mutex_lock (&display->lock); memcpy (data, ((char *) display->user) + off, len); mutex_unlock (&display->lock); return len; } /* Resume the output on the display DISPLAY. */ void display_start_output (display_t display) { mutex_lock (&display->lock); if (display->output.stopped) { display->output.stopped = 0; condition_broadcast (&display->output.resumed); } mutex_unlock (&display->lock); } /* Stop all output on the display DISPLAY. */ void display_stop_output (display_t display) { mutex_lock (&display->lock); display->output.stopped = 1; mutex_unlock (&display->lock); } /* Return the number of pending output bytes for DISPLAY. */ size_t display_pending_output (display_t display) { int output_size; mutex_lock (&display->lock); output_size = display->output.size; mutex_unlock (&display->lock); return output_size; } /* Flush the output buffer, discarding all pending data. */ void display_discard_output (display_t display) { mutex_lock (&display->lock); display->output.size = 0; mutex_unlock (&display->lock); }