use new uinput device to control the cursor
[gnash.git] / gui / fb / fb.cpp
blob9f05771d2252df8aaace072b6d457e8590591c3a
1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software
3 // Foundation, Inc
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 // -----------------------------------------------------------------------------
21 /// \page fb_input FB GUI input devices
22 ///
23 /// The framebuffer GUI supports various input devices through the modern
24 /// Linux Input Subsystem (/dev/input/event*). Both standard mice and
25 /// touchscreen devices are supported.
26 ///
27 /// Since there can be multiple input devices in /dev/input/ you have to
28 /// specify which device to use using the
29 // POINTING_DEVICE environment variable for the mouse and
30 /// KEYBOARD_DEVICE environment variable for the keyboard
32 /// \page fb_calibration FB GUI Touchscreen Calibration
33 ///
34 /// The touchscreen drivers (like "usbtouchscreen") provide raw data from the
35 /// devices. It is up to the user space program to translate this data to
36 /// screen coordinates. Normally this is done by the X server so this
37 /// conversion needs to be done internally by Gnash itself.
38 ///
39 /// The current implementation uses a very simple 2-point calibration where
40 /// the first point is at one fifth of the screen width and height and the
41 /// second point is at the exact opposite part of the screen (at it's lower
42 /// right). The SWF file calibrate.swf provides a simple graphical reference
43 /// for 4:3 sized screens (don't use it for other formats!).
44 ///
45 /// With the current preliminary implementation it's a bit uncomfortable to do
46 /// the calibration:
47 ///
48 /// 1) starting gnash with DUMP_RAW environment variable will show raw
49 /// coordinates on STDOUT:
50 /// DUMP_RAW=1 gnash calibrate.swf
51 ///
52 /// 2) Keep touching the upper left reference point for a while. You'll get
53 /// lots of (similar) coordinates printed. Choose a X/Y coordinate pair you
54 /// think is the best one (ie. the average) and write it down.
55 ///
56 /// 3) Do the same for the lower right reference point.
57 ///
58 /// From now on, start gnash with the TSCALIB enivronment variable set to
59 /// the coordinates you just found out. Write the coordinates separated by
60 /// commas (X,Y,X,Y), like this:
61 /// TSCALIB=491,1635,1581,639 gnash yourmovie.swf
63 ///
64 /// 10-4-2008 N. Coesel
65 /// Added support for tslib. Tslib is a library which allows to create
66 /// a stack / cascade of filters to filter and scale touch-screen output.
67 /// The source doesn't come with much documentation, but writing your
68 /// own filter based on an existing filter is really easy. Tslib can
69 /// deal with old style H3600 style touchscreens and the newer event
70 /// interface. See http://tslib.berlios.de/ to get the source.
71 ///
72 /// The check_tslib() routine assumes filtering for movement and presses
73 /// is properly setup. Scaling is supposed to be performed by tslib or
74 /// the underlying touchscreen driver.
76 #ifdef HAVE_CONFIG_H
77 #include "gnashconfig.h"
78 #endif
80 #include "GnashSystemIOHeaders.h"
81 #include "GnashNumeric.h"
83 #include <sys/types.h>
84 #include <sys/stat.h>
85 #include <sys/time.h>
86 #include <fcntl.h>
87 #include <cerrno>
88 #include <sys/ioctl.h>
89 #include <sys/mman.h>
90 #include <linux/fb.h>
91 #include <linux/kd.h>
92 #include <linux/vt.h>
93 #include <csignal>
94 #include <cstdlib> // getenv
96 #ifdef HAVE_TSLIB_H
97 # include <tslib.h>
98 #endif
99 #if defined(ENABLE_TSLIB) && !defined(HAVE_TSLIB_H)
100 # warning "No tslib.h! Disabling touchscreen support"
101 # undef ENABLE_TSLIB
102 #endif
104 #include "gui.h"
105 #include "rc.h"
106 #include "fbsup.h"
107 #include "log.h"
108 #include "movie_root.h"
109 #include "RunResources.h"
110 #include "GnashSleep.h" // for gnashSleep
111 #include "Renderer.h"
113 #include <linux/input.h> // for /dev/input/event*
114 #include <events/InputDevice.h>
116 #ifdef RENDERER_AGG
117 # include "fb_glue_agg.h"
118 # include "agg/Renderer_agg.h"
119 #endif
121 #ifdef RENDERER_OPENVG
122 # include "VG/openvg.h"
123 # include "openvg/OpenVGRenderer.h"
124 # include "fb_glue_ovg.h"
125 #endif
127 #ifdef RENDERER_GLES1
128 # include "fb_glue_gles1.h"
129 #endif
131 namespace gnash {
133 namespace gui {
135 int terminate_request = false; // global scope to avoid GUI access
137 std::auto_ptr<Gui> createFBGui(unsigned long windowid, float scale,
138 bool do_loop, RunResources& r)
140 // GNASH_REPORT_FUNCTION;
141 return std::auto_ptr<Gui>(new FBGui(windowid, scale, do_loop, r));
144 /// Called on CTRL-C and alike
145 void
146 terminate_signal(int /*signo*/) {
147 terminate_request = true;
150 FBGui::FBGui(unsigned long xid, float scale, bool loop, RunResources& r)
151 : Gui(xid, scale, loop, r),
152 _fd(-1),
153 _original_vt(-1),
154 _original_kd(-1),
155 _own_vt(-1),
156 _xpos(0),
157 _ypos(0),
158 _timeout(0),
159 _fullscreen(true)
161 // GNASH_REPORT_FUNCTION;
163 // initializing to zero helps with debugging and prevents weird bugs
164 // memset(mouse_buf, 0, 256);
165 signal(SIGINT, terminate_signal);
166 signal(SIGTERM, terminate_signal);
169 FBGui::~FBGui()
171 // GNASH_REPORT_FUNCTION;
173 if (_fd > 0) {
174 enable_terminal();
175 log_debug(_("Closing framebuffer device"));
176 close(_fd);
180 bool
181 FBGui::init(int argc, char *** argv)
183 // GNASH_REPORT_FUNCTION;
185 // the current renderer as set on the command line or gnashrc file
186 std::string renderer = _runResources.getRenderBackend();
188 #ifdef RENDERER_OPENVG
189 if (renderer.empty()) {
190 renderer = "openvg";
192 if ((renderer == "openvg") || (renderer == "ovg")) {
193 renderer = "openvg";
194 _glue.reset(new FBOvgGlue(0));
195 // Initialize the glue layer between the renderer and the gui toolkit
196 _glue->init(argc, argv);
198 FBOvgGlue *ovg = reinterpret_cast<FBOvgGlue *>(_glue.get());
199 // Set "window" size
200 _width = ovg->getWidth();
201 _height = ovg->getHeight();
202 log_debug("Width:%d, Height:%d", _width, _height);
203 _renderer.reset(renderer::openvg::create_handler(0));
204 renderer::openvg::Renderer_ovg *rend = reinterpret_cast
205 <renderer::openvg::Renderer_ovg *>(_renderer.get());
206 rend->init(_width, _height);
208 #endif
210 // map framebuffer into memory
211 // Create a new Glue layer
212 #ifdef RENDERER_AGG
213 if (renderer.empty()) {
214 renderer = "agg";
216 if (renderer == "agg") {
217 _glue.reset(new FBAggGlue());
218 // Initialize the glue layer between the renderer and the gui toolkit
219 _glue->init(argc, argv);
220 FBAggGlue *agg = reinterpret_cast<FBAggGlue *>(_glue.get());
221 // Set "window" size
222 _width = agg->width();
223 _height = agg->height();
224 log_debug("Width:%d, Height:%d", _width, _height);
225 _renderer.reset(agg->createRenderHandler());
227 #endif
228 if ((renderer != "openvg") && (renderer != "agg")) {
229 log_error("No renderer! %s not supported.", renderer);
232 disable_terminal();
234 // Look for the User Mode Input (Uinput) device, which is used to
235 // control the movement and coordinates of the mouse cursor.
236 if (_uinput.scanForDevice()) {
237 _uinput.init();
238 _uinput.moveTo(0, 0);
239 } else {
240 log_error("Found no accessible User mode input event device");
243 // Initialize all the input devices
245 // Look for Mice that use the PS/2 mouse protocol
246 std::vector<boost::shared_ptr<InputDevice> > possibles
247 = InputDevice::scanForDevices();
248 if (possibles.empty()) {
249 log_error("Found no accessible input event devices");
250 } else {
251 log_debug("Found %d input event devices.", possibles.size());
254 std::vector<boost::shared_ptr<InputDevice> >::iterator it;
255 for (it=possibles.begin(); it!=possibles.end(); ++it) {
256 // Set the screen size, which is used for calculating absolute
257 // mouse locations from relative ones.
258 (*it)->setScreenSize(_width, _height);
259 // (*it)->dump();
260 if ((*it)->getType() == InputDevice::MOUSE) {
261 #if 0
262 log_debug("WARNING: Mouse support disabled as it conflicts with the input event support.");
263 // For now we only want keyboard input events, as the mouse
264 // interface default of /dev/input/mice supports hotpluging devices,
265 // unlike the regular events.
266 #else
267 _inputs.push_back(*it);
268 #endif
270 if ((*it)->getType() == InputDevice::KEYBOARD) {
271 _inputs.push_back(*it);
273 // TSLib also supports linux input events, so we
274 // use that instead of handling the events directly. The
275 // Babbage is configured as a tablet when using input events.
276 if ((*it)->getType() == InputDevice::TOUCHSCREEN) {
277 log_debug("Enabling Touchscreen support.");
278 _inputs.push_back(*it);
280 if ((*it)->getType() == InputDevice::TABLET) {
281 #if 1
282 log_debug("WARNING: Babbage Tablet support disabled as it conflicts with TSlib");
283 #else
284 log_debug("Enabling Babbage Touchscreen support");
285 _inputs.push_back(*it);
286 #endif
288 if ((*it)->getType() == InputDevice::POWERBUTTON) {
289 _inputs.push_back(*it);
293 // Let -j -k override "window" size
294 optind = 0; opterr = 0; char c;
295 while ((c = getopt (argc, *argv, "j:k:X:Y:")) != -1) {
296 switch (c) {
297 case 'j':
298 _width = clamp<int>(atoi(optarg), 1, _width);
299 break;
300 case 'k':
301 _height = clamp<int>(atoi(optarg), 1, _height);
302 break;
303 case 'X':
304 _xpos = atoi(optarg);
305 break;
306 case 'Y':
307 _ypos = atoi(optarg);
308 break;
312 #if 0
313 // FIXME: this allows to draw in a subsection of the screen. OpenVG
314 // should be able to support this, but right now it just gets in
315 // the way of debugging.
317 if ( _xpos < 0 ) _xpos += _var_screeninfo.xres - _width;
318 _xpos = clamp<int>(_xpos, 0, _var_screeninfo.xres-_width);
320 if ( _ypos < 0 ) _ypos += _var_screeninfo.yres - _height;
321 _ypos = clamp<int>(_ypos, 0, _var_screeninfo.yres-_height);
323 log_debug("X:%d, Y:%d", _xpos, _ypos);
324 #endif
326 _validbounds.setTo(0, 0, _width - 1, _height - 1);
328 return true;
331 bool
332 FBGui::resize_view(int width, int height)
334 GNASH_REPORT_FUNCTION;
336 // _glue.prepDrawingArea(width, height, 0);
337 Gui::resize_view(width, height);
339 return true;
342 bool
343 FBGui::run()
345 // GNASH_REPORT_FUNCTION;
347 #ifdef USE_TSLIB
348 int ts_loop_count;
349 #endif
351 VirtualClock& timer = getClock();
352 int delay = 0;
354 // let the GUI recompute the x/y scale factors to best fit the whole screen
355 resize_view(_validbounds.width(), _validbounds.height());
357 float fps = getFPS();
359 // FIXME: this value is arbitrary, and will make any movie with
360 // less than 12 frames eat up more of the cpu. It should probably
361 // be a much lower value, like 2.
362 if (fps > 12) {
363 delay = static_cast<int>(100000/fps);
364 } else {
365 // 10ms per heart beat
366 delay = 10000;
368 log_debug("Movie Frame Rate is %d, adjusting delay to %dms", fps,
369 _interval * delay);
371 // This loops endlessly at the frame rate
372 while (!terminate_request) {
373 // wait the "heartbeat" inteval. _interval is in milliseconds,
374 // but gnashSleep() wants nanoseconds, so adjust by 1000.
375 gnashSleep(_interval * 1000);
376 // TODO: Do we need to check the real time slept or is it OK when we woke
377 // up early because of some Linux signal sent to our process (and thus
378 // "advance" faster than the "heartbeat" interval)? - Udo
380 #ifdef USE_TSLIB
381 ts_loop_count++; //increase loopcount
382 #endif
384 // check input devices
385 checkForData();
387 // advance movie
388 Gui::advance_movie(this);
390 // check if we've reached a timeout
391 if (_timeout && timer.elapsed() >= _timeout ) {
392 break;
396 return true;
399 void
400 FBGui::renderBuffer()
402 // GNASH_REPORT_FUNCTION;
404 _glue->render();
407 bool
408 FBGui::createWindow(const char* /*title*/, int /*width*/, int /*height*/,
409 int /*xPosition*/, int /*yPosition*/)
411 // GNASH_REPORT_FUNCTION;
413 _runResources.setRenderer(_renderer);
415 return true;
418 bool
419 FBGui::createMenu()
421 // no menu support!
422 return true;
425 bool
426 FBGui::setupEvents()
428 // events currently not supported!
429 return true;
432 void
433 FBGui::setInterval(unsigned int interval)
435 _interval = interval;
438 void
439 FBGui::setTimeout(unsigned int timeout)
441 _timeout = timeout;
444 // The Framebuffer GUI usually always runs fullscreen, which is
445 // the default, but occasionally users want to run the SWF movie
446 // it's actual size, or in different locations.
447 void
448 FBGui::setFullscreen()
450 _fullscreen = true;
453 void
454 FBGui::unsetFullscreen()
456 _fullscreen = false;
459 void
460 FBGui::showMenu(bool /*show*/)
462 log_unimpl(_("This GUI does not yet support menus"));
465 bool
466 FBGui::showMouse(bool /*show*/)
468 log_unimpl(_("This GUI does not yet support a mouse pointer"));
469 // Should return true if the pointer was visible before call,
470 // otherwise false;
471 return true;
474 void
475 FBGui::setInvalidatedRegion(const SWFRect& bounds)
477 // GNASH_REPORT_FUNCTION;
479 setInvalidatedRegion(bounds);
482 void
483 FBGui::setInvalidatedRegions(const InvalidatedRanges& ranges)
485 // GNASH_REPORT_FUNCTION;
487 _glue->setInvalidatedRegions(ranges);
490 char *
491 FBGui::find_accessible_tty(int no)
493 // GNASH_REPORT_FUNCTION;
495 char* fn;
497 fn = find_accessible_tty("/dev/vc/%d", no); if (fn) return fn;
498 fn = find_accessible_tty("/dev/tty%d", no); if (fn) return fn;
499 fn = find_accessible_tty("/dev/tty%02x", no); if (fn) return fn;
500 fn = find_accessible_tty("/dev/tty%x", no); if (fn) return fn;
501 fn = find_accessible_tty("/dev/tty%02d", no); if (fn) return fn;
503 if (no==0) {
504 fn = find_accessible_tty("/dev/tty", no); // just "/dev/tty"
505 if (fn) return fn;
508 return NULL;
511 char *
512 FBGui::find_accessible_tty(const char* format, int no)
514 static char fname[1024];
516 snprintf(fname, sizeof fname, format, no);
518 if (access(fname, R_OK|W_OK) != -1) {
519 return fname;
522 return NULL;
525 bool
526 FBGui::disable_terminal()
528 // GNASH_REPORT_FUNCTION;
530 _original_kd = -1;
533 // Find the TTY device name
535 char* tty = find_accessible_tty(0);
537 int fd;
539 if (!tty) {
540 log_debug(_("WARNING: Could not detect controlling TTY"));
541 return false;
545 // Detect the currently active virtual terminal (so we can switch back to
546 // it later)
548 fd = open(tty, O_RDWR);
549 if (fd < 0) {
550 log_debug(_("WARNING: Could not open %s"), tty);
551 return false;
554 struct vt_stat vts;
555 if (ioctl(fd, VT_GETSTATE, &vts) == -1) {
556 log_debug(_("WARNING: Could not get current VT state"));
557 close(_fd);
558 return false;
561 _original_vt = vts.v_active;
562 log_debug(_("Original TTY NO = %d"), _original_vt);
564 #ifdef REQUEST_NEW_VT
565 // Request a new VT number
566 if (ioctl(fd, VT_OPENQRY, &_own_vt) == -1) {
567 log_debug(_("WARNING: Could not request a new VT"));
568 close(fd);
569 return false;
572 log_debug(_("Own TTY NO = %d"), _own_vt);
574 if (fd > 0) {
575 close(fd);
578 // Activate our new VT
579 tty = find_accessible_tty(_own_vt);
580 if (!tty) {
581 log_debug(_("WARNING: Could not find device for VT number %d"), _own_vt);
582 return false;
585 _fd = open(tty, O_RDWR);
586 if (fd < 0) {
587 log_debug(_("WARNING: Could not open %s"), tty);
588 return false;
591 if (ioctl(fd, VT_ACTIVATE, _own_vt) == -1) {
592 log_debug(_("WARNING: Could not activate VT number %d"), _own_vt);
593 close(fd);
594 return false;
597 if (ioctl(fd, VT_WAITACTIVE, _own_vt) == -1) {
598 log_debug(_("WARNING: Error waiting for VT %d becoming active"),
599 _own_vt);
600 //close(tty);
601 //return false; don't abort
604 #else
606 _own_vt = _original_vt; // keep on using the original VT
608 if (fd > 0) {
609 close(fd);
612 // Activate our new VT
613 tty = find_accessible_tty(_own_vt);
614 if (!tty) {
615 log_debug(_("WARNING: Could not find device for VT number %d"),
616 _own_vt);
617 return false;
620 fd = open(tty, O_RDWR);
621 if (fd < 0) {
622 log_debug(_("WARNING: Could not open %s"), tty);
623 return false;
626 #if 0
627 // Become session leader and attach to terminal
628 setsid();
629 if (ioctl(fd, TIOCSCTTY, 0) == -1) {
630 log_debug(_("WARNING: Could not attach controlling terminal (%s)"), tty);
632 #endif
633 #endif // end of if REQUEST_NEW_VT
635 // Disable keyboard cursor
637 if (ioctl(fd, KDGETMODE, &_original_kd) == -1) {
638 log_debug(_("WARNING: Could not query current keyboard mode on VT"));
641 if (ioctl(fd, KDSETMODE, KD_GRAPHICS) == -1) {
642 log_debug(_("WARNING: Could not switch to graphics mode on new VT"));
645 if (fd > 0) {
646 close(fd);
649 log_debug(_("VT %d ready"), _own_vt);
651 // NOTE: We could also implement virtual console switching by using
652 // VT_GETMODE / VT_SETMODE ioctl calls and handling their signals, but
653 // probably nobody will ever want to switch consoles, so I don't bother...
655 return true;
658 bool
659 FBGui::enable_terminal()
661 // GNASH_REPORT_FUNCTION;
663 log_debug(_("Restoring terminal..."));
665 char* tty = find_accessible_tty(_own_vt);
666 if (!tty) {
667 log_debug(_("WARNING: Could not find device for VT number %d"), _own_vt);
668 return false;
671 int fd = open(tty, O_RDWR);
672 if (fd < 0) {
673 log_debug(_("WARNING: Could not open %s"), tty);
674 return false;
677 if (ioctl(fd, VT_ACTIVATE, _original_vt)) {
678 log_debug(_("WARNING: Could not activate VT number %d"), _original_vt);
679 close(_fd);
680 return false;
683 if (ioctl(fd, VT_WAITACTIVE, _original_vt)) {
684 log_debug(_("WARNING: Error waiting for VT %d becoming active"),
685 _original_vt);
686 //close(tty);
687 //return false; don't abort
690 // Restore keyboard
692 if (ioctl(fd, KDSETMODE, _original_kd)) {
693 log_debug(_("WARNING: Could not restore keyboard mode"));
696 if (fd > 0) {
697 close(fd);
700 return true;
703 void
704 FBGui::checkForData()
706 // GNASH_REPORT_FUNCTION;
708 std::vector<boost::shared_ptr<InputDevice> >::iterator it;
710 for (it=_inputs.begin(); it!=_inputs.end(); ++it) {
711 (*it)->check();
712 boost::shared_ptr<InputDevice::input_data_t> ie = (*it)->popData();
713 if (ie) {
714 // notifyMouseMove(ie->x, ie->y);
715 #if 0
716 std::cerr << "Got data: " << ((ie->pressed) ? "true" : "false");
717 std::cerr << ", " << ie->key << ", " << ie->modifier;
718 std::cerr << ", " << ie->x << ", " << ie->y << std::endl;
719 // cerr << "X = " << coords[0] << endl;
720 // cerr << "Y = " << coords[1] << endl;
721 #endif
722 #if 0
723 // Range check and convert the position from relative to
724 // absolute
725 boost::shared_array<int> coords =
726 MouseDevice::convertCoordinates(ie->x, ie->y,
727 getStage()->getStageWidth(),
728 getStage()->getStageHeight());
729 // The mouse was moved
730 _uinput.moveTo(ie.x, ie.y);
731 if (coords) {
732 notifyMouseMove(coords[0], coords[1]);
734 #endif
735 // See if a mouse button was clicked
736 if (ie->pressed) {
737 notifyMouseClick(true);
738 #if 1
739 double x = 0.655 * ie->x;
740 double y = 0.46875 * ie->y;
741 log_debug("Mouse clicked at: %g:%g", x, y);
742 notifyMouseMove(int(x), int(y));
743 #else
744 notifyMouseMove(ie->x, ie->y);
745 #endif
751 } // end of namespace gui
752 } // end of namespace gnash
754 // Local Variables:
755 // mode: C++
756 // indent-tabs-mode: nil
757 // End: