source: mainline/uspace/srv/hid/remcons/remcons.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: 10.0 KB
Line 
1/*
2 * Copyright (c) 2012 Vojtech Horky
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 remcons
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 <align.h>
47#include <malloc.h>
48#include <as.h>
49#include <fibril_synch.h>
50#include <task.h>
51#include <net/in.h>
52#include <net/inet.h>
53#include <net/socket.h>
54#include <io/console.h>
55#include <inttypes.h>
56#include "telnet.h"
57#include "user.h"
58
59#define APP_GETTERM "/app/getterm"
60#define APP_SHELL "/app/bdsh"
61
62/** Telnet commands to force character mode
63 * (redundant to be on the safe side).
64 * See
65 * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
66 * for discussion.
67 */
68static const telnet_cmd_t telnet_force_character_mode_command[] = {
69 TELNET_IAC, TELNET_WILL, TELNET_ECHO,
70 TELNET_IAC, TELNET_WILL, TELNET_SUPPRESS_GO_AHEAD,
71 TELNET_IAC, TELNET_WONT, TELNET_LINEMODE
72};
73
74static const size_t telnet_force_character_mode_command_count =
75 sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
76
77
78/** Handling client requests (VFS and console interface).
79 *
80 * @param user Telnet user the requests belong to.
81 */
82static void client_connection_message_loop(telnet_user_t *user)
83{
84 while (true) {
85 ipc_call_t call;
86 ipc_callid_t callid = 0;
87
88 /*
89 * The getterm task might terminate while we are here,
90 * waiting for a call. Also, the socket might be closed
91 * meanwhile.
92 * We want to detect this situation early, so we use a
93 * timeout variant of async_get_call().
94 */
95 while (callid == 0) {
96 callid = async_get_call_timeout(&call, 1000);
97
98 if (telnet_user_is_zombie(user)) {
99 if (callid != 0) {
100 async_answer_0(callid, EINTR);
101 }
102 return;
103 }
104 }
105
106 if (!IPC_GET_IMETHOD(call)) {
107 return;
108 }
109
110 switch (IPC_GET_IMETHOD(call)) {
111 case CONSOLE_GET_SIZE:
112 async_answer_2(callid, EOK, 100, 1);
113 break;
114 case CONSOLE_GET_POS:
115 fibril_mutex_lock(&user->guard);
116 async_answer_2(callid, EOK, user->cursor_x, 0);
117 fibril_mutex_unlock(&user->guard);
118 break;
119 case CONSOLE_GET_EVENT: {
120 kbd_event_t event;
121 int rc = telnet_user_get_next_keyboard_event(user, &event);
122 if (rc != EOK) {
123 /* Silently ignore. */
124 async_answer_0(callid, EOK);
125 break;
126 }
127 async_answer_4(callid, EOK, event.type, event.key, event.mods, event.c);
128 break;
129 }
130 case CONSOLE_GOTO: {
131 int new_x = IPC_GET_ARG1(call);
132 telnet_user_update_cursor_x(user, new_x);
133 async_answer_0(callid, ENOTSUP);
134 break;
135 }
136 case VFS_OUT_READ:
137 async_answer_0(callid, ENOTSUP);
138 break;
139 case VFS_OUT_WRITE: {
140 uint8_t *buf;
141 size_t size;
142 int rc = async_data_write_accept((void **)&buf, false, 0, 0, 0, &size);
143
144 if (rc != EOK) {
145 async_answer_0(callid, rc);
146 break;
147 }
148
149 rc = telnet_user_send_data(user, buf, size);
150 free(buf);
151
152 if (rc != EOK) {
153 async_answer_0(callid, rc);
154 break;
155 }
156
157 async_answer_1(callid, EOK, size);
158 break;
159 }
160 case VFS_OUT_SYNC:
161 async_answer_0(callid, EOK);
162 break;
163 case CONSOLE_CLEAR:
164 async_answer_0(callid, EOK);
165 break;
166
167 case CONSOLE_GET_COLOR_CAP:
168 async_answer_1(callid, EOK, CONSOLE_CAP_NONE);
169 break;
170 case CONSOLE_SET_STYLE:
171 async_answer_0(callid, ENOTSUP);
172 break;
173 case CONSOLE_SET_COLOR:
174 async_answer_0(callid, ENOTSUP);
175 break;
176 case CONSOLE_SET_RGB_COLOR:
177 async_answer_0(callid, ENOTSUP);
178 break;
179
180 case CONSOLE_CURSOR_VISIBILITY:
181 async_answer_0(callid, ENOTSUP);
182 break;
183
184 default:
185 async_answer_0(callid, EINVAL);
186 break;
187 }
188 }
189}
190
191/** Callback when client connects to a telnet terminal. */
192static void client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
193{
194 /* Find the user. */
195 telnet_user_t *user = telnet_user_get_for_client_connection(IPC_GET_ARG1(*icall));
196 if (user == NULL) {
197 async_answer_0(iid, ENOENT);
198 return;
199 }
200 async_answer_0(iid, EOK);
201
202 telnet_user_log(user, "New client connected (%" PRIxn").", iid);
203
204 /* Force character mode. */
205 send(user->socket, (void *)telnet_force_character_mode_command,
206 telnet_force_character_mode_command_count, 0);
207
208 /* Handle messages. */
209 client_connection_message_loop(user);
210
211 telnet_user_notify_client_disconnected(user);
212 telnet_user_log(user, "Client disconnected (%" PRIxn").", iid);
213}
214
215/** Fibril for spawning the task running after user connects.
216 *
217 * @param arg Corresponding @c telnet_user_t structure.
218 */
219static int spawn_task_fibril(void *arg)
220{
221 telnet_user_t *user = arg;
222 int rc;
223
224 char term[LOC_NAME_MAXLEN];
225 snprintf(term, LOC_NAME_MAXLEN, "%s/%s", "/loc", user->service_name);
226
227 task_id_t task;
228 rc = task_spawnl(&task, APP_GETTERM, APP_GETTERM, "-w", term, APP_SHELL, NULL);
229 if (rc != EOK) {
230 telnet_user_error(user, "Spawning `%s -w %s %s' failed: %s.",
231 APP_GETTERM, term, APP_SHELL, str_error(rc));
232 fibril_mutex_lock(&user->guard);
233 user->task_finished = true;
234 fibril_condvar_signal(&user->refcount_cv);
235 fibril_mutex_unlock(&user->guard);
236 return EOK;
237 }
238
239 fibril_mutex_lock(&user->guard);
240 user->task_id = task;
241 fibril_mutex_unlock(&user->guard);
242
243 task_exit_t task_exit;
244 int task_retval;
245 task_wait(task, &task_exit, &task_retval);
246 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
247 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
248 task_retval);
249
250 /* Announce destruction. */
251 fibril_mutex_lock(&user->guard);
252 user->task_finished = true;
253 fibril_condvar_signal(&user->refcount_cv);
254 fibril_mutex_unlock(&user->guard);
255
256 return EOK;
257}
258
259/** Tell whether given user can be destroyed (has no active clients).
260 *
261 * @param user The telnet user in question.
262 */
263static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
264{
265 return user->task_finished && user->socket_closed &&
266 (user->locsrv_connection_count == 0);
267}
268
269/** Fibril for each accepted socket.
270 *
271 * @param arg Corresponding @c telnet_user_t structure.
272 */
273static int network_user_fibril(void *arg)
274{
275 telnet_user_t *user = arg;
276
277 int rc = loc_service_register(user->service_name, &user->service_id);
278 if (rc != EOK) {
279 telnet_user_error(user, "Unable to register %s with loc: %s.",
280 user->service_name, str_error(rc));
281 return EOK;
282 }
283
284 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
285 user->service_name, user->service_id);
286
287 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
288 assert(spawn_fibril);
289 fibril_add_ready(spawn_fibril);
290
291 /* Wait for all clients to exit. */
292 fibril_mutex_lock(&user->guard);
293 while (!user_can_be_destroyed_no_lock(user)) {
294 if (user->task_finished) {
295 closesocket(user->socket);
296 user->socket_closed = true;
297 continue;
298 } else if (user->socket_closed) {
299 if (user->task_id != 0) {
300 task_kill(user->task_id);
301 }
302 }
303 fibril_condvar_wait_timeout(&user->refcount_cv, &user->guard, 1000);
304 }
305 fibril_mutex_unlock(&user->guard);
306
307 rc = loc_service_unregister(user->service_id);
308 if (rc != EOK) {
309 telnet_user_error(user,
310 "Unable to unregister %s from loc: %s (ignored).",
311 user->service_name, str_error(rc));
312 }
313
314 telnet_user_log(user, "Destroying...");
315 telnet_user_destroy(user);
316
317 return EOK;
318}
319
320int main(int argc, char *argv[])
321{
322 int port = 2223;
323
324 async_set_client_connection(client_connection);
325 int rc = loc_server_register(NAME);
326 if (rc != EOK) {
327 fprintf(stderr, "%s: Unable to register server\n", NAME);
328 return rc;
329 }
330
331 struct sockaddr_in addr;
332
333 addr.sin_family = AF_INET;
334 addr.sin_port = htons(port);
335
336 rc = inet_pton(AF_INET, "127.0.0.1", (void *)
337 &addr.sin_addr.s_addr);
338 if (rc != EOK) {
339 fprintf(stderr, "Error parsing network address: %s.\n",
340 str_error(rc));
341 return 2;
342 }
343
344 int listen_sd = socket(PF_INET, SOCK_STREAM, 0);
345 if (listen_sd < 0) {
346 fprintf(stderr, "Error creating listening socket: %s.\n",
347 str_error(listen_sd));
348 return 3;
349 }
350
351 rc = bind(listen_sd, (struct sockaddr *) &addr, sizeof(addr));
352 if (rc != EOK) {
353 fprintf(stderr, "Error binding socket: %s.\n",
354 str_error(rc));
355 return 4;
356 }
357
358 rc = listen(listen_sd, BACKLOG_SIZE);
359 if (rc != EOK) {
360 fprintf(stderr, "listen() failed: %s.\n", str_error(rc));
361 return 5;
362 }
363
364 printf("%s: HelenOS Remote console service\n", NAME);
365 task_retval(0);
366
367 while (true) {
368 struct sockaddr_in raddr;
369 socklen_t raddr_len = sizeof(raddr);
370 int conn_sd = accept(listen_sd, (struct sockaddr *) &raddr,
371 &raddr_len);
372
373 if (conn_sd < 0) {
374 fprintf(stderr, "accept() failed: %s.\n",
375 str_error(rc));
376 continue;
377 }
378
379 telnet_user_t *user = telnet_user_create(conn_sd);
380 assert(user);
381
382 fid_t fid = fibril_create(network_user_fibril, user);
383 assert(fid);
384 fibril_add_ready(fid);
385 }
386
387 return 0;
388}
389
390/** @}
391 */
Note: See TracBrowser for help on using the repository browser.