diff options
Diffstat (limited to 'graphtft-fe')
-rw-r--r-- | graphtft-fe/COPYING | 340 | ||||
-rw-r--r-- | graphtft-fe/Makefile | 37 | ||||
-rw-r--r-- | graphtft-fe/README | 24 | ||||
-rw-r--r-- | graphtft-fe/common.cc | 52 | ||||
-rw-r--r-- | graphtft-fe/common.hpp | 14 | ||||
-rw-r--r-- | graphtft-fe/comthread.cc | 231 | ||||
-rw-r--r-- | graphtft-fe/graphtft.cc | 736 | ||||
-rw-r--r-- | graphtft-fe/graphtft.hpp | 143 | ||||
-rw-r--r-- | graphtft-fe/main.cc | 19 | ||||
-rw-r--r-- | graphtft-fe/tcpchannel.cc | 532 | ||||
-rw-r--r-- | graphtft-fe/tcpchannel.h | 97 | ||||
-rw-r--r-- | graphtft-fe/thread.cc | 383 | ||||
-rw-r--r-- | graphtft-fe/thread.h | 160 |
13 files changed, 2768 insertions, 0 deletions
diff --git a/graphtft-fe/COPYING b/graphtft-fe/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/graphtft-fe/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/graphtft-fe/Makefile b/graphtft-fe/Makefile new file mode 100644 index 0000000..95f5b46 --- /dev/null +++ b/graphtft-fe/Makefile @@ -0,0 +1,37 @@ + +CXX ?= g++ +CXXFLAGS ?= -pipe -ggdb -O2 -Wall -W -D_REENTRANT -fPIC +CXXFLAGS += -Wno-deprecated-declarations +LFLAGS = -Wl,--no-undefined +LIBS = -lpthread -ljpeg -lX11 +LIBS += $(shell imlib2-config --libs) +AR = ar + +PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr)) +BINDIR = $(call PKGCFG,bindir) + +TARGET = graphtft-fe + +OBJECTS = fecommon.o \ + comthread.o \ + graphtft.o \ + main.o \ + tcpchannel.o \ + thread.o + +all: + @$(MAKE) $(TARGET) + +$(TARGET): $(OBJECTS) + $(CXX) $(LFLAGS) $(OBJECTS) $(LIBS) -o $(TARGET) +install: + @cp -v --remove-destination graphtft-fe $(DESTDIR)$(BINDIR) + +clean: + rm -f *.o $(TARGET) *~ + +.cc.o: + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" + +fecommon.o : ../common.c ../common.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" diff --git a/graphtft-fe/README b/graphtft-fe/README new file mode 100644 index 0000000..0ac5b33 --- /dev/null +++ b/graphtft-fe/README @@ -0,0 +1,24 @@ + +graphtft-fe ist das X-Sever-Frontend des graphTFT Plugin, die Bedinung des VDR ist mit +entsprechender Theme mittels Maus/Touch und Tastatur möglich. + +Voraussetzungen +--------------- + +Pakete: + +imlib2, imlib2-dev, libjpeg-dev +xorg-x11-devel + +Installation +------------ + +make -s clean all + +Start / Optionen +---------------- + +Eine Liste der Optionen wird mit +./graphtft-fe --help +angezeigt + diff --git a/graphtft-fe/common.cc b/graphtft-fe/common.cc new file mode 100644 index 0000000..0d60aad --- /dev/null +++ b/graphtft-fe/common.cc @@ -0,0 +1,52 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File common.cc +// Date 04.11.06 - Jörg Wendel +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +//*************************************************************************** + +#include <sys/time.h> +#include <stdarg.h> +#include <time.h> +#include <stdio.h> +#include <string.h> + +#include <graphtft.hpp> + +//*************************************************************************** +// Tell +//*************************************************************************** + +int tell(int eloquence, const char* format, ...) +{ + const int sizeTime = 8; // "12:12:34" + const int sizeMSec = 4; // ",142" + const int sizeHeader = sizeTime + sizeMSec + 1; + const int maxBuf = 1000; + + struct timeval tp; + char buf[maxBuf]; + va_list ap; + time_t now; + + va_start(ap, format); + + if (GraphTft::getEloquence() >= eloquence) + { + time(&now); + gettimeofday(&tp, 0); + + vsnprintf(buf + sizeHeader, maxBuf - sizeHeader, format, ap); + strftime(buf, sizeTime+1, "%H:%M:%S", localtime(&now)); + + sprintf(buf+sizeTime, ",%3.3ld", tp.tv_usec / 1000); + + buf[sizeHeader-1] = ' '; + printf("%s\n", buf); + } + + va_end(ap); + + return 0; +} diff --git a/graphtft-fe/common.hpp b/graphtft-fe/common.hpp new file mode 100644 index 0000000..30d6f85 --- /dev/null +++ b/graphtft-fe/common.hpp @@ -0,0 +1,14 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File common.hpp +// Date 04.11.06 - Jörg Wendel +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +//*************************************************************************** + +#ifndef __COMMON_HPP__ +#define __COMMON_HPP__ + +int tell(int eloquence, const char* format, ...); + +#endif // __COMMON_HPP__ diff --git a/graphtft-fe/comthread.cc b/graphtft-fe/comthread.cc new file mode 100644 index 0000000..aa3429c --- /dev/null +++ b/graphtft-fe/comthread.cc @@ -0,0 +1,231 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File comthread.cc +// Date 28.10.06 - Jörg Wendel +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +//-------------------------------------------------------------------------- +// Class ComThread +//*************************************************************************** + +#include <arpa/inet.h> + +#include "tcpchannel.h" +#include "graphtft.hpp" + +//*************************************************************************** +// Object +//*************************************************************************** + +ComThread::ComThread() + : cMyThread() +{ + line = new TcpChannel(); + + bufferSize = maxBuffer; + + buffer = new char[bufferSize+1]; + header = new TcpChannel::Header; + + timeout = 1; + port = -1; + *host = 0; + client = 0; + jpegQuality = na; +} + +ComThread::~ComThread() +{ + if (line->isConnected()) + { + tell(eloAlways, "Logout from server, closing tcp connection"); + line->write(cGraphTftComService::cmdLogout); + line->close(); + } + + delete line; + delete header; + delete[] buffer; +} + +void ComThread::stop() +{ + running = false; + + Cancel(2); +} + +//*************************************************************************** +// Run +//*************************************************************************** + +void ComThread::Action() +{ + const int checkTimeout = 30; + + int status; + time_t lastCheck = time(0); + int quality = htonl(jpegQuality); + + running = true; + + while (running) + { + if (!line->isConnected()) + { + tell(eloAlways, "Trying connecting to '%s' at port (%d)", host, port); + + if (line->open(port, host) == 0) + { + tell(eloAlways, "Connection to '%s' established", host); + + if (jpegQuality > na) + line->write(cGraphTftComService::cmdJpegQuality, (char*)&quality, sizeof(int)); + } + else + tell(eloAlways, "Connecting to '%s' failed", host); + } + + while (line->isConnected() && running) + { + if (lastCheck+checkTimeout < time(0)) + { + line->write(cGraphTftComService::cmdCheck); + lastCheck = time(0); + } + + if ((status = line->look(1)) != success) + { + if (status != TcpChannel::wrnNoEventPending) + { + tell(eloAlways, "Error: Communication problems, closing line! status was (%d)", + status); + line->close(); + + break; + } + + continue; + } + + if ((status = read()) != 0) + { + line->close(); + tell(eloAlways, "Error: Communication problems, closing line! status was (%d)", + status); + } + } + + if (!running) break; + + tell(eloAlways, "Retrying in %ld seconds", timeout); + + for (int i = 0; i < timeout && running; i++) + sleep(1); + } +} + +//*************************************************************************** +// Transmit events +//*************************************************************************** + +int ComThread::mouseEvent(int x, int y, int button, int flag, int data) +{ + GraphTftTouchEvent m; + + m.x = htonl(x); + m.y = htonl(y); + m.button = htonl(button); + m.flag = htonl(flag); + m.data = htonl(data); + + line->write(cGraphTftComService::cmdMouseEvent, (char*)&m, sizeof(GraphTftTouchEvent)); + + return 0; +} + +//*************************************************************************** +// ... +//*************************************************************************** + +int ComThread::keyEvent(int key, int flag) +{ + GraphTftTouchEvent m; + + m.x = htonl(0); + m.y = htonl(0); + m.button = htonl(key); + m.flag = htonl(flag | efKeyboard); + + line->write(cGraphTftComService::cmdMouseEvent, (char*)&m, sizeof(GraphTftTouchEvent)); + + return 0; +} + +//*************************************************************************** +// Read +//*************************************************************************** + +int ComThread::read() +{ + int status; + TcpChannel::Header tmp; + + // es stehen Daten an, erst einmal den Header abholen .. + + if ((status = line->read((char*)&tmp, sizeof(TcpChannel::Header))) == 0) + { + header->command = ntohl(tmp.command); + header->size = ntohl(tmp.size); + + switch (header->command) + { + case cGraphTftComService::cmdWelcome: + { + tell(eloAlways, "Got welcome"); + + break; + } + + case cGraphTftComService::cmdLogout: + { + tell(eloAlways, "Got logout from client, closing line"); + line->close(); + + break; + } + + case cGraphTftComService::cmdData: + { + tell(eloDebug, "Debug: Start reading %d kb from TCP", header->size/1024); + status = line->read(buffer, header->size); + tell(eloDebug, "Debug: Received %d kb", header->size/1024); + + if (status == 0 && client) + client->updateImage((unsigned char*)buffer, header->size); + + break; + } + + case cGraphTftComService::cmdMouseEvent: + { + GraphTftTouchEvent ev; + + status = line->read((char*)&ev, header->size); + tell(eloAlways, "Got mouse event, button (%d) at (%d/%d)", ev.button, ev.x, ev.y); + + break; + } + + default: + { + tell(eloAlways, "Got unexpected protocol (%d), aborting", header->command); + status = -1; + + break; + } + } + } + + return status; +} diff --git a/graphtft-fe/graphtft.cc b/graphtft-fe/graphtft.cc new file mode 100644 index 0000000..693ca5f --- /dev/null +++ b/graphtft-fe/graphtft.cc @@ -0,0 +1,736 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File graphtft.hpp +// Date 28.10.06 - Jörg Wendel +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +//-------------------------------------------------------------------------- +// Class GrapTFT +//*************************************************************************** + +#include <X11/Xutil.h> +#include <X11/cursorfont.h> + +#define XK_MISCELLANY +#include <X11/keysymdef.h> + +#include <stdio.h> +#include <stdlib.h> +#include <jpeglib.h> + +#include "graphtft.hpp" + +//#define _DEBUG + +//*************************************************************************** +// Class GraphTft +//*************************************************************************** + +int GraphTft::eloquence = eloOff; + +//*************************************************************************** +// Object +//*************************************************************************** + +GraphTft::GraphTft() +{ + // init + + showHelp = false; + resize = false; + image = 0; + hideCursorDelay = 0; + managed = true; + vdrWidth = 720; + vdrHeight = 576; + width = 720; + height = 576; + border = 0; + *dump = 0; + cursorVisible = yes; + lastMotion = time(0); + borderVisible = yes; + ignoreEsc = no; + screen = 0; + + thread = new ComThread(); + + // the defaults + + thread->setHost("localhost"); + thread->setPort(2039); +} + +GraphTft::~GraphTft() +{ + if (thread) + { + tell(eloAlways, "Stopping thread"); + + thread->stop(); + + delete thread; + } +} + +void GraphTft::setArgs(int argc, char* argv[]) +{ + if (argc > 1 && (argv[1][0] == '?' || (strcmp(argv[1], "--help") == 0))) + { + showHelp = true; + return ; + } + + for (int i = 0; argv[i]; i++) + { + if (argv[i][0] != '-' || strlen(argv[i]) != 2) + continue; + + switch (argv[i][1]) + { + case 'i': if (argv[i+1]) ignoreEsc = yes; break; + case 'h': if (argv[i+1]) thread->setHost(argv[i+1]); break; + case 'p': if (argv[i+1]) thread->setPort(atoi(argv[i+1])); break; + case 'e': if (argv[i+1]) setEloquence(atoi(argv[i+1])); break; + case 'W': if (argv[i+1]) width = atoi(argv[i+1]); break; + case 'H': if (argv[i+1]) height = atoi(argv[i+1]); break; + case 'd': if (argv[i+1]) strcpy(dump, argv[i+1]); break; + case 'c': if (argv[i+1]) hideCursorDelay = atoi(argv[i+1]); break; + case 'j': if (argv[i+1]) thread->setJpegQuality(atoi(argv[i+1])); break; + + case 'b': borderVisible = no; break; + case 'n': managed = false; break; + case 'r': resize = true; break; + } + } +} + +//*************************************************************************** +// Show Usage +//*************************************************************************** + +void GraphTft::showUsage() +{ + printf("Usage: graphtft-fe\n" + " Parameter:\n" + " -h <host> vdr host no default, please specify\n" + " -p <port> plugin port (default 2039)\n" + " -e <eloquence> log level (default 0)\n" + " -W <width> width (default 720)\n" + " -H <height> height (default 576)\n" + " -d <file> dump each image to file (default off)\n" + " -n not managed (default managed)\n" + " -r resize image (default off)\n" + " -j <qunality> JPEG quality (0-100)\n" + " -c <seconds> hide mouse curser after <seconds>\n" + " -b no boarder\n" + " -i no exit on ESC key\n" + " ?, --help this help\n" + ); +} + +//*************************************************************************** +// Start +//*************************************************************************** + +int GraphTft::start() +{ + if (showHelp) + { + showUsage(); + return 0; + } + + if (init() != success) + return fail; + + run(); + exit(); + + return success; +} + + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> + +//*************************************************************************** +// init/exit +//*************************************************************************** + +int GraphTft::init() +{ + Visual* vis; + Colormap cm; + int depth; + + // init X + + disp = XOpenDisplay(0); + + if (!disp) + { + printf("Invalid display, aborting\n"); + return fail; + } + + // init communication thread + + thread->setClient(this); + thread->Start(); + + // init dispaly + + screen = DefaultScreen(disp); + vis = DefaultVisual(disp, screen); + depth = DefaultDepth(disp, screen); + cm = DefaultColormap(disp, screen); + + // create simple window + + if (managed) + { + const char* appName = "graphtft-fe"; + + win = XCreateSimpleWindow(disp, DefaultRootWindow(disp), + 0, 0, width, height, 0, 0, 0); + + XSetStandardProperties(disp, win, appName, appName, None, + 0, 0, 0); + + + XClassHint* classHint; + XStoreName(disp, win, appName); + + /* set the name and class hints for the window manager to use */ + + classHint = XAllocClassHint(); + + if (classHint) + { + classHint->res_name = (char*)appName; + classHint->res_class = (char*)appName; + } + + XSetClassHint(disp, win, classHint); + XFree(classHint); + + if (!borderVisible) + hideBorder(); + } + else + { + // create window more complex + + // attributes + + XSetWindowAttributes windowAttributes; + + windowAttributes.border_pixel = BlackPixel(disp, screen); + windowAttributes.border_pixmap = CopyFromParent; + windowAttributes.background_pixel = WhitePixel(disp, screen); + windowAttributes.override_redirect = True; + windowAttributes.bit_gravity = NorthWestGravity; + windowAttributes.event_mask = ButtonPressMask | ButtonReleaseMask | + KeyPressMask | ExposureMask | SubstructureNotifyMask; + + + win = XCreateWindow(disp, RootWindow(disp, screen), + 0, 0, width, height, + border, depth, + InputOutput, + vis, + CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWBitGravity | CWEventMask, + &windowAttributes); + + + } + + XSelectInput(disp, win, + ButtonPressMask | + ButtonReleaseMask | + PointerMotionMask | + KeyPressMask | + ClientMessage | + SubstructureNotifyMask | + ExposureMask); // events to receive + + + XMapWindow(disp, win); // show + XFlush(disp); + + Screen* scn = DefaultScreenOfDisplay(disp); + pix = XCreatePixmap(disp, win, width, height, DefaultDepthOfScreen(scn)); + + imlib_set_cache_size(16 * 1024 * 1024); + imlib_set_color_usage(256); + + imlib_context_set_dither(0); // dither for depths < 24bpp + imlib_context_set_display(disp); // set the display + imlib_context_set_visual(vis); // visual, + imlib_context_set_colormap(cm); // colormap + + // imlib_context_set_drawable(win); // and the drawable we are using + imlib_context_set_drawable(pix); // and the drawable we are using + + return 0; +} + +void GraphTft::hideBorder() +{ + struct MwmHints + { + int flags; + int functions; + int decorations; + int input_mode; + int status; + }; + + MwmHints mwmhints; + Atom prop; + + memset(&mwmhints, 0, sizeof(mwmhints)); + mwmhints.flags = 1L << 1; + mwmhints.decorations = 0; + + prop = XInternAtom(disp, "_MOTIF_WM_HINTS", False); + + XChangeProperty(disp, win, prop, prop, 32, PropModeReplace, + (unsigned char*)&mwmhints, sizeof(mwmhints)/sizeof(long)); +} + +int GraphTft::exit() +{ + XFreePixmap(disp, pix); + XCloseDisplay(disp); + imlib_free_image(); + + return 0; +} + +//*************************************************************************** +// Send Exent +//*************************************************************************** + +int GraphTft::sendEvent() +{ + XEvent ev; + Display* d; + + if ((d = XOpenDisplay(0)) == 0) + { + tell(eloAlways, "Error: Sending event failed, cannot open display"); + return fail; + } + + ev.type = Expose; + XSendEvent(d, win, False, 0, &ev); + + XCloseDisplay(d); + + return success; +} + +int fromJpeg(Imlib_Image& image, unsigned char* buffer, int size) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + int w, h; + DATA8 *ptr, *line[16], *data; + DATA32 *ptr2, *dest; + int x, y; + + cinfo.err = jpeg_std_error(&jerr); + + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, buffer, size); + jpeg_read_header(&cinfo, TRUE); + cinfo.do_fancy_upsampling = FALSE; + cinfo.do_block_smoothing = FALSE; + + jpeg_start_decompress(&cinfo); + + w = cinfo.output_width; + h = cinfo.output_height; + + image = imlib_create_image(w, h); + imlib_context_set_image(image); + + dest = ptr2 = imlib_image_get_data(); + data = (DATA8*)malloc(w * 16 * cinfo.output_components); + + for (int i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * cinfo.output_components); + + for (int l = 0; l < h; l += cinfo.rec_outbuf_height) + { + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + int scans = cinfo.rec_outbuf_height; + + if (h - l < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + *ptr2 = (0xff000000) | ((ptr[0]) << 16) | ((ptr[1]) << 8) | (ptr[2]); + ptr += cinfo.output_components; + ptr2++; + } + } + } + + free(data); + + imlib_image_put_back_data(dest); + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return success; +} + +//*************************************************************************** +// Load Image +//*************************************************************************** + +void GraphTft::updateImage(const unsigned char* buffer, int size) +{ + tell(eloAlways, "update image"); + + bufferLock.Lock(); + + if (image) + { + imlib_context_set_image(image); + imlib_free_image(); + } + +#ifdef _DEBUG + + tell(eloAlways, "loading image, from file"); + + image = imlib_load_image("test.jpg"); + sendEvent(); + dumpImage(image); + + bufferLock.Unlock(); + + return ; +#endif + + tell(eloAlways, "loading image, size (%d)", size); + + if (size) + { + fromJpeg(image, (unsigned char*)buffer, size); + + dumpImage(image); + sendEvent(); + } + + bufferLock.Unlock(); +} + +//*************************************************************************** +// Paint +//*************************************************************************** + +int GraphTft::paint() +{ + XWindowAttributes windowAttributes; + + if (!image) + return fail; + + tell(eloAlways, "paint ..."); + + // get actual window size + + XGetWindowAttributes(disp, win, &windowAttributes); + width = windowAttributes.width; + height = windowAttributes.height; + + // lock buffer + + bufferLock.Lock(); + + imlib_context_set_image(image); + + // get VDR's image size + + vdrWidth = imlib_image_get_width(); + vdrHeight = imlib_image_get_height(); + + if (!resize) + { + imlib_render_image_on_drawable(0, 0); // render image on drawable + } + else + { + Imlib_Image buffer; + + buffer = imlib_create_image(width, height); + + imlib_context_set_image(buffer); + + imlib_blend_image_onto_image(image, 0, + 0, 0, vdrWidth, vdrHeight, + 0, 0, width, height); + + imlib_render_image_on_drawable(0, 0); + imlib_free_image(); + } + + XSetWindowBackgroundPixmap(disp, win, pix); + XClearWindow(disp, win); + + bufferLock.Unlock(); + + return success; +} + +//*************************************************************************** +// Dump Image +//*************************************************************************** + +void GraphTft::dumpImage(Imlib_Image image) +{ + if (*dump) + { + imlib_context_set_image(image); + imlib_save_image(dump); + } +} + +//*************************************************************************** +// Run loop +//*************************************************************************** + +int GraphTft::run() +{ + XEvent ev; + KeySym key_symbol; + int running = true; + int update = false; + + while (running) + { + while (XPending(disp)) + { + XNextEvent(disp, &ev); + + switch (ev.type) + { + case Expose: update = true; break; + case CreateNotify: tell(eloAlways, "Create"); break; + case DestroyNotify: tell(eloAlways, "Destroy"); break; + case MotionNotify: + onMotion(); + onButtonPress(ev, na); break; + case ButtonRelease: onButtonPress(ev, no); break; + case ButtonPress: onButtonPress(ev, yes); break; + + case KeyPress: + { + key_symbol = XKeycodeToKeysym(disp, ev.xkey.keycode, 0); + tell(eloAlways, "Key (%ld) pressed", key_symbol); + + if (key_symbol == XK_Escape && !ignoreEsc) + running = false; + else + onKeyPress(ev); + break; + } + + default: + break; + } + } + + if (update) + { + update = false; + paint(); + } + + // check mouse cursor + + if (hideCursorDelay && lastMotion < time(0) - hideCursorDelay && cursorVisible) + hideCursor(); + + if (!XPending(disp)) + usleep(10000); + } + + return 0; +} + +//*************************************************************************** +// On Motion +//*************************************************************************** + +int GraphTft::onMotion() +{ + lastMotion = time(0); + + if (!cursorVisible) + showCursor(); + + return done; +} + +//*************************************************************************** +// On key Press (keyboard) +//*************************************************************************** + +int GraphTft::onKeyPress(XEvent event) +{ + int x = event.xmotion.x; + int y = event.xmotion.y; + int flag = ComThread::efKeyboard; + int button = event.xkey.keycode; + + thread->mouseEvent(x, y, button, flag); + + return 0; +} + +//*************************************************************************** +// On Button Press (mouse) +//*************************************************************************** + +int GraphTft::onButtonPress(XEvent event, int press) +{ + static long lastTime = 0; + static int lastButton = na; + static int lastPressX = 0; + static int lastPressY = 0; + static int lastPressed = na; + + int x = event.xmotion.x; + int y = event.xmotion.y; + int flag = 0; + + if (press != na) + tell(eloAlways, "Button '%s' at (%d/%d) button %d, time (%ld)", + press ? "press" : "release", + event.xmotion.x, event.xmotion.y, + event.xbutton.button, + event.xbutton.time); + + if (resize) + { + x = (int)(((double)event.xmotion.x / (double)width) * (double)vdrWidth); + y = (int)(((double)event.xmotion.y / (double)height) * (double)vdrHeight); + } + + if (press == no) + { + // on button release + + if (abs(y - lastPressY) < 5 && abs(x - lastPressX) < 5) + { + if (lastButton == (int)event.xbutton.button + && event.xbutton.button == cGraphTftComService::mbLeft + && event.xbutton.time-lastTime < 300) + { + tell(eloAlways, "assuming double-click"); + flag |= cGraphTftComService::efDoubleClick; + } + + thread->mouseEvent(x, y, event.xbutton.button, flag); + + lastTime = event.xbutton.time; + lastButton = event.xbutton.button; + } + + lastPressed = press; + } + else if (press == na && lastPressed == yes) + { + // no Button action, only motion with pressed button + + if (abs(y - lastPressY) > 5 || abs(x - lastPressX) > 5) + { + if (abs(y - lastPressY) > abs(x - lastPressX)) + { + tell(eloAlways, "V-Whipe of (%d) pixel detected", y - lastPressY); + thread->mouseEvent(x, y, + lastButton, cGraphTftComService::efVWhipe, + y - lastPressY); + } + else + { + tell(eloAlways, "H-Whipe of (%d) pixel detected", x - lastPressX); + thread->mouseEvent(x, y, + lastButton, cGraphTftComService::efHWhipe, + x - lastPressX); + } + + lastPressX = x; + lastPressY = y; + } + } + else if (press == yes) + { + // on button press + + lastPressX = x; + lastPressY = y; + lastPressed = press; + } + + return success; +} + +//*************************************************************************** +// Hide Cursor +//*************************************************************************** + +void GraphTft::hideCursor() +{ + // Hide the cursor + + Cursor invisibleCursor; + Pixmap bitmapNoData; + XColor black; + + static char noData[] = { 0,0,0,0,0,0,0,0 }; + black.red = black.green = black.blue = 0; + + tell(eloAlways, "Hide mouse cursor"); + + bitmapNoData = XCreateBitmapFromData(disp, win, noData, 8, 8); + invisibleCursor = XCreatePixmapCursor(disp, bitmapNoData, bitmapNoData, + &black, &black, 0, 0); + XDefineCursor(disp, win, invisibleCursor); + XFreeCursor(disp, invisibleCursor); + + cursorVisible = no; +} + +//*************************************************************************** +// Show Cursor +//*************************************************************************** + +void GraphTft::showCursor() +{ + // Restore the X left facing cursor + + Cursor cursor; + + tell(eloAlways, "Show mouse cursor"); + + cursor = XCreateFontCursor(disp, XC_left_ptr); + XDefineCursor(disp, win, cursor); + XFreeCursor(disp, cursor); + + cursorVisible = yes; +} diff --git a/graphtft-fe/graphtft.hpp b/graphtft-fe/graphtft.hpp new file mode 100644 index 0000000..e6f2d9a --- /dev/null +++ b/graphtft-fe/graphtft.hpp @@ -0,0 +1,143 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File graphtft.hpp +// Date 28.10.06 - Jörg Wendel +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +//-------------------------------------------------------------------------- +// Class GraphTft +// Class ComThread +//*************************************************************************** + +#ifndef __GRAPHTFT_HPP__ +#define __GRAPHTFT_HPP__ + +#include <X11/Xlib.h> +#include <Imlib2.h> +#include <string.h> +#include <unistd.h> + +#define __FRONTEND + +#include "../common.h" +#include "../service.h" + +#include "thread.h" +#include "tcpchannel.h" + +class GraphTft; + +//*************************************************************************** +// Communication Thread +//*************************************************************************** + +class ComThread : public cMyThread, public cGraphTftComService +{ + public: + + enum Misc + { + maxBuffer = 1024*1024 + }; + + ComThread(); + virtual ~ComThread(); + + void stop(); + + int mouseEvent(int x, int y, int button, int flag, int data = 0); + int keyEvent(int key, int flag); + + const char* getBuffer() { return buffer; } + int getSize() { return header->size; } + + void setHost(const char* aHost) { strcpy(host, aHost); } + void setPort(unsigned short aPort) { port = aPort; } + void setClient(GraphTft* aClient) { client = aClient; } + void setJpegQuality(int quality) { jpegQuality = quality; } + + protected: + + void Action(); + int read(); + + TcpChannel* line; + + char* buffer; + int bufferSize; + GraphTft* client; + + long timeout; + int running; + int jpegQuality; + TcpChannel::Header* header; + unsigned short port; + char host[100]; +}; + +//*************************************************************************** +// Graph TFT +//*************************************************************************** + +class GraphTft +{ + public: + + GraphTft(); + virtual ~GraphTft(); + + int init(); + int exit(); + int start(); + int run(); + int paint(); + + void setArgs(int argc, char *argv[]); + int sendEvent(); + void updateImage(const unsigned char* buffer, int size); + void dumpImage(Imlib_Image image); + void showUsage(); + int onMotion(); + int onButtonPress(XEvent event, int press); + int onKeyPress(XEvent event); + + static void setEloquence(int aElo) { eloquence = aElo; } + static int getEloquence() { return eloquence; } + + protected: + + // functions + + void hideCursor(); + void showCursor(); + void hideBorder(); + + // data + + Window win; + Display* disp; + int screen; + Pixmap pix; + Imlib_Image image; + ComThread* thread; + int hideCursorDelay; + int resize; + int managed; + int width; + int height; + int border; + char dump[200]; + int showHelp; + cMutex bufferLock; + int vdrWidth; + int vdrHeight; + int cursorVisible; + int borderVisible; + int ignoreEsc; + time_t lastMotion; + + static int eloquence; +}; + +//*************************************************************************** +#endif // __GRAPHTFT_HPP__ diff --git a/graphtft-fe/main.cc b/graphtft-fe/main.cc new file mode 100644 index 0000000..11f8fcc --- /dev/null +++ b/graphtft-fe/main.cc @@ -0,0 +1,19 @@ + + +#include "graphtft.hpp" + +//*************************************************************************** +// Main +//*************************************************************************** + +int main(int argc, char *argv[]) +{ + GraphTft graphTft; + + graphTft.setArgs(argc, argv); + + graphTft.start(); + + return 0; +} + diff --git a/graphtft-fe/tcpchannel.cc b/graphtft-fe/tcpchannel.cc new file mode 100644 index 0000000..4e2a239 --- /dev/null +++ b/graphtft-fe/tcpchannel.cc @@ -0,0 +1,532 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File tcpchannel.cc +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +// (c) 2006-2014 Jörg Wendel +//-------------------------------------------------------------------------- +// Class TcpChannel +//*************************************************************************** + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> + +#include "../common.h" +#include "tcpchannel.h" + +//*************************************************************************** +// Object +//*************************************************************************** + +TcpChannel::TcpChannel(int aTimeout, int aHandle) +{ + handle = aHandle; + timeout = aTimeout; + + localAddr = 0; + port = 0; + remoteAddr = 0; + + *localHost = 0; + *remoteHost = 0; + + nTtlSent = 0; + nTtlReceived = 0; + + lookAheadChar = false; + lookAhead = 0; +} + +TcpChannel::~TcpChannel() +{ + close(); +} + +//*************************************************************************** +// OpenLstn -> Start Listener +//*************************************************************************** + +int TcpChannel::openLstn(unsigned short aPort, const char* aLocalHost) +{ + struct sockaddr_in localSockAddr; + struct hostent* hostInfo; + int value = 1; + int aHandle; + + // clear + + memset((char*)&localSockAddr, 0, sizeof(localSockAddr)); + + // init + + localSockAddr.sin_family = AF_INET; + + // resolve local host + + if (aLocalHost && *aLocalHost) + { + // search alias + + if ((hostInfo = ::gethostbyname(aLocalHost))) + memcpy((char*)&localAddr, hostInfo->h_addr, hostInfo->h_length); + + else if ((unsigned int)(localAddr = inet_addr(aLocalHost)) == INADDR_NONE) + { + tell(1, "unknown hostname '%s'", aLocalHost); + return fail; + } + + // set local endpoint + + memcpy(&localSockAddr.sin_addr, &localAddr, sizeof(struct in_addr)); + } + + // Server-Socket + + localSockAddr.sin_port = htons(aPort); + + // open socket + + if ((aHandle = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) + { + tell(1, "Error: "); + return fail; + } + + // set socket non-blocking + + if (fcntl(aHandle, F_SETFL, O_NONBLOCK) < 0) + tell(1, "Error: Setting socket options failed, errno (%d)", errno); + + setsockopt(aHandle, SOL_SOCKET, SO_REUSEADDR, + (char*)&value, sizeof(value)); + + // bind address to socket + + if (::bind(aHandle, (struct sockaddr*)&localSockAddr, sizeof(localSockAddr)) < 0) + { + ::close(aHandle); + tell(1, "Error: Bind failed, errno (%d)", errno); + + return fail; + } + + if (::listen(aHandle, 5) < 0) + { + ::close(aHandle); + + return fail; + } + + // save + + handle = aHandle; + port = aPort; + + return success; +} + +//*************************************************************************** +// Open +//*************************************************************************** + +int TcpChannel::open(unsigned short aPort, const char* aHost) +{ + const char* hostName; + struct sockaddr_in localSockAddr, remoteSockAddr; + struct hostent* hostInfo; + int aHandle; + + if (!aHost || !*aHost) + return fail; + + hostName = aHost; + + // clear + + memset((char*)&localSockAddr, 0, sizeof(localSockAddr)); + memset((char*)&remoteSockAddr, 0, sizeof(remoteSockAddr)); + + // init + + localSockAddr.sin_family = remoteSockAddr.sin_family = AF_INET; + remoteSockAddr.sin_port = htons(aPort); + + // resolve local host + + if (localHost && *localHost) + { + // search alias + + if ((hostInfo = ::gethostbyname(localHost))) + memcpy((char*)&localAddr, hostInfo->h_addr, hostInfo->h_length); + + else if ((localAddr = inet_addr(localHost)) == (int)INADDR_NONE) + return errUnknownHostname; + + // set local endpoint + + memcpy(&localSockAddr.sin_addr, &localAddr, sizeof(struct in_addr)); + } + + // map hostname to ip + + if ((hostInfo = ::gethostbyname(hostName))) + memcpy((char*)&remoteAddr, hostInfo->h_addr, hostInfo->h_length); + + else if ((remoteAddr = inet_addr(hostName)) == (int)INADDR_NONE) + return errUnknownHostname; + + // save hostname + + strncpy(remoteHost, hostName, sizeof(remoteHost)); + + // set sockaddr + + memcpy(&remoteSockAddr.sin_addr, &remoteAddr, sizeof(struct in_addr)); + + // create new socket + + if ((aHandle = socket(PF_INET, SOCK_STREAM, 0)) < 0) + return errOpenEndpointFailed; + + // bind only if localSockAddr is set + + if (*((int*)&localSockAddr.sin_addr) != 0) + { + // bind local address to socket + + if (::bind(aHandle, (struct sockaddr*)&localSockAddr, sizeof(localSockAddr)) < 0) + { + ::close(aHandle); + + return errBindAddressFailed; + } + } + + // connect to server + + if (connect(aHandle, (struct sockaddr*)&remoteSockAddr, sizeof(remoteSockAddr)) < 0) + { + ::close(aHandle); + + if (errno != ECONNREFUSED) + return errConnectFailed; + + return wrnNoResponseFromServer; + } + + // save results + + handle = aHandle; + port = aPort; + + return success; +} + +//*************************************************************************** +// Read +//*************************************************************************** + +int TcpChannel::read(char* buf, int bufLen) +{ + int nfds, result; + fd_set readFD; + int nReceived; + struct timeval wait; + + if (!handle) + return fail; + + memset(buf, 0, bufLen); + nReceived = 0; + + if (lookAhead) + { + *(buf) = lookAheadChar; + lookAhead = false; + nReceived++; + } + + while (nReceived < bufLen) + { + result = ::read(handle, buf + nReceived, bufLen - nReceived); + + if (result < 0) + { + if (errno != EWOULDBLOCK) + return checkErrno(); + + // time-out for select + + wait.tv_sec = timeout; + wait.tv_usec = 0; + + // clear and set file-descriptors + + FD_ZERO(&readFD); + FD_SET(handle, &readFD); + + // look event + + if ((nfds = ::select(handle+1, &readFD, 0, 0, &wait)) < 0) + return checkErrno(); + + // no event occured -> timeout + + if (nfds == 0) + return wrnTimeout; + } + + else if (result == 0) + { + // connection closed -> eof received + + return errConnectionClosed; + } + + else + { + // inc read char count + + nReceived += result; + } + } + + nTtlReceived += nReceived; + + return success; +} + +//*************************************************************************** +// Look +//*************************************************************************** + +int TcpChannel::look(int aTimeout) +{ + struct timeval tv; + fd_set readFD, writeFD, exceptFD; + int n; + + if (!handle) + return fail; + + // time-out for select + + tv.tv_sec = aTimeout; + tv.tv_usec = 1; + + // clear and set file-descriptors + + FD_ZERO(&readFD); + FD_ZERO(&writeFD); + FD_ZERO(&exceptFD); + + FD_SET(handle, &readFD); + FD_SET(handle, &writeFD); + FD_SET(handle, &exceptFD); + + // look event + + n = ::select(handle+1, &readFD, (aTimeout ? 0 : &writeFD), &exceptFD, &tv); + + if (n < 0) + return checkErrno(); + + // check exception + + if (FD_ISSET(handle, &exceptFD)) + return errUnexpectedEvent; + + // check write ok + + if (!FD_ISSET(handle, &writeFD)) + return wrnChannelBlocked; + + // check read-event + + if (!FD_ISSET(handle, &readFD)) + return wrnNoEventPending; + + // check first-char + + if (::read(handle, &lookAheadChar, 1) == 0) + return errConnectionClosed; + + // look ahead char received + + lookAhead = true; + + return success; +} + +//*************************************************************************** +// Listen +//*************************************************************************** + +int TcpChannel::listen(TcpChannel*& child) +{ + struct sockaddr_in remote; + struct timeval tv; + fd_set readFD; + int aHandle, num, len; + + child = 0; + tv.tv_sec = 0; + tv.tv_usec = 1; + len = sizeof(remote); + + // clear and set file-descriptor + + FD_ZERO(&readFD); + FD_SET(handle, &readFD); + + // call select to look for request + + if ((num = ::select(handle+1, &readFD,(fd_set*)0,(fd_set*)0, &tv)) < 0) + return checkErrno(); + + if (!FD_ISSET(handle, &readFD)) + return wrnNoConnectIndication; + + // accept client + + if ((aHandle = ::accept(handle, (struct sockaddr*)&remote, (socklen_t*)&len)) < 0) + { + tell(1, "Error: Accept failed, errno was %d - '%s'", errno, strerror(errno)); + return errAcceptFailed; + } + + // set none blocking, event for the new connection + + if (fcntl(aHandle, F_SETFL, O_NONBLOCK) < 0) + return fail; + + // create new tcp channel + + child = new TcpChannel(timeout, aHandle); + + return success; +} + +//*************************************************************************** +// Write to client +//*************************************************************************** + +int TcpChannel::write(int command, const char* buf, int bufLen) +{ + struct timeval wait; + int result, nfds; + fd_set writeFD; + int nSent = 0; + Header header; + + if (!handle) + return fail; + +#ifdef VDR_PLUGIN + cMutexLock lock(&_mutex); +#endif + + if (buf && !bufLen) + bufLen = strlen(buf); + + tell(eloDebug, "Writing (%ld) header bytes, command (%d), size (%d)", + sizeof(Header), command, bufLen); + + header.command = htonl(command); + header.size = htonl(bufLen); + result = ::write(handle, &header, sizeof(Header)); + + if (result != sizeof(Header)) + return errIOError; + + if (!buf) + return success; + + tell(eloDebug, "Writing (%d) kb now", bufLen/1024); + + do + { + result = ::write(handle, buf + nSent, bufLen - nSent); + + if (result < 0) + { + if (errno != EWOULDBLOCK) + return checkErrno(); + + // time-out for select + + wait.tv_sec = timeout; + wait.tv_usec = 0; + + // clear and set file-descriptors + + FD_ZERO(&writeFD); + FD_SET(handle, &writeFD); + + // look event + + if ((nfds = ::select(handle+1, 0, &writeFD, 0, &wait)) < 0) + { + // Error: Select failed + + return checkErrno(); + } + + // no event occured -> timeout + + if (nfds == 0) + return wrnTimeout; + } + else + { + nSent += result; + } + + } while (nSent < bufLen); + + // increase send counter + + nTtlSent += nSent; + + return success; +} + +//*************************************************************************** +// Close +//*************************************************************************** + +int TcpChannel::close() +{ + if (handle) + { + ::close(handle); + handle = 0; + } + + return success; +} + +//*************************************************************************** +// Check Errno +//*************************************************************************** + +int TcpChannel::checkErrno() +{ + switch (errno) + { + case EINTR: return wrnSysInterrupt; + case EBADF: return errInvalidEndpoint; + case EWOULDBLOCK: return wrnNoDataAvaileble; + case ECONNRESET: return errConnectionClosed; + default: return errIOError; + } +} diff --git a/graphtft-fe/tcpchannel.h b/graphtft-fe/tcpchannel.h new file mode 100644 index 0000000..c7b5881 --- /dev/null +++ b/graphtft-fe/tcpchannel.h @@ -0,0 +1,97 @@ +//*************************************************************************** +// Group VDR/GraphTFT +// File tcpchannel.h +// Date 31.10.06 +// This code is distributed under the terms and conditions of the +// GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. +// (c) 2006-2014 Jörg Wendel +//-------------------------------------------------------------------------- +// Class TcpChannel +//*************************************************************************** + +#ifndef __GTFT_TCPCHANNEL_H__ +#define __GTFT_TCPCHANNEL_H__ + +//*************************************************************************** +// Class TcpChannel +//*************************************************************************** + +class TcpChannel +{ + public: + + // declarations + + enum Errors + { + errChannel = -100, + + errUnknownHostname, + errBindAddressFailed, + errAcceptFailed, + errListenFailed, + errConnectFailed, + errIOError, + errConnectionClosed, + errInvalidEndpoint, + errOpenEndpointFailed, + + // Warnungen + + wrnNoEventPending, + errUnexpectedEvent, + wrnChannelBlocked, + wrnNoConnectIndication, + wrnNoResponseFromServer, + wrnNoDataAvaileble, + wrnSysInterrupt, + wrnTimeout + }; + +#pragma pack(1) + struct Header + { + int command; + int size; + }; +#pragma pack() + + // object + + TcpChannel(int aTimeout = 2, int aHandle = 0); + ~TcpChannel(); + + // api function + + int openLstn(unsigned short aPort, const char* aLocalHost = 0); + int open(unsigned short aPort, const char* aHost); + int close(); + int listen(TcpChannel*& child); + int look(int aTimeout); + int read(char* buf, int bufLen); + int write(int command, const char* buf = 0, int bufLen = 0); + int isConnected() { return handle != 0; } + + private: + + int checkErrno(); + + int handle; + unsigned short port; + char localHost[100]; + char remoteHost[100]; + long localAddr; + long remoteAddr; + long timeout; + int lookAheadChar; + int lookAhead; + int nTtlReceived; + int nTtlSent; + +#ifdef VDR_PLUGIN + cMutex _mutex; +#endif +}; + +//*************************************************************************** +#endif // __GTFT_TCPCHANNEL_H__ diff --git a/graphtft-fe/thread.cc b/graphtft-fe/thread.cc new file mode 100644 index 0000000..6982341 --- /dev/null +++ b/graphtft-fe/thread.cc @@ -0,0 +1,383 @@ +/* + * thread.c: A simple thread base class + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + */ + +#include <errno.h> +#include <linux/unistd.h> +#include <malloc.h> +#include <stdarg.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <unistd.h> +#include <string.h> + +#include "thread.h" +#include "../common.h" + +static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) +{ + struct timeval now; + if (gettimeofday(&now, NULL) == 0) { // get current time + now.tv_usec += MillisecondsFromNow * 1000; // add the timeout + while (now.tv_usec >= 1000000) { // take care of an overflow + now.tv_sec++; + now.tv_usec -= 1000000; + } + Abstime->tv_sec = now.tv_sec; // seconds + Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds + return true; + } + return false; +} + +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +void cCondWait::SleepMs(int TimeoutMs) +{ + cCondWait w; + w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + while (!signaled) { + if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +// --- cCondVar -------------------------------------------------------------- + +cCondVar::cCondVar(void) +{ + pthread_cond_init(&cond, 0); +} + +cCondVar::~cCondVar() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); +} + +void cCondVar::Wait(cMutex &Mutex) +{ + if (Mutex.locked) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait + // does an implicit unlock of the mutex + pthread_cond_wait(&cond, &Mutex.mutex); + Mutex.locked = locked; + } +} + +bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs) +{ + bool r = true; // true = condition signaled, false = timeout + + if (Mutex.locked) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait + // does an implicit unlock of the mutex. + if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT) + r = false; + Mutex.locked = locked; + } + } + return r; +} + +void cCondVar::Broadcast(void) +{ + pthread_cond_broadcast(&cond); +} + +// --- cRwLock --------------------------------------------------------------- + +cRwLock::cRwLock(bool PreferWriter) +{ + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); + pthread_rwlockattr_setkind_np(&attr, PreferWriter ? PTHREAD_RWLOCK_PREFER_WRITER_NP : PTHREAD_RWLOCK_PREFER_READER_NP); + pthread_rwlock_init(&rwlock, &attr); +} + +cRwLock::~cRwLock() +{ + pthread_rwlock_destroy(&rwlock); +} + +bool cRwLock::Lock(bool Write, int TimeoutMs) +{ + int Result = 0; + struct timespec abstime; + if (TimeoutMs) { + if (!GetAbsTime(&abstime, TimeoutMs)) + TimeoutMs = 0; + } + if (Write) + Result = TimeoutMs ? pthread_rwlock_timedwrlock(&rwlock, &abstime) : pthread_rwlock_wrlock(&rwlock); + else + Result = TimeoutMs ? pthread_rwlock_timedrdlock(&rwlock, &abstime) : pthread_rwlock_rdlock(&rwlock); + return Result == 0; +} + +void cRwLock::Unlock(void) +{ + pthread_rwlock_unlock(&rwlock); +} + +// --- cMutex ---------------------------------------------------------------- + +cMutex::cMutex(void) +{ + locked = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); + pthread_mutex_init(&mutex, &attr); +} + +cMutex::~cMutex() +{ + pthread_mutex_destroy(&mutex); +} + +void cMutex::Lock(void) +{ + pthread_mutex_lock(&mutex); + locked++; +} + +void cMutex::Unlock(void) +{ + if (!--locked) + pthread_mutex_unlock(&mutex); +} + +// --- cMyThread --------------------------------------------------------------- + +tThreadId cMyThread::mainThreadId = 0; + +cMyThread::cMyThread(const char *Description) +{ + active = running = false; + childTid = 0; + childThreadId = 0; + description = NULL; + if (Description) + SetDescription(Description); +} + +cMyThread::~cMyThread() +{ + Cancel(); // just in case the derived class didn't call it + free(description); +} + +void cMyThread::SetDescription(const char *Description) +{ + free(description); + description = NULL; + description = strdup(Description); +} + +void *cMyThread::StartThread(cMyThread *Thread) +{ + Thread->childThreadId = ThreadId(); + if (Thread->description) { + printf("%s thread started (pid=%d, tid=%d)\n", Thread->description, getpid(), Thread->childThreadId); +#ifdef PR_SET_NAME + if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0) + printf("%s thread naming failed (pid=%d, tid=%d)\n", Thread->description, getpid(), Thread->childThreadId); +#endif + } + Thread->Action(); + if (Thread->description) + printf("%s thread ended (pid=%d, tid=%d)\n", Thread->description, getpid(), Thread->childThreadId); + Thread->running = false; + Thread->active = false; + return NULL; +} + +#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop + +bool cMyThread::Start(void) +{ + if (!running) + { + if (active) + { + return true; + } + if (!active) + { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } + else { + printf("Error: ...\n"); + active = running = false; + return false; + } + } + } + return true; +} + +bool cMyThread::Active(void) +{ + if (active) { + // + // Single UNIX Spec v2 says: + // + // The pthread_kill() function is used to request + // that a signal be delivered to the specified thread. + // + // As in kill(), if sig is zero, error checking is + // performed but no signal is actually sent. + // + int err; + if ((err = pthread_kill(childTid, 0)) != 0) { + if (err != ESRCH) + printf("Error: ...\n"); + childTid = 0; + active = running = false; + } + else + return true; + } + return false; +} + +void cMyThread::Cancel(int WaitSeconds) +{ + running = false; + if (active && WaitSeconds > -1) { + if (WaitSeconds > 0) { + for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) { + if (!Active()) + return; + cCondWait::SleepMs(10); + } + printf("ERROR: %s thread %d won't end (waited %d seconds) - canceling it...\n", + description ? description : "", childThreadId, WaitSeconds); + } + pthread_cancel(childTid); + childTid = 0; + active = false; + } +} + +tThreadId cMyThread::ThreadId(void) +{ + return syscall(__NR_gettid); +} + +void cMyThread::SetMainThreadId(void) +{ + if (mainThreadId == 0) + mainThreadId = ThreadId(); + else + printf("ERROR: attempt to set main thread id to %d while it already is %d\n", + ThreadId(), mainThreadId); +} + +// --- cMutexLock ------------------------------------------------------------ + +cMutexLock::cMutexLock(cMutex *Mutex) +{ + mutex = NULL; + locked = false; + Lock(Mutex); +} + +cMutexLock::~cMutexLock() +{ + if (mutex && locked) + mutex->Unlock(); +} + +bool cMutexLock::Lock(cMutex *Mutex) +{ + if (Mutex && !mutex) { + mutex = Mutex; + Mutex->Lock(); + locked = true; + return true; + } + return false; +} + +// --- cMyThreadLock ----------------------------------------------------------- + +cMyThreadLock::cMyThreadLock(cMyThread *Thread) +{ + thread = NULL; + locked = false; + Lock(Thread); +} + +cMyThreadLock::~cMyThreadLock() +{ + if (thread && locked) + thread->Unlock(); +} + +bool cMyThreadLock::Lock(cMyThread *Thread) +{ + if (Thread && !thread) { + thread = Thread; + Thread->Lock(); + locked = true; + return true; + } + return false; +} diff --git a/graphtft-fe/thread.h b/graphtft-fe/thread.h new file mode 100644 index 0000000..e40d222 --- /dev/null +++ b/graphtft-fe/thread.h @@ -0,0 +1,160 @@ +/* + * thread.h: A simple thread base class + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: thread.h 2.0 2007/02/24 16:13:28 kls Exp $ + */ + +#ifndef __MYTHREAD_H +#define __MYTHREAD_H + +#include <pthread.h> +#include <stdio.h> +#include <sys/types.h> + +class cCondWait +{ + private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; + public: + cCondWait(void); + ~cCondWait(); + static void SleepMs(int TimeoutMs); + ///< Creates a cCondWait object and uses it to sleep for TimeoutMs + ///< milliseconds, immediately giving up the calling thread's time + ///< slice and thus avoiding a "busy wait". + ///< In order to avoid a possible busy wait, TimeoutMs will be automatically + ///< limited to values >2. + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is met. +}; + +class cMutex; + +class cCondVar { +private: + pthread_cond_t cond; +public: + cCondVar(void); + ~cCondVar(); + void Wait(cMutex &Mutex); + bool TimedWait(cMutex &Mutex, int TimeoutMs); + void Broadcast(void); + }; + +class cRwLock { +private: + pthread_rwlock_t rwlock; +public: + cRwLock(bool PreferWriter = false); + ~cRwLock(); + bool Lock(bool Write, int TimeoutMs = 0); + void Unlock(void); + }; + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); + }; + +typedef pid_t tThreadId; + +class cMyThread { + friend class cMyThreadLock; +private: + bool active; + bool running; + pthread_t childTid; + tThreadId childThreadId; + cMutex mutex; + char *description; + static tThreadId mainThreadId; + static void *StartThread(cMyThread *Thread); +protected: + void SetPriority(int Priority); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + virtual void Action(void) = 0; + ///< A derived cMyThread class must implement the code it wants to + ///< execute as a separate thread in this function. If this is + ///< a loop, it must check Running() repeatedly to see whether + ///< it's time to stop. + bool Running(void) { return running; } + ///< Returns false if a derived cMyThread object shall leave its Action() + ///< function. + void Cancel(int WaitSeconds = 0); + ///< Cancels the thread by first setting 'running' to false, so that + ///< the Action() loop can finish in an orderly fashion and then waiting + ///< up to WaitSeconds seconds for the thread to actually end. If the + ///< thread doesn't end by itself, it is killed. + ///< If WaitSeconds is -1, only 'running' is set to false and Cancel() + ///< returns immediately, without killing the thread. +public: + cMyThread(const char *Description = NULL); + ///< Creates a new thread. + ///< If Description is present, a log file entry will be made when + ///< the thread starts and stops. The Start() function must be called + ///< to actually start the thread. + virtual ~cMyThread(); + void SetDescription(const char *Description); + bool Start(void); + ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. + bool Active(void); + ///< Checks whether the thread is still alive. + static tThreadId ThreadId(void); + static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } + static void SetMainThreadId(void); + }; + +// cMutexLock can be used to easily set a lock on mutex and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cMutexLock may itself use a cMutexLock to make one longer lock instead of many +// short ones. + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + +// cMyThreadLock can be used to easily set a lock in a thread and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cMyThreadLock may itself use a cMyThreadLock to make one longer lock instead of many +// short ones. + +class cMyThreadLock { +private: + cMyThread *thread; + bool locked; +public: + cMyThreadLock(cMyThread *Thread = NULL); + ~cMyThreadLock(); + bool Lock(cMyThread *Thread); + }; + +//#define LOCK_THREAD cMyThreadLock ThreadLock(this) + +#endif //__MYTHREAD_H |