diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab3e468 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +Makefile +*.pro.user* +*.o +moc_* +./fingerterm +qrc_resources.cpp diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. + + + Copyright (C) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + , 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 Lesser General +Public License instead of this License. diff --git a/data/english.layout b/data/english.layout new file mode 100644 index 0000000..948e997 --- /dev/null +++ b/data/english.layout @@ -0,0 +1,77 @@ +; use keycodes from: +; http://doc.qt.nokia.com/4.7/qt.html#Key-enum +; +; - [] will leave an empty space in the keyboard +; - Recognized modifier keys are shift, alt, ctrl +; - Letters will automatically handle upper/lowercase +; - Empty row in the file acts as a keyboard row separator + +["esc", 0x1000000, "", 0x0] +[">", 0x3e, "<", 0x3c] +["|", 0x7c, "&", 0x26] +["!", 0x21, "?", 0x3f] +["\"", 0x22, "$", 0x24] +["'", 0x27, "`", 0x60] +["_", 0x5f, "@", 0x40] +["=", 0x3d, "~", 0x7e] +["/", 0x2f, "\\", 0x5c] +["*", 0x2a, "del", 0x1000007] +[":backspace", 0x1000003, "", 0x0] + +[":tab", 0x1000001, "", 0x0] +["1", 0x31, "+", 0x2b] +["2", 0x32, "^", 0x5e] +["3", 0x33, "#", 0x23] +["4", 0x34, "%", 0x25] +["5", 0x35, "(", 0x28] +["6", 0x36, ")", 0x29] +["7", 0x37, "[", 0x5b] +["8", 0x38, "]", 0x5d] +["9", 0x39, "{", 0x7b] +["0", 0x30, "}", 0x7d] + +["q", 0x51, "", 0x0] +["w", 0x57, "", 0x0] +["e", 0x45, "", 0x0] +["r", 0x52, "", 0x0] +["t", 0x54, "", 0x0] +["y", 0x59, "", 0x0] +["u", 0x55, "", 0x0] +["i", 0x49, "", 0x0] +["o", 0x4f, "", 0x0] +["p", 0x50, "", 0x0] +[":enter", 0x1000004, "", 0x0] + +["a", 0x41, "", 0x0] +["s", 0x53, "", 0x0] +["d", 0x44, "", 0x0] +["f", 0x46, "", 0x0] +["g", 0x47, "", 0x0] +["h", 0x48, "", 0x0] +["j", 0x4a, "", 0x0] +["k", 0x4b, "", 0x0] +["l", 0x4c, "", 0x0] +[":", 0x3a, "", 0x0] +["pgup", 0x1000016, "home", 0x1000010] + +[":shift", 0x2000000, "", 0x0] +["z", 0x5a, "", 0x0] +["x", 0x58, "", 0x0] +["c", 0x43, "", 0x0] +["v", 0x56, "", 0x0] +["b", 0x42, "", 0x0] +["n", 0x4e, "", 0x0] +["m", 0x4d, "", 0x0] +[";", 0x3b, "", 0x0] +[":up", 0x1000013, "", 0x0] +["pgdn", 0x1000017, "end", 0x1000011] + +["ctrl", 0x4000000, "", 0x0] +["alt", 0x8000000, "", 0x0] +["-", 0x2d, "", 0x0] +[",", 0x2c, "", 0x0] +[[[" ", 0x20, "", 0x0]]] +[".", 0x2e, "", 0x0] +[":left", 0x1000012, "", 0x0] +[":down", 0x1000015, "", 0x0] +[":right", 0x1000014, "", 0x0] diff --git a/data/finnish.layout b/data/finnish.layout new file mode 100644 index 0000000..f76be30 --- /dev/null +++ b/data/finnish.layout @@ -0,0 +1,77 @@ +; use keycodes from: +; http://doc.qt.nokia.com/4.7/qt.html#Key-enum +; +; - [] will leave an empty space in the keyboard +; - Recognized modifier keys are shift, alt, ctrl +; - Letters will automatically handle upper/lowercase +; - Empty row in the file acts as a keyboard row separator + +["esc", 0x1000000, "", 0x0] +[">", 0x3e, "<", 0x3c] +["|", 0x7c, "&", 0x26] +["!", 0x21, "?", 0x3f] +["\"", 0x22, "$", 0x24] +["'", 0x27, "`", 0x60] +["_", 0x5f, "@", 0x40] +["=", 0x3d, "~", 0x7e] +["/", 0x2f, "\\", 0x5c] +["-", 0x2d, "*", 0x2a] +[":backspace", 0x1000003, "", 0x0] + +[":tab", 0x1000001, "", 0x0] +["1", 0x31, "+", 0x2b] +["2", 0x32, "^", 0x5e] +["3", 0x33, "#", 0x23] +["4", 0x34, "%", 0x25] +["5", 0x35, "(", 0x28] +["6", 0x36, ")", 0x29] +["7", 0x37, "[", 0x5b] +["8", 0x38, "]", 0x5d] +["9", 0x39, "{", 0x7b] +["0", 0x30, "}", 0x7d] + +["q", 0x51, "", 0x0] +["w", 0x57, "", 0x0] +["e", 0x45, "", 0x0] +["r", 0x52, "", 0x0] +["t", 0x54, "", 0x0] +["y", 0x59, "", 0x0] +["u", 0x55, "", 0x0] +["i", 0x49, "", 0x0] +["o", 0x4f, "", 0x0] +["p", 0x50, "", 0x0] +[":enter", 0x1000004, "", 0x0] + +["a", 0x41, "", 0x0] +["s", 0x53, "", 0x0] +["d", 0x44, "", 0x0] +["f", 0x46, "", 0x0] +["g", 0x47, "", 0x0] +["h", 0x48, "", 0x0] +["j", 0x4a, "", 0x0] +["k", 0x4b, "", 0x0] +["l", 0x4c, "", 0x0] +["ä", 0xc4, "", 0x0] +["pgup", 0x1000016, "home", 0x1000010] + +[":shift", 0x2000000, "", 0x0] +["z", 0x5a, "", 0x0] +["x", 0x58, "", 0x0] +["c", 0x43, "", 0x0] +["v", 0x56, "", 0x0] +["b", 0x42, "", 0x0] +["n", 0x4e, "", 0x0] +["m", 0x4d, "", 0x0] +["ö", 0xd6, "", 0x0] +[":up", 0x1000013, "", 0x0] +["pgdn", 0x1000017, "end", 0x1000011] + +["ctrl", 0x4000000, "", 0x0] +["alt", 0x8000000, "", 0x0] +["å", 0xc2, "", 0x0] +[",", 0x2c, ";", 0x3b] +[[[" ", 0x20, "", 0x0]]] +[".", 0x2e, ":", 0x3a] +[":left", 0x1000012, "", 0x0] +[":down", 0x1000015, "", 0x0] +[":right", 0x1000014, "", 0x0] diff --git a/data/menu.xml b/data/menu.xml new file mode 100644 index 0000000..702057f --- /dev/null +++ b/data/menu.xml @@ -0,0 +1,29 @@ + + + + ssh screen example + ssh -t user@example.com screen -UDr\r + + irssi + + + ssh example + ssh user@example.com\r + + + screen -Dr + screen -Dr\r + + + Ctrl-A-D + \x01\x04 + + + Ctrl-C + \x03 + + diff --git a/dbusadaptor.cpp b/dbusadaptor.cpp new file mode 100644 index 0000000..d0237f2 --- /dev/null +++ b/dbusadaptor.cpp @@ -0,0 +1,60 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include +#include +#include "dbusadaptor.h" +#include "mainwindow.h" + +#ifdef MEEGO_EDITION_HARMATTAN + +DbusAdaptor::DbusAdaptor(QObject *parent): + MApplicationService("org.hqh.fingerterm", parent), + mainWin(0) +{ +} + +DbusAdaptor::~DbusAdaptor() +{ +} + +void DbusAdaptor::launch() +{ + MApplicationService::launch(); + + if (mainWin) { + mainWin->raise(); + } +} + +void DbusAdaptor::launch(const QStringList ¶meters) +{ + if (parameters.contains("new")) + launchAnotherWithQProcess(); + else + launch(); +} + +void DbusAdaptor::handleServiceRegistrationFailure() +{ + // for some reason the subsequent instances get the default "com.nokia..." prefix + incrementAndRegister(); +} + +#endif // MEEGO_EDITION_HARMATTAN diff --git a/dbusadaptor.h b/dbusadaptor.h new file mode 100644 index 0000000..e78f637 --- /dev/null +++ b/dbusadaptor.h @@ -0,0 +1,54 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef DBUSADAPTOR_H +#define DBUSADAPTOR_H + +#include "qplatformdefs.h" +class MainWindow; + +#ifdef MEEGO_EDITION_HARMATTAN + +// handles dbus registration & multiple instances on harmattan + +#include + +class DbusAdaptor : public MApplicationService +{ + Q_OBJECT + +public: + explicit DbusAdaptor(QObject *parent = 0); + virtual ~DbusAdaptor(); + +public slots: + virtual void launch(); + virtual void launch(const QStringList ¶meters); + virtual void handleServiceRegistrationFailure(); + + void setAppWindow(MainWindow* win) { mainWin = win; } + +private: + Q_DISABLE_COPY(DbusAdaptor) + + MainWindow* mainWin; +}; + +#endif // MEEGO_EDITION_HARMATTAN +#endif // DBUSADAPTOR_H diff --git a/fingerterm.desktop b/fingerterm.desktop new file mode 100644 index 0000000..9205df7 --- /dev/null +++ b/fingerterm.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Name=FingerTerm +GenericName=FingerTerm +Comment=Terminal emulator +Exec=/usr/bin/invoker --single-instance --type=e /opt/fingerterm/bin/fingerterm +Terminal=false +Type=Application +Icon=/usr/share/icons/hicolor/80x80/apps/fingerterm.png diff --git a/fingerterm.png b/fingerterm.png new file mode 100644 index 0000000..bd057c4 Binary files /dev/null and b/fingerterm.png differ diff --git a/fingerterm.pro b/fingerterm.pro new file mode 100644 index 0000000..8a042e3 --- /dev/null +++ b/fingerterm.pro @@ -0,0 +1,75 @@ +QT = core gui declarative opengl dbus + +contains(MEEGO_EDITION,harmattan): { + CONFIG += meegotouch +} + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . +LIBS += -lutil + +system($$PWD/updateversion.sh) + +# Input +HEADERS += \ + ptyiface.h \ + terminal.h \ + textrender.h \ + version.h \ + util.h \ + keyloader.h \ + mainwindow.h \ + dbusadaptor.h +SOURCES += main.cpp terminal.cpp textrender.cpp \ + ptyiface.cpp \ + util.cpp \ + keyloader.cpp \ + mainwindow.cpp \ + dbusadaptor.cpp + +OTHER_FILES += \ + qml/Main.qml \ + qml/Keyboard.qml \ + qml/Key.qml \ + qml/Lineview.qml \ + qtc_packaging/debian_harmattan/rules \ + qtc_packaging/debian_harmattan/copyright \ + qtc_packaging/debian_harmattan/control \ + qtc_packaging/debian_harmattan/compat \ + qtc_packaging/debian_harmattan/changelog \ + qml/Button.qml \ + qml/Menu.qml \ + qml/NotifyWin.qml \ + qml/UrlWindow.qml \ + qml/LayoutWindow.qml + +RESOURCES += \ + resources.qrc + +unix:!symbian:!maemo5 { + target.path = /opt/fingerterm/bin + INSTALLS += target +} + +maemo5 { + target.path = /opt/fingerterm/bin + INSTALLS += target +} + +contains(MEEGO_EDITION,harmattan) { + desktopfile.files = $${TARGET}fingerterm.desktop + desktopfile.path = /usr/share/applications + INSTALLS += desktopfile +} + +contains(MEEGO_EDITION,harmattan) { + icon.files = fingerterm.png + icon.path = /usr/share/icons/hicolor/80x80/apps + INSTALLS += icon +} + + + + diff --git a/icons/backspace.png b/icons/backspace.png new file mode 100644 index 0000000..e1f36e7 Binary files /dev/null and b/icons/backspace.png differ diff --git a/icons/down.png b/icons/down.png new file mode 100644 index 0000000..7a4ff65 Binary files /dev/null and b/icons/down.png differ diff --git a/icons/enter.png b/icons/enter.png new file mode 100644 index 0000000..30f0367 Binary files /dev/null and b/icons/enter.png differ diff --git a/icons/left.png b/icons/left.png new file mode 100644 index 0000000..1ca4b4a Binary files /dev/null and b/icons/left.png differ diff --git a/icons/menu.png b/icons/menu.png new file mode 100644 index 0000000..eb298fd Binary files /dev/null and b/icons/menu.png differ diff --git a/icons/right.png b/icons/right.png new file mode 100644 index 0000000..25bf63c Binary files /dev/null and b/icons/right.png differ diff --git a/icons/scroll-indicator.png b/icons/scroll-indicator.png new file mode 100644 index 0000000..f3d2987 Binary files /dev/null and b/icons/scroll-indicator.png differ diff --git a/icons/shift.png b/icons/shift.png new file mode 100644 index 0000000..fa4254e Binary files /dev/null and b/icons/shift.png differ diff --git a/icons/tab.png b/icons/tab.png new file mode 100644 index 0000000..10e9643 Binary files /dev/null and b/icons/tab.png differ diff --git a/icons/up.png b/icons/up.png new file mode 100644 index 0000000..4b7ebc4 Binary files /dev/null and b/icons/up.png differ diff --git a/keyloader.cpp b/keyloader.cpp new file mode 100644 index 0000000..d91d0fb --- /dev/null +++ b/keyloader.cpp @@ -0,0 +1,232 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include +#include + +#include "keyloader.h" +#include "util.h" + +KeyLoader::KeyLoader(QObject *parent) : + QObject(parent), + iVkbRows(0), + iVkbColumns(0), + iUtil(0) +{ +} + +KeyLoader::~KeyLoader() +{ +} + +bool KeyLoader::loadLayout(QString layout) +{ + bool ret = false; + if(layout.isEmpty() || !iUtil) + return false; + + if (layout.at(0)==':') { // load from resources + QResource res(layout); + QByteArray resArr( reinterpret_cast(res.data()) ); + QBuffer resBuf( &resArr ); + ret = loadLayoutInternal(resBuf); + } + else { // load from file + QFile f(iUtil->configPath() + "/" + layout + ".layout"); + ret = loadLayoutInternal(f); + } + + return ret; +} + +bool KeyLoader::loadLayoutInternal(QIODevice &from) +{ + iKeyData.clear(); + bool ret = true; + + iVkbRows = 0; + iVkbColumns = 0; + bool lastLineHadKey = false; + + if( !from.open(QIODevice::ReadOnly | QIODevice::Text) ) + return false; + + QList keyRow; + while(!from.atEnd()) { + QString line = QString::fromUtf8(from.readLine()).simplified(); + if(line.length()>=2 && line.at(0)!=';' && line.at(0)=='[' && line.at(line.length()-1)==']') + { + KeyData key; + key.label = ""; + key.code = 0; + key.label_alt = ""; + key.code_alt = 0; + key.isModifier = false; + + line.replace("\\\\", "\\x5C"); + line.replace("\" \"", "\\x20"); + line.replace(" ", ""); + line.replace("\"[\"", "\\x5B"); + line.replace("\"]\"", "\\x5D"); + line.replace("\",\"", "\\x2C"); + line.replace("\\\"", "\\x22"); + line.replace("\"", ""); + key.width = line.count('['); + line.replace("[", ""); + line.replace("]", ""); + + line.replace("\\x20", " "); + line.replace("\\x22", "\""); + line.replace("\\x5B", "["); + line.replace("\\x5D", "]"); + line.replace("\\x5C", "\\"); + + QStringList parts = line.split(",", QString::KeepEmptyParts); + if(parts.count()>=2) { + bool ok = true; + key.label = parts.at(0); + key.label.replace("\\x2C",","); + parts[1].replace("0x",""); + key.code = parts.at(1).toInt(&ok,16); + if(!ok) { + ret = false; + break; + } + if(key.code==Qt::AltModifier || key.code==Qt::ControlModifier || key.code==Qt::ShiftModifier) + key.isModifier = true; + if(parts.count()>=4 && !key.isModifier) { + key.label_alt = parts.at(2); + key.label_alt.replace("\\x2C",","); + parts[3].replace("0x",""); + key.code_alt = parts.at(3).toInt(&ok,16); + if(!ok) { + ret = false; + break; + } + } + } + lastLineHadKey = true; + cleanUpKey(key); + keyRow.append(key); + } + else if(line.length()==0 && lastLineHadKey) { + if(keyRow.count() > iVkbColumns) { + iVkbColumns = keyRow.count(); + } + iKeyData.append(keyRow); + keyRow.clear(); + lastLineHadKey = false; + } + else { + lastLineHadKey = false; + } + } + if(keyRow.count() > 0) + iKeyData.append(keyRow); + + iVkbRows = iKeyData.count(); + foreach(QList r, iKeyData) { + if(r.count() > iVkbColumns) + iVkbColumns = r.count(); + } + + from.close(); + + if (iVkbColumns <= 0 || iVkbRows <= 0) + ret = false; + + if (!ret) + iKeyData.clear(); + + return ret; +} + +QVariantList KeyLoader::keyAt(int row, int col) +{ + QVariantList ret; + ret.append(""); //label + ret.append(0); //code + ret.append(""); //label_alt + ret.append(0); //code_alt + ret.append(0); //width + ret.append(false); //isModifier + + if(iKeyData.count() <= row) + return ret; + if(iKeyData.at(row).count() <= col) + return ret; + + ret[0] = iKeyData.at(row).at(col).label; + ret[1] = iKeyData.at(row).at(col).code; + ret[2] = iKeyData.at(row).at(col).label_alt; + ret[3] = iKeyData.at(row).at(col).code_alt; + ret[4] = iKeyData.at(row).at(col).width; + ret[5] = iKeyData.at(row).at(col).isModifier; + + return ret; +} + +const QStringList KeyLoader::availableLayouts() +{ + if (!iUtil) + return QStringList(); + + QDir confDir(iUtil->configPath()); + QStringList filter("*.layout"); + + QStringList results = confDir.entryList(filter, QDir::Files|QDir::Readable, QDir::Name); + + QStringList ret; + foreach(QString s, results) { + ret << s.left(s.lastIndexOf('.')); + } + + return ret; +} + +void KeyLoader::cleanUpKey(KeyData &key) +{ + // make sure that a key does not try to use some (currently) unsupported feature... + + // if the label is an image or a modifier, we do not support an alternative label + if ((key.label.startsWith(':') && key.label.length()>1) || key.isModifier) { + key.label_alt = ""; + key.code_alt = 0; + } + + // if the alternative label is an image (and the default one was not), use it as the (only) default + if (key.label_alt.startsWith(':') && key.label_alt.length()>1) { + key.label = key.label_alt; + key.code = key.code_alt; + key.label_alt = ""; + key.code_alt = 0; + } + + // alphabet letters can't have an alternative, they just switch between lower and upper case + if (key.label.length()==1 && key.label.at(0).isLetter()) { + key.label_alt = ""; + key.code_alt = 0; + } + + // ... also, can't have alphabet letters as an alternative label + if (key.label_alt.length()==1 && key.label_alt.at(0).isLetter()) { + key.label_alt = ""; + key.code_alt = 0; + } +} diff --git a/keyloader.h b/keyloader.h new file mode 100644 index 0000000..0b9202c --- /dev/null +++ b/keyloader.h @@ -0,0 +1,70 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef KEYLOADER_H +#define KEYLOADER_H + +#include +#include + +class Util; + +struct KeyData { + QString label; + int code; + QString label_alt; + int code_alt; + int width; + bool isModifier; +}; + +class KeyLoader : public QObject +{ + Q_OBJECT +public: + explicit KeyLoader(QObject *parent = 0); + virtual ~KeyLoader(); + + void setUtil(Util* util) { iUtil = util; } + + Q_INVOKABLE bool loadLayout(QString layout); + + Q_INVOKABLE int vkbRows() { return iVkbRows; } + Q_INVOKABLE int vkbColumns() { return iVkbColumns; } + Q_INVOKABLE QVariantList keyAt(int row, int col); + Q_INVOKABLE const QStringList availableLayouts(); + +signals: + +public slots: + +private: + Q_DISABLE_COPY(KeyLoader) + bool loadLayoutInternal(QIODevice &from); + void cleanUpKey(KeyData &key); + + int iVkbRows; + int iVkbColumns; + + QList > iKeyData; + + Util *iUtil; +}; + +#endif // KEYLOADER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a8240fe --- /dev/null +++ b/main.cpp @@ -0,0 +1,260 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include "qplatformdefs.h" + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +#ifdef MEEGO_EDITION_HARMATTAN +#include +#include "dbusadaptor.h" +#endif + +#include "mainwindow.h" +#include "ptyiface.h" +#include "terminal.h" +#include "textrender.h" +#include "util.h" +#include "version.h" +#include "keyloader.h" + +void defaultSettings(QSettings* settings); +void copyFileFromResources(QString from, QString to); + +int main(int argc, char *argv[]) +{ + QSettings *settings = new QSettings(QDir::homePath()+"/.config/FingerTerm/settings.ini", QSettings::IniFormat); + defaultSettings(settings); + + // fork the child process before creating QApplication + int socketM; + int pid = forkpty(&socketM,NULL,NULL,NULL); + if( pid==-1 ) { + qFatal("forkpty failed"); + exit(1); + } else if( pid==0 ) { + setenv("TERM", settings->value("terminal/envVarTERM").toByteArray(), 1); + + QString execCmd; + for(int i=0; ivalue("general/execCmd").toString(); + } + if(execCmd.isEmpty()) { + // execute the user's default shell + passwd *pwdstruct = getpwuid(getuid()); + execCmd = QString(pwdstruct->pw_shell); + execCmd.append(" --login"); + } + + if(settings) + delete settings; // don't need 'em here + + QStringList execParts = execCmd.split(' ', QString::SkipEmptyParts); + if(execParts.length()==0) + exit(0); + char *ptrs[execParts.length()+1]; + for(int i=0; i("TextRender",1,0,"TextRender"); + MainWindow view; + +#ifdef MEEGO_EDITION_HARMATTAN + DbusAdaptor *dba = new DbusAdaptor(); + dba->setAppWindow(&view); + + // needed for MFeedback, also creates the dbus interface + MComponentData::createInstance(argc, argv, "fingerterm", dba); +#endif + +#ifdef MEEGO_EDITION_HARMATTAN + if(!app.arguments().contains("-nogl")) { + view.setViewport(new QGLWidget(QGLFormat(QGL::DoubleBuffer))); + view.setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + } +#endif + view.setResizeMode(QDeclarativeView::SizeRootObjectToView); + + Terminal term; + Util util(settings); + term.setUtil(&util); + QString startupErrorMsg; + + // copy the default config files to the config dir if they don't already exist + copyFileFromResources(":/data/menu.xml", util.configPath()+"/menu.xml"); + copyFileFromResources(":/data/english.layout", util.configPath()+"/english.layout"); + copyFileFromResources(":/data/finnish.layout", util.configPath()+"/finnish.layout"); + + KeyLoader keyLoader; + keyLoader.setUtil(&util); + bool ret = keyLoader.loadLayout( settings->value("ui/keyboardLayout").toString() ); + if(!ret) { + // on failure, try to load the default one (english) directly from resources + startupErrorMsg = "There was an error loading the keyboard layout.
\nUsing the default one instead."; + settings->setValue("ui/keyboardLayout", "english"); + ret = keyLoader.loadLayout(":/data/english.layout"); + if(!ret) + qFatal("failure loading keyboard layout"); + } + + QDeclarativeContext* context = view.rootContext(); + context->setContextProperty( "term", &term ); + context->setContextProperty( "util", &util ); + context->setContextProperty( "keyLoader", &keyLoader ); + + view.setSource(QUrl("qrc:/qml/Main.qml")); + + QObject *win = view.rootObject(); + if(!win) + qFatal("no root object - qml error"); + + if(!startupErrorMsg.isEmpty()) + QMetaObject::invokeMethod(win, "showErrorMessage", Qt::QueuedConnection, Q_ARG(QVariant, startupErrorMsg)); + + TextRender *tr = win->findChild("textrender"); + tr->setUtil(&util); + tr->setTerminal(&term); + term.setRenderer(tr); + term.setWindow(&view); + util.setWindow(&view); + util.setTerm(&term); + util.setRenderer(tr); + view.scene()->installEventFilter(&util); //for grabbing mouse drags + + QObject::connect(&term,SIGNAL(displayBufferChanged()),win,SLOT(displayBufferChanged())); + QObject::connect(view.engine(),SIGNAL(quit()),&app,SLOT(quit())); + +#ifdef MEEGO_EDITION_HARMATTAN + view.showFullScreen(); +#else + if ((QApplication::desktop()->width() < 1024 || QApplication::desktop()->height() < 768 || app.arguments().contains("-fs")) + && !app.arguments().contains("-nofs")) + view.showFullScreen(); + else + view.show(); +#endif + + PtyIFace ptyiface(pid, socketM, &term, + settings->value("terminal/charset").toString()); + + if( ptyiface.failed() ) + qFatal("pty failure"); + + return app.exec(); +} + +void defaultSettings(QSettings* settings) +{ + if(!settings->contains("general/execCmd")) + settings->setValue("general/execCmd", ""); + if(!settings->contains("general/visualBell")) + settings->setValue("general/visualBell", true); + if(!settings->contains("general/backgroundBellNotify")) + settings->setValue("general/backgroundBellNotify", true); + if(!settings->contains("general/grabUrlsFromBackbuffer")) + settings->setValue("general/grabUrlsFromBackbuffer", false); + + if(!settings->contains("terminal/envVarTERM")) + settings->setValue("terminal/envVarTERM", "xterm"); + if(!settings->contains("terminal/charset")) + settings->setValue("terminal/charset", "UTF-8"); + + if(!settings->contains("ui/keyboardLayout")) + settings->setValue("ui/keyboardLayout", "english"); + if(!settings->contains("ui/fontFamily")) + settings->setValue("ui/fontFamily", "monospace"); + if(!settings->contains("ui/fontSize")) + settings->setValue("ui/fontSize", 11); + if(!settings->contains("ui/keyboardMargins")) + settings->setValue("ui/keyboardMargins", 10); + if(!settings->contains("ui/allowSwipe")) + settings->setValue("ui/allowSwipe", "auto"); // "true", "false", "auto" + if(!settings->contains("ui/keyboardFadeOutDelay")) + settings->setValue("ui/keyboardFadeOutDelay", 2500); + if(!settings->contains("ui/showExtraLinesFromCursor")) + settings->setValue("ui/showExtraLinesFromCursor", 1); + if(!settings->contains("ui/vkbShowMethod")) + settings->setValue("ui/vkbShowMethod", "fade"); // "fade", "move", "off" + if(!settings->contains("ui/keyPressFeedback")) + settings->setValue("ui/keyPressFeedback", true); + if(!settings->contains("ui/dragMode")) + settings->setValue("ui/dragMode", "gestures"); // "gestures, "scroll", "select" ("off" would also be ok) + + if(!settings->contains("state/showWelcomeScreen")) + settings->setValue("state/showWelcomeScreen", true); + if(!settings->contains("state/createdByVersion")) + settings->setValue("state/createdByVersion", PROGRAM_VERSION); + + if(!settings->contains("gestures/panLeftTitle")) + settings->setValue("gestures/panLeftTitle", "Alt-Right"); + if(!settings->contains("gestures/panLeftCommand")) + settings->setValue("gestures/panLeftCommand", "\\e\\e[C"); + if(!settings->contains("gestures/panRightTitle")) + settings->setValue("gestures/panRightTitle", "Alt-Left"); + if(!settings->contains("gestures/panRightCommand")) + settings->setValue("gestures/panRightCommand", "\\e\\e[D"); + if(!settings->contains("gestures/panUpTitle")) + settings->setValue("gestures/panUpTitle", "Page Down"); + if(!settings->contains("gestures/panUpCommand")) + settings->setValue("gestures/panUpCommand", "\\e[6~"); + if(!settings->contains("gestures/panDownTitle")) + settings->setValue("gestures/panDownTitle", "Page Up"); + if(!settings->contains("gestures/panDownCommand")) + settings->setValue("gestures/panDownCommand", "\\e[5~"); +} + +void copyFileFromResources(QString from, QString to) +{ + // copy a file from resources to the config dir if it does not exist there + QFileInfo toFile(to); + if(!toFile.exists()) { + QFile newToFile(toFile.absoluteFilePath()); + QResource res(from); + if (newToFile.open(QIODevice::WriteOnly)) { + newToFile.write( reinterpret_cast(res.data()) ); + newToFile.close(); + } else { + qFatal("failed to copy default config from resources"); + } + } +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..8b47c67 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,91 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include "qplatformdefs.h" + +#include +#include "mainwindow.h" + +#ifdef MEEGO_EDITION_HARMATTAN +#include +#include +#include +#include +#include +#endif //MEEGO_EDITION_HARMATTAN + +MainWindow::MainWindow(QWidget* parent): QDeclarativeView(parent) +{ +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::focusInEvent(QFocusEvent *event) +{ + QDeclarativeView::focusInEvent(event); + emit focusChanged(true); +} + +void MainWindow::focusOutEvent(QFocusEvent *event) +{ + QDeclarativeView::focusOutEvent(event); + emit focusChanged(false); +} + +void MainWindow::minimize() +{ + setWindowState(windowState() | Qt::WindowMinimized); +} + +void MainWindow::disableSwipe() +{ +#ifdef MEEGO_EDITION_HARMATTAN + resize(MApplication::desktop()->screenGeometry().width(), + MApplication::desktop()->screenGeometry().height()); + showFullScreen(); + + unsigned int customRegion[] = + { + rect().x(), + rect().y(), + rect().width(), + rect().height() + }; + + Display *dpy = QX11Info::display(); + Atom customRegionAtom = XInternAtom(dpy, "_MEEGOTOUCH_CUSTOM_REGION", False); + + XChangeProperty(dpy, effectiveWinId(), customRegionAtom, + XA_CARDINAL, 32, PropModeReplace, + reinterpret_cast(&customRegion[0]), 4); + +#endif //MEEGO_EDITION_HARMATTAN +} + +void MainWindow::enableSwipe() +{ +#ifdef MEEGO_EDITION_HARMATTAN + Display *dpy = QX11Info::display(); + Atom customRegionAtom = XInternAtom(dpy, "_MEEGOTOUCH_CUSTOM_REGION", False); + + XDeleteProperty(dpy, effectiveWinId(), customRegionAtom); +#endif //MEEGO_EDITION_HARMATTAN +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..b572973 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,47 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +class MainWindow : public QDeclarativeView +{ + Q_OBJECT +public: + explicit MainWindow(QWidget* parent=0); + virtual ~MainWindow(); + + void minimize(); + void enableSwipe(); + void disableSwipe(); + +protected: + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + +signals: + void focusChanged(bool in); + +private: + Q_DISABLE_COPY(MainWindow) +}; + +#endif // MAINWINDOW_H diff --git a/ptyiface.cpp b/ptyiface.cpp new file mode 100644 index 0000000..e279856 --- /dev/null +++ b/ptyiface.cpp @@ -0,0 +1,146 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +#include "terminal.h" +#include "ptyiface.h" + +static bool childProcessQuit = false; +static int childProcessPid = 0; + +void sighandler(int sig) +{ + if(sig==SIGCHLD) { + int pid = wait(NULL); + + if(pid > 0 && childProcessPid > 0 && pid==childProcessPid) { + childProcessQuit = true; + childProcessPid = 0; + qApp->quit(); + } + } +} + +PtyIFace::PtyIFace(int pid, int masterFd, Terminal *term, QString charset, QObject *parent) : + QObject(parent), + iTerm(term), + iPid(pid), + iMasterFd(masterFd), + iFailed(false), + iReadNotifier(0), + iTextCodec(0) +{ + childProcessPid = iPid; + + if(!iTerm || childProcessQuit) { + iFailed = true; + qFatal("PtyIFace: null Terminal pointer"); + } + + iTerm->setPtyIFace(this); + + resize(iTerm->termSize()); + connect(iTerm,SIGNAL(termSizeChanged(QSize)),this,SLOT(resize(QSize))); + + iReadNotifier = new QSocketNotifier(iMasterFd, QSocketNotifier::Read, this); + connect(iReadNotifier,SIGNAL(activated(int)),this,SLOT(readActivated())); + + signal(SIGCHLD,&sighandler); + fcntl(iMasterFd, F_SETFL, O_NONBLOCK); // reads from the descriptor should be non-blocking + + if (!charset.isEmpty()) + iTextCodec = QTextCodec::codecForName(charset.toAscii()); + if (!iTextCodec) + iTextCodec = QTextCodec::codecForName("UTF-8"); + if (!iTextCodec) + qFatal("No valid text codec"); +} + +PtyIFace::~PtyIFace() +{ + if(!childProcessQuit) { + // make the process quit + kill(iPid, SIGHUP); + kill(iPid, SIGTERM); + int status=0; + waitpid(-1,&status,0); + } +} + +void PtyIFace::readActivated() +{ + QByteArray data; + readTerm(data); + if(iTerm) + iTerm->insertInBuffer( iTextCodec->toUnicode(data) ); +} + +void PtyIFace::resize(QSize newSize) +{ + if(childProcessQuit) + return; + + winsize winp; + winp.ws_col = newSize.width(); + winp.ws_row = newSize.height(); + + ioctl(iMasterFd, TIOCSWINSZ, &winp); +} + +void PtyIFace::writeTerm(const QString &chars) +{ + writeTerm( iTextCodec->fromUnicode(chars) ); +} + +void PtyIFace::writeTerm(const QByteArray &chars) +{ + if(childProcessQuit) + return; + + int ret = write(iMasterFd, chars, chars.size()); + if(ret != chars.size()) + qDebug() << "write error!"; +} + +void PtyIFace::readTerm(QByteArray &chars) +{ + if(childProcessQuit) + return; + + int ret = 0; + char ch[64]; + while(ret != -1) { + ret = read(iMasterFd, &ch, 64); + if(ret > 0) + chars.append((char*)&ch, ret); + } +} diff --git a/ptyiface.h b/ptyiface.h new file mode 100644 index 0000000..952dbf2 --- /dev/null +++ b/ptyiface.h @@ -0,0 +1,63 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef PTYIFACE_H +#define PTYIFACE_H + +#include +#include +#include +#include +#include + +class Terminal; + +class PtyIFace : public QObject +{ + Q_OBJECT +public: + explicit PtyIFace(int pid, int masterFd, Terminal *term, QString charset, QObject *parent = 0); + virtual ~PtyIFace(); + + void writeTerm(const QString &chars); + bool failed() { return iFailed; } + +public slots: + void resize(QSize newSize); + +private slots: + void readActivated(); + +private: + Q_DISABLE_COPY(PtyIFace) + + void writeTerm(const QByteArray &chars); + void readTerm(QByteArray &chars); + + Terminal *iTerm; + int iPid; + int iMasterFd; + bool iFailed; + + QSocketNotifier *iReadNotifier; + + QTextCodec *iTextCodec; +}; + +#endif // PTYIFACE_H diff --git a/qml/Button.qml b/qml/Button.qml new file mode 100644 index 0000000..69fa634 --- /dev/null +++ b/qml/Button.qml @@ -0,0 +1,85 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: button + property string text: "" + property string textColor: "#ffffff" + property bool enabled: true + property bool highlighted: false + signal clicked(); + + // for custom user menu actions + property bool isShellCommand: false + + color: highlighted ? "#606060" : "#202020" + border.color: "#303030" + border.width: 1 + radius: 5 + z: 0 + clip: true + + width: 180 + height: 68 + + onHighlightedChanged: { + if(highlighted) + button.color = "#606060" + else + button.color = "#202020" + } + + Text { + // decoration for user-defined command buttons + visible: isShellCommand + anchors.fill: parent + font.pointSize: 46 + text: "$" + color: "#305030" + } + + Text { + text: button.text + color: button.enabled ? button.textColor : "#606060" + anchors.centerIn: parent + font.pointSize: util.uiFontSize(); + } + + MouseArea { + id: btnMouseArea + enabled: button.enabled + anchors.fill: parent + onClicked: { + button.clicked(); + } + onPressedChanged: { + if(pressed) { + button.color = "#ffffff" + } else { + if(highlighted) + button.color = "#606060" + else + button.color = "#202020" + } + } + } + +} diff --git a/qml/Key.qml b/qml/Key.qml new file mode 100644 index 0000000..d10cb7a --- /dev/null +++ b/qml/Key.qml @@ -0,0 +1,188 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: key + property string label: "" + property string label_alt: "" + property int code: 0 + property int code_alt: 0 + property int currentCode: code + property string currentLabel: keyLabel.text + property bool sticky: false // can key be stickied? + property int stickiness: 0 // current stickiness status + + width: window.width/12 // some default + height: window.height/8 < 55 ? window.height/8 : 55 + color: label=="" ? "transparent" : keyboard.keyBgColor + border.color: label=="" ? "transparent" : keyboard.keyBorderColor + border.width: 1 + radius: 5 + + Connections { + target: keyboard + onKeyModifiersChanged: { + if( (keyboard.keyModifiers & Qt.ShiftModifier) && !sticky ) { + if(key.label_alt!="") { + keyLabel.text = key.label_alt + keyAltLabel.text = key.label + key.currentCode = key.code_alt + } else if(key.label.length==1) { + keyLabel.text = key.label.toUpperCase() + } + } else if(!sticky) { + if(key.label.length==1) + keyLabel.text = key.label.toLowerCase() + else + keyLabel.text = key.label + + keyAltLabel.text = key.label_alt + key.currentCode = key.code + } + } + } + + Image { + id: keyImage + anchors.centerIn: parent + opacity: 0.4 + source: { if(key.label.length>1 && key.label.charAt(0)==':') return "qrc:/icons/"+key.label.substring(1)+".png"; else return ""; } + } + + Text { + visible: keyImage.source == "" + id: keyLabel + anchors.centerIn: parent + text: key.label + color: keyboard.keyFgColor + font.pointSize: key.label.length>1 ? 18 : 26 + } + Text { + id: keyAltLabel + anchors.bottom: parent.bottom + anchors.right: parent.right + text: key.label_alt + color: keyboard.keyFgColor + font.pointSize: key.label.length>1 ? 10 : 13 + } + Rectangle { + id: stickIndicator + visible: sticky && stickiness>0 + color: keyboard.keyHilightBgColor + anchors.fill: parent + radius: key.radius + opacity: 0.5 + z: 1 + anchors.topMargin: key.height/2 + } + + MouseArea { + enabled: label!="" + id: keyMouseArea + anchors.fill: parent + anchors.margins: -3 // -(half of keyspacing) + onExited: { + keyRepeatStarter.stop(); + keyRepeatTimer.stop(); + + key.color = keyboard.keyBgColor + keyboard.currentKeyPressed = 0; + } + onPressedChanged: { + if(pressed) { + key.color = keyboard.keyHilightBgColor + keyboard.currentKeyPressed = key; + util.keyPressFeedback(); + + keyRepeatStarter.start(); + } else { + keyboard.currentKeyPressed = 0; + if(containsMouse) { + + util.keyReleaseFeedback(); + + keyRepeatStarter.stop(); + keyRepeatTimer.stop(); + + key.color = keyboard.keyBgColor + + setStickiness(-1) + vkbKeypress(currentCode, keyboard.keyModifiers); + + if( !sticky && keyboard.resetSticky != 0 && keyboard.resetSticky !== key ) { + resetSticky.setStickiness(0); + } + } + } + } + } + + Timer { + id: keyRepeatStarter + running: false + repeat: false + interval: 400 + triggeredOnStart: false + onTriggered: { + keyRepeatTimer.start(); + } + } + + Timer { + id: keyRepeatTimer + running: false + repeat: true + triggeredOnStart: true + interval: 80 + onTriggered: { + vkbKeypress(currentCode, keyboard.keyModifiers); + } + } + + function setStickiness(val) + { + if(sticky) { + if( keyboard.resetSticky != 0 && keyboard.resetSticky !== key ) { + resetSticky.setStickiness(0) + } + + if(val===-1) + stickiness = (stickiness+1) % 3 + else + stickiness = val + + if(stickiness>0) { + keyboard.keyModifiers |= code + } else { + keyboard.keyModifiers &= ~code + } + + keyboard.resetSticky = 0 + + if(stickiness==1) { + stickIndicator.anchors.topMargin = key.height/2 + keyboard.resetSticky = key + } else if(stickiness==2) { + stickIndicator.anchors.topMargin = 0 + } + } + } +} diff --git a/qml/Keyboard.qml b/qml/Keyboard.qml new file mode 100644 index 0000000..48c7fba --- /dev/null +++ b/qml/Keyboard.qml @@ -0,0 +1,113 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: keyboard + color: "transparent" + + width: parent.width + height: childrenRect.height + outmargins + + property int keyModifiers: 0 + property variant resetSticky: 0 + property variant currentKeyPressed: 0 + + property string keyFgColor: "#565656" + property string keyBgColor: "#202020" + property string keyHilightBgColor: "#ffffff" + property string keyBorderColor: "#303030" + + property bool active: false + + property int outmargins: util.settingsValue("ui/keyboardMargins") + property int keyspacing: 6 + property int keysPerRow: keyLoader.vkbColumns() + property real keywidth: (keyboard.width - keyspacing*keysPerRow - outmargins*2)/keysPerRow; + + Component { + id: keyboardContents + Column { + id: col + x: (keyboard.width-width)/2 + spacing: keyboard.keyspacing + Repeater { + id: rowRepeater + model: keyLoader.vkbRows() + delegate: Row { + spacing: keyboard.keyspacing + Repeater { + id: colRepeater + property int rowIndex: index + model: keyLoader.vkbColumns() + delegate: Key { + property variant keydata: keyLoader.keyAt(colRepeater.rowIndex, index) + label: keydata[0] + code: keydata[1] + label_alt: keydata[2] + code_alt: keydata[3] + width: keyboard.keywidth * keydata[4] + ((keydata[4]-1)*keyboard.keyspacing) + 1 + sticky: keydata[5] + } + } + } + } + } + } + + Loader { + id: keyboardLoader + } + + Component.onCompleted: { + keyboardLoader.sourceComponent = keyboardContents; + } + + onCurrentKeyPressedChanged: { + if(currentKeyPressed != 0 && currentKeyPressed.currentLabel.length === 1 && currentKeyPressed.currentLabel !== " ") { + visualKeyFeedbackRect.label = currentKeyPressed.currentLabel + visualKeyFeedbackRect.width = currentKeyPressed.width*1.5 + visualKeyFeedbackRect.height = currentKeyPressed.height*1.5 + var mappedCoord = window.mapFromItem(currentKeyPressed, 0, 0); + visualKeyFeedbackRect.x = mappedCoord.x - (visualKeyFeedbackRect.width-currentKeyPressed.width)/2 + visualKeyFeedbackRect.y = mappedCoord.y - currentKeyPressed.height*1.5 + visualKeyFeedbackRect.visible = true; + } else { + visualKeyFeedbackRect.visible = false; + } + } + + function reloadLayout() + { + var ret = keyLoader.loadLayout(util.settingsValue("ui/keyboardLayout")); + if (!ret) { + showErrorMessage("There was an error loading the keyboard layout.
\nUsing the default one instead."); + util.setSettingsValue("ui/keyboardLayout", "english"); + ret = keyLoader.loadLayout(":/data/english.layout"); //try the default as a fallback (load from resources to ensure it will succeed) + if (!ret) { + console.log("keyboard layout fail"); + Qt.quit(); + } + } + // makes the keyboard component reload itself with new data + keyboardLoader.sourceComponent = undefined + keyboardLoader.sourceComponent = keyboardContents + } +} diff --git a/qml/LayoutWindow.qml b/qml/LayoutWindow.qml new file mode 100644 index 0000000..7e9ac1b --- /dev/null +++ b/qml/LayoutWindow.qml @@ -0,0 +1,132 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: layoutWindow + property string currentLayout: util.settingsValue("ui/keyboardLayout"); + + width: window.width-1 + height: window.height-1 + color: "#000000" + z: 100 + property variant layouts: [""] + state: "" + y: -(height+1) + border.color: "#c0c0c0" + border.width: 1 + radius: 10 + + MouseArea { + // event eater + anchors.fill: parent + } + + Component { + id: listDelegate + Rectangle { + color: currentLayout === modelData ? "#909090" : "#404040" + width: parent.width + height: selectButton.height+4 + border.width: 1 + border.color: "#ffffff" + radius: 5 + clip: true + + Text { + text: modelData + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + x: 8 + width: selectButton.x - x + font.pointSize: util.uiFontSize(); + elide: Text.ElideRight + } + Button { + id: selectButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: "Select" + width: 70 + anchors.rightMargin: 5 + onClicked: { + util.setSettingsValue("ui/keyboardLayout", modelData); + vkb.reloadLayout(); + layoutWindow.state = ""; + util.notifyText(modelData); + } + } + } + } + + Text { + id: titleText + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + color: "#ffffff" + text: "Keyboard layout" + font.pointSize: util.uiFontSize() + 4; + } + + ListView { + anchors.fill: parent + anchors.topMargin: titleText.height + 4 + delegate: listDelegate + model: layoutWindow.layouts + spacing: 5 + anchors.margins: 5 + clip: true + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + text: "Back" + onClicked: { + layoutWindow.state = "" + } + } + + states: [ + State { + name: "visible" + PropertyChanges { + target: layoutWindow + y: 0 + } + StateChangeScript { + script: + currentLayout = util.settingsValue("ui/keyboardLayout"); + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "*" + SequentialAnimation { + PropertyAnimation { target: layoutWindow; properties: "y"; duration: 200; easing.type: Easing.InOutCubic } + ScriptAction { script: updateGesturesAllowed(); } + } + } + ] +} diff --git a/qml/Lineview.qml b/qml/Lineview.qml new file mode 100644 index 0000000..e90385f --- /dev/null +++ b/qml/Lineview.qml @@ -0,0 +1,108 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: lineView + property variant lines: [""] + property int fontPointSize: util.settingsValue("ui/fontSize"); + property int cursorX: 1 + property int cursorWidth: 10 + property int cursorHeight: 10 + property int extraLines: 1 + + color: "#404040" + border.width: 2 + border.color: "#909090" + radius: 5 + height: 0 + width: parent.width + + Text { + id: fontHeightHack + visible: false + text: "X" + font.family: util.settingsValue("ui/fontFamily"); + font.pointSize: lineView.fontPointSize + } + + Rectangle { + visible: vkb.active + x: cursorX + y: lineTextCol.y + fontHeightHack.height*(extraLines+1) - cursorHeight - 3 + width: cursorWidth + height: cursorHeight + color: "transparent" + border.color: "#ffffff" + border.width: 1 + } + + Column { + id: lineTextCol + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 2 + anchors.rightMargin: 2 + Repeater { + model: lines + delegate: + Rectangle { + height: fontHeightHack.height + width: lineTextCol.width + color: "transparent" + Text { + color: "#ffffff" + font.family: util.settingsValue("ui/fontFamily"); + font.pointSize: lineView.fontPointSize + text: modelData + textFormat: Text.PlainText + wrapMode: Text.NoWrap + elide: Text.ElideNone + maximumLineCount: 1 + } + } + } + onHeightChanged: { + if(lineView.visible) + lineView.height = height+8 + setPosition(vkb.active) + } + } + + Component.onCompleted: { + extraLines = util.settingsValue("ui/showExtraLinesFromCursor"); + } + + function setPosition(vkbActive) + { + if( util.settingsValue("ui/vkbShowMethod")==="off" ) { + lineView.visible = false; + return; + } + + lineView.visible = true; + if(vkbActive && util.settingsValue("ui/vkbShowMethod")!=="move") { + y = 0; + } else { + y = -(height+5) + } + } +} diff --git a/qml/Main.qml b/qml/Main.qml new file mode 100644 index 0000000..7037401 --- /dev/null +++ b/qml/Main.qml @@ -0,0 +1,374 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 +import TextRender 1.0 + +Rectangle { + property string fgcolor: "black" + property string bgcolor: "#000000" + property int fontSize: 14 + + property int fadeOutTime: 80 + property int fadeInTime: 350 + + property string windowTitle: util.currentWindowTitle(); + + id: window + objectName: "window" + width: 854 + height: 480 + color: bgcolor + + NotifyWin { + id: aboutDialog + property int termW: 0 + property int termH: 0 + text: { + var str = "FingerTerm " + util.versionString() + "
\n" + + "" + + "by Heikki Holstila <heikki.holstila@gmail.com>

\n\n" + + "Config files for adjusting settings are at:
\n" + + util.configPath() + "/

\n" + + "Documentation:
\nhttp://hqh.unlink.org/harmattan" + if (termH != 0 && termW != 0) { + str += "

Current window title: " + windowTitle.substring(0,40) + ""; //cut long window title + if(windowTitle.length>40) + str += "..."; + str += "
Current terminal size: " + termW + "x" + termH + ""; + str += "
Charset: " + util.settingsValue("terminal/charset") + ""; + } + str += "
"; + return str; + } + onDismissed: { + util.setSettingsValue("state/showWelcomeScreen", false); + } + z: 1001 + } + + NotifyWin { + id: errorDialog + text: "" + z: 1002 + } + + UrlWindow { + id: urlWindow + z: 1000 + } + + LayoutWindow { + id: layoutWindow + z: 1000 + } + + TextRender { + id: textrender + objectName: "textrender" + x: 0 + y: 0 + height: parent.height + width: parent.width + myWidth: width + myHeight: height + opacity: 1.0 + property int duration: 0; + property int cutAfter: height + + Behavior on opacity { + NumberAnimation { duration: textrender.duration; easing.type: Easing.InOutQuad } + } + Behavior on y { + NumberAnimation { duration: textrender.duration; easing.type: Easing.InOutQuad } + } + + onFontSizeChanged: { + lineView.fontPointSize = textrender.fontPointSize; + } + + onCutAfterChanged: { + // this property is used in the paint function, so make sure that the element gets + // painted with the updated value (might not otherwise happen because of caching) + textrender.redraw(); + } + + z: 10 + } + + + Lineview { + id: lineView + x: 0 + y: -(height+1) + z: 20 + property int duration: 0; + onFontPointSizeChanged: { + lineView.setPosition(vkb.active) + } + } + + MouseArea { // the area above the keyboard ("wakes up" the keyboard when tapped) + x:0 + y:0 + z:0 + width: window.width + height: vkb.y + onClicked: { + if (util.settingsValue("ui/dragMode") !== "select") { + if (vkb.active) + sleepVKB(); + else + wakeVKB(); + } + } + } + + Rectangle { + //top right corner menu button + x: window.width - width + y: 0 + z: 1 + width: menuImg.width + 60 + height: menuImg.height + 30 + color: "transparent" + opacity: 0.5 + Image { + anchors.centerIn: parent + id: menuImg + source: "qrc:/icons/menu.png" + height: sourceSize.height + width: sourceSize.width + } + MouseArea { + anchors.fill: parent + onClicked: { + menu.showMenu(); + } + } + } + + Image { + // terminal buffer scroll indicator + source: "qrc:/icons/scroll-indicator.png" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + visible: textrender.showBufferScrollIndicator + z: 5 + } + + Menu { + id: menu + x: window.width-width + y: 0 + } + + Keyboard { + id: vkb + x: 0 + y: parent.height-vkb.height + z: 0 + } + + focus: true + Keys.onPressed: { + vkbKeypress(event.key,event.modifiers); + } + + Timer { + id: fadeTimer + running: false + repeat: false + interval: menu.keyboardFadeOutDelay + onTriggered: { + sleepVKB(); + } + } + + Timer { + id: bellTimer + running: false + repeat: false + interval: 80 + onTriggered: { + window.color = window.bgcolor; + } + } + + Connections { + target: util + onVisualBell: { + visualBell(); + } + onGestureNotify: { + textNotify.text = msg; + textNotifyAnim.enabled = false; + textNotify.opacity = 1.0; + textNotifyAnim.enabled = true; + textNotify.opacity = 0; + } + onWindowTitleChanged: { + windowTitle = util.currentWindowTitle(); + } + } + + Text { + // shows large text notification in the middle of the screen (for gestures) + id: textNotify + anchors.centerIn: parent + color: "#ffffff" + z: 100 + opacity: 0 + text: "" + font.pointSize: 40 + Behavior on opacity { + id: textNotifyAnim + NumberAnimation { duration: 500; } + } + } + + Rectangle { + // visual key press feedback... + // easier to work with the coordinates if it's here and not under keyboard element + id: visualKeyFeedbackRect + visible: false + x: 0 + y: 0 + z: 200 + width: 0 + height: 0 + radius: 5 + color: "#ffffff" + property string label: "" + Text { + color: "#000000" + font.pointSize: 34 + anchors.centerIn: parent + text: visualKeyFeedbackRect.label + } + } + + function vkbKeypress(key,modifiers) { + wakeVKB(); + term.keyPress(key,modifiers); + } + + function wakeVKB() + { + if(!vkb.visible) + return; + + lineView.duration = window.fadeOutTime; + textrender.duration = window.fadeOutTime; + vkb.keyFgColor = "#cccccc"; + fadeTimer.restart(); + vkb.active = true; + lineView.setPosition(vkb.active); + util.updateSwipeLock(!vkb.active); + setTextRenderAttributes(); + updateGesturesAllowed(); + } + + function sleepVKB() + { + textrender.duration = window.fadeInTime; + lineView.duration = window.fadeInTime; + vkb.keyFgColor = "#565656"; + vkb.active = false; + lineView.setPosition(vkb.active); + util.updateSwipeLock(!vkb.active); + setTextRenderAttributes(); + updateGesturesAllowed(); + } + + function setTextRenderAttributes() + { + if(util.settingsValue("ui/vkbShowMethod")==="move") + { + vkb.visible = true; + textrender.opacity = 1.0; + if(vkb.active) { + var move = textrender.cursorPixelPos().y + textrender.fontHeight/2 + textrender.fontHeight*util.settingsValue("ui/showExtraLinesFromCursor"); + if(move < vkb.y) { + textrender.y = 0; + textrender.cutAfter = vkb.y; + } else { + textrender.y = 0 - move + vkb.y + textrender.cutAfter = move; + } + } else { + textrender.cutAfter = textrender.height; + textrender.y = 0; + } + } + else if(util.settingsValue("ui/vkbShowMethod")==="fade") + { + vkb.visible = true; + textrender.cutAfter = textrender.height; + textrender.y = 0; + if(vkb.active) + textrender.opacity = 0.3; + else + textrender.opacity = 1.0; + } + else // "off" (vkb disabled) + { + vkb.visible = false; + textrender.cutAfter = textrender.height; + textrender.y = 0; + textrender.opacity = 1.0; + } + } + + function displayBufferChanged() + { + lineView.lines = term.printableLinesFromCursor(util.settingsValue("ui/showExtraLinesFromCursor")); + lineView.cursorX = textrender.cursorPixelPos().x; + lineView.cursorWidth = textrender.cursorPixelSize().width; + lineView.cursorHeight = textrender.cursorPixelSize().height; + setTextRenderAttributes(); + } + + Component.onCompleted: { + util.updateSwipeLock(vkb.active) + if( util.settingsValue("state/showWelcomeScreen") === true ) + aboutDialog.state = "visible"; + } + + function showErrorMessage(string) + { + errorDialog.text = "" + string + ""; + errorDialog.state = "visible" + } + + function visualBell() + { + bellTimer.start(); + window.color = "#ffffff" + } + + function updateGesturesAllowed() + { + if(vkb.active || menu.showing || urlWindow.state=="visible" || + aboutDialog.state=="visible" || layoutWindow.state=="visible") + util.allowGestures = false; + else + util.allowGestures = true; + } +} diff --git a/qml/Menu.qml b/qml/Menu.qml new file mode 100644 index 0000000..6f580e5 --- /dev/null +++ b/qml/Menu.qml @@ -0,0 +1,508 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: menuWin + color: "transparent" + z: 30 + width: window.width + height: window.height + visible: false + property bool showing: false + property bool enableCopy: false + property bool enablePaste: false + property string currentSwipeLocking: util.settingsValue("ui/allowSwipe") + property string currentShowMethod: util.settingsValue("ui/vkbShowMethod") + property string currentDragMode: util.settingsValue("ui/dragMode") + property int keyboardFadeOutDelay: util.settingsValue("ui/keyboardFadeOutDelay") + + Rectangle { + id: fader + color: "#ffffff" + opacity: 0 + y: 0 + x: 0 + width: menuWin.width + height: menuWin.height + MouseArea { + anchors.fill: parent + onClicked: { + hideMenu(); + } + } + Behavior on opacity { + SequentialAnimation { + NumberAnimation { duration: 100; } + ScriptAction { script: menuWin.visible = menuWin.showing; } + } + } + } + Rectangle { + id: rect + color: "#e0e0e0" + y: 0 + x: menuWin.width+1; + width: flickableContent.width + 22; + height: menuWin.height + z: 35 + + MouseArea { + // event eater + anchors.fill: parent + } + + Behavior on x { + NumberAnimation { duration: 100; easing.type: Easing.InOutQuad; } + } + + XmlListModel { + id: xmlModel + xml: term.getUserMenuXml() + query: "/userMenu/item" + + XmlRole { name: "title"; query: "title/string()" } + XmlRole { name: "command"; query: "command/string()" } + XmlRole { name: "disableOn"; query: "disableOn/string()" } + } + + Component { + id: xmlDelegate + Button { + text: title + isShellCommand: true + enabled: disableOn.length === 0 || window.windowTitle.search(disableOn) === -1 + onClicked: { + hideMenu(); + term.putString(command, true); + } + } + } + + Rectangle { + id: scrollIndicator + y: menuFlickArea.visibleArea.yPosition*menuFlickArea.height + 6 + x: parent.width-10 + width: 6 + height: menuFlickArea.visibleArea.heightRatio*menuFlickArea.height + radius: 3 + color: "#202020" + } + + Flickable { + id: menuFlickArea + anchors.fill: parent + anchors.topMargin: 6 + anchors.bottomMargin: 6 + anchors.leftMargin: 6 + anchors.rightMargin: 16 + contentHeight: flickableContent.height + + Column { + id: flickableContent + spacing: 12 + + Row { + id: menuBlocksRow + spacing: 8 + + Column { + spacing: 12 + Repeater { + model: xmlModel + delegate: xmlDelegate + } + } + + Column { + spacing: 12 + + Row { + Button { + text: "Copy" + onClicked: { + hideMenu(); + term.copySelectionToClipboard(); + } + width: 90 + enabled: menuWin.enableCopy + } + Button { + text: "Paste" + onClicked: { + hideMenu(); + term.pasteFromClipboard(); + } + width: 90 + enabled: menuWin.enablePaste + } + } + Button { + text: "URL grabber" + onClicked: { + hideMenu(); + urlWindow.urls = term.grabURLsFromBuffer(); + urlWindow.state = "visible"; + } + } + Rectangle { + width: 180 + height: 68 + radius: 5 + color: "#606060" + border.color: "#000000" + border.width: 1 + Column { + Text { + width: 180 + height: 20 + color: "#ffffff" + font.pointSize: util.uiFontSize()-1; + text: "Font size" + horizontalAlignment: Text.AlignHCenter + } + Row { + Button { + text: "+" + onClicked: { + textrender.fontPointSize = textrender.fontPointSize + 1; + lineView.fontPointSize = textrender.fontPointSize; + util.notifyText(term.termSize().width+"x"+term.termSize().height); + } + width: 90 + height: 48 + } + Button { + text: "-" + onClicked: { + textrender.fontPointSize = textrender.fontPointSize - 1; + lineView.fontPointSize = textrender.fontPointSize; + util.notifyText(term.termSize().width+"x"+term.termSize().height); + } + width: 90 + height: 48 + } + } + } + } + Rectangle { + width: 180 + height: 68 + radius: 5 + color: "#606060" + border.color: "#000000" + border.width: 1 + Column { + Text { + width: 180 + height: 20 + color: "#ffffff" + font.pointSize: util.uiFontSize()-1; + text: "Drag mode" + horizontalAlignment: Text.AlignHCenter + } + Row { + Button { + text: "Gesture" + highlighted: currentDragMode=="gestures" + onClicked: { + util.setSettingsValue("ui/dragMode", "gestures"); + term.clearSelection(); + currentDragMode = "gestures"; + hideMenu(); + } + width: 60 + height: 48 + } + Button { + text: "Scroll" + highlighted: currentDragMode=="scroll" + onClicked: { + util.setSettingsValue("ui/dragMode", "scroll"); + currentDragMode = "scroll"; + term.clearSelection(); + hideMenu(); + } + width: 60 + height: 48 + } + Button { + text: "Select" + highlighted: currentDragMode=="select" + onClicked: { + util.setSettingsValue("ui/dragMode", "select"); + currentDragMode = "select"; + hideMenu(); + } + width: 60 + height: 48 + } + } + } + } + Rectangle { + width: 180 + height: 68 + radius: 5 + color: "#606060" + border.color: "#000000" + border.width: 1 + Column { + Text { + width: 180 + height: 20 + color: "#ffffff" + font.pointSize: util.uiFontSize()-1; + text: "VKB behavior" + horizontalAlignment: Text.AlignHCenter + } + Row { + Button { + text: "Off" + highlighted: currentShowMethod=="off" + onClicked: { + util.setSettingsValue("ui/vkbShowMethod", "off"); + currentShowMethod = "off"; + setTextRenderAttributes(); + hideMenu(); + } + width: 60 + height: 48 + } + Button { + text: "Fade" + highlighted: currentShowMethod=="fade" + onClicked: { + util.setSettingsValue("ui/vkbShowMethod", "fade"); + currentShowMethod = "fade"; + setTextRenderAttributes(); + hideMenu(); + } + width: 60 + height: 48 + } + Button { + text: "Move" + highlighted: currentShowMethod=="move" + onClicked: { + util.setSettingsValue("ui/vkbShowMethod", "move"); + currentShowMethod = "move"; + setTextRenderAttributes(); + hideMenu(); + } + width: 60 + height: 48 + } + } + } + } + Rectangle { + visible: util.isHarmattan() + width: 180 + height: 68 + radius: 5 + color: "#606060" + border.color: "#000000" + border.width: 1 + Column { + Text { + width: 180 + height: 20 + color: "#ffffff" + font.pointSize: util.uiFontSize()-1; + text: "Allow swiping" + horizontalAlignment: Text.AlignHCenter + } + Row { + Button { + text: "No" + width: 60 + height: 48 + highlighted: currentSwipeLocking=="false" + onClicked: { + changeSwipeLocking("false") + } + } + Button { + text: "Yes" + width: 60 + height: 48 + highlighted: currentSwipeLocking=="true" + onClicked: { + changeSwipeLocking("true") + } + } + Button { + text: "Auto" + width: 60 + height: 48 + highlighted: currentSwipeLocking=="auto" + onClicked: { + changeSwipeLocking("auto") + } + } + } + } + } + Button { + visible: util.isHarmattan() + text: "New window" + onClicked: { + hideMenu(); + util.openNewWindow(); + } + } + Button { + text: "VKB layout..." + onClicked: { + hideMenu(); + layoutWindow.layouts = keyLoader.availableLayouts(); + layoutWindow.state = "visible"; + } + } + Button { + text: "About" + onClicked: { + hideMenu(); + aboutDialog.termW = term.termSize().width + aboutDialog.termH = term.termSize().height + aboutDialog.state = "visible" + } + } + Button { + visible: (currentSwipeLocking=="false" && util.isHarmattan()) || !util.isHarmattan(); + text: "Minimize" + onClicked: { + hideMenu(); + util.windowMinimize(); + } + } + Button { + text: "Quit" + onClicked: { + hideMenu(); + Qt.quit(); + } + } + } + } + // VKB delay slider + Rectangle { + id: vkbDelaySliderArea + width: menuBlocksRow.width + height: 68 + radius: 5 + color: "#606060" + border.color: "#000000" + border.width: 1 + Text { + width: parent.width + height: 20 + color: "#ffffff" + font.pointSize: util.uiFontSize()-1; + text: "VKB delay: " + vkbDelaySlider.keyboardFadeOutDelayLabel + " ms" + horizontalAlignment: Text.AlignHCenter + } + Rectangle { + x: 5 + y: vkbDelaySlider.y + vkbDelaySlider.height/2 - height/2 + width: menuBlocksRow.width - 10 + height: 10 + radius: 5 + z: 1 + color: "#909090" + } + Rectangle { + id: vkbDelaySlider + property int keyboardFadeOutDelayLabel: keyboardFadeOutDelay + x: (keyboardFadeOutDelay-1000)/9000 * (vkbDelaySliderArea.width - vkbDelaySlider.width) + y: 20 + width: 60 + radius: 15 + height: parent.height-20 + color: "#202020" + z: 2 + onXChanged: { + if (vkbDelaySliderMA.drag.active) + vkbDelaySlider.keyboardFadeOutDelayLabel = + Math.floor((1000+vkbDelaySlider.x/vkbDelaySliderMA.drag.maximumX*9000)/250)*250; + } + MouseArea { + id: vkbDelaySliderMA + anchors.fill: parent + drag.target: vkbDelaySlider + drag.axis: Drag.XAxis + drag.minimumX: 0 + drag.maximumX: vkbDelaySliderArea.width - vkbDelaySlider.width + drag.onActiveChanged: { + if (!drag.active) { + keyboardFadeOutDelay = vkbDelaySlider.keyboardFadeOutDelayLabel + util.setSettingsValue("ui/keyboardFadeOutDelay", keyboardFadeOutDelay); + } + } + } + } + } + } + } + } + + onWidthChanged: { + if(showing) { + showMenu(); + } else { + hideMenu(); + } + } + + Connections { + target: util + onClipboardOrSelectionChanged: { + enableCopy = util.terminalHasSelection(); + enablePaste = util.canPaste(); + } + } + + function showMenu() + { + showing = true; + visible = true; + fader.opacity = 0.5; + rect.x = menuWin.width-rect.width; + updateGesturesAllowed(); + enableCopy = util.terminalHasSelection(); + enablePaste = util.canPaste(); + } + + function hideMenu() + { + showing = false; + fader.opacity = 0; + rect.x = menuWin.width+1; + updateGesturesAllowed(); + } + + function changeSwipeLocking(state) + { + currentSwipeLocking = state + util.setSettingsValue("ui/allowSwipe", state) + util.updateSwipeLock(!vkb.active); + } +} diff --git a/qml/NotifyWin.qml b/qml/NotifyWin.qml new file mode 100644 index 0000000..03026d8 --- /dev/null +++ b/qml/NotifyWin.qml @@ -0,0 +1,95 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: notifyWin + + property string text: "" + + width: window.width-1 + height: window.height-1 + color: "#000000" + z: 100 + y: -(height+1) + state: "" + border.color: "#c0c0c0" + border.width: 1 + radius: 10 + + signal dismissed(); + + MouseArea { + // event eater + anchors.fill: parent + } + Rectangle { + color: "transparent" + anchors.top: notifyWin.top + anchors.left: notifyWin.left + anchors.right: notifyWin.right + anchors.bottom: okButton.top + + Text { + anchors.centerIn: parent + + color: "#ffffff" + text: notifyWin.text + font.pointSize: util.uiFontSize(); + + onLinkActivated: { + Qt.openUrlExternally(link) + } + } + } + + Button { + id: okButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + text: "OK" + onClicked: { + notifyWin.state = "" + notifyWin.dismissed(); + } + } + + states: [ + State { + name: "visible" + PropertyChanges { + target: notifyWin + y: 0 + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "*" + SequentialAnimation { + PropertyAnimation { target: notifyWin; properties: "y"; duration: 200; easing.type: Easing.InOutCubic } + ScriptAction { script: updateGesturesAllowed(); } + } + } + ] +} diff --git a/qml/UrlWindow.qml b/qml/UrlWindow.qml new file mode 100644 index 0000000..bf9a0ed --- /dev/null +++ b/qml/UrlWindow.qml @@ -0,0 +1,133 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +import QtQuick 1.1 + +Rectangle { + id: urlWindow + + width: window.width-1 + height: window.height-1 + color: "#000000" + z: 100 + property variant urls: [""] + state: "" + y: -(height+1) + border.color: "#c0c0c0" + border.width: 1 + radius: 10 + + MouseArea { + // event eater + anchors.fill: parent + } + + Component { + id: listDelegate + Rectangle { + color: "#909090" + width: parent.width + height: openButton.height+4 + border.width: 1 + border.color: "#ffffff" + radius: 5 + clip: true + + Text { + text: modelData + color: "#ffffff" + anchors.verticalCenter: parent.verticalCenter + x: 8 + width: openButton.x - x + font.pointSize: util.uiFontSize(); + elide: Text.ElideRight + } + Button { + id: openButton + text: "Open" + anchors.right: copyButton.left + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 5 + width: 70 + onClicked: { + Qt.openUrlExternally(modelData); + } + } + Button { + id: copyButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: "Copy" + width: 70 + anchors.rightMargin: 5 + onClicked: { + util.copyTextToClipboard(modelData); + } + } + } + } + + Text { + visible: urlWindow.urls.length == 0 + anchors.centerIn: parent + color: "#ffffff" + text: "No URLs" + font.pointSize: util.uiFontSize() + 4; + } + + ListView { + anchors.fill: parent + delegate: listDelegate + model: urlWindow.urls + spacing: 5 + anchors.margins: 5 + clip: true + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + text: "Back" + onClicked: { + urlWindow.state = "" + } + } + + states: [ + State { + name: "visible" + PropertyChanges { + target: urlWindow + y: 0 + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "*" + SequentialAnimation { + PropertyAnimation { target: urlWindow; properties: "y"; duration: 200; easing.type: Easing.InOutCubic } + ScriptAction { script: updateGesturesAllowed(); } + } + } + ] +} diff --git a/qtc_packaging/debian_harmattan/changelog b/qtc_packaging/debian_harmattan/changelog new file mode 100644 index 0000000..dcdfbba --- /dev/null +++ b/qtc_packaging/debian_harmattan/changelog @@ -0,0 +1,57 @@ +fingerterm (1.0.2) unstable; urgency=low + + * Fixed bug in handling of certain escape sequences (fixes mosh) + + -- Heikki Holstila Tue, 05 Jun 2012 20:10:19 +0300 + +fingerterm (1.0.1) unstable; urgency=low + + * Workaround for URL grabber functionality with scrolled buffer + * Depend on openssh-client + + -- Heikki Holstila Tue, 14 Feb 2012 18:06:29 +0200 + +fingerterm (1.0.0) unstable; urgency=low + + * UI for choosing a keyboard layout + * slider for adjusting keyboard fade-out delay + * rendering performance improvements & other UI tweaks + * selection bugfixes + + -- Heikki Holstila Thu, 02 Feb 2012 21:13:10 +0200 + +fingerterm (0.9.3~beta) unstable; urgency=low + + * selectable finger drag mode (gestures/scroll/selection) + * support for buffer scrolling and text selection + * support for multiple instances ("new window" menu item) + * improved bell notification when in background + * charset option in config file (default: UTF-8) + * default keyboard layout is now "english" ("finnish" also included) + * performance improvements + + -- Heikki Holstila Sun, 08 Jan 2012 19:04:31 +0200 + +fingerterm (0.9.2~beta) unstable; urgency=low + + * configurable keyboard layout + * virtual keyboard behavior setting: off/fade/move + * keypress feedback + * improved pan gestures + * other smaller tweaks and improvements + + -- Heikki Holstila Thu, 24 Nov 2011 20:26:47 +0200 + +fingerterm (0.9.1~beta) unstable; urgency=low + + * various terminal control sequence related bugfixes + * basic pan gesture support (pan left/right to change channels in irssi) + * some ui tweaks + + -- Heikki Holstila Thu, 20 Oct 2011 23:08:51 +0300 + +fingerterm (0.9.0~beta) unstable; urgency=low + + * Initial Release (beta, work in progress) + + -- Heikki Holstila Thu, 20 Oct 2011 21:39:16 +0300 diff --git a/qtc_packaging/debian_harmattan/compat b/qtc_packaging/debian_harmattan/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/qtc_packaging/debian_harmattan/compat @@ -0,0 +1 @@ +7 diff --git a/qtc_packaging/debian_harmattan/control b/qtc_packaging/debian_harmattan/control new file mode 100644 index 0000000..1f68963 --- /dev/null +++ b/qtc_packaging/debian_harmattan/control @@ -0,0 +1,80 @@ +Source: fingerterm +Section: user/other +Priority: optional +Maintainer: Heikki Holstila +Build-Depends: debhelper (>= 5), libc6-dev, libqt4-dev, libmeegotouch-dev +Standards-Version: 3.7.3 +Homepage: http://hqh.unlink.org/harmattan + +Package: fingerterm +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, openssh-client +Description: A terminal emulator with a custom virtual keyboard + A terminal emulator with a custom virtual keyboard and usability-enhancing + features such as URL grabber, pan gestures and customizable shortcut menu. + Designed especially to be used with screen and irssi. +XSBC-Maemo-Display-Name: FingerTerm +XB-MeeGo-Desktop-Entry-Filename: fingerterm +XB-Maemo-Flags: visible +XB-MeeGo-Desktop-Entry: + [Desktop Entry] + Type=Application + Name=FingerTerm + Icon=/usr/share/icons/hicolor/80x80/apps/fingerterm.png +XB-Maemo-Icon-26: + iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz + AAALEwAACxMBAJqcGAAAC+lJREFUeJztXG1sHMUZfmZmd8++fDkmoQ6xnZOgBpKSXKgomFjKORH8 + QtRJVVX8SXICItxesPMrFEWYqBT4UYiTGJKqpUlU8QMigUutRiUSdwET0waaU1UlqFbiSwKBqJg4 + kbiP3dmZ/riP7J7vY+989p0tHmmkm5l33pl97n3nexf4HlMCmamK/jE87JWK4mGEeEHpIkqpFwAY + IR7KmKccncI0I6aUEQAQQoQhxHVTyjDhPHJ/R0e4cq3Pj2kh8Eww2JCor/cxQtZTRfGqiuJL53HO + IaWEME0AgBACQghbeSnlpDRKKQghk9IopcnfjIEQAkVRMvkG5yHBediU8qQrFgut7eycqOiDooIE + joyMeBghXYqibFUUxSulBDdNCNME5xxmirCZAmMMiqKAMgYlRS7nPMw5P2pKOdje3h6pRD1TJnBk + ZMSnqWqPqihdQggYnIMbxowTVgyMMSiqClVRQCmFwfmgbhj72tvbQ1PRWzaBKeL6mKL4uGHAMAxw + zqfSlhmDoihQVRWKqsLkPKQbxp5yiSyZwGAw2OB2u/s0Ve3lhoF4PA4pZTl1Vx2EENTV1UFRVeiG + 0R+NRvd0lthPlkTg8PCw1+VyvUsBTywWqzk3LReMMdTX10MAkUQisamjhBHcMYEjw8PbtLq6w4au + I55IALPU6vKCENS5XFA1DXo87m/v6DjiqJgToZHh4W2qy3U4Ho9D1/UptbPWoWka6urqYCQSjkgs + SuDwyZPbtPr6w/FYDIZhVKSRtQ5VVVFXXw89FvN3rF9/pJBsQQKHg0Gv5naficfjMOa45WVDTVmi + Ho2u7ejszNsn5iUwGAw2uDTtjDBNTzwen55W1jjq6upAGYskdH1tvtFZyZUIAIyxPkjpiUaj09fC + Gkc0GsX8+fM9jLE+ADtzyeS0wGAw6HNpWjD63XdzZqpSLhhjcM+bh4Sud3Z2doay83NaIKO0z9D1 + nCuLsbExvNrfj8jYGB5++GHsCAQq3+oaAucchq6DUdoHIJSdT7MTgidO+AghvmgsBiHlpPCfs2cx + OjoKg3O8f+JETpm5FqKxGAghvuCJE75sviZZIGGsxzCMSdtJaVjTCSGzdhlXCqSUMAwDlLEeZFmh + zQKDx497CKVduq5DSpkzzHO7M/KqquaVm2tB13UQSruCx497rJzZLNBU1S6YJniBCbN1U5MQMveW + dHnADQNC05IcAf3pdBuBBNiq63pe9wUwyWULyc416LoORVW3wkJgxoWDwWADAbyGYRQ1Zyuq7Voz + GQzDAAG8wWCwIf38GQtMJBI+hdLim6I5CCwHXZs3AwAG33mnrPLVAOccQghwzn0ABgELgUTK9Ubq + wKcQsnPLJbBS5WcaBueghKxHNoEgxGtyPomgbEwicIoNml30AaZpgqqqNx2/SaCUPm4YkMUGhewj + yCkOIibnmaPJ2QBuGLAe0yoAMDQ0lDyGdHAoVK4LX7lyBSc//BAXxsYwNjaWSf/FY49h8eLFaGxs + REtLC1atXImVd9+NpUuXOtI700ifaw8NDXkfeeSRsAIARAiPdLiqsMnkGJWzEYvHcezYMfzt+PGc + GxNCCIyPj2N8fByjo6P44IMPAADH3nqrlOeaUUgpQYTwAEgSCEK83MEAkk9ZIRw6dAinRkYqrrea + 4MluxwtgUAEAKeUiIUTJFihR+EE//vhjG3mEEPzkvvvw00cfxbO7dwMAjh4+jGvXruHaxASuXLmC + c+fO4dznn9c0gUIIEEIWAak+UEjp3AJLmAee+uQTW3xDZyeefOIJW5rL5UJTUxOamppw9113YeOG + DUX1Vhucc5DU5ai0BRa1pjRKGUQuXbpki7c/8EDOlcxsg5Wr5PxBCM9UpyO5cOPGDVt8/vz5Fa+j + GpBCAMlBJEmgBDzpZUqxkE10Idnly5fbZP/wxhu4dPmybQPCSZ21FnhyweEBrC7sYEqSItseL1Cm + ra0N58+fz8TPnz+PXc88g2YLsRcvXkRra2vRemsJVq7Sg0jSr50ULhK3YlNXFz49fRr/++YbW/oX + X36Z+f3s7t1obGzEmjVr8ON778WaNWsctKK6kEhyBqQtMOWajvrBEpZydS4Xent68Mc33sBYJJJX + 7ttvv0UwGEQwGMTKlSuxbcsWNDU1FW9LlSBx87kzi9DpGg1bWlrwfF8fnnz8cdx1551gjBWUP3v2 + LJ7dvRvh8IxccS4LVq4yFigcnv9OmoY4HL071q1Dx7p1SCQS+O/oKH736qsAgMUNDbg2YT/055zj + T0eO4MUXXrCdwdQKJllgqTuzNmUlltU0DT9atSpTfu8rr2DPc89h9T332PROTEzgr0NDVd+FLsYD + BQBTSgjAUch2dKflsoO1fMuKFejt7cXtt99u0x25eLFs/dMdTOsgAinLP12bat9pKd/p89mmPV9/ + /XXtnvrZRuFUZDq2s0rRt2b1alteLBaryaWe9UJByQRW6lApV3nr/BAAmpuba57A0gcRiyJZpOyL + L7+MTz/7DKZp5h2I0vFYLIa33n7b1tDW1taqDxTFBpHkSiS1F1iyC+eIW3HhwgUc+v3vccstt6Bj + 3Tosv+02/KCpCUuXLMnIRCIRnPzoI5w+fRqJRCKT7na78dDGjTVpgVLefBWt5LVwOS48Pj6Ov7z3 + XiZuvR7y25deylnGv3UrGhsba5ZAmwtDykj2i3yVwDO7duGB+++3vQCYbkA+3NnWhl/v2oXVWQNK + LSF1JygCpC0QiEgpPU7uuYishy9UZkVrK7Zu2YKfbd6Mf54+jctffIGrV6/i6tWrSF8dXrJkCZqX + L0dzczN+eMcdaGtrK6q32qCUQgIRwLIb43gumC3joMz8efOwwefLxE3TRKCnBwDwm+efL1lf1SFl + xpDSLhwGIZktrUJhkq4yAizdRTnlqx2QdOEwkLJAQsh1x6NwdrwMi8mexsw2SClBCLkO3FzKhZ1e + 1y1lGuO0MbMNJNsCIUTE6bnwdK5EZguEEAAhESBFoH/79vCh118HUPyBKuHCALC/v7/k8k/39pZV + V766y0HaU/3bt1ssEAABQoQQX7HpQ6VduBqYSpsppSCWm/o375UREp6OyfRcAyEEICRz3nBziUDp + SQC9Rf+dKlpgf+oYYKqYcpuTXAGwEKhpWshILeYLVWDLqcB+4GxCuv/TNC2UTssQ6Pf7J17bvz9M + CfGahfrBLML+ffXnuJFoqXhjZwotC09hxSJn1+9S/V/Y7/dnTsFsd2sJIUdL7QdnM3kAcPnGg45l + CSEghBy1ptkJVJTBlJBjpQtdlx3L1iJaFp5yJJfmhSjKoC09W3BgYOBdYRhd+d4T/teZM/jzm29m + 4u76+pIaXEvQXC48tHEjHmxvLyrLGANV1cFAILDJmj7pbU3G2D7JeVc+Re6sg+5oLOa8xTWGaCyG + v7//viMCCSFgjO3LTp/0fkF3d3eIUBrKdwVj2bJluPXWW8tpb01ileWQPx8YYyCUhrq7u0PZebnf + WCdkjyDElytv0cKF+OVTT+Grr77CbP+egtvtxrJly4oLEgJGyJ6cWfnKvLZ//17TNHu//2YCA2Os + /1dPP53zoxN5XxFyL1iwhxASmU1vEVUaqY8+RtwLFuS0PqAAgX6/f8KlKJtKndbMFaSf26Uom6wT + 52wUNK/tgUBYVVV/rs9vzmUQQkAphaqq/u2BQMGLio5YOTgwsM0wjMOON11nMazkdQcCR4rJO+rg + ugOBI5Qx/1x35/TzUcYckQeU+AHGgYEBr9D1dyXg6Ax5NiG1URChmrYpUMRtbeVKqSQQCIRNQtYy + RemfK/1i2mWZovSbhKwthTxgCh+hPXDggE8K0SdN0+f4Xk0NIbM5wFiIULpnx44dobL0TLUhBw4c + 8AnT7JGm2QXU/hlJ2msIY4OUsX3lEpfRV4lGAcDBgwc9XNe7hGlulVJmvilQbUKzPhQUpowdVTRt + sLu7O1IR/ZVQko29e/c2aIz5TCHWS8ArTdNnzZ8uUrP7ZMJYiABhRulJ3TRDO3furN1PwRfDwMCA + lwjhEVJ6pZSLJJC0UtP0yNSLe6WCABEwFkn9DhNCrlNCwpLSSKmDwfeoEv4PzkKXcKOscjIAAAAA + SUVORK5CYII= diff --git a/qtc_packaging/debian_harmattan/copyright b/qtc_packaging/debian_harmattan/copyright new file mode 100644 index 0000000..8ac1f10 --- /dev/null +++ b/qtc_packaging/debian_harmattan/copyright @@ -0,0 +1,14 @@ +Copyright 2011-2012 Heikki Holstila + +FingerTerm 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. + +FingerTerm 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 FingerTerm. If not, see . diff --git a/qtc_packaging/debian_harmattan/manifest.aegis b/qtc_packaging/debian_harmattan/manifest.aegis new file mode 100644 index 0000000..e69de29 diff --git a/qtc_packaging/debian_harmattan/rules b/qtc_packaging/debian_harmattan/rules new file mode 100755 index 0000000..0f19fc2 --- /dev/null +++ b/qtc_packaging/debian_harmattan/rules @@ -0,0 +1,91 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + + +configure: configure-stamp +configure-stamp: + dh_testdir + # qmake PREFIX=/usr# Uncomment this line for use without Qt Creator + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + # $(MAKE) # Uncomment this line for use without Qt Creator + #docbook-to-man debian/fingerterm.sgml > fingerterm.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + $(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/fingerterm. + $(MAKE) INSTALL_ROOT="$(CURDIR)"/debian/fingerterm install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + # dh_shlibdeps # Uncomment this line for use without Qt Creator + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..fb07002 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,26 @@ + + + qml/Main.qml + qml/Key.qml + qml/Keyboard.qml + qml/Lineview.qml + qml/Button.qml + qml/Menu.qml + qml/NotifyWin.qml + icons/backspace.png + icons/down.png + icons/enter.png + icons/left.png + icons/menu.png + icons/right.png + icons/shift.png + icons/tab.png + icons/up.png + qml/UrlWindow.qml + data/menu.xml + icons/scroll-indicator.png + data/english.layout + data/finnish.layout + qml/LayoutWindow.qml + + diff --git a/terminal.cpp b/terminal.cpp new file mode 100644 index 0000000..d2c604a --- /dev/null +++ b/terminal.cpp @@ -0,0 +1,1398 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include +#include + +#include "terminal.h" +#include "ptyiface.h" +#include "textrender.h" +#include "util.h" + +Terminal::Terminal(QObject *parent) : + QObject(parent), iRenderer(0), iPtyIFace(0), iWindow(0), iUtil(0), + iTermSize(0,0), iEmitCursorChangeSignal(true), + iShowCursor(true), iUseAltScreenBuffer(false), iAppCursorKeys(false) +{ + zeroChar.c = ' '; + zeroChar.bgColor = defaultBgColor; + zeroChar.fgColor = defaultFgColor; + zeroChar.attrib = 0; + + escape = -1; + + iTermAttribs.currentFgColor = defaultFgColor; + iTermAttribs.currentBgColor = defaultBgColor; + iTermAttribs.currentAttrib = 0; + iTermAttribs.cursorPos = QPoint(0,0); + iMarginBottom = 0; + iMarginTop = 0; + + resetBackBufferScrollPos(); + + iTermAttribs_saved = iTermAttribs; + iTermAttribs_saved_alt = iTermAttribs; + + resetTerminal(); +} + +void Terminal::setRenderer(TextRender* tr) +{ + iRenderer = tr; + + if(tr) { + tr->updateTermSize(); + connect(this, SIGNAL(displayBufferChanged()), tr, SLOT(redraw())); + connect(this, SIGNAL(cursorPosChanged(QPoint)), tr, SLOT(redraw())); + connect(this, SIGNAL(termSizeChanged(QSize)), tr, SLOT(redraw())); + } else { + qDebug() << "warning: null text renderer"; + } +} + +void Terminal::setPtyIFace(PtyIFace *pty) +{ + iPtyIFace = pty; + if(!pty) { + qDebug() << "warning: null pty iface"; + } +} + +void Terminal::setCursorPos(QPoint pos) +{ + if( iTermAttribs.cursorPos != pos ) { + int tlimit = 1; + int blimit = iTermSize.height(); + if(iTermAttribs.originMode) { + tlimit = iMarginTop; + blimit = iMarginBottom; + } + + if(pos.x() < 1) + pos.setX(1); + if(pos.x() > iTermSize.width()+1) + pos.setX(iTermSize.width()+1); + if(pos.y() < tlimit) + pos.setY(tlimit); + if(pos.y() > blimit) + pos.setY(blimit); + + iTermAttribs.cursorPos=pos; + if(iEmitCursorChangeSignal) + emit cursorPosChanged(pos); + } +} + +QPoint Terminal::cursorPos() +{ + return iTermAttribs.cursorPos; +} + +bool Terminal::showCursor() +{ + if(iBackBufferScrollPos != 0) + return false; + + return iShowCursor; +} + +QList >& Terminal::buffer() +{ + if(iUseAltScreenBuffer) + return iAltBuffer; + + return iBuffer; +} + +void Terminal::setTermSize(QSize size) +{ + if( iTermSize != size ) { + iMarginTop = 1; + iMarginBottom = size.height(); + iTermSize=size; + + resetTabs(); + + emit termSizeChanged(size); + } +} + +void Terminal::putString(QString str, bool unEscape) +{ + if (unEscape) { + str.replace("\\r", "\r"); + str.replace("\\n", "\n"); + str.replace("\\e", QChar(ch_ESC)); + str.replace("\\b", "\b"); + str.replace("\\t", "\t"); + + while(str.indexOf("\\x") != -1) { + int i = str.indexOf("\\x")+2; + QString num; + while(num.length() < 2 && str.length()>i && str.at(i).isNumber() ) { + num.append(str.at(i)); + i++; + } + str.remove(i-2-num.length(), num.length()+2); + bool ok; + str.insert(i-2-num.length(), QChar(num.toInt(&ok,16))); + } + while(str.indexOf("\\0") != -1) { + int i = str.indexOf("\\0")+2; + QString num; + while(num.length() < 3 && str.length()>i && str.at(i).isNumber() ) { + num.append(str.at(i)); + i++; + } + str.remove(i-2-num.length(), num.length()+2); + bool ok; + str.insert(i-2-num.length(), QChar(num.toInt(&ok,8))); + } + } + + if(iPtyIFace) + iPtyIFace->writeTerm(str); +} + +void Terminal::keyPress(int key, int modifiers) +{ + QChar c(key); + //qDebug() << key; + + resetBackBufferScrollPos(); + + if(c.isLetter() && (modifiers & Qt::ShiftModifier)) + c = c.toUpper(); + else if(c.isLetter()) + c = c.toLower(); + + QString toWrite; + + if( key <= 0xFF ) { + char asciiVal = c.toAscii(); + + if(modifiers & Qt::AltModifier) + toWrite.append(ch_ESC); + + if((modifiers & Qt::ControlModifier) && c.isLower()) + asciiVal -= 0x60; + if((modifiers & Qt::ControlModifier) && c.isUpper()) + asciiVal -= 0x40; + toWrite.append(asciiVal); + + if(iPtyIFace) + iPtyIFace->writeTerm(toWrite); + return; + } + + char cursorModif='['; + if(iAppCursorKeys) + cursorModif = 'O'; + + if( key==Qt::Key_Up ) + toWrite += QString("%1%2A").arg(ch_ESC).arg(cursorModif).toAscii(); + if( key==Qt::Key_Down ) + toWrite += QString("%1%2B").arg(ch_ESC).arg(cursorModif).toAscii(); + if( key==Qt::Key_Right ) + toWrite += QString("%1%2C").arg(ch_ESC).arg(cursorModif).toAscii(); + if( key==Qt::Key_Left ) + toWrite += QString("%1%2D").arg(ch_ESC).arg(cursorModif).toAscii(); + + if( key==Qt::Key_Enter || key==Qt::Key_Return ) { + if(iNewLineMode) + toWrite += "\r\n"; + else + toWrite += "\r"; + } + if( key==Qt::Key_Backspace ) + toWrite += "\x7F"; + if( key==Qt::Key_Tab ) + toWrite = "\t"; + + if( key==Qt::Key_PageUp ) + toWrite += QString("%1[5~").arg(ch_ESC).toAscii(); + if( key==Qt::Key_PageDown ) + toWrite += QString("%1[6~").arg(ch_ESC).toAscii(); + if( key==Qt::Key_Home ) + toWrite += QString("%1OH").arg(ch_ESC).toAscii(); + if( key==Qt::Key_End ) + toWrite += QString("%1OF").arg(ch_ESC).toAscii(); + if( key==Qt::Key_Delete ) + toWrite += QString("%1[3~").arg(ch_ESC).toAscii(); + + if( key==Qt::Key_Escape ) { + toWrite += QString(1,ch_ESC); + } + + if(iPtyIFace) + iPtyIFace->writeTerm(toWrite); +} + +void Terminal::insertInBuffer(const QString& chars) +{ + if(iTermSize.isNull()) { + qDebug() << "null size terminal"; + return; + } + + iEmitCursorChangeSignal = false; + + for(int i=0; i cols (terminfo: xenl) + { + if(iNewLineMode) + setCursorPos(QPoint(1,cursorPos().y()+1)); + else + setCursorPos(QPoint(cursorPos().x(), cursorPos().y()+1)); + } + } + else if(ch.toAscii()=='\r') { // carriage return + setCursorPos(QPoint(1,cursorPos().y())); + } + else if(ch.toAscii()=='\b' || ch.toAscii()==127) { //backspace & del (only move cursor, don't erase) + setCursorPos(QPoint(cursorPos().x()-1,cursorPos().y())); + } + else if(ch.toAscii()=='\a') { // BEL + if(escape==']') { // BEL also ends OSC sequence + escape=-1; + oscSequence(oscSeq); + oscSeq.clear(); + } else { + iUtil->bellAlert(); + } + } + else if(ch.toAscii()=='\t') { //tab + if(cursorPos().y() <= iTabStops.size()) { + for(int i=0; i cursorPos().x()) { + setCursorPos(QPoint( iTabStops[cursorPos().y()-1][i], cursorPos().y() )); + break; + } + } + } + } + else if(ch.toAscii()==14 || ch.toAscii()==15) { //SI and SO, related to character set... ignore + } + else { + if( escape>=0 ) { + if( escape==0 && (ch.toAscii()=='[') ) { + escape='['; //ansi sequence + escSeq += ch; + } + else if( escape==0 && (ch.toAscii()==']') ) { + escape=']'; //osc sequence + oscSeq += ch; + } + else if( escape==0 && multiCharEscapes.contains(ch.toAscii())) { + escape = ch.toAscii(); + escSeq += ch; + } + else if( escape==0 && ch.toAscii()=='\\' ) { // ESC\ also ends OSC sequence + escape=-1; + oscSequence(oscSeq); + oscSeq.clear(); + } + else if (ch.toAscii()==ch_ESC) { + escape = 0; + } + else if( escape=='[' || multiCharEscapes.contains(escape) ) { + escSeq += ch; + } + else if( escape==']' ) { + oscSeq += ch; + } + else if( multiCharEscapes.contains(escape) ) { + escSeq += ch; + } + else { + escControlChar(QByteArray(1,ch.toAscii())); + escape=-1; + } + + if( escape=='[' && ch.toAscii() >= 64 && ch.toAscii() <= 126 && ch.toAscii() != '[' ) { + ansiSequence(escSeq); + escape=-1; + escSeq.clear(); + } + if( multiCharEscapes.contains(escape) && escSeq.length()>=2 ) { + escControlChar(escSeq); + escape=-1; + escSeq.clear(); + } + } else { + if (ch.isPrint()) + insertAtCursor(ch, !iReplaceMode); + else if (ch.toAscii()==ch_ESC) + escape=0; + else + qDebug() << "unprintable char" << int(ch.toAscii()); + } + } + } + + iEmitCursorChangeSignal = true; + emit displayBufferChanged(); +} + +void Terminal::insertAtCursor(QChar c, bool overwriteMode, bool advanceCursor) +{ + if(cursorPos().x() > iTermSize.width() && advanceCursor) { + if(iTermAttribs.wrapAroundMode) { + if(cursorPos().y()>=iMarginBottom) { + scrollFwd(1); + setCursorPos(QPoint(1, cursorPos().y())); + } else { + setCursorPos(QPoint(1, cursorPos().y()+1)); + } + } else { + setCursorPos(QPoint(iTermSize.width(), cursorPos().y())); + } + } + + while(currentLine().size() < cursorPos().x() ) + currentLine().append(zeroChar); + + if(!overwriteMode) + currentLine().insert(cursorPos().x()-1,zeroChar); + + currentLine()[cursorPos().x()-1].c = c; + currentLine()[cursorPos().x()-1].fgColor = iTermAttribs.currentFgColor; + currentLine()[cursorPos().x()-1].bgColor = iTermAttribs.currentBgColor; + currentLine()[cursorPos().x()-1].attrib = iTermAttribs.currentAttrib; + + if (advanceCursor) { + setCursorPos(QPoint(cursorPos().x()+1,cursorPos().y())); + } +} + +void Terminal::deleteAt(QPoint pos) +{ + clearAt(pos); + buffer()[pos.y()-1].removeAt(pos.x()-1); +} + +void Terminal::clearAt(QPoint pos) +{ + if(pos.y() <= 0 || pos.y()-1 > buffer().size() || + pos.x() <= 0 || pos.x()-1 > buffer()[pos.y()-1].size()) + { + qDebug() << "warning: trying to clear char out of bounds"; + return; + } + + // just in case... + while(buffer().size() < pos.y()) + buffer().append(QList()); + while(buffer()[pos.y()-1].size() < pos.x() ) + buffer()[pos.y()-1].append(zeroChar); + + buffer()[pos.y()-1][pos.x()-1] = zeroChar; +} + +void Terminal::eraseLineAtCursor(int from, int to) +{ + if(from==-1 && to==-1) { + currentLine().clear(); + return; + } + if(from < 1) + from=1; + from--; + + if(to < 1 || to > currentLine().size()) + to=currentLine().size(); + to--; + + if(from>to) + return; + + for(int i=from; i<=to; i++) { + currentLine()[i] = zeroChar; + } +} + +void Terminal::clearAll(bool wholeBuffer) +{ + clearSelection(); + if(wholeBuffer) { + backBuffer().clear(); + resetBackBufferScrollPos(); + } + buffer().clear(); + setCursorPos(QPoint(1,1)); +} + + +void Terminal::ansiSequence(const QString& seq) +{ + if(seq.length() <= 1 || seq.at(0)!='[') + return; + + QChar cmdChar = seq.at(seq.length()-1); + QString extra; + QList params; + + int x=1; + while(x tmp = seq.mid(x,seq.length()-x-1).split(';'); + foreach(QString b, tmp) { + bool ok=false; + int t = b.toInt(&ok); + if(ok) { + params.append(t); + } + } + if(x>1) + extra = seq.mid(1,x-1); + + bool unhandled = false; + + switch(cmdChar.toAscii()) + { + case 'A': //cursor up + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( cursorPos().x(), qMax(iMarginTop, cursorPos().y()-params.at(0)) )); + break; + case 'B': //cursor down + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( cursorPos().x(), qMin(iMarginBottom, cursorPos().y()+params.at(0)) )); + break; + case 'C': //cursor fwd + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( qMin(iTermSize.width(),cursorPos().x()+params.at(0)), cursorPos().y() )); + break; + case 'D': //cursor back + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( qMax(1,cursorPos().x()-params.at(0)), cursorPos().y() )); + break; + case 'E': //cursor next line + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( 1, qMin(iMarginBottom, cursorPos().y()+params.at(0)) )); + break; + case 'F': //cursor prev line + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( 1, qMax(iMarginTop, cursorPos().y()-params.at(0)) )); + break; + case 'G': //go to column + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( params.at(0), cursorPos().y() )); + break; + case 'H': //cursor pos + case 'f': //cursor pos + if(!extra.isEmpty()) { + unhandled=true; + break; + } + while(params.count()<2) + params.append(1); + if (iTermAttribs.originMode) + setCursorPos(QPoint( params.at(1), params.at(0)+iMarginTop-1 )); + else + setCursorPos(QPoint( params.at(1), params.at(0) )); + break; + case 'J': //erase data + if(!extra.isEmpty() && extra!="?") { + unhandled=true; + break; + } + if(params.count()>=1 && params.at(0)==1) { + eraseLineAtCursor(1,cursorPos().x()); + for(int i=0; i=1 && params.at(0)==2) { + clearAll(); + } else { + eraseLineAtCursor(cursorPos().x()); + for(int i=cursorPos().y(); i=1 && params.at(0)==1) { + eraseLineAtCursor(1,cursorPos().x()); + } + else if(params.count()>=1 && params.at(0)==2) { + currentLine().clear(); + } else { + eraseLineAtCursor(cursorPos().x()); + } + break; + + case 'L': // insert lines + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(cursorPos().y() < iMarginTop || cursorPos().y() > iMarginBottom) + break; + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + if(params.at(0) > iMarginBottom-cursorPos().y()) + scrollBack(iMarginBottom-cursorPos().y(), cursorPos().y()); + else + scrollBack(params.at(0), cursorPos().y()); + setCursorPos(QPoint(1,cursorPos().y())); + break; + case 'M': // delete lines + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(cursorPos().y() < iMarginTop || cursorPos().y() > iMarginBottom) + break; + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + if(params.at(0) > iMarginBottom-cursorPos().y()) + scrollFwd(iMarginBottom-cursorPos().y(), cursorPos().y()); + else + scrollFwd(params.at(0), cursorPos().y()); + setCursorPos(QPoint(1,cursorPos().y())); + break; + + case 'P': // delete characters + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + for(int i=0; iwriteTerm(toWrite); + } else unhandled=true; + break; + + case 'd': //go to row + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count()<1) + params.append(1); + if(params.at(0)==0) + params[0]=1; + setCursorPos(QPoint( cursorPos().x(), params.at(0) )); + break; + + case 'g': //tab stop manipulation + if(params.count()==0) + params.append(0); + if(params.at(0)==0 && extra=="") { //clear tab at current position + if(cursorPos().y() <= iTabStops.size()) { + int idx = iTabStops[cursorPos().y()-1].indexOf(cursorPos().x()); + if(idx != -1) + iTabStops[cursorPos().y()-1].removeAt(idx); + } + } + else if(params.at(0)==3 && extra=="") { //clear all tabs + iTabStops.clear(); + } + break; + + case 'n': + if(params.count()>=1 && params.at(0)==6 && extra=="") { // write cursor pos + QString toWrite = QString("%1[%2;%3R").arg(ch_ESC).arg(cursorPos().y()).arg(cursorPos().x()).toAscii(); + if(iPtyIFace) + iPtyIFace->writeTerm(toWrite); + } else unhandled=true; + break; + + case 'p': + if(extra=="!") { // reset terminal + resetTerminal(); + } else unhandled=true; + break; + + case 's': //save cursor + if(!extra.isEmpty()) { + unhandled=true; + break; + } + iTermAttribs_saved = iTermAttribs; + break; + case 'u': //restore cursor + if(!extra.isEmpty()) { + unhandled=true; + break; + } + iTermAttribs = iTermAttribs_saved; + break; + + case 'm': //graphics mode + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count() > 0) { + if(params.contains(0)) { + iTermAttribs.currentFgColor = defaultFgColor; + iTermAttribs.currentBgColor = defaultBgColor; + iTermAttribs.currentAttrib = attribNone; + } + if(params.contains(1)) + iTermAttribs.currentAttrib |= attribBold; + if(params.contains(4)) + iTermAttribs.currentAttrib |= attribUnderline; + if(params.contains(7)) + iTermAttribs.currentAttrib |= attribNegative; + + if(params.contains(22)) + iTermAttribs.currentAttrib &= ~attribBold; + if(params.contains(24)) + iTermAttribs.currentAttrib &= ~attribUnderline; + if(params.contains(27)) + iTermAttribs.currentAttrib &= ~attribNegative; + + foreach(int p, params) { + if(p >= 30 && p<= 37) { + iTermAttribs.currentFgColor = p-30; + } + if(p >= 40 && p<= 47) { + iTermAttribs.currentBgColor = p-40; + } + } + if(params.contains(39)) + iTermAttribs.currentFgColor = defaultFgColor; + if(params.contains(49)) + iTermAttribs.currentBgColor = defaultBgColor; + } else { + iTermAttribs.currentFgColor = defaultFgColor; + iTermAttribs.currentBgColor = defaultBgColor; + iTermAttribs.currentAttrib = attribNone; + } + break; + + case 'h': + if(params.count()>=1 && params.contains(1) && extra=="?") { // application cursor keys + iAppCursorKeys = true; + } + else if(params.count()>=1 && params.contains(3) && extra=="?") { //column mode + // not supported, just clear screen, move cursor home & reset scrolling region + clearAll(); + resetTabs(); + iMarginTop = 1; + iMarginBottom = iTermSize.height(); + } + else if(params.count()>=1 && params.contains(6) && extra=="?") { //origin mode enable + iTermAttribs.originMode = true; + } + else if(params.count()>=1 && params.contains(7) && extra=="?") { //wraparound mode enable + iTermAttribs.wrapAroundMode = true; + } + else if(params.count()>=1 && params.contains(12) && extra=="?") { // start blinking cursor + // just ignore, we don't blink + } + else if(params.count()>=1 && params.contains(25) && extra=="?") { // show cursor + iShowCursor = true; + } + else if(params.count()>=1 && params.contains(1049) && extra=="?") { //use alt screen buffer & save cursor + iTermAttribs_saved_alt = iTermAttribs; + iUseAltScreenBuffer = true; + iMarginTop = 1; + iMarginBottom = iTermSize.height(); + resetBackBufferScrollPos(); + + clearAll(); + resetTabs(); + emit displayBufferChanged(); + } + else if(params.count()>=1 && params.contains(4) && extra=="") { + iReplaceMode = true; + } + else if(params.count()>=1 && params.contains(20) && extra=="") { + iNewLineMode = true; + } + else unhandled=true; + break; + + case 'l': + if(params.count()>=1 && params.contains(1) && extra=="?") { // normal cursor keys + iAppCursorKeys = false; + } + else if(params.count()>=1 && params.contains(3) && extra=="?") { //column mode + // not supported, just clear screen, move cursor home & reset scrolling region + clearAll(); + resetTabs(); + iMarginTop = 1; + iMarginBottom = iTermSize.height(); + } + else if(params.count()>=1 && params.contains(6) && extra=="?") { //origin mode disable + iTermAttribs.originMode = false; + } + else if(params.count()>=1 && params.contains(7) && extra=="?") { //wraparound mode disable + iTermAttribs.wrapAroundMode = false; + } + else if(params.count()>=1 && params.contains(12) && extra=="?") { // stop blinking cursor + // no need to do anything, we don't blink + } + else if(params.count()>=1 && params.contains(25) && extra=="?") { // hide cursor + iShowCursor = false; + } + else if(params.count()>=1 && params.contains(1049) && extra=="?") { //return from alt screen buffer & restore cursor + iUseAltScreenBuffer = false; + iTermAttribs = iTermAttribs_saved_alt; + iMarginBottom = iTermSize.height(); + iMarginTop = 1; + resetBackBufferScrollPos(); + resetTabs(); + emit displayBufferChanged(); + } + + else if(params.count()>=1 && params.contains(4) && extra=="") { + iReplaceMode = false; + } + else if(params.count()>=1 && params.contains(20) && extra=="") { + iNewLineMode = false; + } + else unhandled=true; + break; + + case 'r': // scrolling region + if(!extra.isEmpty()) { + unhandled=true; + break; + } + if(params.count() < 2) { + while(params.count() < 2) + params.append(1); + params[0] = 1; + params[1] = iTermSize.height(); + } + if(params.at(0) < 1) + params[0] = 1; + if(params.at(1) > iTermSize.height()) + params[1] = iTermSize.height(); + iMarginTop = params.at(0); + iMarginBottom = params.at(1); + if(iMarginTop >= iMarginBottom) { + //invalid scroll region + if(iMarginTop == iTermSize.height()) { + iMarginTop = iMarginBottom - 1; + } else { + iMarginBottom = iMarginTop + 1; + } + } + setCursorPos(QPoint( 1, iMarginTop )); + break; + + default: + unhandled=true; + break; + } + + if (unhandled) + qDebug() << "unhandled ansi sequence " << cmdChar << params << extra; +} + +void Terminal::oscSequence(const QString& seq) +{ + if(seq.length() <= 1 || seq.at(0)!=']') + return; + + // set window title + if( seq.length() >= 3 && seq.at(0)==']' && + (seq.at(1)=='0' || seq.at(1)=='2') && + seq.at(2)==';' ) + { + if(iWindow) { + iUtil->setWindowTitle(seq.mid(3)); + } + return; + } + + qDebug() << "unhandled OSC" << seq; +} + +void Terminal::escControlChar(const QString& seq) +{ + QChar ch; + + if(seq.length()==1) { + ch = seq.at(0); + } else if (seq.length()>1 ){ // control sequences longer than 1 characters + if( seq.at(0) == '(' || seq.at(0)==')' ) // character set, ignore this for now... + return; + if( seq.at(0) == '#' && seq.at(1)=='8' ) { // test mode, fill screen with 'E' + clearAll(true); + for(int i=0; i line; + for(int j=0; j' || ch.toAscii()=='=') { //app keypad/normal keypad - ignore these for now... + } + + else if(ch.toAscii()=='H') { // set a tab stop at cursor position + while(iTabStops.size() < cursorPos().y()) + iTabStops.append(QList()); + + iTabStops[cursorPos().y()-1].append(cursorPos().x()); + qSort(iTabStops[cursorPos().y()-1]); + } + else if(ch.toAscii()=='D') { // cursor down/scroll down one line + scrollFwd(1, cursorPos().y()); + } + else if(ch.toAscii()=='M') { // cursor up/scroll up one line + scrollBack(1, cursorPos().y()); + } + + else if(ch.toAscii()=='E') { // new line + if(cursorPos().y()==iMarginBottom) { + scrollFwd(1); + setCursorPos(QPoint(1,cursorPos().y())); + } else { + setCursorPos(QPoint(1,cursorPos().y()+1)); + } + } + else if(ch.toAscii()=='c') { // full reset + resetTerminal(); + } + else if(ch.toAscii()=='g') { // visual bell + iUtil->bellAlert(); + } + else { + qDebug() << "unhandled escape code ESC" << seq; + } +} + +QList& Terminal::currentLine() +{ + while(buffer().size() <= cursorPos().y()-1) + buffer().append(QList()); + + if( cursorPos().y() >= 1 && + cursorPos().y() <= buffer().size() ) + { + return buffer()[cursorPos().y()-1]; + } + + // we shouldn't get here + return buffer()[buffer().size()-1]; +} + +const QStringList Terminal::printableLinesFromCursor(int lines) +{ + QStringList ret; + + int start = cursorPos().y() - lines; + int end = cursorPos().y() + lines; + + for(int l=start-1; l= 0 && l < buffer().size()) { + for(int i=0; i maxScrollBackLines) { + backBuffer().removeFirst(); + } +} + +void Terminal::scrollBack(int lines, int insertAt) +{ + if(lines <= 0) + return; + + adjustSelectionPosition(lines); + + bool useBackbuffer = true; + if(insertAt==-1) { + insertAt = iMarginTop; + useBackbuffer = false; + } + insertAt--; + + while(lines>0) { + if(!iUseAltScreenBuffer) { + if(iBackBuffer.size()>0 && useBackbuffer) + buffer().insert(insertAt, iBackBuffer.takeLast()); + else + buffer().insert(insertAt, QList()); + } else { + buffer().insert(insertAt, QList()); + } + + int rm = iMarginBottom; + if(rm >= buffer().size()) + rm = buffer().size()-1; + + buffer().removeAt(rm); + + lines--; + } +} + +void Terminal::scrollFwd(int lines, int removeAt) +{ + if(lines <= 0) + return; + + adjustSelectionPosition(-lines); + + if(removeAt==-1) { + removeAt = iMarginTop; + } + removeAt--; + + while(buffer().size() < iMarginBottom) + buffer().append(QList()); + + while(lines>0) { + buffer().insert(iMarginBottom, QList()); + + if(!iUseAltScreenBuffer) + iBackBuffer.append( buffer().takeAt(removeAt) ); + else + buffer().removeAt(removeAt); + + lines--; + } + trimBackBuffer(); +} + +void Terminal::resetTerminal() +{ + iBuffer.clear(); + iAltBuffer.clear(); + iBackBuffer.clear(); + + iTermAttribs.currentFgColor = defaultFgColor; + iTermAttribs.currentBgColor = defaultBgColor; + iTermAttribs.currentAttrib = 0; + iTermAttribs.cursorPos = QPoint(1,1); + iTermAttribs.wrapAroundMode = true; + iTermAttribs.originMode = false; + + iTermAttribs_saved = iTermAttribs; + iTermAttribs_saved_alt = iTermAttribs; + + iMarginBottom = iTermSize.height(); + iMarginTop = 1; + + iShowCursor = true; + iUseAltScreenBuffer = false; + iAppCursorKeys = false; + iReplaceMode = false; + iNewLineMode = false; + + resetBackBufferScrollPos(); + + resetTabs(); + clearSelection(); +} + +void Terminal::resetTabs() +{ + iTabStops.clear(); + for(int i=0; i()); + while(tab <= iTermSize.width()) { + iTabStops.last().append(tab); + tab += 8; + } + } +} + +void Terminal::pasteFromClipboard() +{ + QClipboard *cb = QApplication::clipboard(); + if(cb->mimeData()->hasText() && !cb->mimeData()->text().isEmpty()) { + if(iPtyIFace) { + resetBackBufferScrollPos(); + iPtyIFace->writeTerm(cb->mimeData()->text()); + } + } +} + +const QStringList Terminal::grabURLsFromBuffer() +{ + QStringList ret; + QByteArray buf; + + //backbuffer + if ((iUtil->settingsValue("general/grabUrlsFromBackbuffer").toBool() + && !iUseAltScreenBuffer) + || backBufferScrollPos() > 0) //a lazy workaround: just grab everything when the buffer is being scrolled (TODO: make a proper fix) + { + for (int i=0; iconfigPath()+"/menu.xml" ); + if(f.open(QIODevice::ReadOnly|QIODevice::Text)) { + ret = f.readAll(); + f.close(); + } + + return ret; +} + +void Terminal::scrollBackBufferFwd(int lines) +{ + if(iUseAltScreenBuffer || lines<=0) + return; + + clearSelection(); + + iBackBufferScrollPos -= lines; + if(iBackBufferScrollPos < 0) + iBackBufferScrollPos = 0; + + if (iRenderer) { + iRenderer->setShowBufferScrollIndicator(iBackBufferScrollPos != 0); + iRenderer->redraw(); + } +} + +void Terminal::scrollBackBufferBack(int lines) +{ + if (iUseAltScreenBuffer || lines<=0) + return; + + clearSelection(); + + iBackBufferScrollPos += lines; + if (iBackBufferScrollPos > iBackBuffer.size()) + iBackBufferScrollPos = iBackBuffer.size(); + + if (iRenderer) { + iRenderer->setShowBufferScrollIndicator(iBackBufferScrollPos != 0); + iRenderer->redraw(); + } +} + +void Terminal::resetBackBufferScrollPos() +{ + if(iBackBufferScrollPos==0 && iSelection.isNull()) + return; + + iBackBufferScrollPos = 0; + clearSelection(); + + if (iRenderer) { + iRenderer->setShowBufferScrollIndicator(false); + iRenderer->redraw(); + } +} + +void Terminal::copySelectionToClipboard() +{ + if (selection().isNull()) + return; + + QClipboard *cb = QApplication::clipboard(); + cb->clear(); + + QString text; + QString line; + + // backbuffer + if (iBackBufferScrollPos > 0 && !iUseAltScreenBuffer) { + int lineFrom = iBackBuffer.size() - iBackBufferScrollPos + selection().top() - 1; + int lineTo = iBackBuffer.size() - iBackBufferScrollPos + selection().bottom() - 1; + + for (int i=lineFrom; i<=lineTo; i++) { + if (i >= 0 && i < iBackBuffer.size()) { + line.clear(); + int start = 0; + int end = iBackBuffer[i].size()-1; + if (i==lineFrom) + start = selection().left()-1; + if (i==lineTo) + end = selection().right()-1; + for (int j=start; j<=end; j++) { + if (j >= 0 && j < iBackBuffer[i].size() && iBackBuffer[i][j].c.isPrint()) + line += iBackBuffer[i][j].c; + } + text += line.trimmed() + "\n"; + } + } + } + + // main buffer + int lineFrom = selection().top()-1-iBackBufferScrollPos; + int lineTo = selection().bottom()-1-iBackBufferScrollPos; + for (int i=lineFrom; i<=lineTo; i++) { + if (i >= 0 && i < buffer().size()) { + line.clear(); + int start = 0; + int end = buffer()[i].size()-1; + if (i==lineFrom) + start = selection().left()-1; + if (i==lineTo) + end = selection().right()-1; + for (int j=start; j<=end; j++) { + if (j >= 0 && j < buffer()[i].size() && buffer()[i][j].c.isPrint()) + line += buffer()[i][j].c; + } + text += line.trimmed() + "\n"; + } + } + + //qDebug() << text.trimmed(); + + cb->setText(text.trimmed()); +} + +void Terminal::adjustSelectionPosition(int lines) +{ + // adjust selection position when terminal contents move + + if (iSelection.isNull() || lines==0) + return; + + int tx = iSelection.left(); + int ty = iSelection.top() + lines; + int bx = iSelection.right(); + int by = iSelection.bottom() + lines; + + if (ty<1) { + ty = 1; + tx = 1; + } + if (by>iTermSize.height()) { + by = iTermSize.height(); + bx = iTermSize.width(); + } + if (by<1 || ty>iTermSize.height()) { + clearSelection(); + return; + } + + iSelection = QRect(QPoint(tx,ty), QPoint(bx,by)); + + if (iRenderer) + iRenderer->redraw(); +} + +void Terminal::setSelection(QPoint start, QPoint end) +{ + if (start.y() > end.y()) + qSwap(start, end); + if (start.y() == end.y() && start.x() > end.x()) + qSwap(start, end); + + if (start.x() < 1) + start.rx() = 1; + if (start.y() < 1) + start.ry() = 1; + if (end.x() > iTermSize.width()) + end.rx() = iTermSize.width(); + if (end.y() > iTermSize.height()) + end.ry() = iTermSize.height(); + + iSelection = QRect(start, end); + + if (iRenderer) + iRenderer->redraw(); +} + +void Terminal::clearSelection() +{ + if (iSelection.isNull()) + return; + + iSelection = QRect(); + + if (iUtil) + iUtil->selectionFinished(); + if (iRenderer) + iRenderer->redraw(); +} + +QRect Terminal::selection() +{ + return iSelection; +} diff --git a/terminal.h b/terminal.h new file mode 100644 index 0000000..2a31eba --- /dev/null +++ b/terminal.h @@ -0,0 +1,163 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef TERMINAL_H +#define TERMINAL_H + +#include +#include + +class TextRender; +class PtyIFace; +class Util; + +struct TermChar { + QChar c; + int fgColor; + int bgColor; + int attrib; +}; + +const int attribNone = 0; +const int attribBold = 1; +const int attribUnderline = 2; +const int attribNegative = 4; +const QByteArray multiCharEscapes("().*+-/%#"); + +struct TermAttribs { + QPoint cursorPos; + + bool wrapAroundMode; + bool originMode; + + int currentFgColor; + int currentBgColor; + int currentAttrib; +}; + +class Terminal : public QObject +{ + Q_OBJECT +public: + static const int defaultFgColor = 7; + static const int defaultBgColor = 0; + + explicit Terminal(QObject *parent = 0); + virtual ~Terminal() {} + void setRenderer(TextRender* tr); + void setPtyIFace(PtyIFace* pty); + void setWindow(QWidget* win) { iWindow=win; } + void setUtil(Util* util) { iUtil = util; } + + void insertInBuffer(const QString& chars); + + QPoint cursorPos(); + void setCursorPos(QPoint pos); + bool showCursor(); + + Q_INVOKABLE QSize termSize() { return iTermSize; } + void setTermSize(QSize size); + + QList >& buffer(); + QList >& backBuffer() { return iBackBuffer; } + + QList& currentLine(); + + Q_INVOKABLE void keyPress(int key, int modifiers); + Q_INVOKABLE const QStringList printableLinesFromCursor(int lines); + Q_INVOKABLE void putString(QString str, bool unEscape=false); + + Q_INVOKABLE void pasteFromClipboard(); + Q_INVOKABLE void copySelectionToClipboard(); + Q_INVOKABLE const QStringList grabURLsFromBuffer(); + + Q_INVOKABLE QString getUserMenuXml(); + + void scrollBackBufferFwd(int lines); + void scrollBackBufferBack(int lines); + int backBufferScrollPos() { return iBackBufferScrollPos; } + void resetBackBufferScrollPos(); + + void setSelection(QPoint start, QPoint end); + QRect selection(); + Q_INVOKABLE void clearSelection(); + bool hasSelection(); + + TermChar zeroChar; + +signals: + void cursorPosChanged(QPoint newPos); + void termSizeChanged(QSize newSize); + void displayBufferChanged(); + +private: + Q_DISABLE_COPY(Terminal) + static const char ch_ESC = 0x1B; //escape + static const int maxScrollBackLines = 300; + + void insertAtCursor(QChar c, bool overwriteMode=true, bool advanceCursor=true); + void deleteAt(QPoint pos); + void clearAt(QPoint pos); + void eraseLineAtCursor(int from=-1, int to=-1); + void clearAll(bool wholeBuffer=false); + void ansiSequence(const QString& seq); + void oscSequence(const QString& seq); + void escControlChar(const QString& seq); + void trimBackBuffer(); + void scrollBack(int lines, int insertAt=-1); + void scrollFwd(int lines, int removeAt=-1); + void resetTerminal(); + void resetTabs(); + void adjustSelectionPosition(int lines); + + TextRender* iRenderer; + PtyIFace* iPtyIFace; + QWidget* iWindow; + Util* iUtil; + + QList > iBuffer; + QList > iAltBuffer; + QList > iBackBuffer; + QList > iTabStops; + + QSize iTermSize; + bool iEmitCursorChangeSignal; + + bool iShowCursor; + bool iUseAltScreenBuffer; + bool iAppCursorKeys; + bool iReplaceMode; + bool iNewLineMode; + + int iMarginTop; + int iMarginBottom; + + int iBackBufferScrollPos; + + TermAttribs iTermAttribs; + TermAttribs iTermAttribs_saved; + TermAttribs iTermAttribs_saved_alt; + + QString escSeq; + QString oscSeq; + int escape; + QRect iSelection; +}; + +#endif // TERMINAL_H diff --git a/textrender.cpp b/textrender.cpp new file mode 100644 index 0000000..fcc86c1 --- /dev/null +++ b/textrender.cpp @@ -0,0 +1,316 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include +#include "textrender.h" +#include "terminal.h" +#include "util.h" + +TextRender::TextRender(QDeclarativeItem *parent) : + QDeclarativeItem(parent), + iTerm(0), + iUtil(0) +{ + setFlag(QGraphicsItem::ItemHasNoContents, false); + + connect(this,SIGNAL(widthChanged(int)),this,SLOT(updateTermSize())); + connect(this,SIGNAL(heightChanged(int)),this,SLOT(updateTermSize())); + connect(this,SIGNAL(fontSizeChanged()),this,SLOT(updateTermSize())); + + //normal + iColorTable.append(QColor(0, 0, 0)); + iColorTable.append(QColor(210, 0, 0)); + iColorTable.append(QColor(0, 210, 0)); + iColorTable.append(QColor(210, 210, 0)); + iColorTable.append(QColor(0, 0, 240)); + iColorTable.append(QColor(210, 0, 210)); + iColorTable.append(QColor(0, 210, 210)); + iColorTable.append(QColor(235, 235, 235)); + //bright + iColorTable.append(QColor(127, 127, 127)); + iColorTable.append(QColor(255, 0, 0)); + iColorTable.append(QColor(0, 255, 0)); + iColorTable.append(QColor(255, 255, 0)); + iColorTable.append(QColor(92, 92, 255)); + iColorTable.append(QColor(255, 0, 255)); + iColorTable.append(QColor(0, 255, 255)); + iColorTable.append(QColor(255, 255, 255)); + + if(iColorTable.size()!=16) + qFatal("invalid color table"); + + iShowBufferScrollIndicator = false; + + // caching results in considerably faster redrawing during animations + setCacheMode(QGraphicsItem::DeviceCoordinateCache); +} + +TextRender::~TextRender() +{ +} + +void TextRender::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + if (!iTerm) + return; + + painter->save(); + painter->setFont(iFont); + + int y=0; + if (iTerm->backBufferScrollPos() != 0 && iTerm->backBuffer().size()>0) { + int from = iTerm->backBuffer().size() - iTerm->backBufferScrollPos(); + if(from<0) + from=0; + int to = iTerm->backBuffer().size(); + if(to-from > iTerm->termSize().height()) + to = from + iTerm->termSize().height(); + paintFromBuffer(painter, iTerm->backBuffer(), from, to, y); + if(to-from < iTerm->termSize().height() && iTerm->buffer().size()>0) { + int to2 = iTerm->termSize().height() - (to-from); + if(to2 > iTerm->buffer().size()) + to2 = iTerm->buffer().size(); + paintFromBuffer(painter, iTerm->buffer(), 0, to2, y); + } + } else { + int count = qMin(iTerm->termSize().height(), iTerm->buffer().size()); + paintFromBuffer(painter, iTerm->buffer(), 0, count, y); + } + + // cursor + if (iTerm->showCursor()) { + painter->setOpacity(1.0); + QPoint cursor = cursorPixelPos(); + QSize csize = cursorPixelSize(); + painter->setPen( iColorTable[Terminal::defaultFgColor] ); + painter->setBrush(Qt::transparent); + painter->drawRect(cursor.x(), cursor.y(), csize.width(), csize.height()); + } + + // selection + QRect selection = iTerm->selection(); + if (!selection.isNull()) { + painter->setOpacity(0.5); + painter->setPen(Qt::transparent); + painter->setBrush(Qt::blue); + QPoint start, end; + + if (selection.top() == selection.bottom()) { + start = charsToPixels(selection.topLeft()); + end = charsToPixels(selection.bottomRight()); + painter->drawRect(start.x(), start.y(), + end.x()-start.x()+fontWidth(), end.y()-start.y()+fontHeight()); + } else { + start = charsToPixels(selection.topLeft()); + end = charsToPixels(QPoint(iTerm->termSize().width(), selection.top())); + painter->drawRect(start.x(), start.y(), + end.x()-start.x()+fontWidth(), end.y()-start.y()+fontHeight()); + + start = charsToPixels(QPoint(1, selection.top()+1)); + end = charsToPixels(QPoint(iTerm->termSize().width(), selection.bottom()-1)); + painter->drawRect(start.x(), start.y(), + end.x()-start.x()+fontWidth(), end.y()-start.y()+fontHeight()); + + start = charsToPixels(QPoint(1, selection.bottom())); + end = charsToPixels(selection.bottomRight()); + painter->drawRect(start.x(), start.y(), + end.x()-start.x()+fontWidth(), end.y()-start.y()+fontHeight()); + } + } + + painter->restore(); +} + +void TextRender::paintFromBuffer(QPainter* painter, QList >& buffer, int from, int to, int &y) +{ + const int leftmargin = 2; + int cutAfter = property("cutAfter").toInt() + iFontDescent; + + TermChar tmp = iTerm->zeroChar; + TermChar nextAttrib = iTerm->zeroChar; + TermChar currAttrib = iTerm->zeroChar; + int currentX = leftmargin; + for(int i=from; i= cutAfter) + painter->setOpacity(0.3); + else + painter->setOpacity(1.0); + + int xcount = qMin(buffer.at(i).count(), iTerm->termSize().width()); + + // background for the current line + currentX = leftmargin; + int fragWidth = 0; + for(int j=0; jsetPen(Qt::transparent); + painter->setBrush( iColorTable[style.bgColor] ); + painter->drawRect(x, y, width, iFontHeight); +} + +void TextRender::drawTextFragment(QPainter* painter, int x, int y, QString text, TermChar style) +{ + if (style.attrib & attribNegative) { + int c = style.fgColor; + style.fgColor = style.bgColor; + style.bgColor = c; + } + if (style.attrib & attribBold) { + iFont.setBold(true); + painter->setFont(iFont); + if(style.fgColor < 8) + style.fgColor += 8; + } else if(iFont.bold()) { + iFont.setBold(false); + painter->setFont(iFont); + } + + painter->setPen( iColorTable[style.fgColor] ); + painter->setBrush(Qt::transparent); + painter->drawText(x, y, text); +} + +void TextRender::redraw() +{ + update(boundingRect()); +} + +void TextRender::setTerminal(Terminal *term) +{ + if (!iUtil) + qFatal("textrender: util class not set"); + + iTerm = term; + + iFont = QFont(iUtil->settingsValue("ui/fontFamily").toString(), + iUtil->settingsValue("ui/fontSize").toInt()); + iFont.setBold(false); + QFontMetrics fontMetrics(iFont); + iFontHeight = fontMetrics.height(); + iFontWidth = fontMetrics.maxWidth(); + iFontDescent = fontMetrics.descent(); +} + +void TextRender::updateTermSize() +{ + if (!iTerm) + return; + + QSize s((iWidth-4)/iFontWidth, (iHeight-4)/iFontHeight); + iTerm->setTermSize(s); +} + +void TextRender::setFontPointSize(int psize) +{ + if (iFont.pointSize() != psize) + { + iFont.setPointSize(psize); + QFontMetrics fontMetrics(iFont); + iFontHeight = fontMetrics.height(); + iFontWidth = fontMetrics.maxWidth(); + iFontDescent = fontMetrics.descent(); + + iUtil->setSettingsValue("ui/fontSize", psize); + + emit fontSizeChanged(); + } +} + +QPoint TextRender::cursorPixelPos() +{ + QPoint ret = charsToPixels(iTerm->cursorPos()); + ret.rx() += 2; + ret.ry() += iFontHeight/2; + return ret; +} + +QPoint TextRender::charsToPixels(QPoint pos) +{ + // not 100% accurrate with all fonts + return QPoint(2+(pos.x()-1)*iFontWidth, (pos.y()-1)*iFontHeight+iFontDescent+1); +} + +QSize TextRender::cursorPixelSize() +{ + return (QSize(iFontWidth, iFontHeight/2)); +} diff --git a/textrender.h b/textrender.h new file mode 100644 index 0000000..26bf6a4 --- /dev/null +++ b/textrender.h @@ -0,0 +1,98 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef TEXTRENDER_H +#define TEXTRENDER_H + +#include +#include +#include + +#include "terminal.h" + +class Util; + +class TextRender : public QDeclarativeItem +{ + Q_PROPERTY(int myWidth READ width WRITE setWidth NOTIFY widthChanged) + Q_PROPERTY(int myHeight READ height WRITE setHeight NOTIFY heightChanged) + Q_PROPERTY(int fontWidth READ fontWidth NOTIFY fontSizeChanged) + Q_PROPERTY(int fontHeight READ fontHeight NOTIFY fontSizeChanged) + Q_PROPERTY(int fontPointSize READ fontPointSize WRITE setFontPointSize NOTIFY fontSizeChanged) + Q_PROPERTY(bool showBufferScrollIndicator READ showBufferScrollIndicator WRITE setShowBufferScrollIndicator NOTIFY showBufferScrollIndicatorChanged) + + Q_OBJECT +public: + explicit TextRender(QDeclarativeItem *parent = 0); + virtual ~TextRender(); + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + + void setTerminal(Terminal* term); + void setUtil(Util* util) { iUtil = util; } + + int width() { return iWidth; } + int height() { return iHeight; } + void setWidth(int w) { if(iWidth!=w) { iWidth=w; emit widthChanged(w); } } + void setHeight(int h) { if(iHeight!=h) { iHeight=h; emit heightChanged(h); } } + int fontWidth() { return iFontWidth; } + int fontHeight() { return iFontHeight; } + int fontDescent() { return iFontDescent; } + int fontPointSize() { return iFont.pointSize(); } + void setFontPointSize(int psize); + bool showBufferScrollIndicator() { return iShowBufferScrollIndicator; } + void setShowBufferScrollIndicator(bool s) { if(iShowBufferScrollIndicator!=s) { iShowBufferScrollIndicator=s; emit showBufferScrollIndicatorChanged(); } } + + Q_INVOKABLE QPoint cursorPixelPos(); + Q_INVOKABLE QSize cursorPixelSize(); + +signals: + void widthChanged(int newWidth); + void heightChanged(int newHeight); + void fontSizeChanged(); + void showBufferScrollIndicatorChanged(); + +public slots: + void redraw(); + void updateTermSize(); + +private: + Q_DISABLE_COPY(TextRender) + + void paintFromBuffer(QPainter* painter, QList >& buffer, int from, int to, int &y); + void drawBgFragment(QPainter* painter, int x, int y, int width, TermChar style); + void drawTextFragment(QPainter* painter, int x, int y, QString text, TermChar style); + QPoint charsToPixels(QPoint pos); + + int iWidth; + int iHeight; + QFont iFont; + int iFontWidth; + int iFontHeight; + int iFontDescent; + bool iShowBufferScrollIndicator; + + Terminal *iTerm; + Util *iUtil; + + QList iColorTable; +}; + +QML_DECLARE_TYPE(TextRender) + +#endif // TEXTRENDER_H diff --git a/updateversion.sh b/updateversion.sh new file mode 100755 index 0000000..3127a94 --- /dev/null +++ b/updateversion.sh @@ -0,0 +1,11 @@ +#!/bin/bash +changelog="qtc_packaging/debian_harmattan/changelog" + +ver=`cat $changelog | sed -n -e '1p'| cut -d ' ' -f 2 | tr -d "()" | cut -d '-' -f 1` + +echo -e \ +"#ifndef VERSION_H\n"\ +"#define VERSION_H\n"\ +"const QString PROGRAM_VERSION=\"$ver\";\n"\ +"#endif\n"\ +> version.h diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..3f2c3fc --- /dev/null +++ b/util.cpp @@ -0,0 +1,394 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#include "qplatformdefs.h" + +#include +#include +#include + +#include "mainwindow.h" +#include "terminal.h" +#include "util.h" +#include "textrender.h" +#include "version.h" + +#ifdef MEEGO_EDITION_HARMATTAN +#include +#include +#include +#include +#endif //MEEGO_EDITION_HARMATTAN + +Util::Util(QSettings *settings, QObject *parent) : + QObject(parent), + iAllowGestures(false), + newSelection(true), + iSettings(settings), + iWindow(0), + iRenderer(0) +{ + swipeModeSet = false; + swipeAllowed = true; + + connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SIGNAL(clipboardOrSelectionChanged())); +} + +Util::~Util() +{ + // clear the notifications on quit + clearNotifications(); +} + +void Util::setWindow(QWidget* win) +{ + iWindow = dynamic_cast(win); + if(!iWindow) + qFatal("invalid main window"); + connect(iWindow, SIGNAL(focusChanged(bool)), this, SLOT(onMainWinFocusChanged(bool))); +} + +void Util::setWindowTitle(QString title) +{ + iCurrentWinTitle = title; + iWindow->setWindowTitle(title); + emit windowTitleChanged(); +} + +QString Util::currentWindowTitle() +{ + return iCurrentWinTitle; +} + +void Util::onMainWinFocusChanged(bool in) +{ + if (in) { + clearNotifications(); + + //disable & re-enable swiping when window gains focus (workaround for an "auto mode" bug) + updateSwipeLock(false); + updateSwipeLock(true); + } +} + +void Util::windowMinimize() +{ + iWindow->minimize(); +} + +void Util::openNewWindow() +{ +#ifdef MEEGO_EDITION_HARMATTAN + + QDBusInterface iface(MComponentData::instance()->serviceName(), + "/org/maemo/m", + "com.nokia.MApplicationIf"); + + if (iface.isValid()) { + QStringList params; + params.append("new"); + iface.call("launch", params); + } + +#endif //MEEGO_EDITION_HARMATTAN +} + +void Util::updateSwipeLock(bool suggestedState) +{ +#ifdef MEEGO_EDITION_HARMATTAN + if (settingsValue("ui/allowSwipe").toString()=="auto") { + if(suggestedState) { + enableSwipe(); + } else { + disableSwipe(); + } + } else if (settingsValue("ui/allowSwipe").toString()=="false") { + disableSwipe(); + } else if (settingsValue("ui/allowSwipe").toString()=="true") { + enableSwipe(); + } +#else + Q_UNUSED(suggestedState) +#endif //MEEGO_EDITION_HARMATTAN +} + +void Util::disableSwipe() +{ +#ifdef MEEGO_EDITION_HARMATTAN + if(swipeModeSet && !swipeAllowed) + return; + + if (iWindow) { + iWindow->disableSwipe(); + swipeModeSet = true; + swipeAllowed = false; + } +#endif //MEEGO_EDITION_HARMATTAN +} + +void Util::enableSwipe() +{ +#ifdef MEEGO_EDITION_HARMATTAN + if(swipeModeSet && swipeAllowed) + return; + + if (iWindow) + { + iWindow->enableSwipe(); + swipeModeSet = true; + swipeAllowed = true; + } +#endif //MEEGO_EDITION_HARMATTAN +} + +QString Util::configPath() +{ + if(!iSettings) + return QString(); + + QFileInfo f(iSettings->fileName()); + return f.path(); +} + +QVariant Util::settingsValue(QString key) +{ + if(!iSettings) + return QVariant(); + + return iSettings->value(key); +} + +void Util::setSettingsValue(QString key, QVariant value) +{ + if(iSettings) + iSettings->setValue(key, value); +} + +QString Util::versionString() +{ + return PROGRAM_VERSION; +} + +int Util::uiFontSize() +{ +#ifdef MEEGO_EDITION_HARMATTAN + return 14; +#else + return 12; +#endif +} + +bool Util::isHarmattan() +{ +#ifdef MEEGO_EDITION_HARMATTAN + return true; +#else + return false; +#endif +} + +void Util::keyPressFeedback() +{ + if( !settingsValue("ui/keyPressFeedback").toBool() ) + return; + +#ifdef MEEGO_EDITION_HARMATTAN + MFeedback::play("priority2_static_press"); +#endif +} + +void Util::keyReleaseFeedback() +{ + if( !settingsValue("ui/keyPressFeedback").toBool() ) + return; + +#ifdef MEEGO_EDITION_HARMATTAN + MFeedback::play("priority2_static_release"); +#endif +} + +void Util::bellAlert() +{ + if(!iWindow) + return; + +#ifdef MEEGO_EDITION_HARMATTAN + if(settingsValue("general/backgroundBellNotify").toBool() && + !iWindow->hasFocus()) + { + MRemoteAction act(MComponentData::instance()->serviceName(), + "/org/maemo/m", + "com.nokia.MApplicationIf", + "launch"); + MNotification notif(MNotification::ImReceivedEvent, "FingerTerm", "Terminal alert was received"); + notif.setImage("/usr/share/icons/hicolor/80x80/apps/fingerterm.png"); + notif.setAction(act); + notif.publish(); + } else if( settingsValue("general/visualBell").toBool() ) { + emit visualBell(); + } +#else + if( settingsValue("general/visualBell").toBool() ) + emit visualBell(); +#endif +} + +void Util::clearNotifications() +{ +#ifdef MEEGO_EDITION_HARMATTAN + QList notifs = MNotification::notifications(); + foreach(MNotification* n, notifs) { + if( n->remove() ) + delete n; + } +#endif //MEEGO_EDITION_HARMATTAN +} + +bool Util::eventFilter(QObject *, QEvent *ev) +{ + // event filter used to check if a mouse drag/pan was performed on the scene + + const int reqDragLength = 140; + + if(!iAllowGestures) + return false; + + if(ev->type()==QEvent::GraphicsSceneMousePress) { + QGraphicsSceneMouseEvent *mev = static_cast(ev); + dragOrigin = mev->scenePos(); + newSelection = true; + } + else if(ev->type()==QEvent::GraphicsSceneMouseMove) { + QGraphicsSceneMouseEvent *mev = static_cast(ev); + if(settingsValue("ui/dragMode")=="scroll") { + scrollBackBuffer(mev->scenePos(), mev->lastScenePos()); + } + else if(settingsValue("ui/dragMode")=="select" && iRenderer) { + selectionHelper(mev->scenePos()); + } + } + else if(ev->type()==QEvent::GraphicsSceneMouseRelease) { + QGraphicsSceneMouseEvent *mev = static_cast(ev); + if(settingsValue("ui/dragMode")=="gestures" && mev->lastScenePos() != dragOrigin) { + int xdist = qAbs(mev->scenePos().x() - dragOrigin.x()); + int ydist = qAbs(mev->scenePos().y() - dragOrigin.y()); + if(mev->scenePos().x() < dragOrigin.x()-reqDragLength && xdist > ydist*2) + doGesture(PanLeft); + else if(mev->scenePos().x() > dragOrigin.x()+reqDragLength && xdist > ydist*2) + doGesture(PanRight); + else if(mev->scenePos().y() > dragOrigin.y()+reqDragLength && ydist > xdist*2) + doGesture(PanDown); + else if(mev->scenePos().y() < dragOrigin.y()-reqDragLength && ydist > xdist*2) + doGesture(PanUp); + } + else if(settingsValue("ui/dragMode")=="scroll") { + scrollBackBuffer(mev->scenePos(), mev->lastScenePos()); + } + else if(settingsValue("ui/dragMode")=="select" && iRenderer) { + selectionHelper(mev->scenePos()); + selectionFinished(); + } + } + + return false; +} + +void Util::selectionHelper(QPointF scenePos) +{ + int yCorr = iRenderer->fontDescent(); + + QPoint start(qRound((dragOrigin.x()+2)/iRenderer->fontWidth()), + qRound((dragOrigin.y()+yCorr)/iRenderer->fontHeight())); + QPoint end(qRound((scenePos.x()+2)/iRenderer->fontWidth()), + qRound((scenePos.y()+yCorr)/iRenderer->fontHeight())); + + if (start != end) { + iTerm->setSelection(start, end); + newSelection = false; + } +} + +void Util::scrollBackBuffer(QPointF now, QPointF last) +{ + if(!iTerm) + return; + + int xdist = qAbs(now.x() - last.x()); + int ydist = qAbs(now.y() - last.y()); + + if(now.y() < last.y() && xdist < ydist*2) + iTerm->scrollBackBufferFwd(1); + else if(now.y() > last.y() && xdist < ydist*2) + iTerm->scrollBackBufferBack(1); +} + +void Util::doGesture(Util::PanGesture gesture) +{ + if(!iTerm) + return; + + if( gesture==PanLeft ) { + emit gestureNotify(settingsValue("gestures/panLeftTitle").toString()); + iTerm->putString(settingsValue("gestures/panLeftCommand").toString(), true); + } + else if( gesture==PanRight ) { + emit gestureNotify(settingsValue("gestures/panRightTitle").toString()); + iTerm->putString(settingsValue("gestures/panRightCommand").toString(), true); + } + else if( gesture==PanDown ) { + emit gestureNotify(settingsValue("gestures/panDownTitle").toString()); + iTerm->putString(settingsValue("gestures/panDownCommand").toString(), true); + } + else if( gesture==PanUp ) { + emit gestureNotify(settingsValue("gestures/panUpTitle").toString()); + iTerm->putString(settingsValue("gestures/panUpCommand").toString(), true); + } +} + +void Util::notifyText(QString text) +{ + emit gestureNotify(text); +} + +void Util::copyTextToClipboard(QString str) +{ + QClipboard *cb = QApplication::clipboard(); + cb->clear(); + cb->setText(str); +} + +bool Util::terminalHasSelection() +{ + return !iTerm->selection().isNull(); +} + +bool Util::canPaste() +{ + QClipboard *cb = QApplication::clipboard(); + if(cb->mimeData()->hasText() && !cb->mimeData()->text().isEmpty()) + return true; + + return false; +} + +void Util::selectionFinished() +{ + emit clipboardOrSelectionChanged(); +} + diff --git a/util.h b/util.h new file mode 100644 index 0000000..557a217 --- /dev/null +++ b/util.h @@ -0,0 +1,110 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This file is part of FingerTerm. + + FingerTerm 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. + + FingerTerm 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 FingerTerm. If not, see . +*/ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +class Terminal; +class MainWindow; +class TextRender; + +class Util : public QObject +{ + Q_OBJECT +public: + explicit Util(QSettings* settings, QObject *parent = 0); + virtual ~Util(); + void setWindow(QWidget* win); + void setWindowTitle(QString title); + Q_INVOKABLE QString currentWindowTitle(); + void setTerm(Terminal* term) { iTerm = term; } + void setRenderer(TextRender* r) { iRenderer = r; } + + Q_INVOKABLE void windowMinimize(); + Q_INVOKABLE void openNewWindow(); + Q_INVOKABLE void updateSwipeLock(bool suggestedState); + + Q_INVOKABLE QString versionString(); + Q_INVOKABLE QString configPath(); + Q_INVOKABLE QVariant settingsValue(QString key); + Q_INVOKABLE void setSettingsValue(QString key, QVariant value); + + Q_INVOKABLE int uiFontSize(); + Q_INVOKABLE bool isHarmattan(); + + Q_INVOKABLE void keyPressFeedback(); + Q_INVOKABLE void keyReleaseFeedback(); + Q_INVOKABLE void notifyText(QString text); + + Q_INVOKABLE void copyTextToClipboard(QString str); + Q_INVOKABLE bool canPaste(); + Q_INVOKABLE bool terminalHasSelection(); + + void bellAlert(); + void selectionFinished(); + + bool allowGestures() { return iAllowGestures; } + void setAllowGestures(bool a) { if(iAllowGestures!=a) { iAllowGestures=a; emit allowGesturesChanged(); } } + + Q_PROPERTY(bool allowGestures READ allowGestures WRITE setAllowGestures NOTIFY allowGesturesChanged) + +public slots: + void onMainWinFocusChanged(bool in); + +protected: + virtual bool eventFilter(QObject*, QEvent *ev); + +signals: + void visualBell(); + void allowGesturesChanged(); + void gestureNotify(QString msg); + void clipboardOrSelectionChanged(); + void windowTitleChanged(); + +private: + Q_DISABLE_COPY(Util) + enum PanGesture { PanNone, PanLeft, PanRight, PanUp, PanDown }; + + void enableSwipe(); + void disableSwipe(); + bool swipeModeSet; + bool swipeAllowed; + + void scrollBackBuffer(QPointF now, QPointF last); + void doGesture(PanGesture gesture); + void clearNotifications(); + void selectionHelper(QPointF scenePos); + + QPointF dragOrigin; + + bool iAllowGestures; + bool newSelection; + + QString iCurrentWinTitle; + + QSettings* iSettings; + MainWindow* iWindow; + Terminal* iTerm; + TextRender* iRenderer; +}; + +#endif // UTIL_H diff --git a/version.h b/version.h new file mode 100644 index 0000000..a4a0bce --- /dev/null +++ b/version.h @@ -0,0 +1,5 @@ +#ifndef VERSION_H +#define VERSION_H +const QString PROGRAM_VERSION="1.0.2"; +#endif +