1/*
2 * Copyright (c) 2008, 2009 Apple Computer, Inc.  All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <stdbool.h>
24#include <stdlib.h>
25#include <ctype.h>
26#include <string.h>
27#include <curses.h>
28#include <fcntl.h>
29#include <errno.h>
30#include <sys/types.h>
31#include <sys/uio.h>
32#include <unistd.h>
33#include "preferences.h"
34#include "userinput.h"
35#include "userinput_mode.h"
36#include "userinput_sleep.h"
37#include "userinput_order.h"
38#include "userinput_secondary_order.h"
39#include "userinput_user.h"
40#include "userinput_signal.h"
41#include "userinput_help.h"
42#include "top.h"
43
44static struct user_input_state *current_state = NULL;
45
46static void reset_state(struct user_input_state *s) {
47    s->buf[0] = '\0';
48    s->offset = 0;
49}
50
51static void completion(void *tinst, struct user_input_state *s) {
52    reset_state(s);
53    current_state = NULL;
54}
55
56/* This displays an error for 1 draw interval. */
57static void error_draw(void *tinst, struct user_input_state *s, WINDOW *win,
58		       int row, int column) {
59    char display[60];
60
61    if(s->misc > 1) {
62	s->completion(tinst, s);
63	return;
64    }
65
66    if(-1 == snprintf(display, sizeof(display), "error: %s", s->buf))
67	return;
68
69    mvwaddstr(win, row, column, display);
70
71    s->misc++;
72}
73
74static struct user_input_state error_state = {
75    .offset = 0,
76    .completion = completion,
77    .draw = error_draw
78};
79
80void user_input_set_error_state(const char *err) {
81    current_state = &error_state;
82    reset_state(current_state);
83    current_state->misc = 0;
84    strncpy(current_state->buf, err, sizeof(current_state->buf));
85    current_state->buf[sizeof(current_state->buf) - 1] = '\0';
86}
87
88/* This displays status output for 1 draw interval. */
89static void status_draw(void *tinst, struct user_input_state *s, WINDOW *win,
90			int row, int column) {
91    if(s->misc > 1) {
92	s->completion(tinst, s);
93	return;
94    }
95
96    mvwaddstr(win, row, column, s->buf);
97
98    s->misc++;
99}
100
101
102static struct user_input_state status_state = {
103    .offset = 0,
104    .completion = completion,
105    .draw = status_draw
106};
107
108void user_input_set_status_state(const char *status) {
109    current_state = &status_state;
110    reset_state(current_state);
111    current_state->misc = 0;
112    strncpy(current_state->buf, status, sizeof(current_state->buf));
113    current_state->buf[sizeof(current_state->buf) - 1] = '\0';
114}
115
116
117void user_input_set_state(struct user_input_state *state) {
118    current_state = state;
119}
120
121/* Process stdin data and return true if any was processed. */
122bool user_input_process(void *tinst) {
123
124    if(&error_state == current_state) {
125	/* Sleep a bit in the error state, before switching. */
126	usleep(500000);
127	return false;
128    }
129
130    if(&status_state == current_state) {
131	/* Return until the status state has completed its display. */
132	return false;
133    }
134
135    int c = getch();
136
137    if(ERR == c) {
138	int flags, modflags, readerrno = 0;
139	char tmp;
140	ssize_t r;
141
142	/* Get stdin's flags. */
143	flags = fcntl(STDIN_FILENO, F_GETFL, 0);
144
145	/* Make stdin non-blocking. */
146	modflags = flags | O_NONBLOCK;
147
148	if(-1 == fcntl(STDIN_FILENO, F_SETFL, modflags)) {
149	    perror("fcntl");
150	    exit(EXIT_FAILURE);
151	}
152
153	r = read(STDIN_FILENO, &tmp, sizeof(tmp));
154
155	readerrno = errno;
156
157	if(0 == r) {
158	    /* An EOF occurred, so top should exit. */
159	    exit(EXIT_FAILURE);
160	}
161
162	/* Restore stdin's old flags. */
163	if(-1 == fcntl(STDIN_FILENO, F_SETFL, flags)) {
164	    perror("fcntl");
165	    exit(EXIT_FAILURE);
166	}
167
168	if(-1 == r && EAGAIN == readerrno) {
169	    /*
170	     * The read above returned an error, but it was due to the
171	     * O_NONBLOCK with no available data.
172	     */
173	    return false;
174	}
175
176	/* Set the character we read. */
177	c = tmp;
178
179	/* Fall through and handle the character. */
180    }
181
182    if(current_state) {
183	if(/*del*/ 127 == c) {
184	    if(current_state->offset > 0) {
185		current_state->offset--;
186		current_state->buf[current_state->offset] = '\0';
187	    }
188	} else if('\r' == c) {
189	    current_state->buf[current_state->offset] = '\0';
190	    current_state->completion(tinst, current_state);
191
192	    /*
193	     * The user changing an interactive option triggers the display
194	     * of new data.
195	     */
196	    top_insert(tinst);
197	    top_draw(tinst);
198	} else {
199 	    if(current_state->offset < (sizeof(current_state->buf) - 2)) {
200		current_state->buf[current_state->offset++] = c;
201		current_state->buf[current_state->offset] = '\0';
202	    }
203	}
204
205	return true;
206    }
207
208    switch(c) {
209    case '?':
210	user_input_set_state(&top_user_input_help_state);
211	reset_state(current_state);
212	break;
213
214    case 'c':
215	user_input_set_state(&top_user_input_mode_state);
216	reset_state(current_state);
217	break;
218
219    case 'o':
220	user_input_set_state(&top_user_input_order_state);
221	reset_state(current_state);
222	break;
223
224    case 'O':
225	user_input_set_state(&top_user_input_secondary_order_state);
226	reset_state(current_state);
227	break;
228
229    case 'r':
230	top_prefs_set_mmr(!top_prefs_get_mmr());
231
232	user_input_set_status_state(top_prefs_get_mmr() ?
233				    "Report process memory object maps."
234				    :
235				    "Do not report process memory object maps.");
236	break;
237
238    case 's':
239	user_input_set_state(&top_user_input_sleep_state);
240	reset_state(current_state);
241	break;
242
243    case 'S':
244	user_input_set_state(&top_user_input_signal_state);
245	reset_state(current_state);
246	break;
247
248    case 'U':
249	user_input_set_state(&top_user_input_user_state);
250	reset_state(current_state);
251	break;
252
253    case 'q':
254    case 'Q':
255	exit(EXIT_SUCCESS);
256	break;
257
258    default:
259	if(current_state) {
260	    reset_state(current_state);
261	    return true;
262	}
263    }
264
265    if(/*^L*/ '\x0c' == c || /*space*/ ' ' == c || '\r' == c)  {
266	/* Trigger a new insert regardless of the interval. */
267	top_insert(tinst);
268	top_draw(tinst);
269    }
270
271    return true;
272}
273
274static int row, column;
275void user_input_set_position(int r, int c) {
276    row = r;
277    column = c;
278}
279
280void user_input_draw(void *tinst, WINDOW *win) {
281    if(current_state && current_state->draw) {
282	current_state->draw(tinst, current_state, win, row, column);
283    }
284}
285