Diffstat (limited to 'noncore/apps/opie-console/emulation_layer.cpp') (more/less context) (show whitespace changes)
-rw-r--r-- | noncore/apps/opie-console/emulation_layer.cpp | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/noncore/apps/opie-console/emulation_layer.cpp b/noncore/apps/opie-console/emulation_layer.cpp new file mode 100644 index 0000000..6c420e0 --- a/dev/null +++ b/noncore/apps/opie-console/emulation_layer.cpp | |||
@@ -0,0 +1,372 @@ | |||
1 | /* -------------------------------------------------------------------------- */ | ||
2 | /* */ | ||
3 | /* [emulation_layer.cpp] Terminal Emulation Decoder */ | ||
4 | /* */ | ||
5 | /* -------------------------------------------------------------------------- */ | ||
6 | /* */ | ||
7 | /* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> */ | ||
8 | /* */ | ||
9 | /* This file is part of Konsole - an X terminal for KDE */ | ||
10 | /* */ | ||
11 | /* -------------------------------------------------------------------------- */ | ||
12 | /* */ | ||
13 | /* Ported Konsole to Qt/Embedded */ | ||
14 | /* */ | ||
15 | /* Copyright (C) 2000 by John Ryland <jryland@trolltech.com> */ | ||
16 | /* */ | ||
17 | /* -------------------------------------------------------------------------- */ | ||
18 | /* */ | ||
19 | /* Modified to suit opie-console */ | ||
20 | /* */ | ||
21 | /* Copyright (C) 2002 by opie developers <opie@handhelds.org> */ | ||
22 | /* */ | ||
23 | /* -------------------------------------------------------------------------- */ | ||
24 | |||
25 | /*! \class EmulationLayer | ||
26 | |||
27 | \brief Mediator between Widget and Screen. | ||
28 | |||
29 | This class is responsible to scan the escapes sequences of the terminal | ||
30 | emulation and to map it to their corresponding semantic complements. | ||
31 | Thus this module knows mainly about decoding escapes sequences and | ||
32 | is a stateless device w.r.t. the semantics. | ||
33 | |||
34 | It is also responsible to refresh the Widget by certain rules. | ||
35 | |||
36 | \sa Widget \sa Screen | ||
37 | |||
38 | \par A note on refreshing | ||
39 | |||
40 | Although the modifications to the current screen image could immediately | ||
41 | be propagated via `Widget' to the graphical surface, we have chosen | ||
42 | another way here. | ||
43 | |||
44 | The reason for doing so is twofold. | ||
45 | |||
46 | First, experiments show that directly displaying the operation results | ||
47 | in slowing down the overall performance of emulations. Displaying | ||
48 | individual characters using X11 creates a lot of overhead. | ||
49 | |||
50 | Second, by using the following refreshing method, the screen operations | ||
51 | can be completely separated from the displaying. This greatly simplifies | ||
52 | the programmer's task of coding and maintaining the screen operations, | ||
53 | since one need not worry about differential modifications on the | ||
54 | display affecting the operation of concern. | ||
55 | |||
56 | We use a refreshing algorithm here that has been adoped from rxvt/kvt. | ||
57 | |||
58 | By this, refreshing is driven by a timer, which is (re)started whenever | ||
59 | a new bunch of data to be interpreted by the emulation arives at `onRcvBlock'. | ||
60 | As soon as no more data arrive for `BULK_TIMEOUT' milliseconds, we trigger | ||
61 | refresh. This rule suits both bulk display operation as done by curses as | ||
62 | well as individual characters typed. | ||
63 | (BULK_TIMEOUT < 1000 / max characters received from keyboard per second). | ||
64 | |||
65 | Additionally, we trigger refreshing by newlines comming in to make visual | ||
66 | snapshots of lists as produced by `cat', `ls' and likely programs, thereby | ||
67 | producing the illusion of a permanent and immediate display operation. | ||
68 | |||
69 | As a sort of catch-all needed for cases where none of the above | ||
70 | conditions catch, the screen refresh is also triggered by a count | ||
71 | of incoming bulks (`bulk_incnt'). | ||
72 | */ | ||
73 | |||
74 | /* FIXME | ||
75 | - evtl. the bulk operations could be made more transparent. | ||
76 | */ | ||
77 | |||
78 | #include "emulation_layer.h" | ||
79 | #include "widget.h" | ||
80 | #include "screen.h" | ||
81 | #include <stdio.h> | ||
82 | #include <stdlib.h> | ||
83 | #include <unistd.h> | ||
84 | #include <qkeycode.h> | ||
85 | |||
86 | |||
87 | /* ------------------------------------------------------------------------- */ | ||
88 | /* */ | ||
89 | /* EmulationLayer */ | ||
90 | /* */ | ||
91 | /* ------------------------------------------------------------------------- */ | ||
92 | |||
93 | #define CNTL(c) ((c)-'@') | ||
94 | |||
95 | /*! | ||
96 | */ | ||
97 | |||
98 | EmulationLayer::EmulationLayer(Widget* gui) | ||
99 | : decoder((QTextDecoder*)NULL) | ||
100 | { | ||
101 | this->gui = gui; | ||
102 | |||
103 | screen[0] = new Screen(gui->Lines(),gui->Columns()); | ||
104 | screen[1] = new Screen(gui->Lines(),gui->Columns()); | ||
105 | scr = screen[0]; | ||
106 | |||
107 | bulk_nlcnt = 0; // reset bulk newline counter | ||
108 | bulk_incnt = 0; // reset bulk counter | ||
109 | connected = FALSE; | ||
110 | |||
111 | QObject::connect(&bulk_timer, SIGNAL(timeout()), this, SLOT(showBulk()) ); | ||
112 | QObject::connect(gui,SIGNAL(changedImageSizeSignal(int,int)), | ||
113 | this,SLOT(onImageSizeChange(int,int))); | ||
114 | QObject::connect(gui,SIGNAL(changedHistoryCursor(int)), | ||
115 | this,SLOT(onHistoryCursorChange(int))); | ||
116 | QObject::connect(gui,SIGNAL(keyPressedSignal(QKeyEvent*)), | ||
117 | this,SLOT(onKeyPress(QKeyEvent*))); | ||
118 | QObject::connect(gui,SIGNAL(beginSelectionSignal(const int,const int)), | ||
119 | this,SLOT(onSelectionBegin(const int,const int)) ); | ||
120 | QObject::connect(gui,SIGNAL(extendSelectionSignal(const int,const int)), | ||
121 | this,SLOT(onSelectionExtend(const int,const int)) ); | ||
122 | QObject::connect(gui,SIGNAL(endSelectionSignal(const BOOL)), | ||
123 | this,SLOT(setSelection(const BOOL)) ); | ||
124 | QObject::connect(gui,SIGNAL(clearSelectionSignal()), | ||
125 | this,SLOT(clearSelection()) ); | ||
126 | } | ||
127 | |||
128 | /*! | ||
129 | */ | ||
130 | |||
131 | EmulationLayer::~EmulationLayer() | ||
132 | { | ||
133 | delete screen[0]; | ||
134 | delete screen[1]; | ||
135 | bulk_timer.stop(); | ||
136 | } | ||
137 | |||
138 | /*! change between primary and alternate screen | ||
139 | */ | ||
140 | |||
141 | void EmulationLayer::setScreen(int n) | ||
142 | { | ||
143 | scr = screen[n&1]; | ||
144 | } | ||
145 | |||
146 | void EmulationLayer::setHistory(bool on) | ||
147 | { | ||
148 | screen[0]->setScroll(on); | ||
149 | if (!connected) return; | ||
150 | showBulk(); | ||
151 | } | ||
152 | |||
153 | bool EmulationLayer::history() | ||
154 | { | ||
155 | return screen[0]->hasScroll(); | ||
156 | } | ||
157 | |||
158 | void EmulationLayer::setCodec(int c) | ||
159 | { | ||
160 | //FIXME: check whether we have to free codec | ||
161 | codec = c ? QTextCodec::codecForName("utf8") | ||
162 | : QTextCodec::codecForLocale(); | ||
163 | if (decoder) delete decoder; | ||
164 | decoder = codec->makeDecoder(); | ||
165 | } | ||
166 | |||
167 | void EmulationLayer::setKeytrans(int no) | ||
168 | { | ||
169 | keytrans = KeyTrans::find(no); | ||
170 | } | ||
171 | |||
172 | void EmulationLayer::setKeytrans(const char * no) | ||
173 | { | ||
174 | keytrans = KeyTrans::find(no); | ||
175 | } | ||
176 | |||
177 | // Interpreting Codes --------------------------------------------------------- | ||
178 | |||
179 | /* | ||
180 | This section deals with decoding the incoming character stream. | ||
181 | Decoding means here, that the stream is first seperated into `tokens' | ||
182 | which are then mapped to a `meaning' provided as operations by the | ||
183 | `Screen' class. | ||
184 | */ | ||
185 | |||
186 | /*! | ||
187 | */ | ||
188 | |||
189 | void EmulationLayer::onRcvChar(int c) | ||
190 | // process application unicode input to terminal | ||
191 | // this is a trivial scanner | ||
192 | { | ||
193 | c &= 0xff; | ||
194 | switch (c) | ||
195 | { | ||
196 | case '\b' : scr->BackSpace(); break; | ||
197 | case '\t' : scr->Tabulate(); break; | ||
198 | case '\n' : scr->NewLine(); break; | ||
199 | case '\r' : scr->Return(); break; | ||
200 | case 0x07 : gui->Bell(); break; | ||
201 | default : scr->ShowCharacter(c); break; | ||
202 | }; | ||
203 | } | ||
204 | |||
205 | /* ------------------------------------------------------------------------- */ | ||
206 | /* */ | ||
207 | /* Keyboard Handling */ | ||
208 | /* */ | ||
209 | /* ------------------------------------------------------------------------- */ | ||
210 | |||
211 | /*! | ||
212 | */ | ||
213 | |||
214 | void EmulationLayer::onKeyPress( QKeyEvent* ev ) | ||
215 | { | ||
216 | if (!connected) return; // someone else gets the keys | ||
217 | if (scr->getHistCursor() != scr->getHistLines()); | ||
218 | scr->setHistCursor(scr->getHistLines()); | ||
219 | if (!ev->text().isEmpty()) | ||
220 | { // A block of text | ||
221 | // Note that the text is proper unicode. | ||
222 | // We should do a conversion here, but since this | ||
223 | // routine will never be used, we simply emit plain ascii. | ||
224 | sendString( ev->text().ascii() ); //,ev->text().length()); | ||
225 | } | ||
226 | else if (ev->ascii()>0) | ||
227 | { | ||
228 | QByteArray c = QByteArray( 1 ); | ||
229 | c.at( 0 ) = ev->ascii(); | ||
230 | // ibot: qbytearray is emited not char* | ||
231 | emit sndBlock( (QByteArray) c ); | ||
232 | } | ||
233 | } | ||
234 | |||
235 | // Unblocking, Byte to Unicode translation --------------------------------- -- | ||
236 | |||
237 | /* | ||
238 | We are doing code conversion from locale to unicode first. | ||
239 | */ | ||
240 | |||
241 | void EmulationLayer::onRcvBlock(const QByteArray &s ) | ||
242 | { | ||
243 | bulkStart(); | ||
244 | bulk_incnt += 1; | ||
245 | for (int i = 0; i < s.size(); i++) | ||
246 | { | ||
247 | //TODO: ibot: maybe decoding qbytearray to unicode in io_layer? | ||
248 | QString result = decoder->toUnicode(&s[i],1); | ||
249 | int reslen = result.length(); | ||
250 | for (int j = 0; j < reslen; j++) | ||
251 | onRcvChar(result[j].unicode()); | ||
252 | if (s[i] == '\n') bulkNewline(); | ||
253 | } | ||
254 | bulkEnd(); | ||
255 | } | ||
256 | |||
257 | // Selection --------------------------------------------------------------- -- | ||
258 | |||
259 | void EmulationLayer::onSelectionBegin(const int x, const int y) { | ||
260 | if (!connected) return; | ||
261 | scr->setSelBeginXY(x,y); | ||
262 | showBulk(); | ||
263 | } | ||
264 | |||
265 | void EmulationLayer::onSelectionExtend(const int x, const int y) { | ||
266 | if (!connected) return; | ||
267 | scr->setSelExtentXY(x,y); | ||
268 | showBulk(); | ||
269 | } | ||
270 | |||
271 | void EmulationLayer::setSelection(const BOOL preserve_line_breaks) { | ||
272 | if (!connected) return; | ||
273 | QString t = scr->getSelText(preserve_line_breaks); | ||
274 | if (!t.isNull()) gui->setSelection(t); | ||
275 | } | ||
276 | |||
277 | void EmulationLayer::clearSelection() { | ||
278 | if (!connected) return; | ||
279 | scr->clearSelection(); | ||
280 | showBulk(); | ||
281 | } | ||
282 | |||
283 | // Refreshing -------------------------------------------------------------- -- | ||
284 | |||
285 | #define BULK_TIMEOUT 20 | ||
286 | |||
287 | /*! | ||
288 | called when \n comes in. Evtl. triggers showBulk at endBulk | ||
289 | */ | ||
290 | |||
291 | void EmulationLayer::bulkNewline() | ||
292 | { | ||
293 | bulk_nlcnt += 1; | ||
294 | bulk_incnt = 0; // reset bulk counter since `nl' rule applies | ||
295 | } | ||
296 | |||
297 | /*! | ||
298 | */ | ||
299 | |||
300 | void EmulationLayer::showBulk() | ||
301 | { | ||
302 | bulk_nlcnt = 0; // reset bulk newline counter | ||
303 | bulk_incnt = 0; // reset bulk counter | ||
304 | if (connected) | ||
305 | { | ||
306 | Character* image = scr->getCookedImage(); // get the image | ||
307 | gui->setImage(image, | ||
308 | scr->getLines(), | ||
309 | scr->getColumns()); // actual refresh | ||
310 | free(image); | ||
311 | //FIXME: check that we do not trigger other draw event here. | ||
312 | gui->setScroll(scr->getHistCursor(),scr->getHistLines()); | ||
313 | } | ||
314 | } | ||
315 | |||
316 | void EmulationLayer::bulkStart() | ||
317 | { | ||
318 | if (bulk_timer.isActive()) bulk_timer.stop(); | ||
319 | } | ||
320 | |||
321 | void EmulationLayer::bulkEnd() | ||
322 | { | ||
323 | if ( bulk_nlcnt > gui->Lines() || bulk_incnt > 20 ) | ||
324 | showBulk(); // resets bulk_??cnt to 0, too. | ||
325 | else | ||
326 | bulk_timer.start(BULK_TIMEOUT,TRUE); | ||
327 | } | ||
328 | |||
329 | void EmulationLayer::setConnect(bool c) | ||
330 | { | ||
331 | connected = c; | ||
332 | if ( connected) | ||
333 | { | ||
334 | onImageSizeChange(gui->Lines(), gui->Columns()); | ||
335 | showBulk(); | ||
336 | } | ||
337 | else | ||
338 | { | ||
339 | scr->clearSelection(); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | // --------------------------------------------------------------------------- | ||
344 | |||
345 | /*! triggered by image size change of the Widget `gui'. | ||
346 | |||
347 | This event is simply propagated to the attached screens | ||
348 | and to the related serial line. | ||
349 | */ | ||
350 | |||
351 | void EmulationLayer::onImageSizeChange(int lines, int columns) | ||
352 | { | ||
353 | if (!connected) return; | ||
354 | screen[0]->resizeImage(lines,columns); | ||
355 | screen[1]->resizeImage(lines,columns); | ||
356 | showBulk(); | ||
357 | emit ImageSizeChanged(lines,columns); // propagate event to serial line | ||
358 | } | ||
359 | |||
360 | void EmulationLayer::onHistoryCursorChange(int cursor) | ||
361 | { | ||
362 | if (!connected) return; | ||
363 | scr->setHistCursor(cursor); | ||
364 | showBulk(); | ||
365 | } | ||
366 | |||
367 | void EmulationLayer::setColumns(int columns) | ||
368 | { | ||
369 | //FIXME: this goes strange ways. | ||
370 | // Can we put this straight or explain it at least? | ||
371 | emit changeColumns(columns); | ||
372 | } | ||