1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qmouse_qws.h"
43 #include "qwindowsystem_qws.h"
44 #include "qscreen_qws.h"
45 #include "qapplication.h"
46 #include "qtextstream.h"
47 #include "qfile.h"
48 #include "qdebug.h"
49 #include "qscreen_qws.h"
50 
51 QT_BEGIN_NAMESPACE
52 
53 /*!
54     \class QWSPointerCalibrationData
55     \ingroup qws
56 
57     \brief The QWSPointerCalibrationData class is a container for
58     mouse calibration data in Qt for Embedded Linux.
59 
60     Note that this class is only available in \l{Qt for Embedded Linux}.
61 
62     QWSPointerCalibrationData stores device and screen coordinates in
63     the devPoints and screenPoints variables, respectively.
64 
65     A calibration program should create a QWSPointerCalibrationData
66     object, fill the devPoints and screenPoints variables with its
67     device and screen coordinates, and pass the object to the mouse
68     driver using the QWSMouseHandler::calibrate() function.
69 
70     \sa QWSCalibratedMouseHandler, {Mouse Calibration Example}
71 */
72 
73 /*!
74     \variable QWSPointerCalibrationData::devPoints
75     \brief the raw device coordinates for each value of the Location enum.
76 */
77 
78 /*!
79     \variable QWSPointerCalibrationData::screenPoints
80     \brief the logical screen coordinates for each value of the Location enum.
81 */
82 
83 /*!
84     \enum QWSPointerCalibrationData::Location
85 
86     This enum describes the various logical positions that can be
87     specified by the devPoints and screenPoints variables.
88 
89     \value TopLeft           Index of the top left corner of the screen.
90     \value BottomLeft     Index of the bottom left corner of the screen.
91     \value BottomRight   Index of the bottom right corner of the screen.
92     \value TopRight         Index of the top right corner of the screen.
93     \value Center            Index of the center of the screen.
94     \value LastLocation   Last index in the pointer arrays.
95 */
96 
97 class QWSMouseHandlerPrivate
98 {
99 public:
QWSMouseHandlerPrivate()100     QWSMouseHandlerPrivate() : screen(qt_screen) {}
101 
102     const QScreen *screen;
103 };
104 
105 /*!
106     \class QWSMouseHandler
107     \ingroup qws
108 
109     \brief The QWSMouseHandler class is a base class for mouse drivers in
110     Qt for Embedded Linux.
111 
112     Note that this class is only available in \l{Qt for Embedded Linux}.
113 
114     \l{Qt for Embedded Linux} provides ready-made drivers for several mouse
115     protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer
116     handling} documentation for details. Custom mouse drivers can be
117     implemented by subclassing the QWSMouseHandler class and creating
118     a mouse driver plugin (derived from QMouseDriverPlugin).
119     The default implementation of the QMouseDriverFactory class
120     will automatically detect the plugin, and load the driver into the
121     server application at run-time using Qt's \l {How to Create Qt
122     Plugins}{plugin system}.
123 
124     The mouse driver receives mouse events from the system device and
125     encapsulates each event with an instance of the QWSEvent class
126     which it then passes to the server application (the server is
127     responsible for propagating the event to the appropriate
128     client). To receive mouse events, a QWSMouseHandler object will
129     usually create a QSocketNotifier object for the given device. The
130     QSocketNotifier class provides support for monitoring activity on
131     a file descriptor. When the socket notifier receives data, it will
132     call the mouse driver's mouseChanged() function to send the event
133     to the \l{Qt for Embedded Linux} server application for relaying to
134     clients.
135 
136     If you are creating a driver for a device that needs calibration
137     or noise reduction, such as a touchscreen, use the
138     QWSCalibratedMouseHandler subclass instead to take advantage of
139     the calibrate() and clearCalibration() functions. The \l
140     {qws/mousecalibration}{Mouse Calibration}
141     demonstrates how to write a simple program using the mechanisms
142     provided by the QWSMouseHandler class to calibrate a mouse driver.
143 
144     Note that when deriving from the QWSMouseHandler class, the
145     resume() and suspend() functions must be reimplemented to control
146     the flow of mouse input, i.e., the default implementation does
147     nothing. Reimplementations of these functions typically call the
148     QSocketNotifier::setEnabled() function to enable or disable the
149     socket notifier, respectively.
150 
151     In addition, QWSMouseHandler provides the setScreen() function
152     that allows you to specify a screen for your mouse driver and the
153     limitToScreen() function that ensures that a given position is
154     within this screen's boundaries (changing the position if
155     necessary). Finally, QWSMouseHandler provides the pos() function
156     returning the current mouse position.
157 
158     \sa QMouseDriverPlugin, QMouseDriverFactory, {Qt for Embedded Linux Pointer
159     Handling}
160 */
161 
162 
163 /*!
164     \fn void QWSMouseHandler::suspend()
165 
166     Implement this function to suspend reading and handling of mouse
167     events, e.g., call the QSocketNotifier::setEnabled() function to
168     disable the socket notifier.
169 
170     \sa resume()
171 */
172 
173 /*!
174     \fn void QWSMouseHandler::resume()
175 
176     Implement this function to resume reading and handling mouse
177     events, e.g., call the QSocketNotifier::setEnabled() function to
178     enable the socket notifier.
179 
180     \sa suspend()
181 */
182 
183 /*!
184     \fn const QPoint &QWSMouseHandler::pos() const
185 
186     Returns the current mouse position.
187 
188     \sa mouseChanged(), limitToScreen()
189 */
190 
191 /*!
192     Constructs a mouse driver. The \a driver and \a device arguments
193     are passed by the QWS_MOUSE_PROTO environment variable.
194 
195     Call the QWSServer::setMouseHandler() function to make the newly
196     created mouse driver, the primary driver. Note that the primary
197     driver is controlled by the system, i.e., the system will delete
198     it upon exit.
199 */
QWSMouseHandler(const QString &,const QString &)200 QWSMouseHandler::QWSMouseHandler(const QString &, const QString &)
201     : mousePos(QWSServer::mousePosition), d_ptr(new QWSMouseHandlerPrivate)
202 {
203 }
204 
205 /*!
206     Destroys this mouse driver.
207 
208     Do not call this function if this driver is the primary mouse
209     driver, i.e., if QWSServer::setMouseHandler() function has been
210     called passing this driver as argument. The primary mouse
211     driver is deleted by the system.
212 */
~QWSMouseHandler()213 QWSMouseHandler::~QWSMouseHandler()
214 {
215     delete d_ptr;
216 }
217 
218 /*!
219     Ensures that the given \a position is within the screen's
220     boundaries, changing the \a position if necessary.
221 
222     \sa pos(), setScreen()
223 */
224 
limitToScreen(QPoint & position)225 void QWSMouseHandler::limitToScreen(QPoint &position)
226 {
227     position.setX(qMin(d_ptr->screen->deviceWidth() - 1, qMax(0, position.x())));
228     position.setY(qMin(d_ptr->screen->deviceHeight() - 1, qMax(0, position.y())));
229 }
230 
231 /*!
232     \since 4.2
233 
234     Sets the screen for this mouse driver to be the given \a screen.
235 
236     \sa limitToScreen()
237 */
setScreen(const QScreen * screen)238 void QWSMouseHandler::setScreen(const QScreen *screen)
239 {
240     d_ptr->screen = (screen ? screen : qt_screen);
241 }
242 
243 /*!
244     Notifies the system of a new mouse event.
245 
246     This function updates the current mouse position and sends the
247     event to the \l{Qt for Embedded Linux} server application for
248     delivery to the correct widget. Note that a custom mouse driver must call
249     this function whenever it wants to deliver a new mouse event.
250 
251     The given \a position is the global position of the mouse cursor.
252     The \a state parameter is a bitmask of the Qt::MouseButton enum's
253     values, indicating which mouse buttons are pressed. The \a wheel
254     parameter is the delta value of the mouse wheel as returned by
255     QWheelEvent::delta().
256 
257     \sa pos()
258 */
mouseChanged(const QPoint & position,int state,int wheel)259 void QWSMouseHandler::mouseChanged(const QPoint &position, int state, int wheel)
260 {
261     mousePos = position + d_ptr->screen->offset();
262     QWSServer::sendMouseEvent(mousePos, state, wheel);
263 }
264 
265 /*!
266     \fn QWSMouseHandler::clearCalibration()
267 
268     This virtual function allows subclasses of QWSMouseHandler to
269     clear the calibration information. Note that the default
270     implementation does nothing.
271 
272     \sa QWSCalibratedMouseHandler::clearCalibration(), calibrate()
273 */
274 
275 /*!
276     \fn QWSMouseHandler::calibrate(const QWSPointerCalibrationData *data)
277 
278     This virtual function allows subclasses of QWSMouseHandler to set
279     the calibration information passed in the given \a data. Note that
280     the default implementation does nothing.
281 
282     \sa QWSCalibratedMouseHandler::calibrate(), clearCalibration()
283 */
284 
285 /*! \fn QWSMouseHandler::getCalibration(QWSPointerCalibrationData *data) const
286     This virtual function allows subclasses of QWSMouseHandler
287     to fill in the device coordinates in \a data with values
288     that correspond to screen coordinates that are already in
289     \a data. Note that the default implementation does nothing.
290  */
291 
292 /*!
293     \class QWSCalibratedMouseHandler
294     \ingroup qws
295 
296     \brief The QWSCalibratedMouseHandler class provides mouse
297     calibration and noise reduction in Qt for Embedded Linux.
298 
299     Note that this class is only available in \l{Qt for Embedded Linux}.
300 
301     \l{Qt for Embedded Linux} provides ready-made drivers for several mouse
302     protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer
303     handling} documentation for details. In general, custom mouse
304     drivers can be implemented by subclassing the QWSMouseHandler
305     class. But when the system device does not have a fixed mapping
306     between device and screen coordinates and/or produces noisy events
307     (e.g., a touchscreen), you should derive from the
308     QWSCalibratedMouseHandler class instead to take advantage of its
309     calibration functionality. As always, you must also create a mouse
310     driver plugin (derived from QMouseDriverPlugin);
311     the implementation of the QMouseDriverFactory class will then
312     automatically detect the plugin, and load the driver into the
313     server application at run-time using Qt's
314     \l{How to Create Qt Plugins}{plugin system}.
315 
316     QWSCalibratedMouseHandler provides an implementation of the
317     calibrate() function to update the calibration parameters based on
318     coordinate mapping of the given calibration data. The calibration
319     data is represented by an QWSPointerCalibrationData object. The
320     linear transformation between device coordinates and screen
321     coordinates is performed by calling the transform() function
322     explicitly on the points passed to the
323     QWSMouseHandler::mouseChanged() function. Use the
324     clearCalibration() function to make the mouse driver return mouse
325     events in raw device coordinates and not in screen coordinates.
326 
327     The calibration parameters are recalculated whenever calibrate()
328     is called, and they can be stored using the writeCalibration()
329     function. Previously written parameters can be retrieved at any
330     time using the readCalibration() function (calibration parameters
331     are always read when the class is instantiated). Note that the
332     calibration parameters is written to and read from the file
333     currently specified by the POINTERCAL_FILE environment variable;
334     the default file is \c /etc/pointercal.
335 
336     To achieve noise reduction, QWSCalibratedMouseHandler provides the
337     sendFiltered() function. Use this function instead of
338     mouseChanged() whenever a mouse event occurs. The filter's size
339     can be manipulated using the setFilterSize() function.
340 
341     \sa QWSMouseHandler, QWSPointerCalibrationData,
342     {Mouse Calibration Example}
343 */
344 
345 
346 /*!
347     \internal
348  */
349 
QWSCalibratedMouseHandler(const QString &,const QString &)350 QWSCalibratedMouseHandler::QWSCalibratedMouseHandler(const QString &, const QString &)
351     : samples(5), currSample(0), numSamples(0)
352 {
353     clearCalibration();
354     readCalibration();
355 }
356 
357 /*!
358     Fills \a cd with the device coordinates corresponding to the given
359     screen coordinates.
360 
361     \internal
362 */
getCalibration(QWSPointerCalibrationData * cd) const363 void QWSCalibratedMouseHandler::getCalibration(QWSPointerCalibrationData *cd) const
364 {
365     const qint64 scale = qint64(a) * qint64(e) - qint64(b) * qint64(d);
366     const qint64 xOff = qint64(b) * qint64(f) - qint64(c) * qint64(e);
367     const qint64 yOff = qint64(c) * qint64(d) - qint64(a) * qint64(f);
368     for (int i = 0; i <= QWSPointerCalibrationData::LastLocation; ++i) {
369         const qint64 sX = cd->screenPoints[i].x();
370         const qint64 sY = cd->screenPoints[i].y();
371         const qint64 dX = (s*(e*sX - b*sY) + xOff) / scale;
372         const qint64 dY = (s*(a*sY - d*sX) + yOff) / scale;
373         cd->devPoints[i] = QPoint(dX, dY);
374     }
375 }
376 
377 /*!
378     Clears the current calibration, i.e., makes the mouse
379     driver return mouse events in raw device coordinates instead of
380     screen coordinates.
381 
382     \sa calibrate()
383 */
clearCalibration()384 void QWSCalibratedMouseHandler::clearCalibration()
385 {
386     a = 1;
387     b = 0;
388     c = 0;
389     d = 0;
390     e = 1;
391     f = 0;
392     s = 1;
393 }
394 
395 
396 /*!
397     Saves the current calibration parameters in \c /etc/pointercal
398     (separated by whitespace and in alphabetical order).
399 
400     You can override the default \c /etc/pointercal by specifying
401     another file using the POINTERCAL_FILE environment variable.
402 
403     \sa readCalibration()
404 */
writeCalibration()405 void QWSCalibratedMouseHandler::writeCalibration()
406 {
407     QString calFile;
408     calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE"));
409     if (calFile.isEmpty())
410         calFile = QLatin1String("/etc/pointercal");
411 
412 #ifndef QT_NO_TEXTSTREAM
413     QFile file(calFile);
414     if (file.open(QIODevice::WriteOnly)) {
415         QTextStream t(&file);
416         t << a << ' ' << b << ' ' << c << ' ';
417         t << d << ' ' << e << ' ' << f << ' ' << s << endl;
418     } else
419 #endif
420     {
421         qCritical("QWSCalibratedMouseHandler::writeCalibration: "
422                   "Could not save calibration into %s", qPrintable(calFile));
423     }
424 }
425 
426 /*!
427     Reads previously written calibration parameters which are stored
428     in \c /etc/pointercal (separated by whitespace and in alphabetical
429     order).
430 
431     You can override the default \c /etc/pointercal by specifying
432     another file using the POINTERCAL_FILE environment variable.
433 
434 
435     \sa writeCalibration()
436 */
readCalibration()437 void QWSCalibratedMouseHandler::readCalibration()
438 {
439     QString calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE"));
440     if (calFile.isEmpty())
441         calFile = QLatin1String("/etc/pointercal");
442 
443 #ifndef QT_NO_TEXTSTREAM
444     QFile file(calFile);
445     if (file.open(QIODevice::ReadOnly)) {
446         QTextStream t(&file);
447         t >> a >> b >> c >> d >> e >> f >> s;
448         if (s == 0 || t.status() != QTextStream::Ok) {
449             qCritical("Corrupt calibration data");
450             clearCalibration();
451         }
452     } else
453 #endif
454     {
455         qDebug() << "Could not read calibration:" <<calFile;
456     }
457 }
458 
ilog2(quint32 n)459 static int ilog2(quint32 n)
460 {
461     int result = 0;
462 
463     if (n & 0xffff0000) {
464         n >>= 16;
465         result += 16;
466     }
467     if (n & 0xff00) {
468         n >>= 8;
469         result += 8;}
470     if (n & 0xf0) {
471         n >>= 4;
472         result += 4;
473     }
474     if (n & 0xc) {
475         n >>= 2;
476         result += 2;
477     }
478     if (n & 0x2)
479         result += 1;
480 
481     return result;
482 }
483 
484 /*!
485     Updates the calibration parameters based on coordinate mapping of
486     the given \a data.
487 
488     Create an instance of the QWSPointerCalibrationData class, fill in
489     the device and screen coordinates and pass that object to the mouse
490     driver using this function.
491 
492     \sa clearCalibration(), transform()
493 */
calibrate(const QWSPointerCalibrationData * data)494 void QWSCalibratedMouseHandler::calibrate(const QWSPointerCalibrationData *data)
495 {
496     // Algorithm derived from
497     // "How To Calibrate Touch Screens" by Carlos E. Vidales,
498     // printed in Embedded Systems Programming, Vol. 15 no 6, June 2002
499     // URL: http://www.embedded.com/showArticle.jhtml?articleID=9900629
500 
501     const QPoint pd0 = data->devPoints[QWSPointerCalibrationData::TopLeft];
502     const QPoint pd1 = data->devPoints[QWSPointerCalibrationData::TopRight];
503     const QPoint pd2 = data->devPoints[QWSPointerCalibrationData::BottomRight];
504     const QPoint p0 = data->screenPoints[QWSPointerCalibrationData::TopLeft];
505     const QPoint p1 = data->screenPoints[QWSPointerCalibrationData::TopRight];
506     const QPoint p2 = data->screenPoints[QWSPointerCalibrationData::BottomRight];
507 
508     const qint64 xd0 = pd0.x();
509     const qint64 xd1 = pd1.x();
510     const qint64 xd2 = pd2.x();
511     const qint64 yd0 = pd0.y();
512     const qint64 yd1 = pd1.y();
513     const qint64 yd2 = pd2.y();
514     const qint64 x0 = p0.x();
515     const qint64 x1 = p1.x();
516     const qint64 x2 = p2.x();
517     const qint64 y0 = p0.y();
518     const qint64 y1 = p1.y();
519     const qint64 y2 = p2.y();
520 
521     qint64 scale = ((xd0 - xd2)*(yd1 - yd2) - (xd1 - xd2)*(yd0 - yd2));
522     int shift = 0;
523     qint64 absScale = qAbs(scale);
524     // use maximum 16 bit precision to reduce risk of integer overflow
525     if (absScale > (1 << 16)) {
526         shift = ilog2(absScale >> 16) + 1;
527         scale >>= shift;
528     }
529 
530     s = scale;
531     a = ((x0 - x2)*(yd1 - yd2) - (x1 - x2)*(yd0 - yd2)) >> shift;
532     b = ((xd0 - xd2)*(x1 - x2) - (x0 - x2)*(xd1 - xd2)) >> shift;
533     c = (yd0*(xd2*x1 - xd1*x2) + yd1*(xd0*x2 - xd2*x0) + yd2*(xd1*x0 - xd0*x1)) >> shift;
534     d = ((y0 - y2)*(yd1 - yd2) - (y1 - y2)*(yd0 - yd2)) >> shift;
535     e = ((xd0 - xd2)*(y1 - y2) - (y0 - y2)*(xd1 - xd2)) >> shift;
536     f = (yd0*(xd2*y1 - xd1*y2) + yd1*(xd0*y2 - xd2*y0) + yd2*(xd1*y0 - xd0*y1)) >> shift;
537 
538     writeCalibration();
539 }
540 
541 /*!
542     Transforms the given \a position from device coordinates to screen
543     coordinates, and returns the transformed position.
544 
545     This function is typically called explicitly on the points passed
546     to the QWSMouseHandler::mouseChanged() function.
547 
548     This implementation is a linear transformation using 7 parameters
549     (\c a, \c b, \c c, \c d, \c e, \c f and \c s) to transform the
550     device coordinates (\c Xd, \c Yd) into screen coordinates (\c Xs,
551     \c Ys) using the following equations:
552 
553     \snippet doc/src/snippets/code/src_gui_embedded_qmouse_qws.cpp 0
554 
555     \sa mouseChanged()
556 */
transform(const QPoint & position)557 QPoint QWSCalibratedMouseHandler::transform(const QPoint &position)
558 {
559     QPoint tp;
560 
561     tp.setX((a * position.x() + b * position.y() + c) / s);
562     tp.setY((d * position.x() + e * position.y() + f) / s);
563 
564     return tp;
565 }
566 
567 /*!
568     Sets the size of the filter used in noise reduction to the given
569     \a size.
570 
571     The sendFiltered() function reduces noice by calculating an
572     average position from a collection of mouse event positions. The
573     filter size determines the number of positions that forms the
574     basis for these calculations.
575 
576     \sa sendFiltered()
577 */
setFilterSize(int size)578 void QWSCalibratedMouseHandler::setFilterSize(int size)
579 {
580     samples.resize(qMax(1, size));
581     numSamples = 0;
582     currSample = 0;
583 }
584 
585 /*!
586     \fn bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int state)
587 
588     Notifies the system of a new mouse event \e after applying a noise
589     reduction filter. Returns true if the filtering process is
590     successful; otherwise returns false. Note that if the filtering
591     process failes, the system is not notified about the event.
592 
593     The given \a position is the global position of the mouse. The \a
594     state parameter is a bitmask of the Qt::MouseButton enum's values
595     indicating which mouse buttons are pressed.
596 
597     The noice is reduced by calculating an average position from a
598     collection of mouse event positions and then calling the
599     mouseChanged() function with the new position. The number of
600     positions that is used is determined by the filter size.
601 
602     \sa mouseChanged(), setFilterSize()
603 */
sendFiltered(const QPoint & position,int button)604 bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int button)
605 {
606     if (!button) {
607         if (numSamples >= samples.count())
608             mouseChanged(transform(position), 0);
609         currSample = 0;
610         numSamples = 0;
611         return true;
612     }
613 
614     bool sent = false;
615     samples[currSample] = position;
616     numSamples++;
617     if (numSamples >= samples.count()) {
618 
619         int ignore = -1;
620         if (samples.count() > 2) { // throw away the "worst" sample
621             int maxd = 0;
622             for (int i = 0; i < samples.count(); i++) {
623                 int d = (mousePos - samples[i]).manhattanLength();
624                 if (d > maxd) {
625                     maxd = d;
626                     ignore = i;
627                 }
628             }
629         }
630 
631         // average the rest
632         QPoint pos(0, 0);
633         int numAveraged = 0;
634         for (int i = 0; i < samples.count(); i++) {
635             if (ignore == i)
636                 continue;
637             pos += samples[i];
638             ++numAveraged;
639         }
640         if (numAveraged)
641             pos /= numAveraged;
642 
643         mouseChanged(transform(pos), button);
644         sent = true;
645     }
646     currSample++;
647     if (currSample >= samples.count())
648         currSample = 0;
649 
650     return sent;
651 }
652 
653 QT_END_NAMESPACE
654