Diffstat (limited to 'libopie2/opiecore/device/odevice_ipaq.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r-- | libopie2/opiecore/device/odevice_ipaq.cpp | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/libopie2/opiecore/device/odevice_ipaq.cpp b/libopie2/opiecore/device/odevice_ipaq.cpp new file mode 100644 index 0000000..d928806 --- a/dev/null +++ b/libopie2/opiecore/device/odevice_ipaq.cpp | |||
@@ -0,0 +1,524 @@ | |||
1 | /* | ||
2 | This file is part of the Opie Project | ||
3 | Copyright (C) The Opie Team <opie-devel@handhelds.org> | ||
4 | =. | ||
5 | .=l. | ||
6 | .>+-= | ||
7 | _;:, .> :=|. This program is free software; you can | ||
8 | .> <`_, > . <= redistribute it and/or modify it under | ||
9 | :`=1 )Y*s>-.-- : the terms of the GNU Library General Public | ||
10 | .="- .-=="i, .._ License as published by the Free Software | ||
11 | - . .-<_> .<> Foundation; either version 2 of the License, | ||
12 | ._= =} : or (at your option) any later version. | ||
13 | .%`+i> _;_. | ||
14 | .i_,=:_. -<s. This program is distributed in the hope that | ||
15 | + . -:. = it will be useful, but WITHOUT ANY WARRANTY; | ||
16 | : .. .:, . . . without even the implied warranty of | ||
17 | =_ + =;=|` MERCHANTABILITY or FITNESS FOR A | ||
18 | _.=:. : :=>`: PARTICULAR PURPOSE. See the GNU | ||
19 | ..}^=.= = ; Library General Public License for more | ||
20 | ++= -. .` .: details. | ||
21 | : = ...= . :.=- | ||
22 | -. .:....=;==+<; You should have received a copy of the GNU | ||
23 | -_. . . )=. = Library General Public License along with | ||
24 | -- :-=` this library; see the file COPYING.LIB. | ||
25 | If not, write to the Free Software Foundation, | ||
26 | Inc., 59 Temple Place - Suite 330, | ||
27 | Boston, MA 02111-1307, USA. | ||
28 | */ | ||
29 | |||
30 | #include "odevice.h" | ||
31 | |||
32 | /* QT */ | ||
33 | #include <qapplication.h> | ||
34 | #include <qfile.h> | ||
35 | #include <qtextstream.h> | ||
36 | #include <qwindowsystem_qws.h> | ||
37 | |||
38 | /* OPIE */ | ||
39 | #include <qpe/config.h> | ||
40 | #include <qpe/resource.h> | ||
41 | #include <qpe/sound.h> | ||
42 | #include <qpe/qcopenvelope_qws.h> | ||
43 | |||
44 | /* STD */ | ||
45 | #include <fcntl.h> | ||
46 | #include <math.h> | ||
47 | #include <stdlib.h> | ||
48 | #include <signal.h> | ||
49 | #include <sys/ioctl.h> | ||
50 | #include <sys/time.h> | ||
51 | #include <unistd.h> | ||
52 | #ifndef QT_NO_SOUND | ||
53 | #include <linux/soundcard.h> | ||
54 | #endif | ||
55 | |||
56 | #ifndef ARRAY_SIZE | ||
57 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) | ||
58 | #endif | ||
59 | |||
60 | // _IO and friends are only defined in kernel headers ... | ||
61 | |||
62 | #define OD_IOC(dir,type,number,size) (( dir << 30 ) | ( type << 8 ) | ( number ) | ( size << 16 )) | ||
63 | |||
64 | #define OD_IO(type,number) OD_IOC(0,type,number,0) | ||
65 | #define OD_IOW(type,number,size) OD_IOC(1,type,number,sizeof(size)) | ||
66 | #define OD_IOR(type,number,size) OD_IOC(2,type,number,sizeof(size)) | ||
67 | #define OD_IORW(type,number,size) OD_IOC(3,type,number,sizeof(size)) | ||
68 | |||
69 | typedef struct { | ||
70 | unsigned char OffOnBlink; /* 0=off 1=on 2=Blink */ | ||
71 | unsigned char TotalTime; /* Units of 5 seconds */ | ||
72 | unsigned char OnTime; /* units of 100m/s */ | ||
73 | unsigned char OffTime; /* units of 100m/s */ | ||
74 | } LED_IN; | ||
75 | |||
76 | typedef struct { | ||
77 | unsigned char mode; | ||
78 | unsigned char pwr; | ||
79 | unsigned char brightness; | ||
80 | } FLITE_IN; | ||
81 | |||
82 | #define LED_ON OD_IOW( 'f', 5, LED_IN ) | ||
83 | #define FLITE_ON OD_IOW( 'f', 7, FLITE_IN ) | ||
84 | |||
85 | using namespace Opie; | ||
86 | |||
87 | class iPAQ : public ODevice, public QWSServer::KeyboardFilter | ||
88 | { | ||
89 | |||
90 | protected: | ||
91 | virtual void init(); | ||
92 | virtual void initButtons(); | ||
93 | |||
94 | public: | ||
95 | virtual bool setSoftSuspend( bool soft ); | ||
96 | |||
97 | virtual bool setDisplayBrightness( int b ); | ||
98 | virtual int displayBrightnessResolution() const; | ||
99 | |||
100 | virtual void alarmSound(); | ||
101 | |||
102 | virtual QValueList <OLed> ledList() const; | ||
103 | virtual QValueList <OLedState> ledStateList( OLed led ) const; | ||
104 | virtual OLedState ledState( OLed led ) const; | ||
105 | virtual bool setLedState( OLed led, OLedState st ); | ||
106 | |||
107 | virtual bool hasLightSensor() const; | ||
108 | virtual int readLightSensor(); | ||
109 | virtual int lightSensorResolution() const; | ||
110 | |||
111 | protected: | ||
112 | virtual bool filter( int unicode, int keycode, int modifiers, bool isPress, bool autoRepeat ); | ||
113 | virtual void timerEvent( QTimerEvent *te ); | ||
114 | |||
115 | int m_power_timer; | ||
116 | |||
117 | OLedState m_leds [2]; | ||
118 | }; | ||
119 | |||
120 | struct i_button { | ||
121 | uint model; | ||
122 | Qt::Key code; | ||
123 | char *utext; | ||
124 | char *pix; | ||
125 | char *fpressedservice; | ||
126 | char *fpressedaction; | ||
127 | char *fheldservice; | ||
128 | char *fheldaction; | ||
129 | } ipaq_buttons [] = { | ||
130 | { Model_iPAQ_H31xx | Model_iPAQ_H36xx | Model_iPAQ_H37xx | Model_iPAQ_H38xx | Model_iPAQ_H39xx | Model_iPAQ_H5xxx, | ||
131 | Qt::Key_F9, QT_TRANSLATE_NOOP("Button", "Calendar Button"), | ||
132 | "devicebuttons/ipaq_calendar", | ||
133 | "datebook", "nextView()", | ||
134 | "today", "raise()" }, | ||
135 | { Model_iPAQ_H31xx | Model_iPAQ_H36xx | Model_iPAQ_H37xx | Model_iPAQ_H38xx | Model_iPAQ_H39xx | Model_iPAQ_H5xxx, | ||
136 | Qt::Key_F10, QT_TRANSLATE_NOOP("Button", "Contacts Button"), | ||
137 | "devicebuttons/ipaq_contact", | ||
138 | "addressbook", "raise()", | ||
139 | "addressbook", "beamBusinessCard()" }, | ||
140 | { Model_iPAQ_H31xx | Model_iPAQ_H36xx | Model_iPAQ_H37xx, | ||
141 | Qt::Key_F11, QT_TRANSLATE_NOOP("Button", "Menu Button"), | ||
142 | "devicebuttons/ipaq_menu", | ||
143 | "QPE/TaskBar", "toggleMenu()", | ||
144 | "QPE/TaskBar", "toggleStartMenu()" }, | ||
145 | { Model_iPAQ_H38xx | Model_iPAQ_H39xx | Model_iPAQ_H5xxx, | ||
146 | Qt::Key_F13, QT_TRANSLATE_NOOP("Button", "Mail Button"), | ||
147 | "devicebuttons/ipaq_mail", | ||
148 | "mail", "raise()", | ||
149 | "mail", "newMail()" }, | ||
150 | { Model_iPAQ_H31xx | Model_iPAQ_H36xx | Model_iPAQ_H37xx | Model_iPAQ_H38xx | Model_iPAQ_H39xx | Model_iPAQ_H5xxx, | ||
151 | Qt::Key_F12, QT_TRANSLATE_NOOP("Button", "Home Button"), | ||
152 | "devicebuttons/ipaq_home", | ||
153 | "QPE/Launcher", "home()", | ||
154 | "buttonsettings", "raise()" }, | ||
155 | { Model_iPAQ_H31xx | Model_iPAQ_H36xx | Model_iPAQ_H37xx | Model_iPAQ_H38xx | Model_iPAQ_H39xx | Model_iPAQ_H5xxx, | ||
156 | Qt::Key_F24, QT_TRANSLATE_NOOP("Button", "Record Button"), | ||
157 | "devicebuttons/ipaq_record", | ||
158 | "QPE/VMemo", "toggleRecord()", | ||
159 | "sound", "raise()" }, | ||
160 | }; | ||
161 | |||
162 | void iPAQ::init() | ||
163 | { | ||
164 | d->m_vendorstr = "HP"; | ||
165 | d->m_vendor = Vendor_HP; | ||
166 | |||
167 | QFile f ( "/proc/hal/model" ); | ||
168 | |||
169 | if ( f. open ( IO_ReadOnly )) { | ||
170 | QTextStream ts ( &f ); | ||
171 | |||
172 | d->m_modelstr = "H" + ts. readLine(); | ||
173 | |||
174 | if ( d->m_modelstr == "H3100" ) | ||
175 | d->m_model = Model_iPAQ_H31xx; | ||
176 | else if ( d->m_modelstr == "H3600" ) | ||
177 | d->m_model = Model_iPAQ_H36xx; | ||
178 | else if ( d->m_modelstr == "H3700" ) | ||
179 | d->m_model = Model_iPAQ_H37xx; | ||
180 | else if ( d->m_modelstr == "H3800" ) | ||
181 | d->m_model = Model_iPAQ_H38xx; | ||
182 | else if ( d->m_modelstr == "H3900" ) | ||
183 | d->m_model = Model_iPAQ_H39xx; | ||
184 | else if ( d->m_modelstr == "H5400" ) | ||
185 | d->m_model = Model_iPAQ_H5xxx; | ||
186 | else | ||
187 | d->m_model = Model_Unknown; | ||
188 | |||
189 | f. close(); | ||
190 | } | ||
191 | |||
192 | switch ( d->m_model ) { | ||
193 | case Model_iPAQ_H31xx: | ||
194 | case Model_iPAQ_H38xx: | ||
195 | d->m_rotation = Rot90; | ||
196 | break; | ||
197 | case Model_iPAQ_H36xx: | ||
198 | case Model_iPAQ_H37xx: | ||
199 | case Model_iPAQ_H39xx: | ||
200 | |||
201 | default: | ||
202 | d->m_rotation = Rot270; | ||
203 | break; | ||
204 | case Model_iPAQ_H5xxx: | ||
205 | d->m_rotation = Rot0; | ||
206 | } | ||
207 | |||
208 | f. setName ( "/etc/familiar-version" ); | ||
209 | if ( f. open ( IO_ReadOnly )) { | ||
210 | d->m_systemstr = "Familiar"; | ||
211 | d->m_system = System_Familiar; | ||
212 | |||
213 | QTextStream ts ( &f ); | ||
214 | d->m_sysverstr = ts. readLine(). mid ( 10 ); | ||
215 | |||
216 | f. close(); | ||
217 | } else { | ||
218 | f. setName ( "/etc/oz_version" ); | ||
219 | |||
220 | if ( f. open ( IO_ReadOnly )) { | ||
221 | d->m_systemstr = "OpenEmbedded/iPaq"; | ||
222 | d->m_system = System_Familiar; | ||
223 | |||
224 | QTextStream ts ( &f ); | ||
225 | ts.setDevice ( &f ); | ||
226 | d->m_sysverstr = ts. readLine(); | ||
227 | f. close(); | ||
228 | } | ||
229 | } | ||
230 | |||
231 | m_leds [0] = m_leds [1] = Led_Off; | ||
232 | |||
233 | m_power_timer = 0; | ||
234 | |||
235 | } | ||
236 | |||
237 | void iPAQ::initButtons() | ||
238 | { | ||
239 | if ( d->m_buttons ) | ||
240 | return; | ||
241 | |||
242 | if ( isQWS( ) ) | ||
243 | QWSServer::setKeyboardFilter ( this ); | ||
244 | |||
245 | d->m_buttons = new QValueList <ODeviceButton>; | ||
246 | |||
247 | for ( uint i = 0; i < ( sizeof( ipaq_buttons ) / sizeof( i_button )); i++ ) { | ||
248 | i_button *ib = ipaq_buttons + i; | ||
249 | ODeviceButton b; | ||
250 | |||
251 | if (( ib->model & d->m_model ) == d->m_model ) { | ||
252 | b. setKeycode ( ib->code ); | ||
253 | b. setUserText ( QObject::tr ( "Button", ib->utext )); | ||
254 | b. setPixmap ( Resource::loadPixmap ( ib->pix )); | ||
255 | b. setFactoryPresetPressedAction ( OQCopMessage ( makeChannel ( ib->fpressedservice ), ib->fpressedaction )); | ||
256 | b. setFactoryPresetHeldAction ( OQCopMessage ( makeChannel ( ib->fheldservice ), ib->fheldaction )); | ||
257 | |||
258 | d->m_buttons->append ( b ); | ||
259 | } | ||
260 | } | ||
261 | reloadButtonMapping(); | ||
262 | |||
263 | QCopChannel *sysch = new QCopChannel ( "QPE/System", this ); | ||
264 | connect ( sysch, SIGNAL( received( const QCString &, const QByteArray & )), this, SLOT( systemMessage ( const QCString &, const QByteArray & ))); | ||
265 | } | ||
266 | |||
267 | QValueList <OLed> iPAQ::ledList() const | ||
268 | { | ||
269 | QValueList <OLed> vl; | ||
270 | vl << Led_Power; | ||
271 | |||
272 | if ( d->m_model == Model_iPAQ_H38xx ) | ||
273 | vl << Led_BlueTooth; | ||
274 | return vl; | ||
275 | } | ||
276 | |||
277 | QValueList <OLedState> iPAQ::ledStateList ( OLed l ) const | ||
278 | { | ||
279 | QValueList <OLedState> vl; | ||
280 | |||
281 | if ( l == Led_Power ) | ||
282 | vl << Led_Off << Led_On << Led_BlinkSlow << Led_BlinkFast; | ||
283 | else if ( l == Led_BlueTooth && d->m_model == Model_iPAQ_H38xx ) | ||
284 | vl << Led_Off; // << Led_On << ??? | ||
285 | |||
286 | return vl; | ||
287 | } | ||
288 | |||
289 | OLedState iPAQ::ledState ( OLed l ) const | ||
290 | { | ||
291 | switch ( l ) { | ||
292 | case Led_Power: | ||
293 | return m_leds [0]; | ||
294 | case Led_BlueTooth: | ||
295 | return m_leds [1]; | ||
296 | default: | ||
297 | return Led_Off; | ||
298 | } | ||
299 | } | ||
300 | |||
301 | bool iPAQ::setLedState ( OLed l, OLedState st ) | ||
302 | { | ||
303 | static int fd = ::open ( "/dev/touchscreen/0", O_RDWR | O_NONBLOCK ); | ||
304 | |||
305 | if ( l == Led_Power ) { | ||
306 | if ( fd >= 0 ) { | ||
307 | LED_IN leds; | ||
308 | ::memset ( &leds, 0, sizeof( leds )); | ||
309 | leds. TotalTime = 0; | ||
310 | leds. OnTime = 0; | ||
311 | leds. OffTime = 1; | ||
312 | leds. OffOnBlink = 2; | ||
313 | |||
314 | switch ( st ) { | ||
315 | case Led_Off : leds. OffOnBlink = 0; break; | ||
316 | case Led_On : leds. OffOnBlink = 1; break; | ||
317 | case Led_BlinkSlow: leds. OnTime = 10; leds. OffTime = 10; break; | ||
318 | case Led_BlinkFast: leds. OnTime = 5; leds. OffTime = 5; break; | ||
319 | } | ||
320 | |||
321 | if ( ::ioctl ( fd, LED_ON, &leds ) >= 0 ) { | ||
322 | m_leds [0] = st; | ||
323 | return true; | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | return false; | ||
328 | } | ||
329 | |||
330 | |||
331 | bool iPAQ::filter ( int /*unicode*/, int keycode, int modifiers, bool isPress, bool autoRepeat ) | ||
332 | { | ||
333 | int newkeycode = keycode; | ||
334 | |||
335 | switch ( keycode ) { | ||
336 | // H38xx/H39xx have no "Q" key anymore - this is now the Mail key | ||
337 | case HardKey_Menu: { | ||
338 | if (( d->m_model == Model_iPAQ_H38xx ) || | ||
339 | ( d->m_model == Model_iPAQ_H39xx ) || | ||
340 | ( d->m_model == Model_iPAQ_H5xxx)) { | ||
341 | newkeycode = HardKey_Mail; | ||
342 | } | ||
343 | break; | ||
344 | } | ||
345 | |||
346 | // Rotate cursor keys 180° | ||
347 | case Key_Left : | ||
348 | case Key_Right: | ||
349 | case Key_Up : | ||
350 | case Key_Down : { | ||
351 | if (( d->m_model == Model_iPAQ_H31xx ) || | ||
352 | ( d->m_model == Model_iPAQ_H38xx )) { | ||
353 | newkeycode = Key_Left + ( keycode - Key_Left + 2 ) % 4; | ||
354 | } | ||
355 | break; | ||
356 | } | ||
357 | |||
358 | // map Power Button short/long press to F34/F35 | ||
359 | case Key_SysReq: { | ||
360 | if ( isPress ) { | ||
361 | if ( m_power_timer ) | ||
362 | killTimer ( m_power_timer ); | ||
363 | m_power_timer = startTimer ( 500 ); | ||
364 | } | ||
365 | else if ( m_power_timer ) { | ||
366 | killTimer ( m_power_timer ); | ||
367 | m_power_timer = 0; | ||
368 | QWSServer::sendKeyEvent ( -1, HardKey_Suspend, 0, true, false ); | ||
369 | QWSServer::sendKeyEvent ( -1, HardKey_Suspend, 0, false, false ); | ||
370 | } | ||
371 | newkeycode = Key_unknown; | ||
372 | break; | ||
373 | } | ||
374 | } | ||
375 | |||
376 | if ( newkeycode != keycode ) { | ||
377 | if ( newkeycode != Key_unknown ) | ||
378 | QWSServer::sendKeyEvent ( -1, newkeycode, modifiers, isPress, autoRepeat ); | ||
379 | return true; | ||
380 | } | ||
381 | else | ||
382 | return false; | ||
383 | } | ||
384 | |||
385 | void iPAQ::timerEvent ( QTimerEvent * ) | ||
386 | { | ||
387 | killTimer ( m_power_timer ); | ||
388 | m_power_timer = 0; | ||
389 | QWSServer::sendKeyEvent ( -1, HardKey_Backlight, 0, true, false ); | ||
390 | QWSServer::sendKeyEvent ( -1, HardKey_Backlight, 0, false, false ); | ||
391 | } | ||
392 | |||
393 | |||
394 | void iPAQ::alarmSound() | ||
395 | { | ||
396 | #ifndef QT_NO_SOUND | ||
397 | static Sound snd ( "alarm" ); | ||
398 | int fd; | ||
399 | int vol; | ||
400 | bool vol_reset = false; | ||
401 | |||
402 | if (( fd = ::open ( "/dev/sound/mixer", O_RDWR )) >= 0 ) { | ||
403 | if ( ::ioctl ( fd, MIXER_READ( 0 ), &vol ) >= 0 ) { | ||
404 | Config cfg ( "qpe" ); | ||
405 | cfg. setGroup ( "Volume" ); | ||
406 | |||
407 | int volalarm = cfg. readNumEntry ( "AlarmPercent", 50 ); | ||
408 | if ( volalarm < 0 ) | ||
409 | volalarm = 0; | ||
410 | else if ( volalarm > 100 ) | ||
411 | volalarm = 100; | ||
412 | volalarm |= ( volalarm << 8 ); | ||
413 | |||
414 | if ( ::ioctl ( fd, MIXER_WRITE( 0 ), &volalarm ) >= 0 ) | ||
415 | vol_reset = true; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | snd. play(); | ||
420 | while ( !snd. isFinished()) | ||
421 | qApp->processEvents(); | ||
422 | |||
423 | if ( fd >= 0 ) { | ||
424 | if ( vol_reset ) | ||
425 | ::ioctl ( fd, MIXER_WRITE( 0 ), &vol ); | ||
426 | ::close ( fd ); | ||
427 | } | ||
428 | #endif | ||
429 | } | ||
430 | |||
431 | |||
432 | bool iPAQ::setSoftSuspend ( bool soft ) | ||
433 | { | ||
434 | bool res = false; | ||
435 | int fd; | ||
436 | |||
437 | if (( fd = ::open ( "/proc/sys/ts/suspend_button_mode", O_WRONLY )) >= 0 ) { | ||
438 | if ( ::write ( fd, soft ? "1" : "0", 1 ) == 1 ) | ||
439 | res = true; | ||
440 | else | ||
441 | ::perror ( "write to /proc/sys/ts/suspend_button_mode" ); | ||
442 | |||
443 | ::close ( fd ); | ||
444 | } | ||
445 | else | ||
446 | ::perror ( "/proc/sys/ts/suspend_button_mode" ); | ||
447 | |||
448 | return res; | ||
449 | } | ||
450 | |||
451 | |||
452 | bool iPAQ::setDisplayBrightness ( int bright ) | ||
453 | { | ||
454 | bool res = false; | ||
455 | int fd; | ||
456 | |||
457 | if ( bright > 255 ) | ||
458 | bright = 255; | ||
459 | if ( bright < 0 ) | ||
460 | bright = 0; | ||
461 | |||
462 | if (( fd = ::open ( "/dev/touchscreen/0", O_WRONLY )) >= 0 ) { | ||
463 | FLITE_IN bl; | ||
464 | bl. mode = 1; | ||
465 | bl. pwr = bright ? 1 : 0; | ||
466 | bl. brightness = ( bright * ( displayBrightnessResolution() - 1 ) + 127 ) / 255; | ||
467 | res = ( ::ioctl ( fd, FLITE_ON, &bl ) == 0 ); | ||
468 | ::close ( fd ); | ||
469 | } | ||
470 | return res; | ||
471 | } | ||
472 | |||
473 | int iPAQ::displayBrightnessResolution() const | ||
474 | { | ||
475 | switch ( model()) { | ||
476 | case Model_iPAQ_H31xx: | ||
477 | case Model_iPAQ_H36xx: | ||
478 | case Model_iPAQ_H37xx: | ||
479 | return 128; // really 256, but >128 could damage the LCD | ||
480 | |||
481 | case Model_iPAQ_H38xx: | ||
482 | case Model_iPAQ_H39xx: | ||
483 | return 64; | ||
484 | case Model_iPAQ_H5xxx: | ||
485 | return 255; | ||
486 | |||
487 | default: | ||
488 | return 2; | ||
489 | } | ||
490 | } | ||
491 | |||
492 | |||
493 | bool iPAQ::hasLightSensor() const | ||
494 | { | ||
495 | return true; | ||
496 | } | ||
497 | |||
498 | int iPAQ::readLightSensor() | ||
499 | { | ||
500 | int fd; | ||
501 | int val = -1; | ||
502 | |||
503 | if (( fd = ::open ( "/proc/hal/light_sensor", O_RDONLY )) >= 0 ) { | ||
504 | char buffer [8]; | ||
505 | |||
506 | if ( ::read ( fd, buffer, 5 ) == 5 ) { | ||
507 | char *endptr; | ||
508 | |||
509 | buffer [4] = 0; | ||
510 | val = ::strtol ( buffer + 2, &endptr, 16 ); | ||
511 | |||
512 | if ( *endptr != 0 ) | ||
513 | val = -1; | ||
514 | } | ||
515 | ::close ( fd ); | ||
516 | } | ||
517 | |||
518 | return val; | ||
519 | } | ||
520 | |||
521 | int iPAQ::lightSensorResolution() const | ||
522 | { | ||
523 | return 256; | ||
524 | } | ||