commit c44b7c83a05d3ec4278960bdbf981ef22cd0102e Author: Andrew Lee (李健秋) Date: Sat Aug 15 23:30:20 2015 +0800 Adding upstream version 0.6.0. Signed-off-by: Andrew Lee (李健秋) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..0540a6a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,15 @@ +Originally forked from Konsole by + +Revived by Petr Vanek + +Contributors: + +Adam Treat +Chris Mueller +Christian Surlykke +Daniel O'Neill +Francisco Ballina +Georg Rudoy <0xd34df00d@gmail.com> +Jerome Leclanche +Petr Vanek +@kulti diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bef40ee --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,239 @@ +cmake_minimum_required( VERSION 2.8 ) + +project(qtermwidget) + +option(BUILD_DESIGNER_PLUGIN "Build Qt4 designer plugin" ON) +option(USE_QT5 "Build using Qt5. Default OFF." OFF) +option(BUILD_TEST "Build test application. Default OFF." OFF) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +# just change version for releases +set(QTERMWIDGET_VERSION_MAJOR "0") +set(QTERMWIDGET_VERSION_MINOR "6") +set(QTERMWIDGET_VERSION_PATCH "0") + +set(QTERMWIDGET_VERSION "${QTERMWIDGET_VERSION_MAJOR}.${QTERMWIDGET_VERSION_MINOR}.${QTERMWIDGET_VERSION_PATCH}") + + +include(CheckFunctionExists) +include(GNUInstallDirs) + +include_directories( + "${CMAKE_SOURCE_DIR}/lib" + "${CMAKE_BINARY_DIR}/lib" + "${CMAKE_BINARY_DIR}" +) +add_definitions(-Wall) + + +if(USE_QT5) + set(QTERMWIDGET_LIBRARY_NAME qtermwidget5) + include(qtermwidget5_use) +else() + include(qtermwidget4_use) + set(QTERMWIDGET_LIBRARY_NAME qtermwidget4) +endif() + + +# main library + +set(SRCS + lib/BlockArray.cpp + lib/ColorScheme.cpp + lib/Emulation.cpp + lib/Filter.cpp + lib/History.cpp + lib/HistorySearch.cpp + lib/KeyboardTranslator.cpp + lib/konsole_wcwidth.cpp + lib/kprocess.cpp + lib/kpty.cpp + lib/kptydevice.cpp + lib/kptyprocess.cpp + lib/Pty.cpp + lib/qtermwidget.cpp + lib/Screen.cpp + lib/ScreenWindow.cpp + lib/SearchBar.cpp + lib/Session.cpp + lib/ShellCommand.cpp + lib/TerminalCharacterDecoder.cpp + lib/TerminalDisplay.cpp + lib/tools.cpp + lib/Vt102Emulation.cpp +) + +# Only the Headers that need to be moc'd go here +set(HDRS + lib/Emulation.h + lib/Filter.h + lib/HistorySearch.h + lib/kprocess.h + lib/kptydevice.h + lib/kptyprocess.h + lib/Pty.h + lib/qtermwidget.h + lib/ScreenWindow.h + lib/SearchBar.h + lib/Session.h + lib/TerminalDisplay.h + lib/Vt102Emulation.h +) + +set(UI + lib/SearchBar.ui +) + +# for distribution +set(HDRS_DISTRIB + lib/qtermwidget.h + lib/Filter.h +) + +# dirs +set(KB_LAYOUT_DIR "${CMAKE_INSTALL_DATADIR}/${QTERMWIDGET_LIBRARY_NAME}/kb-layouts/") +message(STATUS "Keyboard layouts will be installed in: ${KB_LAYOUT_DIR}") +add_definitions(-DKB_LAYOUT_DIR="${CMAKE_INSTALL_PREFIX}/${KB_LAYOUT_DIR}") + +set(COLORSCHEMES_DIR "${CMAKE_INSTALL_DATADIR}/${QTERMWIDGET_LIBRARY_NAME}/color-schemes/") +message(STATUS "Color schemes will be installed in: ${COLORSCHEMES_DIR}" ) +add_definitions(-DCOLORSCHEMES_DIR="${CMAKE_INSTALL_PREFIX}/${COLORSCHEMES_DIR}") + +set(QTERMWIDGET_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/${QTERMWIDGET_LIBRARY_NAME}") + +#| Defines +add_definitions(-DHAVE_POSIX_OPENPT -DHAVE_SYS_TIME_H) +if(APPLE) + add_definitions(-DHAVE_UTMPX -D_UTMPX_COMPAT) +endif() +CHECK_FUNCTION_EXISTS(updwtmpx HAVE_UPDWTMPX) +if(HAVE_UPDWTMPX) + add_definitions(-DHAVE_UPDWTMPX) +endif() + + +if(USE_QT5) + qt5_wrap_cpp(MOCS ${HDRS}) + qt5_wrap_ui(UI_SRCS ${UI}) + set(PKG_CONFIG_REQ "Qt5Core, Qt5Xml, Qt5Widgets") +else() + qt4_wrap_cpp(MOCS ${HDRS}) + qt4_wrap_ui(UI_SRCS ${UI}) + set(PKG_CONFIG_REQ "QtCore, QtXml") +endif() + + +add_library(${QTERMWIDGET_LIBRARY_NAME} SHARED ${SRCS} ${MOCS} ${UI_SRCS}) +target_link_libraries(${QTERMWIDGET_LIBRARY_NAME} ${QTERMWIDGET_QT_LIBRARIES}) +set_target_properties( ${QTERMWIDGET_LIBRARY_NAME} PROPERTIES + SOVERSION ${QTERMWIDGET_VERSION_MAJOR} + VERSION ${QTERMWIDGET_VERSION} + ) +if(APPLE) + set (CMAKE_SKIP_RPATH 1) + # this is a must to load the lib correctly + set_target_properties( ${QTERMWIDGET_LIBRARY_NAME} PROPERTIES INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} ) +endif() + +install(TARGETS ${QTERMWIDGET_LIBRARY_NAME} DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES ${HDRS_DISTRIB} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${QTERMWIDGET_LIBRARY_NAME}") +# keyboard layouts +install(DIRECTORY lib/kb-layouts/ DESTINATION "${KB_LAYOUT_DIR}" FILES_MATCHING PATTERN "*.keytab" ) +# color schemes +install(DIRECTORY lib/color-schemes/ DESTINATION "${COLORSCHEMES_DIR}" FILES_MATCHING PATTERN "*.*schem*") + +include(create_pkgconfig_file) +create_pkgconfig_file(${QTERMWIDGET_LIBRARY_NAME} + "QTermWidget library for Qt ${QTERMWIDGET_VERSION_MAJOR}.x" + ${PKG_CONFIG_REQ} + ${QTERMWIDGET_LIBRARY_NAME} + ${QTERMWIDGET_VERSION} +) + +configure_file( + "${CMAKE_SOURCE_DIR}/cmake/${QTERMWIDGET_LIBRARY_NAME}-config.cmake.in" + "${CMAKE_BINARY_DIR}/${QTERMWIDGET_LIBRARY_NAME}-config.cmake" + @ONLY +) +install(FILES + "${CMAKE_BINARY_DIR}/${QTERMWIDGET_LIBRARY_NAME}-config.cmake" + "${CMAKE_SOURCE_DIR}/cmake/${QTERMWIDGET_LIBRARY_NAME}_use.cmake" + DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${QTERMWIDGET_LIBRARY_NAME}" +) +# end of main library + + +# designer plugin +if (BUILD_DESIGNER_PLUGIN) + if(USE_QT5) + message(FATAL_ERROR "Building Qt designer plugin is not supported for Qt5 yet. Use -DBUILD_DESIGNER_PLUGIN=0") + endif() + message(STATUS "Building Qt designer plugin") + + include_directories(designer "${QT_QTDESIGNER_INCLUDE_DIR}") + + set(DESIGNER_SRC lib/designer/qtermwidgetplugin.cpp) + qt4_wrap_cpp(DESIGNER_MOC lib/designer/qtermwidgetplugin.h) + qt4_add_resources(DESIGNER_QRC lib/designer/qtermwidgetplugin.qrc) + + link_directories(${CMAKE_BINARY_DIR}) + add_library(qtermwidget4plugin SHARED + ${DESIGNER_MOC} + ${DESIGNER_QRC} + ${DESIGNER_SRC} + ) + add_dependencies(qtermwidget4plugin qtermwidget4) + + target_link_libraries(qtermwidget4plugin + ${QT_QTCORE_LIBRARY} + ${QT_QTDESIGNER_LIBRARY} + ${QT_QTDESIGNERCOMPONENTS_LIBRARY} + ${QTERMWIDGET_LIBRARY_NAME} + ) + + if(APPLE) + # this is a must to load the lib correctly + set_target_properties(qtermwidget4plugin PROPERTIES + INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/qt4/plugins/designer" + ) + endif() + + install(TARGETS qtermwidget4plugin DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/qt4/plugins/designer") + +endif (BUILD_DESIGNER_PLUGIN) +# end of designer plugin + + +# test application +if(BUILD_TEST) + set(TEST_SRC src/main.cpp) + add_executable(test ${TEST_SRC}) + add_dependencies(test ${QTERMWIDGET_LIBRARY_NAME}) + link_directories(${CMAKE_BINARY_DIR}) + target_link_libraries(test ${QTERMWIDGET_QT_LIBRARIES} ${QTERMWIDGET_LIBRARY_NAME} util) +endif (BUILD_TEST) +# end of test application + + +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY +) +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" +) + + +# make dist custom target +SET(CPACK_PACKAGE_NAME "qtermwidget") +# TODO/FIXME: versioning from player subdir... I don't know why it's separated... +SET(CPACK_PACKAGE_VERSION ${QTERMWIDGET_VERSION_MAJOR}.${QTERMWIDGET_VERSION_MINOR}.${QTERMWIDGET_VERSION_PATCH}) +SET(CPACK_SOURCE_GENERATOR "TGZ;TBZ2") +SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") +SET(CPACK_IGNORE_FILES "/\\\\.git/;\\\\.swp$;\\\\.#;/#;\\\\.tar.gz$;/CMakeFiles/;CMakeCache.txt;\\\\.qm$;/build/;\\\\.diff$;.DS_Store'") +SET(CPACK_SOURCE_IGNORE_FILES ${CPACK_IGNORE_FILES}) +INCLUDE(CPack) +# simulate autotools' "make dist" +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) 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/Changelog b/Changelog new file mode 100644 index 0000000..454c716 --- /dev/null +++ b/Changelog @@ -0,0 +1,27 @@ +0.6.0 (2014-10-21) + * Full Qt4 + Qt5 support + * Fixed Ctrl+Arrows in Linux emulation + * Fixed Drag & Drop support + + +## Old changelog + +31.07.2008 +Interface class from c-style conversions rewritten with pimpl support. + + +16.07.2008 +Added optional scrollbar + + +06.06.2008 +Some artefacts were removed, some added... +Also added support for color schemes, and 3 color schemes provided (classical - white on black, green on black, black on light yellow). Is it enough or not? + + +26.05.2008 +Added file release as an archive with source code. But preferrable way is still getting code from CVS, cause file release can be outdated. + + +11.05.2008 +Initial CVS import - first version comes with number 0.0.1 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..e26c91a --- /dev/null +++ b/INSTALL @@ -0,0 +1,23 @@ +Requirements: + + Qt4 or Qt5 + cmake + +Supported (tested) platforms: + + Linux + *BSD + Mac OS X + +Build: + + A shadow build (out of source) is strongly recommended + http://www.cmake.org/Wiki/CMake_FAQ#Out-of-source_build_trees + + 1) mkdir -p build && cd build + 2a) cmake path/to/source -DUSE_QT5=true # Qt 5 + 2b) cmake path/to/source # Qt 4 only + 3) make + 4) optional: make install + + Read cmake docs to fine tune the build process (CMAKE_INSTALL_PREFIX, etc...) diff --git a/README b/README new file mode 100644 index 0000000..6983ca5 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +QTermWidget is an opensource project originally based on KDE4 Konsole application, +but it took its own direction later. +The main goal of this project is to provide unicode-enabled, embeddable +Qt widget for using as a built-in console (or terminal emulation widget). + + +Current maintainer: Petr Vanek +License: GPLv2+ + + diff --git a/TODO b/TODO new file mode 100644 index 0000000..e69de29 diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..02cb12d --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,23 @@ +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +# this works on Linux, but not on mac. +#FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +#STRING(REGEX REPLACE "\n" ";" files "${files}") +#FOREACH(file ${files}) +# MESSAGE(STATUS "Uninstalling \"${file}\"") +# IF(NOT EXISTS "${file}") +# MESSAGE(FATAL_ERROR "File \"${file}\" does not exists.") +# ENDIF(NOT EXISTS "${file}") +# EXEC_PROGRAM("@CMAKE_COMMAND@" ARGS "-E remove \"${file}\"" +# OUTPUT_VARIABLE rm_out +# RETURN_VARIABLE rm_retval) +# IF("${rm_retval}" GREATER 0) +# MESSAGE(FATAL_ERROR "Problem when removing \"${file}\"") +# ENDIF("${rm_retval}" GREATER 0) +#ENDFOREACH(file) + +EXEC_PROGRAM("xargs rm < @CMAKE_BINARY_DIR@/install_manifest.txt" + OUTPUT_VARIABLE rm_out + RETURN_VARIABLE rm_ret) diff --git a/cmake/create_pkgconfig_file.cmake b/cmake/create_pkgconfig_file.cmake new file mode 100644 index 0000000..c3e775b --- /dev/null +++ b/cmake/create_pkgconfig_file.cmake @@ -0,0 +1,29 @@ +# +# Write a pkg-config pc file for given "name" with "decription" +# Arguments: +# name: a library name (withoud "lib" prefix and "so" suffixes +# desc: a desription string +# requires: required libraries +# include_rel_dir: include directory, relative to includedir +# version: package version +# +macro (create_pkgconfig_file name desc requires include_rel_dir version) + set(_pkgfname "${CMAKE_CURRENT_BINARY_DIR}/${name}.pc") + message(STATUS "${name}: writing pkgconfig file ${_pkgfname}") + + file(WRITE "${_pkgfname}" + "prefix=${CMAKE_INSTALL_PREFIX}\n" + "libdir=\${prefix}/${CMAKE_INSTALL_LIBDIR}\n" + "includedir=\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}\n" + "\n" + "Name: ${name}\n" + "Description: ${desc}\n" + "Version: ${version}\n" + "Requires: ${requires}\n" + "Libs: -L\${libdir} -l${name}\n" + "Cflags: -I\${includedir} -I\${includedir}/${include_rel_dir}\n" + "\n" + ) + + install(FILES ${_pkgfname} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endmacro() diff --git a/cmake/qtermwidget4-config.cmake.in b/cmake/qtermwidget4-config.cmake.in new file mode 100644 index 0000000..7f3a10f --- /dev/null +++ b/cmake/qtermwidget4-config.cmake.in @@ -0,0 +1,47 @@ +# - Find the QTermWidget include and library dirs and define a some macros +# +# The module defines the following variables +# QTERMWIDGET_FOUND - Set to TRUE if all of the above has been found +# +# QTERMWIDGET_INCLUDE_DIR - The QTermWidget include directory +# +# QTERMWIDGET_INCLUDE_DIRS - The QTermWidget include directory +# +# QTERMWIDGET_LIBRARIES - The libraries needed to use QTermWidget +# +# QTERMWIDGET_USE_FILE - The variable QTERMWIDGET_USE_FILE is set which is the path +# to a CMake file that can be included to compile qtermwidget +# applications and libraries. It sets up the compilation +# environment for include directories and populates a +# QTERMWIDGET_LIBRARIES variable. +# +# QTERMWIDGET_QT_LIBRARIES - The Qt libraries needed by QTermWidget +# +# Typical usage: +# option(USE_QT5 "Build using Qt5. Default off" OFF) +# if (USE_QT5) +# find_package(QTERMWIDGET4) +# else() +# find_package(QTERMWIDGET5) +# endif() +# +# include(${QTERMWIDGET_USE_FILE}) +# add_executable(foo main.cpp) +# target_link_libraries(foo ${QTERMWIDGET_QT_LIBRARIES} ${QTERMWIDGET_LIBRARIES}) + +set(QTERMWIDGET_INCLUDE_DIR @QTERMWIDGET_INCLUDE_DIR@) +set(QTERMWIDGET_LIBRARY @QTERMWIDGET_LIBRARY_NAME@) + +set(QTERMWIDGET_LIBRARIES ${QTERMWIDGET_LIBRARY}) +set(QTERMWIDGET_INCLUDE_DIRS "${QTERMWIDGET_INCLUDE_DIR}") + +set(QTERMWIDGET_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/qtermwidget4_use.cmake") +set(QTERMWIDGET_FOUND 1) + +set(QTERMWIDGET_VERSION_MAJOR @QTERMWIDGET_VERSION_MAJOR@) +set(QTERMWIDGET_VERSION_MINOR @QTERMWIDGET_VERSION_MINOR@) +set(QTERMWIDGET_VERSION_PATCH @QTERMWIDGET_VERSION_PATCH@) +set(QTERMWIDGET_VERSION @QTERMWIDGET_VERSION@) + +mark_as_advanced(QTERMWIDGET_LIBRARY QTERMWIDGET_INCLUDE_DIR) + diff --git a/cmake/qtermwidget4_use.cmake b/cmake/qtermwidget4_use.cmake new file mode 100644 index 0000000..5f2d732 --- /dev/null +++ b/cmake/qtermwidget4_use.cmake @@ -0,0 +1,8 @@ + +find_package(Qt4 REQUIRED QUIET) +include(${QT_USE_FILE}) + +set(QTERMWIDGET_QT_LIBRARIES ${QT_LIBRARIES}) + +include_directories(${QTERMWIDGET_INCLUDE_DIRS}) + diff --git a/cmake/qtermwidget5-config.cmake.in b/cmake/qtermwidget5-config.cmake.in new file mode 100644 index 0000000..5f8edb9 --- /dev/null +++ b/cmake/qtermwidget5-config.cmake.in @@ -0,0 +1,47 @@ +# - Find the QTermWidget include and library dirs and define a some macros +# +# The module defines the following variables +# QTERMWIDGET_FOUND - Set to TRUE if all of the above has been found +# +# QTERMWIDGET_INCLUDE_DIR - The QTermWidget include directory +# +# QTERMWIDGET_INCLUDE_DIRS - The QTermWidget include directory +# +# QTERMWIDGET_LIBRARIES - The libraries needed to use QTermWidget +# +# QTERMWIDGET_USE_FILE - The variable QTERMWIDGET_USE_FILE is set which is the path +# to a CMake file that can be included to compile qtermwidget +# applications and libraries. It sets up the compilation +# environment for include directories and populates a +# QTERMWIDGET_LIBRARIES variable. +# +# QTERMWIDGET_QT_LIBRARIES - The Qt libraries needed by QTermWidget +# +# Typical usage: +# option(USE_QT5 "Build using Qt5. Default off" OFF) +# if (USE_QT5) +# find_package(QTERMWIDGET4) +# else() +# find_package(QTERMWIDGET5) +# endif() +# +# include(${QTERMWIDGET_USE_FILE}) +# add_executable(foo main.cpp) +# target_link_libraries(foo ${QTERMWIDGET_QT_LIBRARIES} ${QTERMWIDGET_LIBRARIES}) + +set(QTERMWIDGET_INCLUDE_DIR @QTERMWIDGET_INCLUDE_DIR@) +set(QTERMWIDGET_LIBRARY @QTERMWIDGET_LIBRARY_NAME@) + +set(QTERMWIDGET_LIBRARIES ${QTERMWIDGET_LIBRARY}) +set(QTERMWIDGET_INCLUDE_DIRS "${QTERMWIDGET_INCLUDE_DIR}") + +set(QTERMWIDGET_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/qtermwidget5_use.cmake") +set(QTERMWIDGET_FOUND 1) + +set(QTERMWIDGET_VERSION_MAJOR @QTERMWIDGET_VERSION_MAJOR@) +set(QTERMWIDGET_VERSION_MINOR @QTERMWIDGET_VERSION_MINOR@) +set(QTERMWIDGET_VERSION_PATCH @QTERMWIDGET_VERSION_PATCH@) +set(QTERMWIDGET_VERSION @QTERMWIDGET_VERSION@) + +mark_as_advanced(QTERMWIDGET_LIBRARY QTERMWIDGET_INCLUDE_DIR) + diff --git a/cmake/qtermwidget5_use.cmake b/cmake/qtermwidget5_use.cmake new file mode 100644 index 0000000..3db35fa --- /dev/null +++ b/cmake/qtermwidget5_use.cmake @@ -0,0 +1,9 @@ +find_package(Qt5Widgets REQUIRED) + +include_directories(${Qt5Widgets_INCLUDE_DIRS}) +add_definitions(${Qt5Core_DEFINITIONS}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +set(QTERMWIDGET_QT_LIBRARIES ${Qt5Widgets_LIBRARIES}) + +include_directories(${QTERMWIDGET_INCLUDE_DIRS}) + diff --git a/lib/BlockArray.cpp b/lib/BlockArray.cpp new file mode 100644 index 0000000..3f36390 --- /dev/null +++ b/lib/BlockArray.cpp @@ -0,0 +1,379 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 2000 by Stephan Kulow + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. + +*/ + +#include + +// Own +#include "BlockArray.h" + +// System +#include +#include +#include +#include +#include + + +using namespace Konsole; + +static int blocksize = 0; + +BlockArray::BlockArray() + : size(0), + current(size_t(-1)), + index(size_t(-1)), + lastmap(0), + lastmap_index(size_t(-1)), + lastblock(0), ion(-1), + length(0) +{ + // lastmap_index = index = current = size_t(-1); + if (blocksize == 0) { + blocksize = ((sizeof(Block) / getpagesize()) + 1) * getpagesize(); + } + +} + +BlockArray::~BlockArray() +{ + setHistorySize(0); + assert(!lastblock); +} + +size_t BlockArray::append(Block * block) +{ + if (!size) { + return size_t(-1); + } + + ++current; + if (current >= size) { + current = 0; + } + + int rc; + rc = lseek(ion, current * blocksize, SEEK_SET); + if (rc < 0) { + perror("HistoryBuffer::add.seek"); + setHistorySize(0); + return size_t(-1); + } + rc = write(ion, block, blocksize); + if (rc < 0) { + perror("HistoryBuffer::add.write"); + setHistorySize(0); + return size_t(-1); + } + + length++; + if (length > size) { + length = size; + } + + ++index; + + delete block; + return current; +} + +size_t BlockArray::newBlock() +{ + if (!size) { + return size_t(-1); + } + append(lastblock); + + lastblock = new Block(); + return index + 1; +} + +Block * BlockArray::lastBlock() const +{ + return lastblock; +} + +bool BlockArray::has(size_t i) const +{ + if (i == index + 1) { + return true; + } + + if (i > index) { + return false; + } + if (index - i >= length) { + return false; + } + return true; +} + +const Block * BlockArray::at(size_t i) +{ + if (i == index + 1) { + return lastblock; + } + + if (i == lastmap_index) { + return lastmap; + } + + if (i > index) { + qDebug() << "BlockArray::at() i > index\n"; + return 0; + } + +// if (index - i >= length) { +// kDebug(1211) << "BlockArray::at() index - i >= length\n"; +// return 0; +// } + + size_t j = i; // (current - (index - i) + (index/size+1)*size) % size ; + + assert(j < size); + unmap(); + + Block * block = (Block *)mmap(0, blocksize, PROT_READ, MAP_PRIVATE, ion, j * blocksize); + + if (block == (Block *)-1) { + perror("mmap"); + return 0; + } + + lastmap = block; + lastmap_index = i; + + return block; +} + +void BlockArray::unmap() +{ + if (lastmap) { + int res = munmap((char *)lastmap, blocksize); + if (res < 0) { + perror("munmap"); + } + } + lastmap = 0; + lastmap_index = size_t(-1); +} + +bool BlockArray::setSize(size_t newsize) +{ + return setHistorySize(newsize * 1024 / blocksize); +} + +bool BlockArray::setHistorySize(size_t newsize) +{ +// kDebug(1211) << "setHistorySize " << size << " " << newsize; + + if (size == newsize) { + return false; + } + + unmap(); + + if (!newsize) { + delete lastblock; + lastblock = 0; + if (ion >= 0) { + close(ion); + } + ion = -1; + current = size_t(-1); + return true; + } + + if (!size) { + FILE * tmp = tmpfile(); + if (!tmp) { + perror("konsole: cannot open temp file.\n"); + } else { + ion = dup(fileno(tmp)); + if (ion<0) { + perror("konsole: cannot dup temp file.\n"); + fclose(tmp); + } + } + if (ion < 0) { + return false; + } + + assert(!lastblock); + + lastblock = new Block(); + size = newsize; + return false; + } + + if (newsize > size) { + increaseBuffer(); + size = newsize; + return false; + } else { + decreaseBuffer(newsize); + ftruncate(ion, length*blocksize); + size = newsize; + + return true; + } +} + +void moveBlock(FILE * fion, int cursor, int newpos, char * buffer2) +{ + int res = fseek(fion, cursor * blocksize, SEEK_SET); + if (res) { + perror("fseek"); + } + res = fread(buffer2, blocksize, 1, fion); + if (res != 1) { + perror("fread"); + } + + res = fseek(fion, newpos * blocksize, SEEK_SET); + if (res) { + perror("fseek"); + } + res = fwrite(buffer2, blocksize, 1, fion); + if (res != 1) { + perror("fwrite"); + } + // printf("moving block %d to %d\n", cursor, newpos); +} + +void BlockArray::decreaseBuffer(size_t newsize) +{ + if (index < newsize) { // still fits in whole + return; + } + + int offset = (current - (newsize - 1) + size) % size; + + if (!offset) { + return; + } + + // The Block constructor could do somthing in future... + char * buffer1 = new char[blocksize]; + + FILE * fion = fdopen(dup(ion), "w+b"); + if (!fion) { + delete [] buffer1; + perror("fdopen/dup"); + return; + } + + int firstblock; + if (current <= newsize) { + firstblock = current + 1; + } else { + firstblock = 0; + } + + size_t oldpos; + for (size_t i = 0, cursor=firstblock; i < newsize; i++) { + oldpos = (size + cursor + offset) % size; + moveBlock(fion, oldpos, cursor, buffer1); + if (oldpos < newsize) { + cursor = oldpos; + } else { + cursor++; + } + } + + current = newsize - 1; + length = newsize; + + delete [] buffer1; + + fclose(fion); + +} + +void BlockArray::increaseBuffer() +{ + if (index < size) { // not even wrapped once + return; + } + + int offset = (current + size + 1) % size; + if (!offset) { // no moving needed + return; + } + + // The Block constructor could do somthing in future... + char * buffer1 = new char[blocksize]; + char * buffer2 = new char[blocksize]; + + int runs = 1; + int bpr = size; // blocks per run + + if (size % offset == 0) { + bpr = size / offset; + runs = offset; + } + + FILE * fion = fdopen(dup(ion), "w+b"); + if (!fion) { + perror("fdopen/dup"); + delete [] buffer1; + delete [] buffer2; + return; + } + + int res; + for (int i = 0; i < runs; i++) { + // free one block in chain + int firstblock = (offset + i) % size; + res = fseek(fion, firstblock * blocksize, SEEK_SET); + if (res) { + perror("fseek"); + } + res = fread(buffer1, blocksize, 1, fion); + if (res != 1) { + perror("fread"); + } + int newpos = 0; + for (int j = 1, cursor=firstblock; j < bpr; j++) { + cursor = (cursor + offset) % size; + newpos = (cursor - offset + size) % size; + moveBlock(fion, cursor, newpos, buffer2); + } + res = fseek(fion, i * blocksize, SEEK_SET); + if (res) { + perror("fseek"); + } + res = fwrite(buffer1, blocksize, 1, fion); + if (res != 1) { + perror("fwrite"); + } + } + current = size - 1; + length = size; + + delete [] buffer1; + delete [] buffer2; + + fclose(fion); + +} + diff --git a/lib/BlockArray.h b/lib/BlockArray.h new file mode 100644 index 0000000..4fee623 --- /dev/null +++ b/lib/BlockArray.h @@ -0,0 +1,130 @@ +/* + This file is part of Konsole, an X terminal. + Copyright (C) 2000 by Stephan Kulow + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. +*/ + +#ifndef BLOCKARRAY_H +#define BLOCKARRAY_H + +#include + +//#error Do not use in KDE 2.1 + +#define BlockSize (1 << 12) +#define ENTRIES ((BlockSize - sizeof(size_t) ) / sizeof(unsigned char)) + +namespace Konsole { + +struct Block { + Block() { + size = 0; + } + unsigned char data[ENTRIES]; + size_t size; +}; + +// /////////////////////////////////////////////////////// + +class BlockArray { +public: + /** + * Creates a history file for holding + * maximal size blocks. If more blocks + * are requested, then it drops earlier + * added ones. + */ + BlockArray(); + + /// destructor + ~BlockArray(); + + /** + * adds the Block at the end of history. + * This may drop other blocks. + * + * The ownership on the block is transfered. + * An unique index number is returned for accessing + * it later (if not yet dropped then) + * + * Note, that the block may be dropped completely + * if history is turned off. + */ + size_t append(Block * block); + + /** + * gets the block at the index. Function may return + * 0 if the block isn't available any more. + * + * The returned block is strictly readonly as only + * maped in memory - and will be invalid on the next + * operation on this class. + */ + const Block * at(size_t index); + + /** + * reorders blocks as needed. If newsize is null, + * the history is emptied completely. The indices + * returned on append won't change their semantic, + * but they may not be valid after this call. + */ + bool setHistorySize(size_t newsize); + + size_t newBlock(); + + Block * lastBlock() const; + + /** + * Convenient function to set the size in KBytes + * instead of blocks + */ + bool setSize(size_t newsize); + + size_t len() const { + return length; + } + + bool has(size_t index) const; + + size_t getCurrent() const { + return current; + } + +private: + void unmap(); + void increaseBuffer(); + void decreaseBuffer(size_t newsize); + + size_t size; + // current always shows to the last inserted block + size_t current; + size_t index; + + Block * lastmap; + size_t lastmap_index; + Block * lastblock; + + int ion; + size_t length; + +}; + +} + +#endif diff --git a/lib/Character.h b/lib/Character.h new file mode 100644 index 0000000..ce0f1b3 --- /dev/null +++ b/lib/Character.h @@ -0,0 +1,224 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef CHARACTER_H +#define CHARACTER_H + +// Qt +#include + +// Local +#include "CharacterColor.h" + +namespace Konsole +{ + +typedef unsigned char LineProperty; + +static const int LINE_DEFAULT = 0; +static const int LINE_WRAPPED = (1 << 0); +static const int LINE_DOUBLEWIDTH = (1 << 1); +static const int LINE_DOUBLEHEIGHT = (1 << 2); + +#define DEFAULT_RENDITION 0 +#define RE_BOLD (1 << 0) +#define RE_BLINK (1 << 1) +#define RE_UNDERLINE (1 << 2) +#define RE_REVERSE (1 << 3) // Screen only +#define RE_INTENSIVE (1 << 3) // Widget only +#define RE_CURSOR (1 << 4) +#define RE_EXTENDED_CHAR (1 << 5) + +/** + * A single character in the terminal which consists of a unicode character + * value, foreground and background colors and a set of rendition attributes + * which specify how it should be drawn. + */ +class Character +{ +public: + /** + * Constructs a new character. + * + * @param _c The unicode character value of this character. + * @param _f The foreground color used to draw the character. + * @param _b The color used to draw the character's background. + * @param _r A set of rendition flags which specify how this character is to be drawn. + */ + inline Character(quint16 _c = ' ', + CharacterColor _f = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor _b = CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + quint8 _r = DEFAULT_RENDITION) + : character(_c), rendition(_r), foregroundColor(_f), backgroundColor(_b) {} + + union + { + /** The unicode character value for this character. */ + quint16 character; + /** + * Experimental addition which allows a single Character instance to contain more than + * one unicode character. + * + * charSequence is a hash code which can be used to look up the unicode + * character sequence in the ExtendedCharTable used to create the sequence. + */ + quint16 charSequence; + }; + + /** A combination of RENDITION flags which specify options for drawing the character. */ + quint8 rendition; + + /** The foreground color used to draw this character. */ + CharacterColor foregroundColor; + /** The color used to draw this character's background. */ + CharacterColor backgroundColor; + + /** + * Returns true if this character has a transparent background when + * it is drawn with the specified @p palette. + */ + bool isTransparent(const ColorEntry* palette) const; + /** + * Returns true if this character should always be drawn in bold when + * it is drawn with the specified @p palette, independent of whether + * or not the character has the RE_BOLD rendition flag. + */ + ColorEntry::FontWeight fontWeight(const ColorEntry* base) const; + + /** + * returns true if the format (color, rendition flag) of the compared characters is equal + */ + bool equalsFormat(const Character &other) const; + + /** + * Compares two characters and returns true if they have the same unicode character value, + * rendition and colors. + */ + friend bool operator == (const Character& a, const Character& b); + /** + * Compares two characters and returns true if they have different unicode character values, + * renditions or colors. + */ + friend bool operator != (const Character& a, const Character& b); +}; + +inline bool operator == (const Character& a, const Character& b) +{ + return a.character == b.character && + a.rendition == b.rendition && + a.foregroundColor == b.foregroundColor && + a.backgroundColor == b.backgroundColor; +} + +inline bool operator != (const Character& a, const Character& b) +{ + return a.character != b.character || + a.rendition != b.rendition || + a.foregroundColor != b.foregroundColor || + a.backgroundColor != b.backgroundColor; +} + +inline bool Character::isTransparent(const ColorEntry* base) const +{ + return ((backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) && + base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].transparent) + || ((backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) && + base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].transparent); +} + +inline bool Character::equalsFormat(const Character& other) const +{ + return + backgroundColor==other.backgroundColor && + foregroundColor==other.foregroundColor && + rendition==other.rendition; +} + +inline ColorEntry::FontWeight Character::fontWeight(const ColorEntry* base) const +{ + if (backgroundColor._colorSpace == COLOR_SPACE_DEFAULT) + return base[backgroundColor._u+0+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else if (backgroundColor._colorSpace == COLOR_SPACE_SYSTEM) + return base[backgroundColor._u+2+(backgroundColor._v?BASE_COLORS:0)].fontWeight; + else + return ColorEntry::UseCurrentFormat; +} + +extern unsigned short vt100_graphics[32]; + + +/** + * A table which stores sequences of unicode characters, referenced + * by hash keys. The hash key itself is the same size as a unicode + * character ( ushort ) so that it can occupy the same space in + * a structure. + */ +class ExtendedCharTable +{ +public: + /** Constructs a new character table. */ + ExtendedCharTable(); + ~ExtendedCharTable(); + + /** + * Adds a sequences of unicode characters to the table and returns + * a hash code which can be used later to look up the sequence + * using lookupExtendedChar() + * + * If the same sequence already exists in the table, the hash + * of the existing sequence will be returned. + * + * @param unicodePoints An array of unicode character points + * @param length Length of @p unicodePoints + */ + ushort createExtendedChar(ushort* unicodePoints , ushort length); + /** + * Looks up and returns a pointer to a sequence of unicode characters + * which was added to the table using createExtendedChar(). + * + * @param hash The hash key returned by createExtendedChar() + * @param length This variable is set to the length of the + * character sequence. + * + * @return A unicode character sequence of size @p length. + */ + ushort* lookupExtendedChar(ushort hash , ushort& length) const; + + /** The global ExtendedCharTable instance. */ + static ExtendedCharTable instance; +private: + // calculates the hash key of a sequence of unicode points of size 'length' + ushort extendedCharHash(ushort* unicodePoints , ushort length) const; + // tests whether the entry in the table specified by 'hash' matches the + // character sequence 'unicodePoints' of size 'length' + bool extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const; + // internal, maps hash keys to character sequence buffers. The first ushort + // in each value is the length of the buffer, followed by the ushorts in the buffer + // themselves. + QHash extendedCharTable; +}; + +} +Q_DECLARE_TYPEINFO(Konsole::Character, Q_MOVABLE_TYPE); + +#endif // CHARACTER_H + diff --git a/lib/CharacterColor.h b/lib/CharacterColor.h new file mode 100644 index 0000000..a17c5ce --- /dev/null +++ b/lib/CharacterColor.h @@ -0,0 +1,299 @@ +/* + This file is part of Konsole, KDE's terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef CHARACTERCOLOR_H +#define CHARACTERCOLOR_H + +// Qt +#include + +//#include +#define KDE_NO_EXPORT + +namespace Konsole +{ + +/** + * An entry in a terminal display's color palette. + * + * A color palette is an array of 16 ColorEntry instances which map + * system color indexes (from 0 to 15) into actual colors. + * + * Each entry can be set as bold, in which case any text + * drawn using the color should be drawn in bold. + * + * Each entry can also be transparent, in which case the terminal + * display should avoid drawing the background for any characters + * using the entry as a background. + */ +class ColorEntry +{ +public: + /** Specifies the weight to use when drawing text with this color. */ + enum FontWeight + { + /** Always draw text in this color with a bold weight. */ + Bold, + /** Always draw text in this color with a normal weight. */ + Normal, + /** + * Use the current font weight set by the terminal application. + * This is the default behavior. + */ + UseCurrentFormat + }; + + /** + * Constructs a new color palette entry. + * + * @param c The color value for this entry. + * @param tr Specifies that the color should be transparent when used as a background color. + * @param weight Specifies the font weight to use when drawing text with this color. + */ + ColorEntry(QColor c, bool tr, FontWeight weight = UseCurrentFormat) + : color(c), transparent(tr), fontWeight(weight) {} + + /** + * Constructs a new color palette entry with an undefined color, and + * with the transparent and bold flags set to false. + */ + ColorEntry() : transparent(false), fontWeight(UseCurrentFormat) {} + + /** + * Sets the color, transparency and boldness of this color to those of @p rhs. + */ + void operator=(const ColorEntry& rhs) + { + color = rhs.color; + transparent = rhs.transparent; + fontWeight = rhs.fontWeight; + } + + /** The color value of this entry for display. */ + QColor color; + + /** + * If true character backgrounds using this color should be transparent. + * This is not applicable when the color is used to render text. + */ + bool transparent; + /** + * Specifies the font weight to use when drawing text with this color. + * This is not applicable when the color is used to draw a character's background. + */ + FontWeight fontWeight; +}; + + +// Attributed Character Representations /////////////////////////////// + +// Colors + +#define BASE_COLORS (2+8) +#define INTENSITIES 2 +#define TABLE_COLORS (INTENSITIES*BASE_COLORS) + +#define DEFAULT_FORE_COLOR 0 +#define DEFAULT_BACK_COLOR 1 + +//a standard set of colors using black text on a white background. +//defined in TerminalDisplay.cpp + +extern const ColorEntry base_color_table[TABLE_COLORS] KDE_NO_EXPORT; + +/* CharacterColor is a union of the various color spaces. + + Assignment is as follows: + + Type - Space - Values + + 0 - Undefined - u: 0, v:0 w:0 + 1 - Default - u: 0..1 v:intense w:0 + 2 - System - u: 0..7 v:intense w:0 + 3 - Index(256) - u: 16..255 v:0 w:0 + 4 - RGB - u: 0..255 v:0..256 w:0..256 + + Default colour space has two separate colours, namely + default foreground and default background colour. +*/ + +#define COLOR_SPACE_UNDEFINED 0 +#define COLOR_SPACE_DEFAULT 1 +#define COLOR_SPACE_SYSTEM 2 +#define COLOR_SPACE_256 3 +#define COLOR_SPACE_RGB 4 + +/** + * Describes the color of a single character in the terminal. + */ +class CharacterColor +{ + friend class Character; + +public: + /** Constructs a new CharacterColor whoose color and color space are undefined. */ + CharacterColor() + : _colorSpace(COLOR_SPACE_UNDEFINED), + _u(0), + _v(0), + _w(0) + {} + + /** + * Constructs a new CharacterColor using the specified @p colorSpace and with + * color value @p co + * + * The meaning of @p co depends on the @p colorSpace used. + * + * TODO : Document how @p co relates to @p colorSpace + * + * TODO : Add documentation about available color spaces. + */ + CharacterColor(quint8 colorSpace, int co) + : _colorSpace(colorSpace), + _u(0), + _v(0), + _w(0) + { + switch (colorSpace) + { + case COLOR_SPACE_DEFAULT: + _u = co & 1; + break; + case COLOR_SPACE_SYSTEM: + _u = co & 7; + _v = (co >> 3) & 1; + break; + case COLOR_SPACE_256: + _u = co & 255; + break; + case COLOR_SPACE_RGB: + _u = co >> 16; + _v = co >> 8; + _w = co; + break; + default: + _colorSpace = COLOR_SPACE_UNDEFINED; + } + } + + /** + * Returns true if this character color entry is valid. + */ + bool isValid() + { + return _colorSpace != COLOR_SPACE_UNDEFINED; + } + + /** + * Toggles the value of this color between a normal system color and the corresponding intensive + * system color. + * + * This is only applicable if the color is using the COLOR_SPACE_DEFAULT or COLOR_SPACE_SYSTEM + * color spaces. + */ + void toggleIntensive(); + + /** + * Returns the color within the specified color @p palette + * + * The @p palette is only used if this color is one of the 16 system colors, otherwise + * it is ignored. + */ + QColor color(const ColorEntry* palette) const; + + /** + * Compares two colors and returns true if they represent the same color value and + * use the same color space. + */ + friend bool operator == (const CharacterColor& a, const CharacterColor& b); + /** + * Compares two colors and returns true if they represent different color values + * or use different color spaces. + */ + friend bool operator != (const CharacterColor& a, const CharacterColor& b); + +private: + quint8 _colorSpace; + + // bytes storing the character color + quint8 _u; + quint8 _v; + quint8 _w; +}; + +inline bool operator == (const CharacterColor& a, const CharacterColor& b) +{ + return a._colorSpace == b._colorSpace && + a._u == b._u && + a._v == b._v && + a._w == b._w; +} +inline bool operator != (const CharacterColor& a, const CharacterColor& b) +{ + return !operator==(a,b); +} + +inline const QColor color256(quint8 u, const ColorEntry* base) +{ + // 0.. 16: system colors + if (u < 8) return base[u+2 ].color; u -= 8; + if (u < 8) return base[u+2+BASE_COLORS].color; u -= 8; + + // 16..231: 6x6x6 rgb color cube + if (u < 216) return QColor(((u/36)%6) ? (40*((u/36)%6)+55) : 0, + ((u/ 6)%6) ? (40*((u/ 6)%6)+55) : 0, + ((u/ 1)%6) ? (40*((u/ 1)%6)+55) : 0); u -= 216; + + // 232..255: gray, leaving out black and white + int gray = u*10+8; return QColor(gray,gray,gray); +} + +inline QColor CharacterColor::color(const ColorEntry* base) const +{ + switch (_colorSpace) + { + case COLOR_SPACE_DEFAULT: return base[_u+0+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_SYSTEM: return base[_u+2+(_v?BASE_COLORS:0)].color; + case COLOR_SPACE_256: return color256(_u,base); + case COLOR_SPACE_RGB: return QColor(_u,_v,_w); + case COLOR_SPACE_UNDEFINED: return QColor(); + } + + Q_ASSERT(false); // invalid color space + + return QColor(); +} + +inline void CharacterColor::toggleIntensive() +{ + if (_colorSpace == COLOR_SPACE_SYSTEM || _colorSpace == COLOR_SPACE_DEFAULT) + { + _v = !_v; + } +} + + +} + +#endif // CHARACTERCOLOR_H + diff --git a/lib/ColorScheme.cpp b/lib/ColorScheme.cpp new file mode 100644 index 0000000..fc17cad --- /dev/null +++ b/lib/ColorScheme.cpp @@ -0,0 +1,788 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +// Own +#include "ColorScheme.h" +#include "tools.h" + +// Qt +#include +#include +#include +#include +#include +#include + + +// KDE +//#include +//#include +//#include +//#include +//#include +//#include + +using namespace Konsole; + +const ColorEntry ColorScheme::defaultTable[TABLE_COLORS] = + // The following are almost IBM standard color codes, with some slight + // gamma correction for the dim colors to compensate for bright X screens. + // It contains the 8 ansiterm/xterm colors in 2 intensities. +{ + ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry( +QColor(0xFF,0xFF,0xFF), 1), // Dfore, Dback + ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry( +QColor(0xB2,0x18,0x18), 0), // Black, Red + ColorEntry( QColor(0x18,0xB2,0x18), 0), ColorEntry( +QColor(0xB2,0x68,0x18), 0), // Green, Yellow + ColorEntry( QColor(0x18,0x18,0xB2), 0), ColorEntry( +QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta + ColorEntry( QColor(0x18,0xB2,0xB2), 0), ColorEntry( +QColor(0xB2,0xB2,0xB2), 0), // Cyan, White + // intensive + ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry( +QColor(0xFF,0xFF,0xFF), 1), + ColorEntry( QColor(0x68,0x68,0x68), 0), ColorEntry( +QColor(0xFF,0x54,0x54), 0), + ColorEntry( QColor(0x54,0xFF,0x54), 0), ColorEntry( +QColor(0xFF,0xFF,0x54), 0), + ColorEntry( QColor(0x54,0x54,0xFF), 0), ColorEntry( +QColor(0xFF,0x54,0xFF), 0), + ColorEntry( QColor(0x54,0xFF,0xFF), 0), ColorEntry( +QColor(0xFF,0xFF,0xFF), 0) +}; + +const char* const ColorScheme::colorNames[TABLE_COLORS] = +{ + "Foreground", + "Background", + "Color0", + "Color1", + "Color2", + "Color3", + "Color4", + "Color5", + "Color6", + "Color7", + "ForegroundIntense", + "BackgroundIntense", + "Color0Intense", + "Color1Intense", + "Color2Intense", + "Color3Intense", + "Color4Intense", + "Color5Intense", + "Color6Intense", + "Color7Intense" +}; +// dummy silently comment out the tr_NOOP +#define tr_NOOP +const char* const ColorScheme::translatedColorNames[TABLE_COLORS] = +{ + tr_NOOP("Foreground"), + tr_NOOP("Background"), + tr_NOOP("Color 1"), + tr_NOOP("Color 2"), + tr_NOOP("Color 3"), + tr_NOOP("Color 4"), + tr_NOOP("Color 5"), + tr_NOOP("Color 6"), + tr_NOOP("Color 7"), + tr_NOOP("Color 8"), + tr_NOOP("Foreground (Intense)"), + tr_NOOP("Background (Intense)"), + tr_NOOP("Color 1 (Intense)"), + tr_NOOP("Color 2 (Intense)"), + tr_NOOP("Color 3 (Intense)"), + tr_NOOP("Color 4 (Intense)"), + tr_NOOP("Color 5 (Intense)"), + tr_NOOP("Color 6 (Intense)"), + tr_NOOP("Color 7 (Intense)"), + tr_NOOP("Color 8 (Intense)") +}; + +ColorScheme::ColorScheme() +{ + _table = 0; + _randomTable = 0; + _opacity = 1.0; +} +ColorScheme::ColorScheme(const ColorScheme& other) + : _opacity(other._opacity) + ,_table(0) + ,_randomTable(0) +{ + setName(other.name()); + setDescription(other.description()); + + if ( other._table != 0 ) + { + for ( int i = 0 ; i < TABLE_COLORS ; i++ ) + setColorTableEntry(i,other._table[i]); + } + + if ( other._randomTable != 0 ) + { + for ( int i = 0 ; i < TABLE_COLORS ; i++ ) + { + const RandomizationRange& range = other._randomTable[i]; + setRandomizationRange(i,range.hue,range.saturation,range.value); + } + } +} +ColorScheme::~ColorScheme() +{ + delete[] _table; + delete[] _randomTable; +} + +void ColorScheme::setDescription(const QString& description) { _description = description; } +QString ColorScheme::description() const { return _description; } + +void ColorScheme::setName(const QString& name) { _name = name; } +QString ColorScheme::name() const { return _name; } + +void ColorScheme::setColorTableEntry(int index , const ColorEntry& entry) +{ + Q_ASSERT( index >= 0 && index < TABLE_COLORS ); + + if ( !_table ) + { + _table = new ColorEntry[TABLE_COLORS]; + + for (int i=0;i= 0 && index < TABLE_COLORS ); + + if ( randomSeed != 0 ) + qsrand(randomSeed); + + ColorEntry entry = colorTable()[index]; + + if ( randomSeed != 0 && + _randomTable != 0 && + !_randomTable[index].isNull() ) + { + const RandomizationRange& range = _randomTable[index]; + + + int hueDifference = range.hue ? (qrand() % range.hue) - range.hue/2 : 0; + int saturationDifference = range.saturation ? (qrand() % range.saturation) - range.saturation/2 : 0; + int valueDifference = range.value ? (qrand() % range.value) - range.value/2 : 0; + + QColor& color = entry.color; + + int newHue = qAbs( (color.hue() + hueDifference) % MAX_HUE ); + int newValue = qMin( qAbs(color.value() + valueDifference) , 255 ); + int newSaturation = qMin( qAbs(color.saturation() + saturationDifference) , 255 ); + + color.setHsv(newHue,newSaturation,newValue); + } + + return entry; +} +void ColorScheme::getColorTable(ColorEntry* table , uint randomSeed) const +{ + for ( int i = 0 ; i < TABLE_COLORS ; i++ ) + table[i] = colorEntry(i,randomSeed); +} +bool ColorScheme::randomizedBackgroundColor() const +{ + return _randomTable == 0 ? false : !_randomTable[1].isNull(); +} +void ColorScheme::setRandomizedBackgroundColor(bool randomize) +{ + // the hue of the background colour is allowed to be randomly + // adjusted as much as possible. + // + // the value and saturation are left alone to maintain read-ability + if ( randomize ) + { + setRandomizationRange( 1 /* background color index */ , MAX_HUE , 255 , 0 ); + } + else + { + if ( _randomTable ) + setRandomizationRange( 1 /* background color index */ , 0 , 0 , 0 ); + } +} + +void ColorScheme::setRandomizationRange( int index , quint16 hue , quint8 saturation , + quint8 value ) +{ + Q_ASSERT( hue <= MAX_HUE ); + Q_ASSERT( index >= 0 && index < TABLE_COLORS ); + + if ( _randomTable == 0 ) + _randomTable = new RandomizationRange[TABLE_COLORS]; + + _randomTable[index].hue = hue; + _randomTable[index].value = value; + _randomTable[index].saturation = saturation; +} + +const ColorEntry* ColorScheme::colorTable() const +{ + if ( _table ) + return _table; + else + return defaultTable; +} +QColor ColorScheme::foregroundColor() const +{ + return colorTable()[0].color; +} +QColor ColorScheme::backgroundColor() const +{ + return colorTable()[1].color; +} +bool ColorScheme::hasDarkBackground() const +{ + // value can range from 0 - 255, with larger values indicating higher brightness. + // so 127 is in the middle, anything less is deemed 'dark' + return backgroundColor().value() < 127; +} +void ColorScheme::setOpacity(qreal opacity) { _opacity = opacity; } +qreal ColorScheme::opacity() const { return _opacity; } + +void ColorScheme::read(const QString & fileName) +{ + QSettings s(fileName, QSettings::IniFormat); + s.beginGroup("General"); + + _description = s.value("Description", QObject::tr("Un-named Color Scheme")).toString(); + _opacity = s.value("Opacity",qreal(1.0)).toDouble(); + s.endGroup(); + + for (int i=0 ; i < TABLE_COLORS ; i++) + { + readColorEntry(&s, i); + } +} +#if 0 +// implemented upstream - user apps +void ColorScheme::read(KConfig& config) +{ + KConfigGroup configGroup = config.group("General"); + + QString description = configGroup.readEntry("Description", QObject::tr("Un-named Color Scheme")); + + _description = tr(description.toUtf8()); + _opacity = configGroup.readEntry("Opacity",qreal(1.0)); + + for (int i=0 ; i < TABLE_COLORS ; i++) + { + readColorEntry(config,i); + } +} +void ColorScheme::write(KConfig& config) const +{ + KConfigGroup configGroup = config.group("General"); + + configGroup.writeEntry("Description",_description); + configGroup.writeEntry("Opacity",_opacity); + + for (int i=0 ; i < TABLE_COLORS ; i++) + { + RandomizationRange random = _randomTable != 0 ? _randomTable[i] : RandomizationRange(); + writeColorEntry(config,colorNameForIndex(i),colorTable()[i],random); + } +} +#endif + +QString ColorScheme::colorNameForIndex(int index) +{ + Q_ASSERT( index >= 0 && index < TABLE_COLORS ); + + return QString(colorNames[index]); +} +QString ColorScheme::translatedColorNameForIndex(int index) +{ + Q_ASSERT( index >= 0 && index < TABLE_COLORS ); + + return translatedColorNames[index]; +} + +void ColorScheme::readColorEntry(QSettings * s , int index) +{ + s->beginGroup(colorNameForIndex(index)); + + ColorEntry entry; + + QStringList rgbList = s->value("Color", QStringList()).toStringList(); + if (rgbList.count() != 3) + { + Q_ASSERT(0); + } + int r, g, b; + r = rgbList[0].toInt(); + g = rgbList[1].toInt(); + b = rgbList[2].toInt(); + entry.color = QColor(r, g, b); + + entry.transparent = s->value("Transparent",false).toBool(); + + // Deprecated key from KDE 4.0 which set 'Bold' to true to force + // a color to be bold or false to use the current format + // + // TODO - Add a new tri-state key which allows for bold, normal or + // current format + if (s->contains("Bold")) + entry.fontWeight = s->value("Bold",false).toBool() ? ColorEntry::Bold : + ColorEntry::UseCurrentFormat; + + quint16 hue = s->value("MaxRandomHue",0).toInt(); + quint8 value = s->value("MaxRandomValue",0).toInt(); + quint8 saturation = s->value("MaxRandomSaturation",0).toInt(); + + setColorTableEntry( index , entry ); + + if ( hue != 0 || value != 0 || saturation != 0 ) + setRandomizationRange( index , hue , saturation , value ); + + s->endGroup(); +} +#if 0 +// implemented upstream - user apps +void ColorScheme::writeColorEntry(KConfig& config , const QString& colorName, const ColorEntry& entry , const RandomizationRange& random) const +{ + KConfigGroup configGroup(&config,colorName); + + configGroup.writeEntry("Color",entry.color); + configGroup.writeEntry("Transparency",(bool)entry.transparent); + if (entry.fontWeight != ColorEntry::UseCurrentFormat) + { + configGroup.writeEntry("Bold",entry.fontWeight == ColorEntry::Bold); + } + + // record randomization if this color has randomization or + // if one of the keys already exists + if ( !random.isNull() || configGroup.hasKey("MaxRandomHue") ) + { + configGroup.writeEntry("MaxRandomHue",(int)random.hue); + configGroup.writeEntry("MaxRandomValue",(int)random.value); + configGroup.writeEntry("MaxRandomSaturation",(int)random.saturation); + } +} +#endif + +// +// Work In Progress - A color scheme for use on KDE setups for users +// with visual disabilities which means that they may have trouble +// reading text with the supplied color schemes. +// +// This color scheme uses only the 'safe' colors defined by the +// KColorScheme class. +// +// A complication this introduces is that each color provided by +// KColorScheme is defined as a 'background' or 'foreground' color. +// Only foreground colors are allowed to be used to render text and +// only background colors are allowed to be used for backgrounds. +// +// The ColorEntry and TerminalDisplay classes do not currently +// support this restriction. +// +// Requirements: +// - A color scheme which uses only colors from the KColorScheme class +// - Ability to restrict which colors the TerminalDisplay widget +// uses as foreground and background color +// - Make use of KGlobalSettings::allowDefaultBackgroundImages() as +// a hint to determine whether this accessible color scheme should +// be used by default. +// +// +// -- Robert Knight 21/07/2007 +// +AccessibleColorScheme::AccessibleColorScheme() + : ColorScheme() +{ +#if 0 +// It's not finished in konsole and it breaks Qt4 compilation as well + // basic attributes + setName("accessible"); + setDescription(QObject::tr("Accessible Color Scheme")); + + // setup colors + const int ColorRoleCount = 8; + + const KColorScheme colorScheme(QPalette::Active); + + QBrush colors[ColorRoleCount] = + { + colorScheme.foreground( colorScheme.NormalText ), + colorScheme.background( colorScheme.NormalBackground ), + + colorScheme.foreground( colorScheme.InactiveText ), + colorScheme.foreground( colorScheme.ActiveText ), + colorScheme.foreground( colorScheme.LinkText ), + colorScheme.foreground( colorScheme.VisitedText ), + colorScheme.foreground( colorScheme.NegativeText ), + colorScheme.foreground( colorScheme.NeutralText ) + }; + + for ( int i = 0 ; i < TABLE_COLORS ; i++ ) + { + ColorEntry entry; + entry.color = colors[ i % ColorRoleCount ].color(); + + setColorTableEntry( i , entry ); + } +#endif +} + +KDE3ColorSchemeReader::KDE3ColorSchemeReader( QIODevice* device ) : + _device(device) +{ +} +ColorScheme* KDE3ColorSchemeReader::read() +{ + Q_ASSERT( _device->openMode() == QIODevice::ReadOnly || + _device->openMode() == QIODevice::ReadWrite ); + + ColorScheme* scheme = new ColorScheme(); + + QRegExp comment("#.*$"); + while ( !_device->atEnd() ) + { + QString line(_device->readLine()); + line.remove(comment); + line = line.simplified(); + + if ( line.isEmpty() ) + continue; + + if ( line.startsWith(QLatin1String("color")) ) + { + if (!readColorLine(line,scheme)) + qDebug() << "Failed to read KDE 3 color scheme line" << line; + } + else if ( line.startsWith(QLatin1String("title")) ) + { + if (!readTitleLine(line,scheme)) + qDebug() << "Failed to read KDE 3 color scheme title line" << line; + } + else + { + qDebug() << "KDE 3 color scheme contains an unsupported feature, '" << + line << "'"; + } + } + + return scheme; +} +bool KDE3ColorSchemeReader::readColorLine(const QString& line,ColorScheme* scheme) +{ + QStringList list = line.split(QChar(' ')); + + if (list.count() != 7) + return false; + if (list.first() != "color") + return false; + + int index = list[1].toInt(); + int red = list[2].toInt(); + int green = list[3].toInt(); + int blue = list[4].toInt(); + int transparent = list[5].toInt(); + int bold = list[6].toInt(); + + const int MAX_COLOR_VALUE = 255; + + if( (index < 0 || index >= TABLE_COLORS ) + || (red < 0 || red > MAX_COLOR_VALUE ) + || (blue < 0 || blue > MAX_COLOR_VALUE ) + || (green < 0 || green > MAX_COLOR_VALUE ) + || (transparent != 0 && transparent != 1 ) + || (bold != 0 && bold != 1) ) + return false; + + ColorEntry entry; + entry.color = QColor(red,green,blue); + entry.transparent = ( transparent != 0 ); + entry.fontWeight = ( bold != 0 ) ? ColorEntry::Bold : ColorEntry::UseCurrentFormat; + + scheme->setColorTableEntry(index,entry); + return true; +} +bool KDE3ColorSchemeReader::readTitleLine(const QString& line,ColorScheme* scheme) +{ + if( !line.startsWith(QLatin1String("title")) ) + return false; + + int spacePos = line.indexOf(' '); + if( spacePos == -1 ) + return false; + + QString description = line.mid(spacePos+1); + + scheme->setDescription(description.toUtf8()); + return true; +} +ColorSchemeManager::ColorSchemeManager() + : _haveLoadedAll(false) +{ +} +ColorSchemeManager::~ColorSchemeManager() +{ + QHashIterator iter(_colorSchemes); + while (iter.hasNext()) + { + iter.next(); + delete iter.value(); + } +} +void ColorSchemeManager::loadAllColorSchemes() +{ + qDebug() << "loadAllColorSchemes"; + int success = 0; + int failed = 0; + + QList nativeColorSchemes = listColorSchemes(); + + QListIterator nativeIter(nativeColorSchemes); + while ( nativeIter.hasNext() ) + { + if ( loadColorScheme( nativeIter.next() ) ) + success++; + else + failed++; + } + + QList kde3ColorSchemes = listKDE3ColorSchemes(); + QListIterator kde3Iter(kde3ColorSchemes); + while ( kde3Iter.hasNext() ) + { + if ( loadKDE3ColorScheme( kde3Iter.next() ) ) + success++; + else + failed++; + } + + if ( failed > 0 ) + qDebug() << "failed to load " << failed << " color schemes."; + + _haveLoadedAll = true; +} +QList ColorSchemeManager::allColorSchemes() +{ + if ( !_haveLoadedAll ) + { + loadAllColorSchemes(); + } + + return _colorSchemes.values(); +} +bool ColorSchemeManager::loadKDE3ColorScheme(const QString& filePath) +{ + QFile file(filePath); + if (!filePath.endsWith(QLatin1String(".schema")) || !file.open(QIODevice::ReadOnly)) + return false; + + KDE3ColorSchemeReader reader(&file); + ColorScheme* scheme = reader.read(); + scheme->setName(QFileInfo(file).baseName()); + file.close(); + + if (scheme->name().isEmpty()) + { + qDebug() << "color scheme name is not valid."; + delete scheme; + return false; + } + + QFileInfo info(filePath); + + if ( !_colorSchemes.contains(info.baseName()) ) + _colorSchemes.insert(scheme->name(),scheme); + else + { + qDebug() << "color scheme with name" << scheme->name() << "has already been" << + "found, ignoring."; + delete scheme; + } + + return true; +} +#if 0 +void ColorSchemeManager::addColorScheme(ColorScheme* scheme) +{ + _colorSchemes.insert(scheme->name(),scheme); + + // save changes to disk + QString path = KGlobal::dirs()->saveLocation("data","konsole/") + scheme->name() + ".colorscheme"; + KConfig config(path , KConfig::NoGlobals); + + scheme->write(config); +} +#endif + +bool ColorSchemeManager::loadCustomColorScheme(const QString& path) +{ + if (path.endsWith(QLatin1String(".colorscheme"))) + return loadColorScheme(path); + else if (path.endsWith(QLatin1String(".schema"))) + return loadKDE3ColorScheme(path); + else + return false; +} + +bool ColorSchemeManager::loadColorScheme(const QString& filePath) +{ + if ( !filePath.endsWith(QLatin1String(".colorscheme")) || !QFile::exists(filePath) ) + return false; + + QFileInfo info(filePath); + + const QString& schemeName = info.baseName(); + + ColorScheme* scheme = new ColorScheme(); + scheme->setName(schemeName); + scheme->read(filePath); + + if (scheme->name().isEmpty()) + { + qDebug() << "Color scheme in" << filePath << "does not have a valid name and was not loaded."; + delete scheme; + return false; + } + + if ( !_colorSchemes.contains(schemeName) ) + { + _colorSchemes.insert(schemeName,scheme); + } + else + { + qDebug() << "color scheme with name" << schemeName << "has already been" << + "found, ignoring."; + + delete scheme; + } + + return true; +} +QList ColorSchemeManager::listKDE3ColorSchemes() +{ + QString dname(get_color_schemes_dir()); + QDir dir(dname); + QStringList filters; + filters << "*.schema"; + dir.setNameFilters(filters); + QStringList list = dir.entryList(filters); + QStringList ret; + foreach(QString i, list) + ret << dname + "/" + i; + return ret; + //return KGlobal::dirs()->findAllResources("data", + // "konsole/*.schema", + // KStandardDirs::NoDuplicates); + // +} +QList ColorSchemeManager::listColorSchemes() +{ + QString dname(get_color_schemes_dir()); + QDir dir(dname); + QStringList filters; + filters << "*.colorscheme"; + dir.setNameFilters(filters); + QStringList list = dir.entryList(filters); + QStringList ret; + foreach(QString i, list) + ret << dname + "/" + i; + return ret; +// return KGlobal::dirs()->findAllResources("data", +// "konsole/*.colorscheme", +// KStandardDirs::NoDuplicates); +} +const ColorScheme ColorSchemeManager::_defaultColorScheme; +const ColorScheme* ColorSchemeManager::defaultColorScheme() const +{ + return &_defaultColorScheme; +} +bool ColorSchemeManager::deleteColorScheme(const QString& name) +{ + Q_ASSERT( _colorSchemes.contains(name) ); + + // lookup the path and delete + QString path = findColorSchemePath(name); + if ( QFile::remove(path) ) + { + _colorSchemes.remove(name); + return true; + } + else + { + qDebug() << "Failed to remove color scheme -" << path; + return false; + } +} +QString ColorSchemeManager::findColorSchemePath(const QString& name) const +{ +// QString path = KStandardDirs::locate("data","konsole/"+name+".colorscheme"); + QString path(get_color_schemes_dir() + "/"+ name + ".colorscheme"); + if ( !path.isEmpty() ) + return path; + + //path = KStandardDirs::locate("data","konsole/"+name+".schema"); + path = get_color_schemes_dir() + "/"+ name + ".schema"; + + return path; +} +const ColorScheme* ColorSchemeManager::findColorScheme(const QString& name) +{ + if ( name.isEmpty() ) + return defaultColorScheme(); + + if ( _colorSchemes.contains(name) ) + return _colorSchemes[name]; + else + { + // look for this color scheme + QString path = findColorSchemePath(name); + if ( !path.isEmpty() && loadColorScheme(path) ) + { + return findColorScheme(name); + } + else + { + if (!path.isEmpty() && loadKDE3ColorScheme(path)) + return findColorScheme(name); + } + + qDebug() << "Could not find color scheme - " << name; + + return 0; + } +} + +ColorSchemeManager* ColorSchemeManager::theColorSchemeManager = 0; +//K_GLOBAL_STATIC( ColorSchemeManager , theColorSchemeManager ) +ColorSchemeManager* ColorSchemeManager::instance() +{ + if (! theColorSchemeManager) + theColorSchemeManager = new ColorSchemeManager(); + return theColorSchemeManager; +} diff --git a/lib/ColorScheme.h b/lib/ColorScheme.h new file mode 100644 index 0000000..3b5c211 --- /dev/null +++ b/lib/ColorScheme.h @@ -0,0 +1,359 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +#ifndef COLORSCHEME_H +#define COLORSCHEME_H + +// Qt +#include +#include +#include +#include +#include +#include + +// Konsole +#include "CharacterColor.h" + +class QIODevice; +//class KConfig; + +namespace Konsole +{ + +/** + * Represents a color scheme for a terminal display. + * + * The color scheme includes the palette of colors used to draw the text and character backgrounds + * in the display and the opacity level of the display background. + */ +class ColorScheme +{ +public: + /** + * Constructs a new color scheme which is initialised to the default color set + * for Konsole. + */ + ColorScheme(); + ColorScheme(const ColorScheme& other); + ~ColorScheme(); + + /** Sets the descriptive name of the color scheme. */ + void setDescription(const QString& description); + /** Returns the descriptive name of the color scheme. */ + QString description() const; + + /** Sets the name of the color scheme */ + void setName(const QString& name); + /** Returns the name of the color scheme */ + QString name() const; + +#if 0 +// Implemented upstream - in user apps + /** Reads the color scheme from the specified configuration source */ + void read(KConfig& config); + /** Writes the color scheme to the specified configuration source */ + void write(KConfig& config) const; +#endif + void read(const QString & filename); + + /** Sets a single entry within the color palette. */ + void setColorTableEntry(int index , const ColorEntry& entry); + + /** + * Copies the color entries which form the palette for this color scheme + * into @p table. @p table should be an array with TABLE_COLORS entries. + * + * @param table Array into which the color entries for this color scheme + * are copied. + * @param randomSeed Color schemes may allow certain colors in their + * palette to be randomized. The seed is used to pick the random color. + */ + void getColorTable(ColorEntry* table, uint randomSeed = 0) const; + + /** + * Retrieves a single color entry from the table. + * + * See getColorTable() + */ + ColorEntry colorEntry(int index , uint randomSeed = 0) const; + + /** + * Convenience method. Returns the + * foreground color for this scheme, + * this is the primary color used to draw the + * text in this scheme. + */ + QColor foregroundColor() const; + /** + * Convenience method. Returns the background color for + * this scheme, this is the primary color used to + * draw the terminal background in this scheme. + */ + QColor backgroundColor() const; + + /** + * Returns true if this color scheme has a dark background. + * The background color is said to be dark if it has a value of less than 127 + * in the HSV color space. + */ + bool hasDarkBackground() const; + + /** + * Sets the opacity level of the display background. @p opacity ranges + * between 0 (completely transparent background) and 1 (completely + * opaque background). + * + * Defaults to 1. + * + * TODO: More documentation + */ + void setOpacity(qreal opacity); + /** + * Returns the opacity level for this color scheme, see setOpacity() + * TODO: More documentation + */ + qreal opacity() const; + + /** + * Enables randomization of the background color. This will cause + * the palette returned by getColorTable() and colorEntry() to + * be adjusted depending on the value of the random seed argument + * to them. + */ + void setRandomizedBackgroundColor(bool randomize); + + /** Returns true if the background color is randomized. */ + bool randomizedBackgroundColor() const; + + static QString colorNameForIndex(int index); + static QString translatedColorNameForIndex(int index); + +private: + // specifies how much a particular color can be randomized by + class RandomizationRange + { + public: + RandomizationRange() : hue(0) , saturation(0) , value(0) {} + + bool isNull() const + { + return ( hue == 0 && saturation == 0 && value == 0 ); + } + + quint16 hue; + quint8 saturation; + quint8 value; + }; + + // returns the active color table. if none has been set specifically, + // this is the default color table. + const ColorEntry* colorTable() const; + +#if 0 +// implemented upstream - user apps + // reads a single colour entry from a KConfig source + // and sets the palette entry at 'index' to the entry read. + void readColorEntry(KConfig& config , int index); + // writes a single colour entry to a KConfig source + void writeColorEntry(KConfig& config , const QString& colorName, const ColorEntry& entry,const RandomizationRange& range) const; +#endif + void readColorEntry(QSettings *s, int index); + + // sets the amount of randomization allowed for a particular color + // in the palette. creates the randomization table if + // it does not already exist + void setRandomizationRange( int index , quint16 hue , quint8 saturation , quint8 value ); + + QString _description; + QString _name; + qreal _opacity; + ColorEntry* _table; // pointer to custom color table or 0 if the default + // color scheme is being used + + + static const quint16 MAX_HUE = 340; + + RandomizationRange* _randomTable; // pointer to randomization table or 0 + // if no colors in the color scheme support + // randomization + + static const char* const colorNames[TABLE_COLORS]; + static const char* const translatedColorNames[TABLE_COLORS]; + + static const ColorEntry defaultTable[]; // table of default color entries +}; + +/** + * A color scheme which uses colors from the standard KDE color palette. + * + * This is designed primarily for the benefit of users who are using specially + * designed colors. + * + * TODO Implement and make it the default on systems with specialized KDE + * color schemes. + */ +class AccessibleColorScheme : public ColorScheme +{ +public: + AccessibleColorScheme(); +}; + +/** + * Reads a color scheme stored in the .schema format used in the KDE 3 incarnation + * of Konsole + * + * Only the basic essentials ( title and color palette entries ) are currently + * supported. Additional options such as background image and background + * blend colors are ignored. + */ +class KDE3ColorSchemeReader +{ +public: + /** + * Constructs a new reader which reads from the specified device. + * The device should be open in read-only mode. + */ + KDE3ColorSchemeReader( QIODevice* device ); + + /** + * Reads and parses the contents of the .schema file from the input + * device and returns the ColorScheme defined within it. + * + * Returns a null pointer if an error occurs whilst parsing + * the contents of the file. + */ + ColorScheme* read(); + +private: + // reads a line from the file specifying a colour palette entry + // format is: color [index] [red] [green] [blue] [transparent] [bold] + bool readColorLine(const QString& line , ColorScheme* scheme); + bool readTitleLine(const QString& line , ColorScheme* scheme); + + QIODevice* _device; +}; + +/** + * Manages the color schemes available for use by terminal displays. + * See ColorScheme + */ +class ColorSchemeManager +{ +public: + + /** + * Constructs a new ColorSchemeManager and loads the list + * of available color schemes. + * + * The color schemes themselves are not loaded until they are first + * requested via a call to findColorScheme() + */ + ColorSchemeManager(); + /** + * Destroys the ColorSchemeManager and saves any modified color schemes to disk. + */ + ~ColorSchemeManager(); + + /** + * Returns the default color scheme for Konsole + */ + const ColorScheme* defaultColorScheme() const; + + /** + * Returns the color scheme with the given name or 0 if no + * scheme with that name exists. If @p name is empty, the + * default color scheme is returned. + * + * The first time that a color scheme with a particular name is + * requested, the configuration information is loaded from disk. + */ + const ColorScheme* findColorScheme(const QString& name); + +#if 0 + /** + * Adds a new color scheme to the manager. If @p scheme has the same name as + * an existing color scheme, it replaces the existing scheme. + * + * TODO - Ensure the old color scheme gets deleted + */ + void addColorScheme(ColorScheme* scheme); +#endif + /** + * Deletes a color scheme. Returns true on successful deletion or false otherwise. + */ + bool deleteColorScheme(const QString& name); + + /** + * Returns a list of the all the available color schemes. + * This may be slow when first called because all of the color + * scheme resources on disk must be located, read and parsed. + * + * Subsequent calls will be inexpensive. + */ + QList allColorSchemes(); + + /** Returns the global color scheme manager instance. */ + static ColorSchemeManager* instance(); + + /** @brief Loads a custom color scheme under given \em path. + * + * The \em path may refer to either KDE 4 .colorscheme or KDE 3 + * .schema file + * + * The loaded color scheme is available under the name equal to + * the base name of the \em path via the allColorSchemes() and + * findColorScheme() methods after this call if loaded successfully. + * + * @param[in] path The path to KDE 4 .colorscheme or KDE 3 .schema. + * @return Whether the color scheme is loaded successfully. + */ + bool loadCustomColorScheme(const QString& path); +private: + // loads a color scheme from a KDE 4+ .colorscheme file + bool loadColorScheme(const QString& path); + // loads a color scheme from a KDE 3 .schema file + bool loadKDE3ColorScheme(const QString& path); + // returns a list of paths of color schemes in the KDE 4+ .colorscheme file format + QList listColorSchemes(); + // returns a list of paths of color schemes in the .schema file format + // used in KDE 3 + QList listKDE3ColorSchemes(); + // loads all of the color schemes + void loadAllColorSchemes(); + // finds the path of a color scheme + QString findColorSchemePath(const QString& name) const; + + QHash _colorSchemes; + QSet _modifiedSchemes; + + bool _haveLoadedAll; + + static const ColorScheme _defaultColorScheme; + + static ColorSchemeManager * theColorSchemeManager; +}; + +} + +Q_DECLARE_METATYPE(const Konsole::ColorScheme*) + +#endif //COLORSCHEME_H diff --git a/lib/ColorTables.h b/lib/ColorTables.h new file mode 100644 index 0000000..57b0bd1 --- /dev/null +++ b/lib/ColorTables.h @@ -0,0 +1,55 @@ +#ifndef _COLOR_TABLE_H +#define _COLOR_TABLE_H + +#include "CharacterColor.h" + +//using namespace Konsole; +#if 0 +static const ColorEntry whiteonblack_color_table[TABLE_COLORS] = { + // normal + ColorEntry(QColor(0xFF,0xFF,0xFF), false ), ColorEntry( QColor(0x00,0x00,0x00), true ), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), false ), ColorEntry( QColor(0xB2,0x18,0x18), false ), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), false ), ColorEntry( QColor(0xB2,0x68,0x18), false ), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), false ), ColorEntry( QColor(0xB2,0x18,0xB2), false ), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), false ), ColorEntry( QColor(0xB2,0xB2,0xB2), false ), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), false ), ColorEntry( QColor(0xFF,0xFF,0xFF), true ), + ColorEntry(QColor(0x68,0x68,0x68), false ), ColorEntry( QColor(0xFF,0x54,0x54), false ), + ColorEntry(QColor(0x54,0xFF,0x54), false ), ColorEntry( QColor(0xFF,0xFF,0x54), false ), + ColorEntry(QColor(0x54,0x54,0xFF), false ), ColorEntry( QColor(0xFF,0x54,0xFF), false ), + ColorEntry(QColor(0x54,0xFF,0xFF), false ), ColorEntry( QColor(0xFF,0xFF,0xFF), false ) +}; + +static const ColorEntry greenonblack_color_table[TABLE_COLORS] = { + ColorEntry(QColor( 24, 240, 24), false), ColorEntry(QColor( 0, 0, 0), true), + ColorEntry(QColor( 0, 0, 0), false), ColorEntry(QColor( 178, 24, 24), false), + ColorEntry(QColor( 24, 178, 24), false), ColorEntry(QColor( 178, 104, 24), false), + ColorEntry(QColor( 24, 24, 178), false), ColorEntry(QColor( 178, 24, 178), false), + ColorEntry(QColor( 24, 178, 178), false), ColorEntry(QColor( 178, 178, 178), false), + // intensive colors + ColorEntry(QColor( 24, 240, 24), false ), ColorEntry(QColor( 0, 0, 0), true ), + ColorEntry(QColor( 104, 104, 104), false ), ColorEntry(QColor( 255, 84, 84), false ), + ColorEntry(QColor( 84, 255, 84), false ), ColorEntry(QColor( 255, 255, 84), false ), + ColorEntry(QColor( 84, 84, 255), false ), ColorEntry(QColor( 255, 84, 255), false ), + ColorEntry(QColor( 84, 255, 255), false ), ColorEntry(QColor( 255, 255, 255), false ) +}; + +static const ColorEntry blackonlightyellow_color_table[TABLE_COLORS] = { + ColorEntry(QColor( 0, 0, 0), false), ColorEntry(QColor( 255, 255, 221), true), + ColorEntry(QColor( 0, 0, 0), false), ColorEntry(QColor( 178, 24, 24), false), + ColorEntry(QColor( 24, 178, 24), false), ColorEntry(QColor( 178, 104, 24), false), + ColorEntry(QColor( 24, 24, 178), false), ColorEntry(QColor( 178, 24, 178), false), + ColorEntry(QColor( 24, 178, 178), false), ColorEntry(QColor( 178, 178, 178), false), + ColorEntry(QColor( 0, 0, 0), false), ColorEntry(QColor( 255, 255, 221), true), + ColorEntry(QColor(104, 104, 104), false), ColorEntry(QColor( 255, 84, 84), false), + ColorEntry(QColor( 84, 255, 84), false), ColorEntry(QColor( 255, 255, 84), false), + ColorEntry(QColor( 84, 84, 255), false), ColorEntry(QColor( 255, 84, 255), false), + ColorEntry(QColor( 84, 255, 255), false), ColorEntry(QColor( 255, 255, 255), false) +}; + + +#endif + + +#endif + diff --git a/lib/DefaultTranslatorText.h b/lib/DefaultTranslatorText.h new file mode 100644 index 0000000..e47417c --- /dev/null +++ b/lib/DefaultTranslatorText.h @@ -0,0 +1,2 @@ +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\" \0" diff --git a/lib/Emulation.cpp b/lib/Emulation.cpp new file mode 100644 index 0000000..8cc32ef --- /dev/null +++ b/lib/Emulation.cpp @@ -0,0 +1,460 @@ +/* + Copyright 2007-2008 Robert Knight + Copyright 1997,1998 by Lars Doelle + Copyright 1996 by Matthias Ettrich + + 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. +*/ + +// Own +#include "Emulation.h" + +// System +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +#include + +// KDE +//#include + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" +#include "TerminalCharacterDecoder.h" +#include "ScreenWindow.h" + +using namespace Konsole; + +Emulation::Emulation() : + _currentScreen(0), + _codec(0), + _decoder(0), + _keyTranslator(0), + _usesMouse(false) +{ + // create screens with a default size + _screen[0] = new Screen(40,80); + _screen[1] = new Screen(40,80); + _currentScreen = _screen[0]; + + QObject::connect(&_bulkTimer1, SIGNAL(timeout()), this, SLOT(showBulk()) ); + QObject::connect(&_bulkTimer2, SIGNAL(timeout()), this, SLOT(showBulk()) ); + + // listen for mouse status changes + connect( this , SIGNAL(programUsesMouseChanged(bool)) , + SLOT(usesMouseChanged(bool)) ); +} + +bool Emulation::programUsesMouse() const +{ + return _usesMouse; +} + +void Emulation::usesMouseChanged(bool usesMouse) +{ + _usesMouse = usesMouse; +} + +ScreenWindow* Emulation::createWindow() +{ + ScreenWindow* window = new ScreenWindow(); + window->setScreen(_currentScreen); + _windows << window; + + connect(window , SIGNAL(selectionChanged()), + this , SLOT(bufferedUpdate())); + + connect(this , SIGNAL(outputChanged()), + window , SLOT(notifyOutputChanged()) ); + return window; +} + +Emulation::~Emulation() +{ + QListIterator windowIter(_windows); + + while (windowIter.hasNext()) + { + delete windowIter.next(); + } + + delete _screen[0]; + delete _screen[1]; + delete _decoder; +} + +void Emulation::setScreen(int n) +{ + Screen *old = _currentScreen; + _currentScreen = _screen[n & 1]; + if (_currentScreen != old) + { + // tell all windows onto this emulation to switch to the newly active screen + foreach(ScreenWindow* window,_windows) + window->setScreen(_currentScreen); + } +} + +void Emulation::clearHistory() +{ + _screen[0]->setScroll( _screen[0]->getScroll() , false ); +} +void Emulation::setHistory(const HistoryType& t) +{ + _screen[0]->setScroll(t); + + showBulk(); +} + +const HistoryType& Emulation::history() const +{ + return _screen[0]->getScroll(); +} + +void Emulation::setCodec(const QTextCodec * qtc) +{ + if (qtc) + _codec = qtc; + else + setCodec(LocaleCodec); + + delete _decoder; + _decoder = _codec->makeDecoder(); + + emit useUtf8Request(utf8()); +} + +void Emulation::setCodec(EmulationCodec codec) +{ + if ( codec == Utf8Codec ) + setCodec( QTextCodec::codecForName("utf8") ); + else if ( codec == LocaleCodec ) + setCodec( QTextCodec::codecForLocale() ); +} + +void Emulation::setKeyBindings(const QString& name) +{ + _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name); + if (!_keyTranslator) + { + _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator(); + } +} + +QString Emulation::keyBindings() const +{ + return _keyTranslator->name(); +} + +void Emulation::receiveChar(int c) +// process application unicode input to terminal +// this is a trivial scanner +{ + c &= 0xff; + switch (c) + { + case '\b' : _currentScreen->backspace(); break; + case '\t' : _currentScreen->tab(); break; + case '\n' : _currentScreen->newLine(); break; + case '\r' : _currentScreen->toStartOfLine(); break; + case 0x07 : emit stateSet(NOTIFYBELL); + break; + default : _currentScreen->displayCharacter(c); break; + }; +} + +void Emulation::sendKeyEvent( QKeyEvent* ev ) +{ + emit stateSet(NOTIFYNORMAL); + + if (!ev->text().isEmpty()) + { // A block of text + // Note that the text is proper unicode. + // We should do a conversion here + emit sendData(ev->text().toUtf8(),ev->text().length()); + } +} + +void Emulation::sendString(const char*,int) +{ + // default implementation does nothing +} + +void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) +{ + // default implementation does nothing +} + +/* + We are doing code conversion from locale to unicode first. +TODO: Character composition from the old code. See #96536 +*/ + +void Emulation::receiveData(const char* text, int length) +{ + emit stateSet(NOTIFYACTIVITY); + + bufferedUpdate(); + + QString unicodeText = _decoder->toUnicode(text,length); + + //send characters to terminal emulator + for (int i=0;i 3) && (strncmp(text+i+1, "B00", 3) == 0)) + emit zmodemDetected(); + } + } +} + +//OLDER VERSION +//This version of onRcvBlock was commented out because +// a) It decoded incoming characters one-by-one, which is slow in the current version of Qt (4.2 tech preview) +// b) It messed up decoding of non-ASCII characters, with the result that (for example) chinese characters +// were not printed properly. +// +//There is something about stopping the _decoder if "we get a control code halfway a multi-byte sequence" (see below) +//which hasn't been ported into the newer function (above). Hopefully someone who understands this better +//can find an alternative way of handling the check. + + +/*void Emulation::onRcvBlock(const char *s, int len) +{ + emit notifySessionState(NOTIFYACTIVITY); + + bufferedUpdate(); + for (int i = 0; i < len; i++) + { + + QString result = _decoder->toUnicode(&s[i],1); + int reslen = result.length(); + + // If we get a control code halfway a multi-byte sequence + // we flush the _decoder and continue with the control code. + if ((s[i] < 32) && (s[i] > 0)) + { + // Flush _decoder + while(!result.length()) + result = _decoder->toUnicode(&s[i],1); + reslen = 1; + result.resize(reslen); + result[0] = QChar(s[i]); + } + + for (int j = 0; j < reslen; j++) + { + if (result[j].characterategory() == QChar::Mark_NonSpacing) + _currentScreen->compose(result.mid(j,1)); + else + onRcvChar(result[j].unicode()); + } + if (s[i] == '\030') + { + if ((len-i-1 > 3) && (strncmp(s+i+1, "B00", 3) == 0)) + emit zmodemDetected(); + } + } +}*/ + +void Emulation::writeToStream( TerminalCharacterDecoder* _decoder , + int startLine , + int endLine) +{ + _currentScreen->writeLinesToStream(_decoder,startLine,endLine); +} + +int Emulation::lineCount() const +{ + // sum number of lines currently on _screen plus number of lines in history + return _currentScreen->getLines() + _currentScreen->getHistLines(); +} + +#define BULK_TIMEOUT1 10 +#define BULK_TIMEOUT2 40 + +void Emulation::showBulk() +{ + _bulkTimer1.stop(); + _bulkTimer2.stop(); + + emit outputChanged(); + + _currentScreen->resetScrolledLines(); + _currentScreen->resetDroppedLines(); +} + +void Emulation::bufferedUpdate() +{ + _bulkTimer1.setSingleShot(true); + _bulkTimer1.start(BULK_TIMEOUT1); + if (!_bulkTimer2.isActive()) + { + _bulkTimer2.setSingleShot(true); + _bulkTimer2.start(BULK_TIMEOUT2); + } +} + +char Emulation::eraseChar() const +{ + return '\b'; +} + +void Emulation::setImageSize(int lines, int columns) +{ + if ((lines < 1) || (columns < 1)) + return; + + QSize screenSize[2] = { QSize(_screen[0]->getColumns(), + _screen[0]->getLines()), + QSize(_screen[1]->getColumns(), + _screen[1]->getLines()) }; + QSize newSize(columns,lines); + + if (newSize == screenSize[0] && newSize == screenSize[1]) + return; + + _screen[0]->resizeImage(lines,columns); + _screen[1]->resizeImage(lines,columns); + + emit imageSizeChanged(lines,columns); + + bufferedUpdate(); +} + +QSize Emulation::imageSize() const +{ + return QSize(_currentScreen->getColumns(), _currentScreen->getLines()); +} + +ushort ExtendedCharTable::extendedCharHash(ushort* unicodePoints , ushort length) const +{ + ushort hash = 0; + for ( ushort i = 0 ; i < length ; i++ ) + { + hash = 31*hash + unicodePoints[i]; + } + return hash; +} +bool ExtendedCharTable::extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const +{ + ushort* entry = extendedCharTable[hash]; + + // compare given length with stored sequence length ( given as the first ushort in the + // stored buffer ) + if ( entry == 0 || entry[0] != length ) + return false; + // if the lengths match, each character must be checked. the stored buffer starts at + // entry[1] + for ( int i = 0 ; i < length ; i++ ) + { + if ( entry[i+1] != unicodePoints[i] ) + return false; + } + return true; +} +ushort ExtendedCharTable::createExtendedChar(ushort* unicodePoints , ushort length) +{ + // look for this sequence of points in the table + ushort hash = extendedCharHash(unicodePoints,length); + + // check existing entry for match + while ( extendedCharTable.contains(hash) ) + { + if ( extendedCharMatch(hash,unicodePoints,length) ) + { + // this sequence already has an entry in the table, + // return its hash + return hash; + } + else + { + // if hash is already used by another, different sequence of unicode character + // points then try next hash + hash++; + } + } + + + // add the new sequence to the table and + // return that index + ushort* buffer = new ushort[length+1]; + buffer[0] = length; + for ( int i = 0 ; i < length ; i++ ) + buffer[i+1] = unicodePoints[i]; + + extendedCharTable.insert(hash,buffer); + + return hash; +} + +ushort* ExtendedCharTable::lookupExtendedChar(ushort hash , ushort& length) const +{ + // lookup index in table and if found, set the length + // argument and return a pointer to the character sequence + + ushort* buffer = extendedCharTable[hash]; + if ( buffer ) + { + length = buffer[0]; + return buffer+1; + } + else + { + length = 0; + return 0; + } +} + +ExtendedCharTable::ExtendedCharTable() +{ +} +ExtendedCharTable::~ExtendedCharTable() +{ + // free all allocated character buffers + QHashIterator iter(extendedCharTable); + while ( iter.hasNext() ) + { + iter.next(); + delete[] iter.value(); + } +} + +// global instance +ExtendedCharTable ExtendedCharTable::instance; + + +//#include "Emulation.moc" + diff --git a/lib/Emulation.h b/lib/Emulation.h new file mode 100644 index 0000000..feecdf0 --- /dev/null +++ b/lib/Emulation.h @@ -0,0 +1,471 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef EMULATION_H +#define EMULATION_H + +// System +#include + +// Qt +#include +//#include +#include +#include +#include + +// Konsole +//#include "konsole_export.h" +#define KONSOLEPRIVATE_EXPORT + +namespace Konsole +{ + +class KeyboardTranslator; +class HistoryType; +class Screen; +class ScreenWindow; +class TerminalCharacterDecoder; + +/** + * This enum describes the available states which + * the terminal emulation may be set to. + * + * These are the values used by Emulation::stateChanged() + */ +enum +{ + /** The emulation is currently receiving user input. */ + NOTIFYNORMAL=0, + /** + * The terminal program has triggered a bell event + * to get the user's attention. + */ + NOTIFYBELL=1, + /** + * The emulation is currently receiving data from its + * terminal input. + */ + NOTIFYACTIVITY=2, + + // unused here? + NOTIFYSILENCE=3 +}; + +/** + * Base class for terminal emulation back-ends. + * + * The back-end is responsible for decoding an incoming character stream and + * producing an output image of characters. + * + * When input from the terminal is received, the receiveData() slot should be called with + * the data which has arrived. The emulation will process the data and update the + * screen image accordingly. The codec used to decode the incoming character stream + * into the unicode characters used internally can be specified using setCodec() + * + * The size of the screen image can be specified by calling setImageSize() with the + * desired number of lines and columns. When new lines are added, old content + * is moved into a history store, which can be set by calling setHistory(). + * + * The screen image can be accessed by creating a ScreenWindow onto this emulation + * by calling createWindow(). Screen windows provide access to a section of the + * output. Each screen window covers the same number of lines and columns as the + * image size returned by imageSize(). The screen window can be moved up and down + * and provides transparent access to both the current on-screen image and the + * previous output. The screen windows emit an outputChanged signal + * when the section of the image they are looking at changes. + * Graphical views can then render the contents of a screen window, listening for notifications + * of output changes from the screen window which they are associated with and updating + * accordingly. + * + * The emulation also is also responsible for converting input from the connected views such + * as keypresses and mouse activity into a character string which can be sent + * to the terminal program. Key presses can be processed by calling the sendKeyEvent() slot, + * while mouse events can be processed using the sendMouseEvent() slot. When the character + * stream has been produced, the emulation will emit a sendData() signal with a pointer + * to the character buffer. This data should be fed to the standard input of the terminal + * process. The translation of key presses into an output character stream is performed + * using a lookup in a set of key bindings which map key sequences to output + * character sequences. The name of the key bindings set used can be specified using + * setKeyBindings() + * + * The emulation maintains certain state information which changes depending on the + * input received. The emulation can be reset back to its starting state by calling + * reset(). + * + * The emulation also maintains an activity state, which specifies whether + * terminal is currently active ( when data is received ), normal + * ( when the terminal is idle or receiving user input ) or trying + * to alert the user ( also known as a "Bell" event ). The stateSet() signal + * is emitted whenever the activity state is set. This can be used to determine + * how long the emulation has been active/idle for and also respond to + * a 'bell' event in different ways. + */ +class KONSOLEPRIVATE_EXPORT Emulation : public QObject +{ +Q_OBJECT + +public: + + /** Constructs a new terminal emulation */ + Emulation(); + ~Emulation(); + + /** + * Creates a new window onto the output from this emulation. The contents + * of the window are then rendered by views which are set to use this window using the + * TerminalDisplay::setScreenWindow() method. + */ + ScreenWindow* createWindow(); + + /** Returns the size of the screen image which the emulation produces */ + QSize imageSize() const; + + /** + * Returns the total number of lines, including those stored in the history. + */ + int lineCount() const; + + /** + * Sets the history store used by this emulation. When new lines + * are added to the output, older lines at the top of the screen are transferred to a history + * store. + * + * The number of lines which are kept and the storage location depend on the + * type of store. + */ + void setHistory(const HistoryType&); + /** Returns the history store used by this emulation. See setHistory() */ + const HistoryType& history() const; + /** Clears the history scroll. */ + void clearHistory(); + + /** + * Copies the output history from @p startLine to @p endLine + * into @p stream, using @p decoder to convert the terminal + * characters into text. + * + * @param decoder A decoder which converts lines of terminal characters with + * appearance attributes into output text. PlainTextDecoder is the most commonly + * used decoder. + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + virtual void writeToStream(TerminalCharacterDecoder* decoder,int startLine,int endLine); + + /** Returns the codec used to decode incoming characters. See setCodec() */ + const QTextCodec* codec() const { return _codec; } + /** Sets the codec used to decode incoming characters. */ + void setCodec(const QTextCodec*); + + /** + * Convenience method. + * Returns true if the current codec used to decode incoming + * characters is UTF-8 + */ + bool utf8() const + { Q_ASSERT(_codec); return _codec->mibEnum() == 106; } + + + /** TODO Document me */ + virtual char eraseChar() const; + + /** + * Sets the key bindings used to key events + * ( received through sendKeyEvent() ) into character + * streams to send to the terminal. + */ + void setKeyBindings(const QString& name); + /** + * Returns the name of the emulation's current key bindings. + * See setKeyBindings() + */ + QString keyBindings() const; + + /** + * Copies the current image into the history and clears the screen. + */ + virtual void clearEntireScreen() =0; + + /** Resets the state of the terminal. */ + virtual void reset() =0; + + /** + * Returns true if the active terminal program wants + * mouse input events. + * + * The programUsesMouseChanged() signal is emitted when this + * changes. + */ + bool programUsesMouse() const; + +public slots: + + /** Change the size of the emulation's image */ + virtual void setImageSize(int lines, int columns); + + /** + * Interprets a sequence of characters and sends the result to the terminal. + * This is equivalent to calling sendKeyEvent() for each character in @p text in succession. + */ + virtual void sendText(const QString& text) = 0; + + /** + * Interprets a key press event and emits the sendData() signal with + * the resulting character stream. + */ + virtual void sendKeyEvent(QKeyEvent*); + + /** + * Converts information about a mouse event into an xterm-compatible escape + * sequence and emits the character sequence via sendData() + */ + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + + /** + * Sends a string of characters to the foreground terminal process. + * + * @param string The characters to send. + * @param length Length of @p string or if set to a negative value, @p string will + * be treated as a null-terminated string and its length will be determined automatically. + */ + virtual void sendString(const char* string, int length = -1) = 0; + + /** + * Processes an incoming stream of characters. receiveData() decodes the incoming + * character buffer using the current codec(), and then calls receiveChar() for + * each unicode character in the resulting buffer. + * + * receiveData() also starts a timer which causes the outputChanged() signal + * to be emitted when it expires. The timer allows multiple updates in quick + * succession to be buffered into a single outputChanged() signal emission. + * + * @param buffer A string of characters received from the terminal program. + * @param len The length of @p buffer + */ + void receiveData(const char* buffer,int len); + +signals: + + /** + * Emitted when a buffer of data is ready to send to the + * standard input of the terminal. + * + * @param data The buffer of data ready to be sent + * @param len The length of @p data in bytes + */ + void sendData(const char* data,int len); + + /** + * Requests that sending of input to the emulation + * from the terminal process be suspended or resumed. + * + * @param suspend If true, requests that sending of + * input from the terminal process' stdout be + * suspended. Otherwise requests that sending of + * input be resumed. + */ + void lockPtyRequest(bool suspend); + + /** + * Requests that the pty used by the terminal process + * be set to UTF 8 mode. + * + * TODO: More documentation + */ + void useUtf8Request(bool); + + /** + * Emitted when the activity state of the emulation is set. + * + * @param state The new activity state, one of NOTIFYNORMAL, NOTIFYACTIVITY + * or NOTIFYBELL + */ + void stateSet(int state); + + /** TODO Document me */ + void zmodemDetected(); + + + /** + * Requests that the color of the text used + * to represent the tabs associated with this + * emulation be changed. This is a Konsole-specific + * extension from pre-KDE 4 times. + * + * TODO: Document how the parameter works. + */ + void changeTabTextColorRequest(int color); + + /** + * This is emitted when the program running in the shell indicates whether or + * not it is interested in mouse events. + * + * @param usesMouse This will be true if the program wants to be informed about + * mouse events or false otherwise. + */ + void programUsesMouseChanged(bool usesMouse); + + /** + * Emitted when the contents of the screen image change. + * The emulation buffers the updates from successive image changes, + * and only emits outputChanged() at sensible intervals when + * there is a lot of terminal activity. + * + * Normally there is no need for objects other than the screen windows + * created with createWindow() to listen for this signal. + * + * ScreenWindow objects created using createWindow() will emit their + * own outputChanged() signal in response to this signal. + */ + void outputChanged(); + + /** + * Emitted when the program running in the terminal wishes to update the + * session's title. This also allows terminal programs to customize other + * aspects of the terminal emulation display. + * + * This signal is emitted when the escape sequence "\033]ARG;VALUE\007" + * is received in the input string, where ARG is a number specifying what + * should change and VALUE is a string specifying the new value. + * + * TODO: The name of this method is not very accurate since this method + * is used to perform a whole range of tasks besides just setting + * the user-title of the session. + * + * @param title Specifies what to change. + *
    + *
  • 0 - Set window icon text and session title to @p newTitle
  • + *
  • 1 - Set window icon text to @p newTitle
  • + *
  • 2 - Set session title to @p newTitle
  • + *
  • 11 - Set the session's default background color to @p newTitle, + * where @p newTitle can be an HTML-style string ("#RRGGBB") or a named + * color (eg 'red', 'blue'). + * See http://doc.trolltech.com/4.2/qcolor.html#setNamedColor for more + * details. + *
  • + *
  • 31 - Supposedly treats @p newTitle as a URL and opens it (NOT IMPLEMENTED)
  • + *
  • 32 - Sets the icon associated with the session. @p newTitle is the name + * of the icon to use, which can be the name of any icon in the current KDE icon + * theme (eg: 'konsole', 'kate', 'folder_home')
  • + *
+ * @param newTitle Specifies the new title + */ + + void titleChanged(int title,const QString& newTitle); + + /** + * Emitted when the program running in the terminal changes the + * screen size. + */ + void imageSizeChanged(int lineCount , int columnCount); + + /** + * Emitted when the terminal program requests to change various properties + * of the terminal display. + * + * A profile change command occurs when a special escape sequence, followed + * by a string containing a series of name and value pairs is received. + * This string can be parsed using a ProfileCommandParser instance. + * + * @param text A string expected to contain a series of key and value pairs in + * the form: name=value;name2=value2 ... + */ + void profileChangeCommandReceived(const QString& text); + + /** + * Emitted when a flow control key combination ( Ctrl+S or Ctrl+Q ) is pressed. + * @param suspendKeyPressed True if Ctrl+S was pressed to suspend output or Ctrl+Q to + * resume output. + */ + void flowControlKeyPressed(bool suspendKeyPressed); + +protected: + virtual void setMode(int mode) = 0; + virtual void resetMode(int mode) = 0; + + /** + * Processes an incoming character. See receiveData() + * @p ch A unicode character code. + */ + virtual void receiveChar(int ch); + + /** + * Sets the active screen. The terminal has two screens, primary and alternate. + * The primary screen is used by default. When certain interactive programs such + * as Vim are run, they trigger a switch to the alternate screen. + * + * @param index 0 to switch to the primary screen, or 1 to switch to the alternate screen + */ + void setScreen(int index); + + enum EmulationCodec + { + LocaleCodec = 0, + Utf8Codec = 1 + }; + void setCodec(EmulationCodec codec); // codec number, 0 = locale, 1=utf8 + + + QList _windows; + + Screen* _currentScreen; // pointer to the screen which is currently active, + // this is one of the elements in the screen[] array + + Screen* _screen[2]; // 0 = primary screen ( used by most programs, including the shell + // scrollbars are enabled in this mode ) + // 1 = alternate ( used by vi , emacs etc. + // scrollbars are not enabled in this mode ) + + + //decodes an incoming C-style character stream into a unicode QString using + //the current text codec. (this allows for rendering of non-ASCII characters in text files etc.) + const QTextCodec* _codec; + QTextDecoder* _decoder; + const KeyboardTranslator* _keyTranslator; // the keyboard layout + +protected slots: + /** + * Schedules an update of attached views. + * Repeated calls to bufferedUpdate() in close succession will result in only a single update, + * much like the Qt buffered update of widgets. + */ + void bufferedUpdate(); + +private slots: + + // triggered by timer, causes the emulation to send an updated screen image to each + // view + void showBulk(); + + void usesMouseChanged(bool usesMouse); + +private: + bool _usesMouse; + QTimer _bulkTimer1; + QTimer _bulkTimer2; + +}; + +} + +#endif // ifndef EMULATION_H diff --git a/lib/ExtendedDefaultTranslator.h b/lib/ExtendedDefaultTranslator.h new file mode 100644 index 0000000..6403c72 --- /dev/null +++ b/lib/ExtendedDefaultTranslator.h @@ -0,0 +1,74 @@ +"keyboard \"Default (XFree 4)\"" +"key Escape : \"\\E\"" +"key Tab -Shift : \"\\t\"\n" +"key Tab +Shift+Ansi : \"\\E[Z\"\n" +"key Tab +Shift-Ansi : \"\\t\"\n" +"key Backtab +Ansi : \"\\E[Z\"\n" +"key Backtab -Ansi : \"\\t\"\n" +"key Return-Shift-NewLine : \"\\r\"\n" +"key Return-Shift+NewLine : \"\\r\\n\"\n" +"key Return+Shift : \"\\EOM\"\n" +"key Backspace : \"\\x7f\"\n" +"key Up -Shift-Ansi : \"\\EA\"\n" +"key Down -Shift-Ansi : \"\\EB\"\n" +"key Right-Shift-Ansi : \"\\EC\"\n" +"key Left -Shift-Ansi : \"\\ED\"\n" +"key Up -Shift-AnyMod+Ansi+AppCuKeys : \"\\EOA\"\n" +"key Down -Shift-AnyMod+Ansi+AppCuKeys : \"\\EOB\"\n" +"key Right -Shift-AnyMod+Ansi+AppCuKeys : \"\\EOC\"\n" +"key Left -Shift-AnyMod+Ansi+AppCuKeys : \"\\EOD\"\n" +"key Up -Shift-AnyMod+Ansi-AppCuKeys : \"\\E[A\"\n" +"key Down -Shift-AnyMod+Ansi-AppCuKeys : \"\\E[B\"\n" +"key Right -Shift-AnyMod+Ansi-AppCuKeys : \"\\E[C\"\n" +"key Left -Shift-AnyMod+Ansi-AppCuKeys : \"\\E[D\"\n" +"key Up -Shift+AnyMod+Ansi : \"\\E[1;*A\"\n" +"key Down -Shift+AnyMod+Ansi : \"\\E[1;*B\"\n" +"key Right -Shift+AnyMod+Ansi : \"\\E[1;*C\"\n" +"key Left -Shift+AnyMod+Ansi : \"\\E[1;*D\"\n" +"key Enter+NewLine : \"\\r\\n\"\n" +"key Enter-NewLine : \"\\r\"\n" +"key Home -AnyMod -AppCuKeys : \"\\E[H\" \n" +"key End -AnyMod -AppCuKeys : \"\\E[F\" \n" +"key Home -AnyMod +AppCuKeys : \"\\EOH\" \n" +"key End -AnyMod +AppCuKeys : \"\\EOF\" \n" +"key Home +AnyMod : \"\\E[1;*H\"\n" +"key End +AnyMod : \"\\E[1;*F\"\n" +"key Insert -AnyMod : \"\\E[2~\"\n" +"key Delete -AnyMod : \"\\E[3~\"\n" +"key Insert +AnyMod : \"\\E[2;*~\"\n" +"key Delete +AnyMod : \"\\E[3;*~\"\n" +"key Prior -Shift-AnyMod : \"\\E[5~\"\n" +"key Next -Shift-AnyMod : \"\\E[6~\"\n" +"key Prior -Shift+AnyMod : \"\\E[5;*~\"\n" +"key Next -Shift+AnyMod : \"\\E[6;*~\"\n" +"key F1 -AnyMod : \"\\EOP\"\n" +"key F2 -AnyMod : \"\\EOQ\"\n" +"key F3 -AnyMod : \"\\EOR\"\n" +"key F4 -AnyMod : \"\\EOS\"\n" +"key F5 -AnyMod : \"\\E[15~\"\n" +"key F6 -AnyMod : \"\\E[17~\"\n" +"key F7 -AnyMod : \"\\E[18~\"\n" +"key F8 -AnyMod : \"\\E[19~\"\n" +"key F9 -AnyMod : \"\\E[20~\"\n" +"key F10 -AnyMod : \"\\E[21~\"\n" +"key F11 -AnyMod : \"\\E[23~\"\n" +"key F12 -AnyMod : \"\\E[24~\"\n" +"key F1 +AnyMod : \"\\EO*P\"\n" +"key F2 +AnyMod : \"\\EO*Q\"\n" +"key F3 +AnyMod : \"\\EO*R\"\n" +"key F4 +AnyMod : \"\\EO*S\"\n" +"key F5 +AnyMod : \"\\E[15;*~\"\n" +"key F6 +AnyMod : \"\\E[17;*~\"\n" +"key F7 +AnyMod : \"\\E[18;*~\"\n" +"key F8 +AnyMod : \"\\E[19;*~\"\n" +"key F9 +AnyMod : \"\\E[20;*~\"\n" +"key F10 +AnyMod : \"\\E[21;*~\"\n" +"key F11 +AnyMod : \"\\E[23;*~\"\n" +"key F12 +AnyMod : \"\\E[24;*~\"\n" +"key Space +Control : \"\\x00\"\n" +"key Up +Shift-AppScreen : scrollLineUp\n" +"key Prior +Shift-AppScreen : scrollPageUp\n" +"key Down +Shift-AppScreen : scrollLineDown\n" +"key Next +Shift-AppScreen : scrollPageDown\n" +"key ScrollLock : scrollLock\n" +"\0" diff --git a/lib/Filter.cpp b/lib/Filter.cpp new file mode 100644 index 0000000..93de72c --- /dev/null +++ b/lib/Filter.cpp @@ -0,0 +1,557 @@ +/* + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +// Own +#include "Filter.h" + +// System +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +//#include +//#include + +// Konsole +#include "TerminalCharacterDecoder.h" +#include "konsole_wcwidth.h" + +using namespace Konsole; + +FilterChain::~FilterChain() +{ + QMutableListIterator iter(*this); + + while ( iter.hasNext() ) + { + Filter* filter = iter.next(); + iter.remove(); + delete filter; + } +} + +void FilterChain::addFilter(Filter* filter) +{ + append(filter); +} +void FilterChain::removeFilter(Filter* filter) +{ + removeAll(filter); +} +bool FilterChain::containsFilter(Filter* filter) +{ + return contains(filter); +} +void FilterChain::reset() +{ + QListIterator iter(*this); + while (iter.hasNext()) + iter.next()->reset(); +} +void FilterChain::setBuffer(const QString* buffer , const QList* linePositions) +{ + QListIterator iter(*this); + while (iter.hasNext()) + iter.next()->setBuffer(buffer,linePositions); +} +void FilterChain::process() +{ + QListIterator iter(*this); + while (iter.hasNext()) + iter.next()->process(); +} +void FilterChain::clear() +{ + QList::clear(); +} +Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const +{ + QListIterator iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + Filter::HotSpot* spot = filter->hotSpotAt(line,column); + if ( spot != 0 ) + { + return spot; + } + } + + return 0; +} + +QList FilterChain::hotSpots() const +{ + QList list; + QListIterator iter(*this); + while (iter.hasNext()) + { + Filter* filter = iter.next(); + list << filter->hotSpots(); + } + return list; +} +//QList FilterChain::hotSpotsAtLine(int line) const; + +TerminalImageFilterChain::TerminalImageFilterChain() +: _buffer(0) +, _linePositions(0) +{ +} + +TerminalImageFilterChain::~TerminalImageFilterChain() +{ + delete _buffer; + delete _linePositions; +} + +void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector& lineProperties) +{ + if (empty()) + return; + + // reset all filters and hotspots + reset(); + + PlainTextDecoder decoder; + decoder.setTrailingWhitespace(false); + + // setup new shared buffers for the filters to process on + QString* newBuffer = new QString(); + QList* newLinePositions = new QList(); + setBuffer( newBuffer , newLinePositions ); + + // free the old buffers + delete _buffer; + delete _linePositions; + + _buffer = newBuffer; + _linePositions = newLinePositions; + + QTextStream lineStream(_buffer); + decoder.begin(&lineStream); + + for (int i=0 ; i < lines ; i++) + { + _linePositions->append(_buffer->length()); + decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT); + + // pretend that each line ends with a newline character. + // this prevents a link that occurs at the end of one line + // being treated as part of a link that occurs at the start of the next line + // + // the downside is that links which are spread over more than one line are not + // highlighted. + // + // TODO - Use the "line wrapped" attribute associated with lines in a + // terminal image to avoid adding this imaginary character for wrapped + // lines + if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) ) + lineStream << QChar('\n'); + } + decoder.end(); +} + +Filter::Filter() : +_linePositions(0), +_buffer(0) +{ +} + +Filter::~Filter() +{ + QListIterator iter(_hotspotList); + while (iter.hasNext()) + { + delete iter.next(); + } +} +void Filter::reset() +{ + _hotspots.clear(); + _hotspotList.clear(); +} + +void Filter::setBuffer(const QString* buffer , const QList* linePositions) +{ + _buffer = buffer; + _linePositions = linePositions; +} + +void Filter::getLineColumn(int position , int& startLine , int& startColumn) +{ + Q_ASSERT( _linePositions ); + Q_ASSERT( _buffer ); + + + for (int i = 0 ; i < _linePositions->count() ; i++) + { + int nextLine = 0; + + if ( i == _linePositions->count()-1 ) + nextLine = _buffer->length() + 1; + else + nextLine = _linePositions->value(i+1); + + if ( _linePositions->value(i) <= position && position < nextLine ) + { + startLine = i; + startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i))); + return; + } + } +} + + +/*void Filter::addLine(const QString& text) +{ + _linePositions << _buffer.length(); + _buffer.append(text); +}*/ + +const QString* Filter::buffer() +{ + return _buffer; +} +Filter::HotSpot::~HotSpot() +{ +} +void Filter::addHotSpot(HotSpot* spot) +{ + _hotspotList << spot; + + for (int line = spot->startLine() ; line <= spot->endLine() ; line++) + { + _hotspots.insert(line,spot); + } +} +QList Filter::hotSpots() const +{ + return _hotspotList; +} +QList Filter::hotSpotsAtLine(int line) const +{ + return _hotspots.values(line); +} + +Filter::HotSpot* Filter::hotSpotAt(int line , int column) const +{ + QListIterator spotIter(_hotspots.values(line)); + + while (spotIter.hasNext()) + { + HotSpot* spot = spotIter.next(); + + if ( spot->startLine() == line && spot->startColumn() > column ) + continue; + if ( spot->endLine() == line && spot->endColumn() < column ) + continue; + + return spot; + } + + return 0; +} + +Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn) + : _startLine(startLine) + , _startColumn(startColumn) + , _endLine(endLine) + , _endColumn(endColumn) + , _type(NotSpecified) +{ +} +QString Filter::HotSpot::tooltip() const +{ + return QString(); +} +QList Filter::HotSpot::actions() +{ + return QList(); +} +int Filter::HotSpot::startLine() const +{ + return _startLine; +} +int Filter::HotSpot::endLine() const +{ + return _endLine; +} +int Filter::HotSpot::startColumn() const +{ + return _startColumn; +} +int Filter::HotSpot::endColumn() const +{ + return _endColumn; +} +Filter::HotSpot::Type Filter::HotSpot::type() const +{ + return _type; +} +void Filter::HotSpot::setType(Type type) +{ + _type = type; +} + +RegExpFilter::RegExpFilter() +{ +} + +RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) + : Filter::HotSpot(startLine,startColumn,endLine,endColumn) +{ + setType(Marker); +} + +void RegExpFilter::HotSpot::activate(const QString&) +{ +} + +void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts) +{ + _capturedTexts = texts; +} +QStringList RegExpFilter::HotSpot::capturedTexts() const +{ + return _capturedTexts; +} + +void RegExpFilter::setRegExp(const QRegExp& regExp) +{ + _searchText = regExp; +} +QRegExp RegExpFilter::regExp() const +{ + return _searchText; +} +/*void RegExpFilter::reset(int) +{ + _buffer = QString(); +}*/ +void RegExpFilter::process() +{ + int pos = 0; + const QString* text = buffer(); + + Q_ASSERT( text ); + + // ignore any regular expressions which match an empty string. + // otherwise the while loop below will run indefinitely + static const QString emptyString(""); + if ( _searchText.exactMatch(emptyString) ) + return; + + while(pos >= 0) + { + pos = _searchText.indexIn(*text,pos); + + if ( pos >= 0 ) + { + int startLine = 0; + int endLine = 0; + int startColumn = 0; + int endColumn = 0; + + getLineColumn(pos,startLine,startColumn); + getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn); + + RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn, + endLine,endColumn); + spot->setCapturedTexts(_searchText.capturedTexts()); + + addHotSpot( spot ); + pos += _searchText.matchedLength(); + + // if matchedLength == 0, the program will get stuck in an infinite loop + if ( _searchText.matchedLength() == 0 ) + pos = -1; + } + } +} + +RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn, + int endLine,int endColumn) +{ + return new RegExpFilter::HotSpot(startLine,startColumn, + endLine,endColumn); +} +RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine, + int endColumn) +{ + HotSpot *spot = new UrlFilter::HotSpot(startLine,startColumn, + endLine,endColumn); + connect(spot->getUrlObject(), SIGNAL(activated(QUrl)), this, SIGNAL(activated(QUrl))); + return spot; +} + +UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) +: RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn) +, _urlObject(new FilterObject(this)) +{ + setType(Link); +} + +QString UrlFilter::HotSpot::tooltip() const +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + if ( kind == StandardUrl ) + return QString(); + else if ( kind == Email ) + return QString(); + else + return QString(); +} +UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const +{ + QString url = capturedTexts().first(); + + if ( FullUrlRegExp.exactMatch(url) ) + return StandardUrl; + else if ( EmailAddressRegExp.exactMatch(url) ) + return Email; + else + return Unknown; +} + +void UrlFilter::HotSpot::activate(const QString& actionName) +{ + QString url = capturedTexts().first(); + + const UrlType kind = urlType(); + + if ( actionName == "copy-action" ) + { + QApplication::clipboard()->setText(url); + return; + } + + if ( actionName.isEmpty() || actionName == "open-action" ) + { + if ( kind == StandardUrl ) + { + // if the URL path does not include the protocol ( eg. "www.kde.org" ) then + // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" ) + if (!url.contains("://")) + { + url.prepend("http://"); + } + } + else if ( kind == Email ) + { + url.prepend("mailto:"); + } + + _urlObject->emitActivated(url); + } +} + +// Note: Altering these regular expressions can have a major effect on the performance of the filters +// used for finding URLs in the text, especially if they are very general and could match very long +// pieces of text. +// Please be careful when altering them. + +//regexp matches: +// full url: +// protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot +const QRegExp UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"); +// email address: +// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] +const QRegExp UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"); + +// matches full url or email address +const QRegExp UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp.pattern()+'|'+ + EmailAddressRegExp.pattern()+')'); + +UrlFilter::UrlFilter() +{ + setRegExp( CompleteUrlRegExp ); +} + +UrlFilter::HotSpot::~HotSpot() +{ + delete _urlObject; +} + +void FilterObject::emitActivated(const QUrl& url) +{ + emit activated(url); +} + +void FilterObject::activated() +{ + _filter->activate(sender()->objectName()); +} + +FilterObject* UrlFilter::HotSpot::getUrlObject() const +{ + return _urlObject; +} + +QList UrlFilter::HotSpot::actions() +{ + QList list; + + const UrlType kind = urlType(); + + QAction* openAction = new QAction(_urlObject); + QAction* copyAction = new QAction(_urlObject);; + + Q_ASSERT( kind == StandardUrl || kind == Email ); + + if ( kind == StandardUrl ) + { + openAction->setText(QObject::tr("Open Link")); + copyAction->setText(QObject::tr("Copy Link Address")); + } + else if ( kind == Email ) + { + openAction->setText(QObject::tr("Send Email To...")); + copyAction->setText(QObject::tr("Copy Email Address")); + } + + // object names are set here so that the hotspot performs the + // correct action when activated() is called with the triggered + // action passed as a parameter. + openAction->setObjectName( QLatin1String("open-action" )); + copyAction->setObjectName( QLatin1String("copy-action" )); + + QObject::connect( openAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + QObject::connect( copyAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) ); + + list << openAction; + list << copyAction; + + return list; +} + +//#include "Filter.moc" diff --git a/lib/Filter.h b/lib/Filter.h new file mode 100644 index 0000000..161773b --- /dev/null +++ b/lib/Filter.h @@ -0,0 +1,396 @@ +/* + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +#ifndef FILTER_H +#define FILTER_H + +// Qt +#include +#include +#include +#include +#include +#include + +// Local + +namespace Konsole +{ + +typedef unsigned char LineProperty; +class Character; + +/** + * A filter processes blocks of text looking for certain patterns (such as URLs or keywords from a list) + * and marks the areas which match the filter's patterns as 'hotspots'. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. + * + * Different subclasses of filter will return different types of hotspot. + * Subclasses must reimplement the process() method to examine a block of text and identify sections of interest. + * When processing the text they should create instances of Filter::HotSpot subclasses for sections of interest + * and add them to the filter's list of hotspots using addHotSpot() + */ +class Filter : public QObject +{ +public: + /** + * Represents an area of text which matched the pattern a particular filter has been looking for. + * + * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), + * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact + * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's + * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. + * + * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. + * Hotspots may have more than one action, in which case the list of actions can be obtained using the + * actions() method. These actions may then be displayed in a popup menu or toolbar for example. + */ + class HotSpot + { + public: + /** + * Constructs a new hotspot which covers the area from (@p startLine,@p startColumn) to (@p endLine,@p endColumn) + * in a block of text. + */ + HotSpot(int startLine , int startColumn , int endLine , int endColumn); + virtual ~HotSpot(); + + enum Type + { + // the type of the hotspot is not specified + NotSpecified, + // this hotspot represents a clickable link + Link, + // this hotspot represents a marker + Marker + }; + + /** Returns the line when the hotspot area starts */ + int startLine() const; + /** Returns the line where the hotspot area ends */ + int endLine() const; + /** Returns the column on startLine() where the hotspot area starts */ + int startColumn() const; + /** Returns the column on endLine() where the hotspot area ends */ + int endColumn() const; + /** + * Returns the type of the hotspot. This is usually used as a hint for views on how to represent + * the hotspot graphically. eg. Link hotspots are typically underlined when the user mouses over them + */ + Type type() const; + /** + * Causes the an action associated with a hotspot to be triggered. + * + * @param action The action to trigger. This is + * typically empty ( in which case the default action should be performed ) or + * one of the object names from the actions() list. In which case the associated + * action should be performed. + */ + virtual void activate(const QString& action = QString()) = 0; + /** + * Returns a list of actions associated with the hotspot which can be used in a + * menu or toolbar + */ + virtual QList actions(); + + /** + * Returns the text of a tooltip to be shown when the mouse moves over the hotspot, or + * an empty string if there is no tooltip associated with this hotspot. + * + * The default implementation returns an empty string. + */ + virtual QString tooltip() const; + + protected: + /** Sets the type of a hotspot. This should only be set once */ + void setType(Type type); + + private: + int _startLine; + int _startColumn; + int _endLine; + int _endColumn; + Type _type; + + }; + + /** Constructs a new filter. */ + Filter(); + virtual ~Filter(); + + /** Causes the filter to process the block of text currently in its internal buffer */ + virtual void process() = 0; + + /** + * Empties the filters internal buffer and resets the line count back to 0. + * All hotspots are deleted. + */ + void reset(); + + /** Adds a new line of text to the filter and increments the line count */ + //void addLine(const QString& string); + + /** Returns the hotspot which covers the given @p line and @p column, or 0 if no hotspot covers that area */ + HotSpot* hotSpotAt(int line , int column) const; + + /** Returns the list of hotspots identified by the filter */ + QList hotSpots() const; + + /** Returns the list of hotspots identified by the filter which occur on a given line */ + QList hotSpotsAtLine(int line) const; + + /** + * TODO: Document me + */ + void setBuffer(const QString* buffer , const QList* linePositions); + +protected: + /** Adds a new hotspot to the list */ + void addHotSpot(HotSpot*); + /** Returns the internal buffer */ + const QString* buffer(); + /** Converts a character position within buffer() to a line and column */ + void getLineColumn(int position , int& startLine , int& startColumn); + +private: + QMultiHash _hotspots; + QList _hotspotList; + + const QList* _linePositions; + const QString* _buffer; +}; + +/** + * A filter which searches for sections of text matching a regular expression and creates a new RegExpFilter::HotSpot + * instance for them. + * + * Subclasses can reimplement newHotSpot() to return custom hotspot types when matches for the regular expression + * are found. + */ +class RegExpFilter : public Filter +{ +public: + /** + * Type of hotspot created by RegExpFilter. The capturedTexts() method can be used to find the text + * matched by the filter's regular expression. + */ + class HotSpot : public Filter::HotSpot + { + public: + HotSpot(int startLine, int startColumn, int endLine , int endColumn); + virtual void activate(const QString& action = QString()); + + /** Sets the captured texts associated with this hotspot */ + void setCapturedTexts(const QStringList& texts); + /** Returns the texts found by the filter when matching the filter's regular expression */ + QStringList capturedTexts() const; + private: + QStringList _capturedTexts; + }; + + /** Constructs a new regular expression filter */ + RegExpFilter(); + + /** + * Sets the regular expression which the filter searches for in blocks of text. + * + * Regular expressions which match the empty string are treated as not matching + * anything. + */ + void setRegExp(const QRegExp& text); + /** Returns the regular expression which the filter searches for in blocks of text */ + QRegExp regExp() const; + + /** + * Reimplemented to search the filter's text buffer for text matching regExp() + * + * If regexp matches the empty string, then process() will return immediately + * without finding results. + */ + virtual void process(); + +protected: + /** + * Called when a match for the regular expression is encountered. Subclasses should reimplement this + * to return custom hotspot types + */ + virtual RegExpFilter::HotSpot* newHotSpot(int startLine,int startColumn, + int endLine,int endColumn); + +private: + QRegExp _searchText; +}; + +class FilterObject; + +/** A filter which matches URLs in blocks of text */ +class UrlFilter : public RegExpFilter +{ + Q_OBJECT +public: + /** + * Hotspot type created by UrlFilter instances. The activate() method opens a web browser + * at the given URL when called. + */ + class HotSpot : public RegExpFilter::HotSpot + { + public: + HotSpot(int startLine,int startColumn,int endLine,int endColumn); + virtual ~HotSpot(); + + FilterObject* getUrlObject() const; + + virtual QList actions(); + + /** + * Open a web browser at the current URL. The url itself can be determined using + * the capturedTexts() method. + */ + virtual void activate(const QString& action = QString()); + + virtual QString tooltip() const; + private: + enum UrlType + { + StandardUrl, + Email, + Unknown + }; + UrlType urlType() const; + + FilterObject* _urlObject; + }; + + UrlFilter(); + +protected: + virtual RegExpFilter::HotSpot* newHotSpot(int,int,int,int); + +private: + + static const QRegExp FullUrlRegExp; + static const QRegExp EmailAddressRegExp; + + // combined OR of FullUrlRegExp and EmailAddressRegExp + static const QRegExp CompleteUrlRegExp; +signals: + void activated(const QUrl& url); +}; + +class FilterObject : public QObject +{ + Q_OBJECT +public: + FilterObject(Filter::HotSpot* filter) : _filter(filter) {} + + void emitActivated(const QUrl& url); +private slots: + void activated(); +private: + Filter::HotSpot* _filter; +signals: + void activated(const QUrl& url); +}; + +/** + * A chain which allows a group of filters to be processed as one. + * The chain owns the filters added to it and deletes them when the chain itself is destroyed. + * + * Use addFilter() to add a new filter to the chain. + * When new text to be filtered arrives, use addLine() to add each additional + * line of text which needs to be processed and then after adding the last line, use + * process() to cause each filter in the chain to process the text. + * + * After processing a block of text, the reset() method can be used to set the filter chain's + * internal cursor back to the first line. + * + * The hotSpotAt() method will return the first hotspot which covers a given position. + * + * The hotSpots() and hotSpotsAtLine() method return all of the hotspots in the text and on + * a given line respectively. + */ +class FilterChain : protected QList +{ +public: + virtual ~FilterChain(); + + /** Adds a new filter to the chain. The chain will delete this filter when it is destroyed */ + void addFilter(Filter* filter); + /** Removes a filter from the chain. The chain will no longer delete the filter when destroyed */ + void removeFilter(Filter* filter); + /** Returns true if the chain contains @p filter */ + bool containsFilter(Filter* filter); + /** Removes all filters from the chain */ + void clear(); + + /** Resets each filter in the chain */ + void reset(); + /** + * Processes each filter in the chain + */ + void process(); + + /** Sets the buffer for each filter in the chain to process. */ + void setBuffer(const QString* buffer , const QList* linePositions); + + /** Returns the first hotspot which occurs at @p line, @p column or 0 if no hotspot was found */ + Filter::HotSpot* hotSpotAt(int line , int column) const; + /** Returns a list of all the hotspots in all the chain's filters */ + QList hotSpots() const; + /** Returns a list of all hotspots at the given line in all the chain's filters */ + QList hotSpotsAtLine(int line) const; + +}; + +/** A filter chain which processes character images from terminal displays */ +class TerminalImageFilterChain : public FilterChain +{ +public: + TerminalImageFilterChain(); + virtual ~TerminalImageFilterChain(); + + /** + * Set the current terminal image to @p image. + * + * @param image The terminal image + * @param lines The number of lines in the terminal image + * @param columns The number of columns in the terminal image + * @param lineProperties The line properties to set for image + */ + void setImage(const Character* const image , int lines , int columns, + const QVector& lineProperties); + +private: + QString* _buffer; + QList* _linePositions; +}; + +} + +typedef Konsole::Filter Filter; + +#endif //FILTER_H diff --git a/lib/History.cpp b/lib/History.cpp new file mode 100644 index 0000000..0f9c13f --- /dev/null +++ b/lib/History.cpp @@ -0,0 +1,987 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +// Own +#include "History.h" + +// System +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// KDE +//#include +//#include + +// Reasonable line size +#define LINE_SIZE 1024 +#define KDE_lseek lseek + +using namespace Konsole; + +/* + An arbitrary long scroll. + + One can modify the scroll only by adding either cells + or newlines, but access it randomly. + + The model is that of an arbitrary wide typewriter scroll + in that the scroll is a serie of lines and each line is + a serie of cells with no overwriting permitted. + + The implementation provides arbitrary length and numbers + of cells and line/column indexed read access to the scroll + at constant costs. + +KDE4: Can we use QTemporaryFile here, instead of KTempFile? + +FIXME: some complain about the history buffer comsuming the + memory of their machines. This problem is critical + since the history does not behave gracefully in cases + where the memory is used up completely. + + I put in a workaround that should handle it problem + now gracefully. I'm not satisfied with the solution. + +FIXME: Terminating the history is not properly indicated + in the menu. We should throw a signal. + +FIXME: There is noticeable decrease in speed, also. Perhaps, + there whole feature needs to be revisited therefore. + Disadvantage of a more elaborated, say block-oriented + scheme with wrap around would be it's complexity. +*/ + +//FIXME: tempory replacement for tmpfile +// this is here one for debugging purpose. + +//#define tmpfile xTmpFile + +// History File /////////////////////////////////////////// + +/* + A Row(X) data type which allows adding elements to the end. +*/ + +HistoryFile::HistoryFile() + : ion(-1), + length(0), + fileMap(0) +{ + if (tmpFile.open()) + { + tmpFile.setAutoRemove(true); + ion = tmpFile.handle(); + } +} + +HistoryFile::~HistoryFile() +{ + if (fileMap) + unmap(); +} + +//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large, +//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time, +//to avoid this. +void HistoryFile::map() +{ + assert( fileMap == 0 ); + + fileMap = (char*)mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 ); + + //if mmap'ing fails, fall back to the read-lseek combination + if ( fileMap == MAP_FAILED ) + { + readWriteBalance = 0; + fileMap = 0; + qDebug() << __FILE__ << __LINE__ << ": mmap'ing history failed. errno = " << errno; + } +} + +void HistoryFile::unmap() +{ + int result = munmap( fileMap , length ); + assert( result == 0 ); Q_UNUSED( result ); + + fileMap = 0; +} + +bool HistoryFile::isMapped() +{ + return (fileMap != 0); +} + +void HistoryFile::add(const unsigned char* bytes, int len) +{ + if ( fileMap ) + unmap(); + + readWriteBalance++; + + int rc = 0; + + rc = KDE_lseek(ion,length,SEEK_SET); if (rc < 0) { perror("HistoryFile::add.seek"); return; } + rc = write(ion,bytes,len); if (rc < 0) { perror("HistoryFile::add.write"); return; } + length += rc; +} + +void HistoryFile::get(unsigned char* bytes, int len, int loc) +{ + //count number of get() calls vs. number of add() calls. + //If there are many more get() calls compared with add() + //calls (decided by using MAP_THRESHOLD) then mmap the log + //file to improve performance. + readWriteBalance--; + if ( !fileMap && readWriteBalance < MAP_THRESHOLD ) + map(); + + if ( fileMap ) + { + for (int i=0;i length) + fprintf(stderr,"getHist(...,%d,%d): invalid args.\n",len,loc); + rc = KDE_lseek(ion,loc,SEEK_SET); if (rc < 0) { perror("HistoryFile::get.seek"); return; } + rc = read(ion,bytes,len); if (rc < 0) { perror("HistoryFile::get.read"); return; } + } +} + +int HistoryFile::len() +{ + return length; +} + + +// History Scroll abstract base class ////////////////////////////////////// + + +HistoryScroll::HistoryScroll(HistoryType* t) + : m_histType(t) +{ +} + +HistoryScroll::~HistoryScroll() +{ + delete m_histType; +} + +bool HistoryScroll::hasScroll() +{ + return true; +} + +// History Scroll File ////////////////////////////////////// + +/* + The history scroll makes a Row(Row(Cell)) from + two history buffers. The index buffer contains + start of line positions which refere to the cells + buffer. + + Note that index[0] addresses the second line + (line #1), while the first line (line #0) starts + at 0 in cells. +*/ + +HistoryScrollFile::HistoryScrollFile(const QString &logFileName) + : HistoryScroll(new HistoryTypeFile(logFileName)), + m_logFileName(logFileName) +{ +} + +HistoryScrollFile::~HistoryScrollFile() +{ +} + +int HistoryScrollFile::getLines() +{ + return index.len() / sizeof(int); +} + +int HistoryScrollFile::getLineLen(int lineno) +{ + return (startOfLine(lineno+1) - startOfLine(lineno)) / sizeof(Character); +} + +bool HistoryScrollFile::isWrappedLine(int lineno) +{ + if (lineno>=0 && lineno <= getLines()) { + unsigned char flag; + lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char)); + return flag; + } + return false; +} + +int HistoryScrollFile::startOfLine(int lineno) +{ + if (lineno <= 0) return 0; + if (lineno <= getLines()) + { + + if (!index.isMapped()) + index.map(); + + int res; + index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int)); + return res; + } + return cells.len(); +} + +void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[]) +{ + cells.get((unsigned char*)res,count*sizeof(Character),startOfLine(lineno)+colno*sizeof(Character)); +} + +void HistoryScrollFile::addCells(const Character text[], int count) +{ + cells.add((unsigned char*)text,count*sizeof(Character)); +} + +void HistoryScrollFile::addLine(bool previousWrapped) +{ + if (index.isMapped()) + index.unmap(); + + int locn = cells.len(); + index.add((unsigned char*)&locn,sizeof(int)); + unsigned char flags = previousWrapped ? 0x01 : 0x00; + lineflags.add((unsigned char*)&flags,sizeof(unsigned char)); +} + + +// History Scroll Buffer ////////////////////////////////////// +HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount) + : HistoryScroll(new HistoryTypeBuffer(maxLineCount)) + ,_historyBuffer() + ,_maxLineCount(0) + ,_usedLines(0) + ,_head(0) +{ + setMaxNbLines(maxLineCount); +} + +HistoryScrollBuffer::~HistoryScrollBuffer() +{ + delete[] _historyBuffer; +} + +void HistoryScrollBuffer::addCellsVector(const QVector& cells) +{ + _head++; + if ( _usedLines < _maxLineCount ) + _usedLines++; + + if ( _head >= _maxLineCount ) + { + _head = 0; + } + + _historyBuffer[bufferIndex(_usedLines-1)] = cells; + _wrappedLine[bufferIndex(_usedLines-1)] = false; +} +void HistoryScrollBuffer::addCells(const Character a[], int count) +{ + HistoryLine newLine(count); + qCopy(a,a+count,newLine.begin()); + + addCellsVector(newLine); +} + +void HistoryScrollBuffer::addLine(bool previousWrapped) +{ + _wrappedLine[bufferIndex(_usedLines-1)] = previousWrapped; +} + +int HistoryScrollBuffer::getLines() +{ + return _usedLines; +} + +int HistoryScrollBuffer::getLineLen(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if ( lineNumber < _usedLines ) + { + return _historyBuffer[bufferIndex(lineNumber)].size(); + } + else + { + return 0; + } +} + +bool HistoryScrollBuffer::isWrappedLine(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount ); + + if (lineNumber < _usedLines) + { + //kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)]; + return _wrappedLine[bufferIndex(lineNumber)]; + } + else + return false; +} + +void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, Character buffer[]) +{ + if ( count == 0 ) return; + + Q_ASSERT( lineNumber < _maxLineCount ); + + if (lineNumber >= _usedLines) + { + memset(buffer, 0, count * sizeof(Character)); + return; + } + + const HistoryLine& line = _historyBuffer[bufferIndex(lineNumber)]; + + //kDebug() << "startCol " << startColumn; + //kDebug() << "line.size() " << line.size(); + //kDebug() << "count " << count; + + Q_ASSERT( startColumn <= line.size() - count ); + + memcpy(buffer, line.constData() + startColumn , count * sizeof(Character)); +} + +void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount) +{ + HistoryLine* oldBuffer = _historyBuffer; + HistoryLine* newBuffer = new HistoryLine[lineCount]; + + for ( int i = 0 ; i < qMin(_usedLines,(int)lineCount) ; i++ ) + { + newBuffer[i] = oldBuffer[bufferIndex(i)]; + } + + _usedLines = qMin(_usedLines,(int)lineCount); + _maxLineCount = lineCount; + _head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines-1; + + _historyBuffer = newBuffer; + delete[] oldBuffer; + + _wrappedLine.resize(lineCount); + dynamic_cast(m_histType)->m_nbLines = lineCount; +} + +int HistoryScrollBuffer::bufferIndex(int lineNumber) +{ + Q_ASSERT( lineNumber >= 0 ); + Q_ASSERT( lineNumber < _maxLineCount ); + Q_ASSERT( (_usedLines == _maxLineCount) || lineNumber <= _head ); + + if ( _usedLines == _maxLineCount ) + { + return (_head+lineNumber+1) % _maxLineCount; + } + else + { + return lineNumber; + } +} + + +// History Scroll None ////////////////////////////////////// + +HistoryScrollNone::HistoryScrollNone() + : HistoryScroll(new HistoryTypeNone()) +{ +} + +HistoryScrollNone::~HistoryScrollNone() +{ +} + +bool HistoryScrollNone::hasScroll() +{ + return false; +} + +int HistoryScrollNone::getLines() +{ + return 0; +} + +int HistoryScrollNone::getLineLen(int) +{ + return 0; +} + +bool HistoryScrollNone::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollNone::getCells(int, int, int, Character []) +{ +} + +void HistoryScrollNone::addCells(const Character [], int) +{ +} + +void HistoryScrollNone::addLine(bool) +{ +} + +// History Scroll BlockArray ////////////////////////////////////// + +HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size) + : HistoryScroll(new HistoryTypeBlockArray(size)) +{ + m_blockArray.setHistorySize(size); // nb. of lines. +} + +HistoryScrollBlockArray::~HistoryScrollBlockArray() +{ +} + +int HistoryScrollBlockArray::getLines() +{ + return m_lineLengths.count(); +} + +int HistoryScrollBlockArray::getLineLen(int lineno) +{ + if ( m_lineLengths.contains(lineno) ) + return m_lineLengths[lineno]; + else + return 0; +} + +bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/) +{ + return false; +} + +void HistoryScrollBlockArray::getCells(int lineno, int colno, + int count, Character res[]) +{ + if (!count) return; + + const Block *b = m_blockArray.at(lineno); + + if (!b) { + memset(res, 0, count * sizeof(Character)); // still better than random data + return; + } + + assert(((colno + count) * sizeof(Character)) < ENTRIES); + memcpy(res, b->data + (colno * sizeof(Character)), count * sizeof(Character)); +} + +void HistoryScrollBlockArray::addCells(const Character a[], int count) +{ + Block *b = m_blockArray.lastBlock(); + + if (!b) return; + + // put cells in block's data + assert((count * sizeof(Character)) < ENTRIES); + + memset(b->data, 0, ENTRIES); + + memcpy(b->data, a, count * sizeof(Character)); + b->size = count * sizeof(Character); + + size_t res = m_blockArray.newBlock(); + assert (res > 0); + Q_UNUSED( res ); + + m_lineLengths.insert(m_blockArray.getCurrent(), count); +} + +void HistoryScrollBlockArray::addLine(bool) +{ +} + +//////////////////////////////////////////////////////////////// +// Compact History Scroll ////////////////////////////////////// +//////////////////////////////////////////////////////////////// +void* CompactHistoryBlock::allocate ( size_t length ) +{ + Q_ASSERT ( length > 0 ); + if ( tail-blockStart+length > blockLength ) + return NULL; + + void* block = tail; + tail += length; + //kDebug() << "allocated " << length << " bytes at address " << block; + allocCount++; + return block; +} + +void CompactHistoryBlock::deallocate ( ) +{ + allocCount--; + Q_ASSERT ( allocCount >= 0 ); +} + +void* CompactHistoryBlockList::allocate(size_t size) +{ + CompactHistoryBlock* block; + if ( list.isEmpty() || list.last()->remaining() < size) + { + block = new CompactHistoryBlock(); + list.append ( block ); + //kDebug() << "new block created, remaining " << block->remaining() << "number of blocks=" << list.size(); + } + else + { + block = list.last(); + //kDebug() << "old block used, remaining " << block->remaining(); + } + return block->allocate(size); +} + +void CompactHistoryBlockList::deallocate(void* ptr) +{ + Q_ASSERT( !list.isEmpty()); + + int i=0; + CompactHistoryBlock *block = list.at(i); + while ( icontains(ptr) ) + { + i++; + block=list.at(i); + } + + Q_ASSERT( ideallocate(); + + if (!block->isInUse()) + { + list.removeAt(i); + delete block; + //kDebug() << "block deleted, new size = " << list.size(); + } +} + +CompactHistoryBlockList::~CompactHistoryBlockList() +{ + qDeleteAll ( list.begin(), list.end() ); + list.clear(); +} + +void* CompactHistoryLine::operator new (size_t size, CompactHistoryBlockList& blockList) +{ + return blockList.allocate(size); +} + +CompactHistoryLine::CompactHistoryLine ( const TextLine& line, CompactHistoryBlockList& bList ) + : blockList(bList), + formatLength(0) +{ + length=line.size(); + + if (line.size() > 0) { + formatLength=1; + int k=1; + + // count number of different formats in this text line + Character c = line[0]; + while ( k0) { + blockList.deallocate(text); + blockList.deallocate(formatArray); + } + blockList.deallocate(this); +} + +void CompactHistoryLine::getCharacter ( int index, Character &r ) +{ + Q_ASSERT ( index < length ); + int formatPos=0; + while ( ( formatPos+1 ) < formatLength && index >= formatArray[formatPos+1].startPos ) + formatPos++; + + r.character=text[index]; + r.rendition = formatArray[formatPos].rendition; + r.foregroundColor = formatArray[formatPos].fgColor; + r.backgroundColor = formatArray[formatPos].bgColor; +} + +void CompactHistoryLine::getCharacters ( Character* array, int length, int startColumn ) +{ + Q_ASSERT ( startColumn >= 0 && length >= 0 ); + Q_ASSERT ( startColumn+length <= ( int ) getLength() ); + + for ( int i=startColumn; i ( int ) _maxLineCount ) + { + delete lines.takeAt ( 0 ); + } + lines.append ( line ); +} + +void CompactHistoryScroll::addCells ( const Character a[], int count ) +{ + TextLine newLine ( count ); + qCopy ( a,a+count,newLine.begin() ); + addCellsVector ( newLine ); +} + +void CompactHistoryScroll::addLine ( bool previousWrapped ) +{ + CompactHistoryLine *line = lines.last(); + //kDebug() << "last line at address " << line; + line->setWrapped(previousWrapped); +} + +int CompactHistoryScroll::getLines() +{ + return lines.size(); +} + +int CompactHistoryScroll::getLineLen ( int lineNumber ) +{ + Q_ASSERT ( lineNumber >= 0 && lineNumber < lines.size() ); + CompactHistoryLine* line = lines[lineNumber]; + //kDebug() << "request for line at address " << line; + return line->getLength(); +} + + +void CompactHistoryScroll::getCells ( int lineNumber, int startColumn, int count, Character buffer[] ) +{ + if ( count == 0 ) return; + Q_ASSERT ( lineNumber < lines.size() ); + CompactHistoryLine* line = lines[lineNumber]; + Q_ASSERT ( startColumn >= 0 ); + Q_ASSERT ( (unsigned int)startColumn <= line->getLength() - count ); + line->getCharacters ( buffer, count, startColumn ); +} + +void CompactHistoryScroll::setMaxNbLines ( unsigned int lineCount ) +{ + _maxLineCount = lineCount; + + while (lines.size() > (int) lineCount) { + delete lines.takeAt(0); + } + //kDebug() << "set max lines to: " << _maxLineCount; +} + +bool CompactHistoryScroll::isWrappedLine ( int lineNumber ) +{ + Q_ASSERT ( lineNumber < lines.size() ); + return lines[lineNumber]->isWrapped(); +} + + +////////////////////////////////////////////////////////////////////// +// History Types +////////////////////////////////////////////////////////////////////// + +HistoryType::HistoryType() +{ +} + +HistoryType::~HistoryType() +{ +} + +////////////////////////////// + +HistoryTypeNone::HistoryTypeNone() +{ +} + +bool HistoryTypeNone::isEnabled() const +{ + return false; +} + +HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollNone(); +} + +int HistoryTypeNone::maximumLineCount() const +{ + return 0; +} + +////////////////////////////// + +HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size) + : m_size(size) +{ +} + +bool HistoryTypeBlockArray::isEnabled() const +{ + return true; +} + +int HistoryTypeBlockArray::maximumLineCount() const +{ + return m_size; +} + +HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const +{ + delete old; + return new HistoryScrollBlockArray(m_size); +} + + +////////////////////////////// + +HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines) + : m_nbLines(nbLines) +{ +} + +bool HistoryTypeBuffer::isEnabled() const +{ + return true; +} + +int HistoryTypeBuffer::maximumLineCount() const +{ + return m_nbLines; +} + +HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const +{ + if (old) + { + HistoryScrollBuffer *oldBuffer = dynamic_cast(old); + if (oldBuffer) + { + oldBuffer->setMaxNbLines(m_nbLines); + return oldBuffer; + } + + HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines); + int lines = old->getLines(); + int startLine = 0; + if (lines > (int) m_nbLines) + startLine = lines - m_nbLines; + + Character line[LINE_SIZE]; + for(int i = startLine; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + delete old; + return newScroll; + } + return new HistoryScrollBuffer(m_nbLines); +} + +////////////////////////////// + +HistoryTypeFile::HistoryTypeFile(const QString& fileName) + : m_fileName(fileName) +{ +} + +bool HistoryTypeFile::isEnabled() const +{ + return true; +} + +const QString& HistoryTypeFile::getFileName() const +{ + return m_fileName; +} + +HistoryScroll* HistoryTypeFile::scroll(HistoryScroll *old) const +{ + if (dynamic_cast(old)) + return old; // Unchanged. + + HistoryScroll *newScroll = new HistoryScrollFile(m_fileName); + + Character line[LINE_SIZE]; + int lines = (old != 0) ? old->getLines() : 0; + for(int i = 0; i < lines; i++) + { + int size = old->getLineLen(i); + if (size > LINE_SIZE) + { + Character *tmp_line = new Character[size]; + old->getCells(i, 0, size, tmp_line); + newScroll->addCells(tmp_line, size); + newScroll->addLine(old->isWrappedLine(i)); + delete [] tmp_line; + } + else + { + old->getCells(i, 0, size, line); + newScroll->addCells(line, size); + newScroll->addLine(old->isWrappedLine(i)); + } + } + + delete old; + return newScroll; +} + +int HistoryTypeFile::maximumLineCount() const +{ + return 0; +} + +////////////////////////////// + +CompactHistoryType::CompactHistoryType ( unsigned int nbLines ) + : m_nbLines ( nbLines ) +{ +} + +bool CompactHistoryType::isEnabled() const +{ + return true; +} + +int CompactHistoryType::maximumLineCount() const +{ + return m_nbLines; +} + +HistoryScroll* CompactHistoryType::scroll ( HistoryScroll *old ) const +{ + if ( old ) + { + CompactHistoryScroll *oldBuffer = dynamic_cast ( old ); + if ( oldBuffer ) + { + oldBuffer->setMaxNbLines ( m_nbLines ); + return oldBuffer; + } + delete old; + } + return new CompactHistoryScroll ( m_nbLines ); +} diff --git a/lib/History.h b/lib/History.h new file mode 100644 index 0000000..3f2a134 --- /dev/null +++ b/lib/History.h @@ -0,0 +1,493 @@ +/* + This file is part of Konsole, an X terminal. + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef TEHISTORY_H +#define TEHISTORY_H + +// Qt +#include +#include +#include +#include + +// KDE +//#include + +// Konsole +#include "BlockArray.h" +#include "Character.h" + +// map +#include + +namespace Konsole +{ + +#if 1 +/* + An extendable tmpfile(1) based buffer. +*/ + +class HistoryFile +{ +public: + HistoryFile(); + virtual ~HistoryFile(); + + virtual void add(const unsigned char* bytes, int len); + virtual void get(unsigned char* bytes, int len, int loc); + virtual int len(); + + //mmaps the file in read-only mode + void map(); + //un-mmaps the file + void unmap(); + //returns true if the file is mmap'ed + bool isMapped(); + + +private: + int ion; + int length; + QTemporaryFile tmpFile; + + //pointer to start of mmap'ed file data, or 0 if the file is not mmap'ed + char* fileMap; + + //incremented whenver 'add' is called and decremented whenever + //'get' is called. + //this is used to detect when a large number of lines are being read and processed from the history + //and automatically mmap the file for better performance (saves the overhead of many lseek-read calls). + int readWriteBalance; + + //when readWriteBalance goes below this threshold, the file will be mmap'ed automatically + static const int MAP_THRESHOLD = -1000; +}; +#endif + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// Abstract base class for file and buffer versions +////////////////////////////////////////////////////////////////////// +class HistoryType; + +class HistoryScroll +{ +public: + HistoryScroll(HistoryType*); + virtual ~HistoryScroll(); + + virtual bool hasScroll(); + + // access to history + virtual int getLines() = 0; + virtual int getLineLen(int lineno) = 0; + virtual void getCells(int lineno, int colno, int count, Character res[]) = 0; + virtual bool isWrappedLine(int lineno) = 0; + + // backward compatibility (obsolete) + Character getCell(int lineno, int colno) { Character res; getCells(lineno,colno,1,&res); return res; } + + // adding lines. + virtual void addCells(const Character a[], int count) = 0; + // convenience method - this is virtual so that subclasses can take advantage + // of QVector's implicit copying + virtual void addCellsVector(const QVector& cells) + { + addCells(cells.data(),cells.size()); + } + + virtual void addLine(bool previousWrapped=false) = 0; + + // + // FIXME: Passing around constant references to HistoryType instances + // is very unsafe, because those references will no longer + // be valid if the history scroll is deleted. + // + const HistoryType& getType() { return *m_histType; } + +protected: + HistoryType* m_histType; + +}; + +#if 1 + +////////////////////////////////////////////////////////////////////// +// File-based history (e.g. file log, no limitation in length) +////////////////////////////////////////////////////////////////////// + +class HistoryScrollFile : public HistoryScroll +{ +public: + HistoryScrollFile(const QString &logFileName); + virtual ~HistoryScrollFile(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +private: + int startOfLine(int lineno); + + QString m_logFileName; + HistoryFile index; // lines Row(int) + HistoryFile cells; // text Row(Character) + HistoryFile lineflags; // flags Row(unsigned char) +}; + + +////////////////////////////////////////////////////////////////////// +// Buffer-based history (limited to a fixed nb of lines) +////////////////////////////////////////////////////////////////////// +class HistoryScrollBuffer : public HistoryScroll +{ +public: + typedef QVector HistoryLine; + + HistoryScrollBuffer(unsigned int maxNbLines = 1000); + virtual ~HistoryScrollBuffer(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addCellsVector(const QVector& cells); + virtual void addLine(bool previousWrapped=false); + + void setMaxNbLines(unsigned int nbLines); + unsigned int maxNbLines() { return _maxLineCount; } + + +private: + int bufferIndex(int lineNumber); + + HistoryLine* _historyBuffer; + QBitArray _wrappedLine; + int _maxLineCount; + int _usedLines; + int _head; + + //QVector m_histBuffer; + //QBitArray m_wrappedLine; + //unsigned int m_maxNbLines; + //unsigned int m_nbLines; + //unsigned int m_arrayIndex; + //bool m_buffFilled; +}; + +/*class HistoryScrollBufferV2 : public HistoryScroll +{ +public: + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addCells(const QVector& cells); + virtual void addLine(bool previousWrapped=false); + +};*/ + +#endif + +////////////////////////////////////////////////////////////////////// +// Nothing-based history (no history :-) +////////////////////////////////////////////////////////////////////// +class HistoryScrollNone : public HistoryScroll +{ +public: + HistoryScrollNone(); + virtual ~HistoryScrollNone(); + + virtual bool hasScroll(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); +}; + +////////////////////////////////////////////////////////////////////// +// BlockArray-based history +////////////////////////////////////////////////////////////////////// +class HistoryScrollBlockArray : public HistoryScroll +{ +public: + HistoryScrollBlockArray(size_t size); + virtual ~HistoryScrollBlockArray(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addLine(bool previousWrapped=false); + +protected: + BlockArray m_blockArray; + QHash m_lineLengths; +}; + +////////////////////////////////////////////////////////////////////// +// History using compact storage +// This implementation uses a list of fixed-sized blocks +// where history lines are allocated in (avoids heap fragmentation) +////////////////////////////////////////////////////////////////////// +typedef QVector TextLine; + +class CharacterFormat +{ +public: + bool equalsFormat(const CharacterFormat &other) const { + return other.rendition==rendition && other.fgColor==fgColor && other.bgColor==bgColor; + } + + bool equalsFormat(const Character &c) const { + return c.rendition==rendition && c.foregroundColor==fgColor && c.backgroundColor==bgColor; + } + + void setFormat(const Character& c) { + rendition=c.rendition; + fgColor=c.foregroundColor; + bgColor=c.backgroundColor; + } + + CharacterColor fgColor, bgColor; + quint16 startPos; + quint8 rendition; +}; + +class CompactHistoryBlock +{ +public: + + CompactHistoryBlock(){ + blockLength = 4096*64; // 256kb + head = (quint8*) mmap(0, blockLength, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + //head = (quint8*) malloc(blockLength); + Q_ASSERT(head != MAP_FAILED); + tail = blockStart = head; + allocCount=0; + } + + virtual ~CompactHistoryBlock(){ + //free(blockStart); + munmap(blockStart, blockLength); + } + + virtual unsigned int remaining(){ return blockStart+blockLength-tail;} + virtual unsigned length() { return blockLength; } + virtual void* allocate(size_t length); + virtual bool contains(void *addr) {return addr>=blockStart && addr<(blockStart+blockLength);} + virtual void deallocate(); + virtual bool isInUse(){ return allocCount!=0; } ; + +private: + size_t blockLength; + quint8* head; + quint8* tail; + quint8* blockStart; + int allocCount; +}; + +class CompactHistoryBlockList { +public: + CompactHistoryBlockList() {}; + ~CompactHistoryBlockList(); + + void *allocate( size_t size ); + void deallocate(void *); + int length() {return list.size();} +private: + QList list; +}; + +class CompactHistoryLine +{ +public: + CompactHistoryLine(const TextLine&, CompactHistoryBlockList& blockList); + virtual ~CompactHistoryLine(); + + // custom new operator to allocate memory from custom pool instead of heap + static void *operator new( size_t size, CompactHistoryBlockList& blockList); + static void operator delete( void *) { /* do nothing, deallocation from pool is done in destructor*/ } ; + + virtual void getCharacters(Character* array, int length, int startColumn) ; + virtual void getCharacter(int index, Character &r) ; + virtual bool isWrapped() const {return wrapped;}; + virtual void setWrapped(bool isWrapped) { wrapped=isWrapped;}; + virtual unsigned int getLength() const {return length;}; + +protected: + CompactHistoryBlockList& blockList; + CharacterFormat* formatArray; + quint16 length; + quint16* text; + quint16 formatLength; + bool wrapped; +}; + +class CompactHistoryScroll : public HistoryScroll +{ + typedef QList HistoryArray; + +public: + CompactHistoryScroll(unsigned int maxNbLines = 1000); + virtual ~CompactHistoryScroll(); + + virtual int getLines(); + virtual int getLineLen(int lineno); + virtual void getCells(int lineno, int colno, int count, Character res[]); + virtual bool isWrappedLine(int lineno); + + virtual void addCells(const Character a[], int count); + virtual void addCellsVector(const TextLine& cells); + virtual void addLine(bool previousWrapped=false); + + void setMaxNbLines(unsigned int nbLines); + unsigned int maxNbLines() const { return _maxLineCount; } + +private: + bool hasDifferentColors(const TextLine& line) const; + HistoryArray lines; + CompactHistoryBlockList blockList; + + unsigned int _maxLineCount; +}; + +////////////////////////////////////////////////////////////////////// +// History type +////////////////////////////////////////////////////////////////////// + +class HistoryType +{ +public: + HistoryType(); + virtual ~HistoryType(); + + /** + * Returns true if the history is enabled ( can store lines of output ) + * or false otherwise. + */ + virtual bool isEnabled() const = 0; + /** + * Returns true if the history size is unlimited. + */ + bool isUnlimited() const { return maximumLineCount() == 0; } + /** + * Returns the maximum number of lines which this history type + * can store or 0 if the history can store an unlimited number of lines. + */ + virtual int maximumLineCount() const = 0; + + virtual HistoryScroll* scroll(HistoryScroll *) const = 0; +}; + +class HistoryTypeNone : public HistoryType +{ +public: + HistoryTypeNone(); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; +}; + +class HistoryTypeBlockArray : public HistoryType +{ +public: + HistoryTypeBlockArray(size_t size); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + size_t m_size; +}; + +#if 1 +class HistoryTypeFile : public HistoryType +{ +public: + HistoryTypeFile(const QString& fileName=QString()); + + virtual bool isEnabled() const; + virtual const QString& getFileName() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + QString m_fileName; +}; + + +class HistoryTypeBuffer : public HistoryType +{ + friend class HistoryScrollBuffer; + +public: + HistoryTypeBuffer(unsigned int nbLines); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + unsigned int m_nbLines; +}; + +class CompactHistoryType : public HistoryType +{ +public: + CompactHistoryType(unsigned int size); + + virtual bool isEnabled() const; + virtual int maximumLineCount() const; + + virtual HistoryScroll* scroll(HistoryScroll *) const; + +protected: + unsigned int m_nbLines; +}; + + +#endif + +} + +#endif // TEHISTORY_H diff --git a/lib/HistorySearch.cpp b/lib/HistorySearch.cpp new file mode 100644 index 0000000..05bedf3 --- /dev/null +++ b/lib/HistorySearch.cpp @@ -0,0 +1,157 @@ +/* + Copyright 2013 Christian Surlykke + + 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. +*/ +#include +#include +#include + +#include "TerminalCharacterDecoder.h" +#include "Emulation.h" +#include "HistorySearch.h" + +HistorySearch::HistorySearch(EmulationPtr emulation, QRegExp regExp, + bool forwards, int startColumn, int startLine, + QObject* parent) : +QObject(parent), +m_emulation(emulation), +m_regExp(regExp), +m_forwards(forwards), +m_startColumn(startColumn), +m_startLine(startLine) { +} + +HistorySearch::~HistorySearch() { +} + +void HistorySearch::search() { + bool found = false; + + if (! m_regExp.isEmpty()) + { + if (m_forwards) { + found = search(m_startColumn, m_startLine, -1, m_emulation->lineCount()) || search(0, 0, m_startColumn, m_startLine); + } else { + found = search(0, 0, m_startColumn, m_startLine) || search(m_startColumn, m_startLine, -1, m_emulation->lineCount()); + } + + if (found) { + emit matchFound(m_foundStartColumn, m_foundStartLine, m_foundEndColumn, m_foundEndLine); + } + else { + emit noMatchFound(); + } + } + + deleteLater(); +} + +bool HistorySearch::search(int startColumn, int startLine, int endColumn, int endLine) { + qDebug() << "search from" << startColumn << "," << startLine + << "to" << endColumn << "," << endLine; + + int linesRead = 0; + int linesToRead = endLine - startLine + 1; + + qDebug() << "linesToRead:" << linesToRead; + + // We read process history from (and including) startLine to (and including) endLine in + // blocks of at most 10K lines so that we do not use unhealthy amounts of memory + int blockSize; + while ((blockSize = qMin(10000, linesToRead - linesRead)) > 0) { + + QString string; + QTextStream searchStream(&string); + PlainTextDecoder decoder; + decoder.begin(&searchStream); + decoder.setRecordLinePositions(true); + + // Calculate lines to read and read them + int blockStartLine = m_forwards ? startLine + linesRead : endLine - linesRead - blockSize + 1; + int chunkEndLine = blockStartLine + blockSize - 1; + m_emulation->writeToStream(&decoder, blockStartLine, chunkEndLine); + + // We search between startColumn in the first line of the string and endColumn in the last + // line of the string. First we calculate the position (in the string) of endColumn in the + // last line of the string + int endPosition; + + // The String that Emulator.writeToStream produces has a newline at the end, and so ends with an + // empty line - we ignore that. + int numberOfLinesInString = decoder.linePositions().size() - 1; + if (numberOfLinesInString > 0 && endColumn > -1 ) + { + endPosition = decoder.linePositions().at(numberOfLinesInString - 1) + endColumn; + } + else + { + endPosition = string.size(); + } + + // So now we can log for m_regExp in the string between startColumn and endPosition + int matchStart; + if (m_forwards) + { + matchStart = string.indexOf(m_regExp, startColumn); + if (matchStart >= endPosition) + matchStart = -1; + } + else + { + matchStart = string.lastIndexOf(m_regExp, endPosition - 1); + if (matchStart < startColumn) + matchStart = -1; + } + + if (matchStart > -1) + { + int matchEnd = matchStart + m_regExp.matchedLength() - 1; + qDebug() << "Found in string from" << matchStart << "to" << matchEnd; + + // Translate startPos and endPos to startColum, startLine, endColumn and endLine in history. + int startLineNumberInString = findLineNumberInString(decoder.linePositions(), matchStart); + m_foundStartColumn = matchStart - decoder.linePositions().at(startLineNumberInString); + m_foundStartLine = startLineNumberInString + startLine + linesRead; + + int endLineNumberInString = findLineNumberInString(decoder.linePositions(), matchEnd); + m_foundEndColumn = matchEnd - decoder.linePositions().at(endLineNumberInString); + m_foundEndLine = endLineNumberInString + startLine + linesRead; + + qDebug() << "m_foundStartColumn" << m_foundStartColumn + << "m_foundStartLine" << m_foundEndLine + << "m_foundEndColumn" << m_foundEndColumn + << "m_foundEndLine" << m_foundEndLine; + + return true; + } + + + linesRead += blockSize; + } + + qDebug() << "Not found"; + return false; +} + + +int HistorySearch::findLineNumberInString(QList linePositions, int position) { + int lineNum = 0; + while (lineNum + 1 < linePositions.size() && linePositions[lineNum + 1] <= position) + lineNum++; + + return lineNum; +} \ No newline at end of file diff --git a/lib/HistorySearch.h b/lib/HistorySearch.h new file mode 100644 index 0000000..c315299 --- /dev/null +++ b/lib/HistorySearch.h @@ -0,0 +1,70 @@ +/* + Copyright 2013 Christian Surlykke + + 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. +*/ +#ifndef TASK_H +#define TASK_H + +#include +#include +#include + +#include +#include + +#include "Emulation.h" +#include "TerminalCharacterDecoder.h" + +using namespace Konsole; + +typedef QPointer EmulationPtr; + +class HistorySearch : public QObject +{ + Q_OBJECT + +public: + explicit HistorySearch(EmulationPtr emulation, QRegExp regExp, bool forwards, + int startColumn, int startLine, QObject* parent); + + ~HistorySearch(); + + void search(); + +signals: + void matchFound(int startColumn, int startLine, int endColumn, int endLine); + void noMatchFound(); + +private: + bool search(int startColumn, int startLine, int endColumn, int endLine); + int findLineNumberInString(QList linePositions, int position); + + + EmulationPtr m_emulation; + QRegExp m_regExp; + bool m_forwards; + int m_startColumn; + int m_startLine; + + int m_foundStartColumn; + int m_foundStartLine; + int m_foundEndColumn; + int m_foundEndLine; +}; + +#endif /* TASK_H */ + diff --git a/lib/KeyboardTranslator.cpp b/lib/KeyboardTranslator.cpp new file mode 100644 index 0000000..7530421 --- /dev/null +++ b/lib/KeyboardTranslator.cpp @@ -0,0 +1,894 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +// Own +#include "KeyboardTranslator.h" + +// System +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +#include "tools.h" + +// KDE +//#include +//#include +//#include + +using namespace Konsole; + + +const QByteArray KeyboardTranslatorManager::defaultTranslatorText( +"keyboard \"Fallback Key Translator\"\n" +"key Tab : \"\\t\"" +); + +KeyboardTranslatorManager::KeyboardTranslatorManager() + : _haveLoadedAll(false) +{ +} +KeyboardTranslatorManager::~KeyboardTranslatorManager() +{ + qDeleteAll(_translators); +} +QString KeyboardTranslatorManager::findTranslatorPath(const QString& name) +{ + return QString(get_kb_layout_dir() + name + ".keytab"); + //return KGlobal::dirs()->findResource("data","konsole/"+name+".keytab"); +} + +void KeyboardTranslatorManager::findTranslators() +{ + QDir dir(get_kb_layout_dir()); + QStringList filters; + filters << "*.keytab"; + dir.setNameFilters(filters); + QStringList list = dir.entryList(filters); + list = dir.entryList(filters); +// QStringList list = KGlobal::dirs()->findAllResources("data", +// "konsole/*.keytab", +// KStandardDirs::NoDuplicates); + + // add the name of each translator to the list and associated + // the name with a null pointer to indicate that the translator + // has not yet been loaded from disk + QStringListIterator listIter(list); + while (listIter.hasNext()) + { + QString translatorPath = listIter.next(); + + QString name = QFileInfo(translatorPath).baseName(); + + if ( !_translators.contains(name) ) + _translators.insert(name,0); + } + + _haveLoadedAll = true; +} + +const KeyboardTranslator* KeyboardTranslatorManager::findTranslator(const QString& name) +{ + if ( name.isEmpty() ) + return defaultTranslator(); + + if ( _translators.contains(name) && _translators[name] != 0 ) + return _translators[name]; + + KeyboardTranslator* translator = loadTranslator(name); + + if ( translator != 0 ) + _translators[name] = translator; + else if ( !name.isEmpty() ) + qDebug() << "Unable to load translator" << name; + + return translator; +} + +bool KeyboardTranslatorManager::saveTranslator(const KeyboardTranslator* translator) +{ +qDebug() << "KeyboardTranslatorManager::saveTranslator" << "unimplemented"; +Q_UNUSED(translator); +#if 0 + const QString path = KGlobal::dirs()->saveLocation("data","konsole/")+translator->name() + +".keytab"; + + //kDebug() << "Saving translator to" << path; + + QFile destination(path); + if (!destination.open(QIODevice::WriteOnly | QIODevice::Text)) + { + qDebug() << "Unable to save keyboard translation:" + << destination.errorString(); + return false; + } + + { + KeyboardTranslatorWriter writer(&destination); + writer.writeHeader(translator->description()); + + QListIterator iter(translator->entries()); + while ( iter.hasNext() ) + writer.writeEntry(iter.next()); + } + + destination.close(); +#endif + return true; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(const QString& name) +{ + const QString& path = findTranslatorPath(name); + + QFile source(path); + if (name.isEmpty() || !source.open(QIODevice::ReadOnly | QIODevice::Text)) + return 0; + + return loadTranslator(&source,name); +} + +const KeyboardTranslator* KeyboardTranslatorManager::defaultTranslator() +{ + // Try to find the default.keytab file if it exists, otherwise + // fall back to the hard-coded one + const KeyboardTranslator* translator = findTranslator("default"); + if (!translator) + { + QBuffer textBuffer; + textBuffer.setData(defaultTranslatorText); + textBuffer.open(QIODevice::ReadOnly); + translator = loadTranslator(&textBuffer,"fallback"); + } + return translator; +} + +KeyboardTranslator* KeyboardTranslatorManager::loadTranslator(QIODevice* source,const QString& name) +{ + KeyboardTranslator* translator = new KeyboardTranslator(name); + KeyboardTranslatorReader reader(source); + translator->setDescription( reader.description() ); + while ( reader.hasNextEntry() ) + translator->addEntry(reader.nextEntry()); + + source->close(); + + if ( !reader.parseError() ) + { + return translator; + } + else + { + delete translator; + return 0; + } +} + +KeyboardTranslatorWriter::KeyboardTranslatorWriter(QIODevice* destination) +: _destination(destination) +{ + Q_ASSERT( destination && destination->isWritable() ); + + _writer = new QTextStream(_destination); +} +KeyboardTranslatorWriter::~KeyboardTranslatorWriter() +{ + delete _writer; +} +void KeyboardTranslatorWriter::writeHeader( const QString& description ) +{ + *_writer << "keyboard \"" << description << '\"' << '\n'; +} +void KeyboardTranslatorWriter::writeEntry( const KeyboardTranslator::Entry& entry ) +{ + QString result; + if ( entry.command() != KeyboardTranslator::NoCommand ) + result = entry.resultToString(); + else + result = '\"' + entry.resultToString() + '\"'; + + *_writer << "key " << entry.conditionToString() << " : " << result << '\n'; +} + + +// each line of the keyboard translation file is one of: +// +// - keyboard "name" +// - key KeySequence : "characters" +// - key KeySequence : CommandName +// +// KeySequence begins with the name of the key ( taken from the Qt::Key enum ) +// and is followed by the keyboard modifiers and state flags ( with + or - in front +// of each modifier or flag to indicate whether it is required ). All keyboard modifiers +// and flags are optional, if a particular modifier or state is not specified it is +// assumed not to be a part of the sequence. The key sequence may contain whitespace +// +// eg: "key Up+Shift : scrollLineUp" +// "key Next-Shift : "\E[6~" +// +// (lines containing only whitespace are ignored, parseLine assumes that comments have +// already been removed) +// + +KeyboardTranslatorReader::KeyboardTranslatorReader( QIODevice* source ) + : _source(source) + , _hasNext(false) +{ + // read input until we find the description + while ( _description.isEmpty() && !source->atEnd() ) + { + QList tokens = tokenize( QString(source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::TitleKeyword ) + _description = tokens[1].text.toUtf8(); + } + // read first entry (if any) + readNext(); +} +void KeyboardTranslatorReader::readNext() +{ + // find next entry + while ( !_source->atEnd() ) + { + const QList& tokens = tokenize( QString(_source->readLine()) ); + if ( !tokens.isEmpty() && tokens.first().type == Token::KeyKeyword ) + { + KeyboardTranslator::States flags = KeyboardTranslator::NoState; + KeyboardTranslator::States flagMask = KeyboardTranslator::NoState; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + Qt::KeyboardModifiers modifierMask = Qt::NoModifier; + + int keyCode = Qt::Key_unknown; + + decodeSequence(tokens[1].text.toLower(), + keyCode, + modifiers, + modifierMask, + flags, + flagMask); + + KeyboardTranslator::Command command = KeyboardTranslator::NoCommand; + QByteArray text; + + // get text or command + if ( tokens[2].type == Token::OutputText ) + { + text = tokens[2].text.toLocal8Bit(); + } + else if ( tokens[2].type == Token::Command ) + { + // identify command + if (!parseAsCommand(tokens[2].text,command)) + qDebug() << "Command" << tokens[2].text << "not understood."; + } + + KeyboardTranslator::Entry newEntry; + newEntry.setKeyCode( keyCode ); + newEntry.setState( flags ); + newEntry.setStateMask( flagMask ); + newEntry.setModifiers( modifiers ); + newEntry.setModifierMask( modifierMask ); + newEntry.setText( text ); + newEntry.setCommand( command ); + + _nextEntry = newEntry; + + _hasNext = true; + + return; + } + } + + _hasNext = false; +} + +bool KeyboardTranslatorReader::parseAsCommand(const QString& text,KeyboardTranslator::Command& command) +{ + if ( text.compare("erase",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::EraseCommand; + else if ( text.compare("scrollpageup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageUpCommand; + else if ( text.compare("scrollpagedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollPageDownCommand; + else if ( text.compare("scrolllineup",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineUpCommand; + else if ( text.compare("scrolllinedown",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLineDownCommand; + else if ( text.compare("scrolllock",Qt::CaseInsensitive) == 0 ) + command = KeyboardTranslator::ScrollLockCommand; + else + return false; + + return true; +} + +bool KeyboardTranslatorReader::decodeSequence(const QString& text, + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& flags, + KeyboardTranslator::States& flagMask) +{ + bool isWanted = true; + bool endOfItem = false; + QString buffer; + + Qt::KeyboardModifiers tempModifiers = modifiers; + Qt::KeyboardModifiers tempModifierMask = modifierMask; + KeyboardTranslator::States tempFlags = flags; + KeyboardTranslator::States tempFlagMask = flagMask; + + for ( int i = 0 ; i < text.count() ; i++ ) + { + const QChar& ch = text[i]; + bool isFirstLetter = i == 0; + bool isLastLetter = ( i == text.count()-1 ); + endOfItem = true; + if ( ch.isLetterOrNumber() ) + { + endOfItem = false; + buffer.append(ch); + } else if ( isFirstLetter ) + { + buffer.append(ch); + } + + if ( (endOfItem || isLastLetter) && !buffer.isEmpty() ) + { + Qt::KeyboardModifier itemModifier = Qt::NoModifier; + int itemKeyCode = 0; + KeyboardTranslator::State itemFlag = KeyboardTranslator::NoState; + + if ( parseAsModifier(buffer,itemModifier) ) + { + tempModifierMask |= itemModifier; + + if ( isWanted ) + tempModifiers |= itemModifier; + } + else if ( parseAsStateFlag(buffer,itemFlag) ) + { + tempFlagMask |= itemFlag; + + if ( isWanted ) + tempFlags |= itemFlag; + } + else if ( parseAsKeyCode(buffer,itemKeyCode) ) + keyCode = itemKeyCode; + else + qDebug() << "Unable to parse key binding item:" << buffer; + + buffer.clear(); + } + + // check if this is a wanted / not-wanted flag and update the + // state ready for the next item + if ( ch == '+' ) + isWanted = true; + else if ( ch == '-' ) + isWanted = false; + } + + modifiers = tempModifiers; + modifierMask = tempModifierMask; + flags = tempFlags; + flagMask = tempFlagMask; + + return true; +} + +bool KeyboardTranslatorReader::parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier) +{ + if ( item == "shift" ) + modifier = Qt::ShiftModifier; + else if ( item == "ctrl" || item == "control" ) + modifier = Qt::ControlModifier; + else if ( item == "alt" ) + modifier = Qt::AltModifier; + else if ( item == "meta" ) + modifier = Qt::MetaModifier; + else if ( item == "keypad" ) + modifier = Qt::KeypadModifier; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsStateFlag(const QString& item , KeyboardTranslator::State& flag) +{ + if ( item == "appcukeys" || item == "appcursorkeys" ) + flag = KeyboardTranslator::CursorKeysState; + else if ( item == "ansi" ) + flag = KeyboardTranslator::AnsiState; + else if ( item == "newline" ) + flag = KeyboardTranslator::NewLineState; + else if ( item == "appscreen" ) + flag = KeyboardTranslator::AlternateScreenState; + else if ( item == "anymod" || item == "anymodifier" ) + flag = KeyboardTranslator::AnyModifierState; + else if ( item == "appkeypad" ) + flag = KeyboardTranslator::ApplicationKeypadState; + else + return false; + + return true; +} +bool KeyboardTranslatorReader::parseAsKeyCode(const QString& item , int& keyCode) +{ + QKeySequence sequence = QKeySequence::fromString(item); + if ( !sequence.isEmpty() ) + { + keyCode = sequence[0]; + + if ( sequence.count() > 1 ) + { + qDebug() << "Unhandled key codes in sequence: " << item; + } + } + // additional cases implemented for backwards compatibility with KDE 3 + else if ( item == "prior" ) + keyCode = Qt::Key_PageUp; + else if ( item == "next" ) + keyCode = Qt::Key_PageDown; + else + return false; + + return true; +} + +QString KeyboardTranslatorReader::description() const +{ + return _description; +} +bool KeyboardTranslatorReader::hasNextEntry() +{ + return _hasNext; +} +KeyboardTranslator::Entry KeyboardTranslatorReader::createEntry( const QString& condition , + const QString& result ) +{ + QString entryString("keyboard \"temporary\"\nkey "); + entryString.append(condition); + entryString.append(" : "); + + // if 'result' is the name of a command then the entry result will be that command, + // otherwise the result will be treated as a string to echo when the key sequence + // specified by 'condition' is pressed + KeyboardTranslator::Command command; + if (parseAsCommand(result,command)) + entryString.append(result); + else + entryString.append('\"' + result + '\"'); + + QByteArray array = entryString.toUtf8(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + KeyboardTranslatorReader reader(&buffer); + + KeyboardTranslator::Entry entry; + if ( reader.hasNextEntry() ) + entry = reader.nextEntry(); + + return entry; +} + +KeyboardTranslator::Entry KeyboardTranslatorReader::nextEntry() +{ + Q_ASSERT( _hasNext ); + KeyboardTranslator::Entry entry = _nextEntry; + readNext(); + return entry; +} +bool KeyboardTranslatorReader::parseError() +{ + return false; +} +QList KeyboardTranslatorReader::tokenize(const QString& line) +{ + QString text = line; + + // remove comments + bool inQuotes = false; + int commentPos = -1; + for (int i=text.length()-1;i>=0;i--) + { + QChar ch = text[i]; + if (ch == '\"') + inQuotes = !inQuotes; + else if (ch == '#' && !inQuotes) + commentPos = i; + } + if (commentPos != -1) + text.remove(commentPos,text.length()); + + text = text.simplified(); + + // title line: keyboard "title" + static QRegExp title("keyboard\\s+\"(.*)\""); + // key line: key KeySequence : "output" + // key line: key KeySequence : command + static QRegExp key("key\\s+([\\w\\+\\s\\-\\*\\.]+)\\s*:\\s*(\"(.*)\"|\\w+)"); + + QList list; + if ( text.isEmpty() ) + { + return list; + } + + if ( title.exactMatch(text) ) + { + Token titleToken = { Token::TitleKeyword , QString() }; + Token textToken = { Token::TitleText , title.capturedTexts()[1] }; + + list << titleToken << textToken; + } + else if ( key.exactMatch(text) ) + { + Token keyToken = { Token::KeyKeyword , QString() }; + Token sequenceToken = { Token::KeySequence , key.capturedTexts()[1].remove(' ') }; + + list << keyToken << sequenceToken; + + if ( key.capturedTexts()[3].isEmpty() ) + { + // capturedTexts()[2] is a command + Token commandToken = { Token::Command , key.capturedTexts()[2] }; + list << commandToken; + } + else + { + // capturedTexts()[3] is the output string + Token outputToken = { Token::OutputText , key.capturedTexts()[3] }; + list << outputToken; + } + } + else + { + qDebug() << "Line in keyboard translator file could not be understood:" << text; + } + + return list; +} + +QList KeyboardTranslatorManager::allTranslators() +{ + if ( !_haveLoadedAll ) + { + findTranslators(); + } + + return _translators.keys(); +} + +KeyboardTranslator::Entry::Entry() +: _keyCode(0) +, _modifiers(Qt::NoModifier) +, _modifierMask(Qt::NoModifier) +, _state(NoState) +, _stateMask(NoState) +, _command(NoCommand) +{ +} + +bool KeyboardTranslator::Entry::operator==(const Entry& rhs) const +{ + return _keyCode == rhs._keyCode && + _modifiers == rhs._modifiers && + _modifierMask == rhs._modifierMask && + _state == rhs._state && + _stateMask == rhs._stateMask && + _command == rhs._command && + _text == rhs._text; +} + +bool KeyboardTranslator::Entry::matches(int keyCode , + Qt::KeyboardModifiers modifiers, + States testState) const +{ + if ( _keyCode != keyCode ) + return false; + + if ( (modifiers & _modifierMask) != (_modifiers & _modifierMask) ) + return false; + + // if modifiers is non-zero, the 'any modifier' state is implicit + if ( modifiers != 0 ) + testState |= AnyModifierState; + + if ( (testState & _stateMask) != (_state & _stateMask) ) + return false; + + // special handling for the 'Any Modifier' state, which checks for the presence of + // any or no modifiers. In this context, the 'keypad' modifier does not count. + bool anyModifiersSet = modifiers != 0 && modifiers != Qt::KeypadModifier; + bool wantAnyModifier = _state & KeyboardTranslator::AnyModifierState; + if ( _stateMask & KeyboardTranslator::AnyModifierState ) + { + if ( wantAnyModifier != anyModifiersSet ) + return false; + } + + return true; +} +QByteArray KeyboardTranslator::Entry::escapedText(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray result(text(expandWildCards,modifiers)); + + for ( int i = 0 ; i < result.count() ; i++ ) + { + char ch = result[i]; + char replacement = 0; + + switch ( ch ) + { + case 27 : replacement = 'E'; break; + case 8 : replacement = 'b'; break; + case 12 : replacement = 'f'; break; + case 9 : replacement = 't'; break; + case 13 : replacement = 'r'; break; + case 10 : replacement = 'n'; break; + default: + // any character which is not printable is replaced by an equivalent + // \xhh escape sequence (where 'hh' are the corresponding hex digits) + if ( !QChar(ch).isPrint() ) + replacement = 'x'; + } + + if ( replacement == 'x' ) + { + result.replace(i,1,"\\x"+QByteArray(1,ch).toHex()); + } else if ( replacement != 0 ) + { + result.remove(i,1); + result.insert(i,'\\'); + result.insert(i+1,replacement); + } + } + + return result; +} +QByteArray KeyboardTranslator::Entry::unescape(const QByteArray& input) const +{ + QByteArray result(input); + + for ( int i = 0 ; i < result.count()-1 ; i++ ) + { + + QByteRef ch = result[i]; + if ( ch == '\\' ) + { + char replacement[2] = {0,0}; + int charsToRemove = 2; + bool escapedChar = true; + + switch ( result[i+1] ) + { + case 'E' : replacement[0] = 27; break; + case 'b' : replacement[0] = 8 ; break; + case 'f' : replacement[0] = 12; break; + case 't' : replacement[0] = 9 ; break; + case 'r' : replacement[0] = 13; break; + case 'n' : replacement[0] = 10; break; + case 'x' : + { + // format is \xh or \xhh where 'h' is a hexadecimal + // digit from 0-9 or A-F which should be replaced + // with the corresponding character value + char hexDigits[3] = {0}; + + if ( (i < result.count()-2) && isxdigit(result[i+2]) ) + hexDigits[0] = result[i+2]; + if ( (i < result.count()-3) && isxdigit(result[i+3]) ) + hexDigits[1] = result[i+3]; + + unsigned charValue = 0; + sscanf(hexDigits,"%x",&charValue); + + replacement[0] = (char)charValue; + charsToRemove = 2 + strlen(hexDigits); + } + break; + default: + escapedChar = false; + } + + if ( escapedChar ) + result.replace(i,charsToRemove,replacement); + } + } + + return result; +} + +void KeyboardTranslator::Entry::insertModifier( QString& item , int modifier ) const +{ + if ( !(modifier & _modifierMask) ) + return; + + if ( modifier & _modifiers ) + item += '+'; + else + item += '-'; + + if ( modifier == Qt::ShiftModifier ) + item += "Shift"; + else if ( modifier == Qt::ControlModifier ) + item += "Ctrl"; + else if ( modifier == Qt::AltModifier ) + item += "Alt"; + else if ( modifier == Qt::MetaModifier ) + item += "Meta"; + else if ( modifier == Qt::KeypadModifier ) + item += "KeyPad"; +} +void KeyboardTranslator::Entry::insertState( QString& item , int state ) const +{ + if ( !(state & _stateMask) ) + return; + + if ( state & _state ) + item += '+' ; + else + item += '-' ; + + if ( state == KeyboardTranslator::AlternateScreenState ) + item += "AppScreen"; + else if ( state == KeyboardTranslator::NewLineState ) + item += "NewLine"; + else if ( state == KeyboardTranslator::AnsiState ) + item += "Ansi"; + else if ( state == KeyboardTranslator::CursorKeysState ) + item += "AppCursorKeys"; + else if ( state == KeyboardTranslator::AnyModifierState ) + item += "AnyModifier"; + else if ( state == KeyboardTranslator::ApplicationKeypadState ) + item += "AppKeypad"; +} +QString KeyboardTranslator::Entry::resultToString(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + if ( !_text.isEmpty() ) + return escapedText(expandWildCards,modifiers); + else if ( _command == EraseCommand ) + return "Erase"; + else if ( _command == ScrollPageUpCommand ) + return "ScrollPageUp"; + else if ( _command == ScrollPageDownCommand ) + return "ScrollPageDown"; + else if ( _command == ScrollLineUpCommand ) + return "ScrollLineUp"; + else if ( _command == ScrollLineDownCommand ) + return "ScrollLineDown"; + else if ( _command == ScrollLockCommand ) + return "ScrollLock"; + + return QString(); +} +QString KeyboardTranslator::Entry::conditionToString() const +{ + QString result = QKeySequence(_keyCode).toString(); + + insertModifier( result , Qt::ShiftModifier ); + insertModifier( result , Qt::ControlModifier ); + insertModifier( result , Qt::AltModifier ); + insertModifier( result , Qt::MetaModifier ); + insertModifier( result , Qt::KeypadModifier ); + + insertState( result , KeyboardTranslator::AlternateScreenState ); + insertState( result , KeyboardTranslator::NewLineState ); + insertState( result , KeyboardTranslator::AnsiState ); + insertState( result , KeyboardTranslator::CursorKeysState ); + insertState( result , KeyboardTranslator::AnyModifierState ); + insertState( result , KeyboardTranslator::ApplicationKeypadState ); + + return result; +} + +KeyboardTranslator::KeyboardTranslator(const QString& name) +: _name(name) +{ +} + +void KeyboardTranslator::setDescription(const QString& description) +{ + _description = description; +} +QString KeyboardTranslator::description() const +{ + return _description; +} +void KeyboardTranslator::setName(const QString& name) +{ + _name = name; +} +QString KeyboardTranslator::name() const +{ + return _name; +} + +QList KeyboardTranslator::entries() const +{ + return _entries.values(); +} + +void KeyboardTranslator::addEntry(const Entry& entry) +{ + const int keyCode = entry.keyCode(); + _entries.insert(keyCode,entry); +} +void KeyboardTranslator::replaceEntry(const Entry& existing , const Entry& replacement) +{ + if ( !existing.isNull() ) + _entries.remove(existing.keyCode(),existing); + _entries.insert(replacement.keyCode(),replacement); +} +void KeyboardTranslator::removeEntry(const Entry& entry) +{ + _entries.remove(entry.keyCode(),entry); +} +KeyboardTranslator::Entry KeyboardTranslator::findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state) const +{ + foreach(const Entry& entry, _entries.values(keyCode)) + { + if ( entry.matches(keyCode,modifiers,state) ) + return entry; + } + return Entry(); // entry not found +} +void KeyboardTranslatorManager::addTranslator(KeyboardTranslator* translator) +{ + _translators.insert(translator->name(),translator); + + if ( !saveTranslator(translator) ) + qDebug() << "Unable to save translator" << translator->name() + << "to disk."; +} +bool KeyboardTranslatorManager::deleteTranslator(const QString& name) +{ + Q_ASSERT( _translators.contains(name) ); + + // locate and delete + QString path = findTranslatorPath(name); + if ( QFile::remove(path) ) + { + _translators.remove(name); + return true; + } + else + { + qDebug() << "Failed to remove translator - " << path; + return false; + } +} +//K_GLOBAL_STATIC( KeyboardTranslatorManager , theKeyboardTranslatorManager ) +KeyboardTranslatorManager* KeyboardTranslatorManager::theKeyboardTranslatorManager = 0; +KeyboardTranslatorManager* KeyboardTranslatorManager::instance() +{ + if (! theKeyboardTranslatorManager) + theKeyboardTranslatorManager = new KeyboardTranslatorManager(); + return theKeyboardTranslatorManager; +} diff --git a/lib/KeyboardTranslator.h b/lib/KeyboardTranslator.h new file mode 100644 index 0000000..15a2980 --- /dev/null +++ b/lib/KeyboardTranslator.h @@ -0,0 +1,587 @@ +/* + This source file is part of Konsole, a terminal emulator. + + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +#ifndef KEYBOARDTRANSLATOR_H +#define KEYBOARDTRANSLATOR_H + +// Qt +#include +#include +#include +#include +#include + +// Konsole +//#include "konsole_export.h" +#define KONSOLEPRIVATE_EXPORT + +class QIODevice; +class QTextStream; + +namespace Konsole +{ + +/** + * A convertor which maps between key sequences pressed by the user and the + * character strings which should be sent to the terminal and commands + * which should be invoked when those character sequences are pressed. + * + * Konsole supports multiple keyboard translators, allowing the user to + * specify the character sequences which are sent to the terminal + * when particular key sequences are pressed. + * + * A key sequence is defined as a key code, associated keyboard modifiers + * (Shift,Ctrl,Alt,Meta etc.) and state flags which indicate the state + * which the terminal must be in for the key sequence to apply. + */ +class KeyboardTranslator +{ +public: + /** + * The meaning of a particular key sequence may depend upon the state which + * the terminal emulation is in. Therefore findEntry() may return a different + * Entry depending upon the state flags supplied. + * + * This enum describes the states which may be associated with with a particular + * entry in the keyboard translation entry. + */ + enum State + { + /** Indicates that no special state is active */ + NoState = 0, + /** + * TODO More documentation + */ + NewLineState = 1, + /** + * Indicates that the terminal is in 'Ansi' mode. + * TODO: More documentation + */ + AnsiState = 2, + /** + * TODO More documentation + */ + CursorKeysState = 4, + /** + * Indicates that the alternate screen ( typically used by interactive programs + * such as screen or vim ) is active + */ + AlternateScreenState = 8, + /** Indicates that any of the modifier keys is active. */ + AnyModifierState = 16, + /** Indicates that the numpad is in application mode. */ + ApplicationKeypadState = 32 + }; + Q_DECLARE_FLAGS(States,State) + + /** + * This enum describes commands which are associated with particular key sequences. + */ + enum Command + { + /** Indicates that no command is associated with this command sequence */ + NoCommand = 0, + /** TODO Document me */ + SendCommand = 1, + /** Scroll the terminal display up one page */ + ScrollPageUpCommand = 2, + /** Scroll the terminal display down one page */ + ScrollPageDownCommand = 4, + /** Scroll the terminal display up one line */ + ScrollLineUpCommand = 8, + /** Scroll the terminal display down one line */ + ScrollLineDownCommand = 16, + /** Toggles scroll lock mode */ + ScrollLockCommand = 32, + /** Echos the operating system specific erase character. */ + EraseCommand = 64 + }; + Q_DECLARE_FLAGS(Commands,Command) + + /** + * Represents an association between a key sequence pressed by the user + * and the character sequence and commands associated with it for a particular + * KeyboardTranslator. + */ + class Entry + { + public: + /** + * Constructs a new entry for a keyboard translator. + */ + Entry(); + + /** + * Returns true if this entry is null. + * This is true for newly constructed entries which have no properties set. + */ + bool isNull() const; + + /** Returns the commands associated with this entry */ + Command command() const; + /** Sets the command associated with this entry. */ + void setCommand(Command command); + + /** + * Returns the character sequence associated with this entry, optionally replacing + * wildcard '*' characters with numbers to indicate the keyboard modifiers being pressed. + * + * TODO: The numbers used to replace '*' characters are taken from the Konsole/KDE 3 code. + * Document them. + * + * @param expandWildCards Specifies whether wild cards (occurrences of the '*' character) in + * the entry should be replaced with a number to indicate the modifier keys being pressed. + * + * @param modifiers The keyboard modifiers being pressed. + */ + QByteArray text(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Sets the character sequence associated with this entry */ + void setText(const QByteArray& text); + + /** + * Returns the character sequence associated with this entry, + * with any non-printable characters replaced with escape sequences. + * + * eg. \\E for Escape, \\t for tab, \\n for new line. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QByteArray escapedText(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** Returns the character code ( from the Qt::Key enum ) associated with this entry */ + int keyCode() const; + /** Sets the character code associated with this entry */ + void setKeyCode(int keyCode); + + /** + * Returns a bitwise-OR of the enabled keyboard modifiers associated with this entry. + * If a modifier is set in modifierMask() but not in modifiers(), this means that the entry + * only matches when that modifier is NOT pressed. + * + * If a modifier is not set in modifierMask() then the entry matches whether the modifier + * is pressed or not. + */ + Qt::KeyboardModifiers modifiers() const; + + /** Returns the keyboard modifiers which are valid in this entry. See modifiers() */ + Qt::KeyboardModifiers modifierMask() const; + + /** See modifiers() */ + void setModifiers( Qt::KeyboardModifiers modifiers ); + /** See modifierMask() and modifiers() */ + void setModifierMask( Qt::KeyboardModifiers modifiers ); + + /** + * Returns a bitwise-OR of the enabled state flags associated with this entry. + * If flag is set in stateMask() but not in state(), this means that the entry only + * matches when the terminal is NOT in that state. + * + * If a state is not set in stateMask() then the entry matches whether the terminal + * is in that state or not. + */ + States state() const; + + /** Returns the state flags which are valid in this entry. See state() */ + States stateMask() const; + + /** See state() */ + void setState( States state ); + /** See stateMask() */ + void setStateMask( States mask ); + + /** + * Returns the key code and modifiers associated with this entry + * as a QKeySequence + */ + //QKeySequence keySequence() const; + + /** + * Returns this entry's conditions ( ie. its key code, modifier and state criteria ) + * as a string. + */ + QString conditionToString() const; + + /** + * Returns this entry's result ( ie. its command or character sequence ) + * as a string. + * + * @param expandWildCards See text() + * @param modifiers See text() + */ + QString resultToString(bool expandWildCards = false, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; + + /** + * Returns true if this entry matches the given key sequence, specified + * as a combination of @p keyCode , @p modifiers and @p state. + */ + bool matches( int keyCode , + Qt::KeyboardModifiers modifiers , + States flags ) const; + + bool operator==(const Entry& rhs) const; + + private: + void insertModifier( QString& item , int modifier ) const; + void insertState( QString& item , int state ) const; + QByteArray unescape(const QByteArray& text) const; + + int _keyCode; + Qt::KeyboardModifiers _modifiers; + Qt::KeyboardModifiers _modifierMask; + States _state; + States _stateMask; + + Command _command; + QByteArray _text; + }; + + /** Constructs a new keyboard translator with the given @p name */ + KeyboardTranslator(const QString& name); + + //KeyboardTranslator(const KeyboardTranslator& other); + + /** Returns the name of this keyboard translator */ + QString name() const; + + /** Sets the name of this keyboard translator */ + void setName(const QString& name); + + /** Returns the descriptive name of this keyboard translator */ + QString description() const; + + /** Sets the descriptive name of this keyboard translator */ + void setDescription(const QString& description); + + /** + * Looks for an entry in this keyboard translator which matches the given + * key code, keyboard modifiers and state flags. + * + * Returns the matching entry if found or a null Entry otherwise ( ie. + * entry.isNull() will return true ) + * + * @param keyCode A key code from the Qt::Key enum + * @param modifiers A combination of modifiers + * @param state Optional flags which specify the current state of the terminal + */ + Entry findEntry(int keyCode , + Qt::KeyboardModifiers modifiers , + States state = NoState) const; + + /** + * Adds an entry to this keyboard translator's table. Entries can be looked up according + * to their key sequence using findEntry() + */ + void addEntry(const Entry& entry); + + /** + * Replaces an entry in the translator. If the @p existing entry is null, + * then this is equivalent to calling addEntry(@p replacement) + */ + void replaceEntry(const Entry& existing , const Entry& replacement); + + /** + * Removes an entry from the table. + */ + void removeEntry(const Entry& entry); + + /** Returns a list of all entries in the translator. */ + QList entries() const; + +private: + + QMultiHash _entries; // entries in this keyboard translation, + // entries are indexed according to + // their keycode + QString _name; + QString _description; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::States) +Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::Commands) + +/** + * Parses the contents of a Keyboard Translator (.keytab) file and + * returns the entries found in it. + * + * Usage example: + * + * @code + * QFile source( "/path/to/keytab" ); + * source.open( QIODevice::ReadOnly ); + * + * KeyboardTranslator* translator = new KeyboardTranslator( "name-of-translator" ); + * + * KeyboardTranslatorReader reader(source); + * while ( reader.hasNextEntry() ) + * translator->addEntry(reader.nextEntry()); + * + * source.close(); + * + * if ( !reader.parseError() ) + * { + * // parsing succeeded, do something with the translator + * } + * else + * { + * // parsing failed + * } + * @endcode + */ +class KeyboardTranslatorReader +{ +public: + /** Constructs a new reader which parses the given @p source */ + KeyboardTranslatorReader( QIODevice* source ); + + /** + * Returns the description text. + * TODO: More documentation + */ + QString description() const; + + /** Returns true if there is another entry in the source stream */ + bool hasNextEntry(); + /** Returns the next entry found in the source stream */ + KeyboardTranslator::Entry nextEntry(); + + /** + * Returns true if an error occurred whilst parsing the input or + * false if no error occurred. + */ + bool parseError(); + + /** + * Parses a condition and result string for a translator entry + * and produces a keyboard translator entry. + * + * The condition and result strings are in the same format as in + */ + static KeyboardTranslator::Entry createEntry( const QString& condition , + const QString& result ); +private: + struct Token + { + enum Type + { + TitleKeyword, + TitleText, + KeyKeyword, + KeySequence, + Command, + OutputText + }; + Type type; + QString text; + }; + QList tokenize(const QString&); + void readNext(); + bool decodeSequence(const QString& , + int& keyCode, + Qt::KeyboardModifiers& modifiers, + Qt::KeyboardModifiers& modifierMask, + KeyboardTranslator::States& state, + KeyboardTranslator::States& stateFlags); + + static bool parseAsModifier(const QString& item , Qt::KeyboardModifier& modifier); + static bool parseAsStateFlag(const QString& item , KeyboardTranslator::State& state); + static bool parseAsKeyCode(const QString& item , int& keyCode); + static bool parseAsCommand(const QString& text , KeyboardTranslator::Command& command); + + QIODevice* _source; + QString _description; + KeyboardTranslator::Entry _nextEntry; + bool _hasNext; +}; + +/** Writes a keyboard translation to disk. */ +class KeyboardTranslatorWriter +{ +public: + /** + * Constructs a new writer which saves data into @p destination. + * The caller is responsible for closing the device when writing is complete. + */ + KeyboardTranslatorWriter(QIODevice* destination); + ~KeyboardTranslatorWriter(); + + /** + * Writes the header for the keyboard translator. + * @param description Description of the keyboard translator. + */ + void writeHeader( const QString& description ); + /** Writes a translator entry. */ + void writeEntry( const KeyboardTranslator::Entry& entry ); + +private: + QIODevice* _destination; + QTextStream* _writer; +}; + +/** + * Manages the keyboard translations available for use by terminal sessions, + * see KeyboardTranslator. + */ +class KONSOLEPRIVATE_EXPORT KeyboardTranslatorManager +{ +public: + /** + * Constructs a new KeyboardTranslatorManager and loads the list of + * available keyboard translations. + * + * The keyboard translations themselves are not loaded until they are + * first requested via a call to findTranslator() + */ + KeyboardTranslatorManager(); + ~KeyboardTranslatorManager(); + + /** + * Adds a new translator. If a translator with the same name + * already exists, it will be replaced by the new translator. + * + * TODO: More documentation. + */ + void addTranslator(KeyboardTranslator* translator); + + /** + * Deletes a translator. Returns true on successful deletion or false otherwise. + * + * TODO: More documentation + */ + bool deleteTranslator(const QString& name); + + /** Returns the default translator for Konsole. */ + const KeyboardTranslator* defaultTranslator(); + + /** + * Returns the keyboard translator with the given name or 0 if no translator + * with that name exists. + * + * The first time that a translator with a particular name is requested, + * the on-disk .keyboard file is loaded and parsed. + */ + const KeyboardTranslator* findTranslator(const QString& name); + /** + * Returns a list of the names of available keyboard translators. + * + * The first time this is called, a search for available + * translators is started. + */ + QList allTranslators(); + + /** Returns the global KeyboardTranslatorManager instance. */ + static KeyboardTranslatorManager* instance(); + +private: + static const QByteArray defaultTranslatorText; + + void findTranslators(); // locate the available translators + KeyboardTranslator* loadTranslator(const QString& name); // loads the translator + // with the given name + KeyboardTranslator* loadTranslator(QIODevice* device,const QString& name); + + bool saveTranslator(const KeyboardTranslator* translator); + QString findTranslatorPath(const QString& name); + + QHash _translators; // maps translator-name -> KeyboardTranslator + // instance + bool _haveLoadedAll; + + static KeyboardTranslatorManager * theKeyboardTranslatorManager; +}; + +inline int KeyboardTranslator::Entry::keyCode() const { return _keyCode; } +inline void KeyboardTranslator::Entry::setKeyCode(int keyCode) { _keyCode = keyCode; } + +inline void KeyboardTranslator::Entry::setModifiers( Qt::KeyboardModifiers modifier ) +{ + _modifiers = modifier; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifiers() const { return _modifiers; } + +inline void KeyboardTranslator::Entry::setModifierMask( Qt::KeyboardModifiers mask ) +{ + _modifierMask = mask; +} +inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifierMask() const { return _modifierMask; } + +inline bool KeyboardTranslator::Entry::isNull() const +{ + return ( *this == Entry() ); +} + +inline void KeyboardTranslator::Entry::setCommand( Command command ) +{ + _command = command; +} +inline KeyboardTranslator::Command KeyboardTranslator::Entry::command() const { return _command; } + +inline void KeyboardTranslator::Entry::setText( const QByteArray& text ) +{ + _text = unescape(text); +} +inline int oneOrZero(int value) +{ + return value ? 1 : 0; +} +inline QByteArray KeyboardTranslator::Entry::text(bool expandWildCards,Qt::KeyboardModifiers modifiers) const +{ + QByteArray expandedText = _text; + + if (expandWildCards) + { + int modifierValue = 1; + modifierValue += oneOrZero(modifiers & Qt::ShiftModifier); + modifierValue += oneOrZero(modifiers & Qt::AltModifier) << 1; + modifierValue += oneOrZero(modifiers & Qt::ControlModifier) << 2; + + for (int i=0;i<_text.length();i++) + { + if (expandedText[i] == '*') + expandedText[i] = '0' + modifierValue; + } + } + + return expandedText; +} + +inline void KeyboardTranslator::Entry::setState( States state ) +{ + _state = state; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::state() const { return _state; } + +inline void KeyboardTranslator::Entry::setStateMask( States stateMask ) +{ + _stateMask = stateMask; +} +inline KeyboardTranslator::States KeyboardTranslator::Entry::stateMask() const { return _stateMask; } + +} + +Q_DECLARE_METATYPE(Konsole::KeyboardTranslator::Entry) +Q_DECLARE_METATYPE(const Konsole::KeyboardTranslator*) + +#endif // KEYBOARDTRANSLATOR_H + diff --git a/lib/LineFont.h b/lib/LineFont.h new file mode 100644 index 0000000..9c080ea --- /dev/null +++ b/lib/LineFont.h @@ -0,0 +1,21 @@ +// WARNING: Autogenerated by "fontembedder ./linefont.src". +// You probably do not want to hand-edit this! + +static const quint32 LineChars[] = { + 0x00007c00, 0x000fffe0, 0x00421084, 0x00e739ce, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00427000, 0x004e7380, 0x00e77800, 0x00ef7bc0, + 0x00421c00, 0x00439ce0, 0x00e73c00, 0x00e7bde0, 0x00007084, 0x000e7384, 0x000079ce, 0x000f7bce, + 0x00001c84, 0x00039ce4, 0x00003dce, 0x0007bdee, 0x00427084, 0x004e7384, 0x004279ce, 0x00e77884, + 0x00e779ce, 0x004f7bce, 0x00ef7bc4, 0x00ef7bce, 0x00421c84, 0x00439ce4, 0x00423dce, 0x00e73c84, + 0x00e73dce, 0x0047bdee, 0x00e7bde4, 0x00e7bdee, 0x00427c00, 0x0043fce0, 0x004e7f80, 0x004fffe0, + 0x004fffe0, 0x00e7fde0, 0x006f7fc0, 0x00efffe0, 0x00007c84, 0x0003fce4, 0x000e7f84, 0x000fffe4, + 0x00007dce, 0x0007fdee, 0x000f7fce, 0x000fffee, 0x00427c84, 0x0043fce4, 0x004e7f84, 0x004fffe4, + 0x00427dce, 0x00e77c84, 0x00e77dce, 0x0047fdee, 0x004e7fce, 0x00e7fde4, 0x00ef7f84, 0x004fffee, + 0x00efffe4, 0x00e7fdee, 0x00ef7fce, 0x00efffee, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000f83e0, 0x00a5294a, 0x004e1380, 0x00a57800, 0x00ad0bc0, 0x004390e0, 0x00a53c00, 0x00a5a1e0, + 0x000e1384, 0x0000794a, 0x000f0b4a, 0x000390e4, 0x00003d4a, 0x0007a16a, 0x004e1384, 0x00a5694a, + 0x00ad2b4a, 0x004390e4, 0x00a52d4a, 0x00a5a16a, 0x004f83e0, 0x00a57c00, 0x00ad83e0, 0x000f83e4, + 0x00007d4a, 0x000f836a, 0x004f93e4, 0x00a57d4a, 0x00ad836a, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00001c00, 0x00001084, 0x00007000, 0x00421000, + 0x00039ce0, 0x000039ce, 0x000e7380, 0x00e73800, 0x000e7f80, 0x00e73884, 0x0003fce0, 0x004239ce +}; diff --git a/lib/LineFont.src b/lib/LineFont.src new file mode 100644 index 0000000..6835253 --- /dev/null +++ b/lib/LineFont.src @@ -0,0 +1,786 @@ +#2500: single horizontal line +2500 + + +----- + + + +#2501: triple horizontal line +2501 + +----- +----- +----- + + +#2502: single vertical line +2502 + | + | + | + | + | + +#2503: triple vertical line +2503 + ||| + ||| + ||| + ||| + ||| + +#2504-250B are dashed - not handled + +#250C: top-left corner (lines on bottom + right) +250C + + + .-- + | + | + +#250D: as above, but top line triple-width +250D + + .-- + .-- + |-- + | + +#250E: now the vert line triple-width +250E + + + ..-- + ||| + ||| + +#250F: and now both lines triple-width +250F + + .___ + |.-- + ||._ + ||| + +#2510: top-right corner +2510 + + +--. + | + | + +2511 + +==. +==. +==| + | + +2512 + + +==.. + ||| + ||| + +2513 + +===. +==.| +=.|| + ||| + +#2514: bottom-left corner +2514 + | + | + .== + + + +2515 + | + |== + |== + === + + + +2516 + ||| + ||| + |.== + + + +2517 + ||| + ||.= + |.== + .=== + + +#2518: bottm-right corner +2518 + | + | +==. + + + +2519 + | +==| +==| +=== + + + +251A + ||| + ||| +==== + + + +251B + ||| +=.|| +==.| +===. + + +#251C: Join of vertical line and one from the right +251C + | + | + |== + | + | + +251D + | + |== + |== + |== + | + +251E + ||| + ||| + ||== + | + | + +251F + | + | + ||== + ||| + ||| + + +2520 + ||| + ||| + ||== + ||| + ||| + +2521 + ||| + |||= + ||== + .|== + | + +2522 + | + .|== + ||== + |||= + ||| + +2523 + ||| + ||.= + ||== + ||.= + ||| + +#2524: Join of vertical line and one from the left +2524 + | + | +==| + | + | + +2525 + | +==| +==| +==| + | + +2526 + ||| + ||| +==+| + | + | + +2527 + | + | +==+| + ||| + ||| + +2528 + ||| + ||| +==+| + ||| + ||| + +2529 + ||| +=+|| +==+| +===+ + | + +252A + | +=+|| +==+| +===+ + ||| + +252B + ||| +=+|| +==+| +=+|| + ||| + +#252C: horizontal line joined to from below +252C + + +===== + | + | + +252D + +=== +==|== +==| + | + +252E + + === +==|== + |== + | + +252F + +==+== +==|== +==|== + | + +2530 + +===== +===== +==|== + | + +2531 + +===| +==||= +=||| + ||| + +2532 + + |=== +=||== + ||== + || + +2533 + +===== +==|== +=+|+= + ||| + +#2534: bottom line, connected to from top +2534 + | + | +===== + + + +2535 + | +==| +===== +=== + + +2536 + | + |== +===== + === + + +2537 + | +==|== +===== +===== + + +2538 + ||| + ||| +===== + + + +2539 + ||| +==|| +===== +===| + + + +253A + ||| + ||== +=|=== + |=== + + +253B + ||| +==|== +===== +===== + + +#253C: vertical + horizontal lines intersecting +253C + | + | +===== + | + | + +253D + | +==| +===== +==| + | + +253E + | + |== +===== + |== + | + +253F + | +==|== +===== +==|== + | + +2540 + ||| + ||| +===== + | + | + +2541 + | + | +===== + ||| + ||| + +2542 + ||| + ||| +===== + ||| + ||| + +2543 + ||| +=||| +===== +==|+ + | + +2544 + ||| + ||== +===== + |== + | + +2545 + | +==|+ +===== +=||| + ||| + +2546 + | + |== +===== + ||== + ||| + +2547 + ||| +=|||= +===== +=|||= + | + +2548 + | +=|||= +===== +=|||= + ||| + +2549 + ||| +=||| +===== +=||| + ||| + +254A + ||| + |||= +===== + |||= + ||| + +254B + ||| +=|||= +===== +=|||= + ||| + +#254C-254F are dashed +2550 + +_____ + +_____ + + +2551 + | | + | | + | | + | | + | | + +2552 + + |-- + | + |-- + | + +2553 + + + ---- + | | + | | + +2554 + + +--- + | + + +- + | | + +2555 + +--+ + | +--+ + | + +2556 + + +-+-+ + | | + | | + +2557 + +---+ + | +-+ | + | | + +2558 + | + +-- + | + +-- + +2559 + | | + | | + +-+- + + + +255A + | | + | +- + | + +--- + + +255B + | +--+ + | +--+ + + +255C + | | + | | +-+-+ + + +255D + | | +-+ | + | +---+ + + +255E + | + +-- + | + +-- + | + +255F + | | + | | + | +- + | | + | | + +2560 + | | + | +- + | | + | +- + | | + +2561 + | +--+ + | +--+ + | + +2562 + | | + | | +-+ + + | | + | | + +2563 + | | +-+ | + | +-+ | + | | + +2564 + +----- + +--+-- + | + +2565 + + +-+-+- + | | + | | + +2566 + +----- + +-+ +- + | | + +2567 + | +--+-- + +----- + + +2568 + | | + | | +-+-+- + + + +2569 + | | +-+ +- + +----- + + +256A + | +--+-- + | +--+-- + | + +256B + | | + | | +-+-+- + | | + | | + +256C + | | +-+ +- + +-+ +- + | | + +#256F-2570 are curly, +#2571-2573 are slashes and X + +2574 + + +___ + + + +2575 + | + | + | + + + +2576 + + + ___ + + + +2577 + + + | + | + | + +2578 + +___ +___ +___ + + +2579 + ||| + ||| + ||| + + + +257A + + ___ + ___ + ___ + + +257B + + + ||| + ||| + ||| + +257C + + ___ +_____ + ___ + + +257D + | + | + ||| + ||| + ||| + +257E + +___ +_____ +___ + + +257F + ||| + ||| + ||| + | + | diff --git a/lib/Pty.cpp b/lib/Pty.cpp new file mode 100644 index 0000000..c986d93 --- /dev/null +++ b/lib/Pty.cpp @@ -0,0 +1,316 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of Konsole, an X terminal. + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +// Own +#include "Pty.h" + +// System +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include + +#include "kpty.h" +#include "kptydevice.h" + +using namespace Konsole; + +void Pty::setWindowSize(int lines, int cols) +{ + _windowColumns = cols; + _windowLines = lines; + + if (pty()->masterFd() >= 0) + pty()->setWinSize(lines, cols); +} +QSize Pty::windowSize() const +{ + return QSize(_windowColumns,_windowLines); +} + +void Pty::setFlowControlEnabled(bool enable) +{ + _xonXoff = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); + if (!pty()->tcSetAttr(&ttmode)) + qWarning() << "Unable to set terminal attributes."; + } +} +bool Pty::flowControlEnabled() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + return ttmode.c_iflag & IXOFF && + ttmode.c_iflag & IXON; + } + qWarning() << "Unable to get flow control status, terminal not connected."; + return false; +} + +void Pty::setUtf8Mode(bool enable) +{ +#ifdef IUTF8 // XXX not a reasonable place to check it. + _utf8 = enable; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!enable) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; + if (!pty()->tcSetAttr(&ttmode)) + qWarning() << "Unable to set terminal attributes."; + } +#endif +} + +void Pty::setErase(char erase) +{ + _eraseChar = erase; + + if (pty()->masterFd() >= 0) + { + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + ttmode.c_cc[VERASE] = erase; + if (!pty()->tcSetAttr(&ttmode)) + qWarning() << "Unable to set terminal attributes."; + } +} + +char Pty::erase() const +{ + if (pty()->masterFd() >= 0) + { + struct ::termios ttyAttributes; + pty()->tcGetAttr(&ttyAttributes); + return ttyAttributes.c_cc[VERASE]; + } + + return _eraseChar; +} + +void Pty::addEnvironmentVariables(const QStringList& environment) +{ + QListIterator iter(environment); + while (iter.hasNext()) + { + QString pair = iter.next(); + + // split on the first '=' character + int pos = pair.indexOf('='); + + if ( pos >= 0 ) + { + QString variable = pair.left(pos); + QString value = pair.mid(pos+1); + + setEnv(variable,value); + } + } +} + +int Pty::start(const QString& program, + const QStringList& programArguments, + const QStringList& environment, + ulong winid, + bool addToUtmp + //const QString& dbusService, + //const QString& dbusSession + ) +{ + clearProgram(); + + // For historical reasons, the first argument in programArguments is the + // name of the program to execute, so create a list consisting of all + // but the first argument to pass to setProgram() + Q_ASSERT(programArguments.count() >= 1); + setProgram(program.toLatin1(),programArguments.mid(1)); + + addEnvironmentVariables(environment); + + setEnv("WINDOWID", QString::number(winid)); + + // unless the LANGUAGE environment variable has been set explicitly + // set it to a null string + // this fixes the problem where KCatalog sets the LANGUAGE environment + // variable during the application's startup to something which + // differs from LANG,LC_* etc. and causes programs run from + // the terminal to display messages in the wrong language + // + // this can happen if LANG contains a language which KDE + // does not have a translation for + // + // BR:149300 + setEnv("LANGUAGE",QString(),false /* do not overwrite existing value if any */); + + setUseUtmp(addToUtmp); + + struct ::termios ttmode; + pty()->tcGetAttr(&ttmode); + if (!_xonXoff) + ttmode.c_iflag &= ~(IXOFF | IXON); + else + ttmode.c_iflag |= (IXOFF | IXON); +#ifdef IUTF8 // XXX not a reasonable place to check it. + if (!_utf8) + ttmode.c_iflag &= ~IUTF8; + else + ttmode.c_iflag |= IUTF8; +#endif + + if (_eraseChar != 0) + ttmode.c_cc[VERASE] = _eraseChar; + + if (!pty()->tcSetAttr(&ttmode)) + qWarning() << "Unable to set terminal attributes."; + + pty()->setWinSize(_windowLines, _windowColumns); + + KProcess::start(); + + if (!waitForStarted()) + return -1; + + return 0; +} + +void Pty::setWriteable(bool writeable) +{ + struct stat sbuf; + stat(pty()->ttyName(), &sbuf); + if (writeable) + chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP); + else + chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP|S_IWOTH)); +} + +Pty::Pty(int masterFd, QObject* parent) + : KPtyProcess(masterFd,parent) +{ + init(); +} +Pty::Pty(QObject* parent) + : KPtyProcess(parent) +{ + init(); +} +void Pty::init() +{ + _windowColumns = 0; + _windowLines = 0; + _eraseChar = 0; + _xonXoff = true; + _utf8 =true; + + connect(pty(), SIGNAL(readyRead()) , this , SLOT(dataReceived())); + setPtyChannels(KPtyProcess::AllChannels); +} + +Pty::~Pty() +{ +} + +void Pty::sendData(const char* data, int length) +{ + if (!length) + return; + + if (!pty()->write(data,length)) + { + qWarning() << "Pty::doSendJobs - Could not send input data to terminal process."; + return; + } +} + +void Pty::dataReceived() +{ + QByteArray data = pty()->readAll(); + emit receivedData(data.constData(),data.count()); +} + +void Pty::lockPty(bool lock) +{ + Q_UNUSED(lock); + +// TODO: Support for locking the Pty + //if (lock) + //suspend(); + //else + //resume(); +} + +int Pty::foregroundProcessGroup() const +{ + int pid = tcgetpgrp(pty()->masterFd()); + + if ( pid != -1 ) + { + return pid; + } + + return 0; +} + +void Pty::setupChildProcess() +{ + KPtyProcess::setupChildProcess(); + + // reset all signal handlers + // this ensures that terminal applications respond to + // signals generated via key sequences such as Ctrl+C + // (which sends SIGINT) + struct sigaction action; + sigset_t sigset; + sigemptyset(&action.sa_mask); + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + for (int signal=1;signal < NSIG; signal++) { + sigaction(signal,&action,0L); + sigaddset(&sigset, signal); + } + sigprocmask(SIG_UNBLOCK, &sigset, NULL); +} diff --git a/lib/Pty.h b/lib/Pty.h new file mode 100644 index 0000000..0d28fe7 --- /dev/null +++ b/lib/Pty.h @@ -0,0 +1,209 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of Konsole, KDE's terminal emulator. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef PTY_H +#define PTY_H + +// Qt +#include +#include +#include +#include + +// KDE +#include "kptyprocess.h" + +namespace Konsole { + +/** + * The Pty class is used to start the terminal process, + * send data to it, receive data from it and manipulate + * various properties of the pseudo-teletype interface + * used to communicate with the process. + * + * To use this class, construct an instance and connect + * to the sendData slot and receivedData signal to + * send data to or receive data from the process. + * + * To start the terminal process, call the start() method + * with the program name and appropriate arguments. + */ +class Pty: public KPtyProcess +{ +Q_OBJECT + + public: + + /** + * Constructs a new Pty. + * + * Connect to the sendData() slot and receivedData() signal to prepare + * for sending and receiving data from the terminal process. + * + * To start the terminal process, call the run() method with the + * name of the program to start and appropriate arguments. + */ + explicit Pty(QObject* parent = 0); + + /** + * Construct a process using an open pty master. + * See KPtyProcess::KPtyProcess() + */ + explicit Pty(int ptyMasterFd, QObject* parent = 0); + + ~Pty(); + + /** + * Starts the terminal process. + * + * Returns 0 if the process was started successfully or non-zero + * otherwise. + * + * @param program Path to the program to start + * @param arguments Arguments to pass to the program being started + * @param environment A list of key=value pairs which will be added + * to the environment for the new process. At the very least this + * should include an assignment for the TERM environment variable. + * @param winid Specifies the value of the WINDOWID environment variable + * in the process's environment. + * @param addToUtmp Specifies whether a utmp entry should be created for + * the pty used. See K3Process::setUsePty() + * @param dbusService Specifies the value of the KONSOLE_DBUS_SERVICE + * environment variable in the process's environment. + * @param dbusSession Specifies the value of the KONSOLE_DBUS_SESSION + * environment variable in the process's environment. + */ + int start( const QString& program, + const QStringList& arguments, + const QStringList& environment, + ulong winid, + bool addToUtmp + ); + + /** TODO: Document me */ + void setWriteable(bool writeable); + + /** + * Enables or disables Xon/Xoff flow control. The flow control setting + * may be changed later by a terminal application, so flowControlEnabled() + * may not equal the value of @p on in the previous call to setFlowControlEnabled() + */ + void setFlowControlEnabled(bool on); + + /** Queries the terminal state and returns true if Xon/Xoff flow control is enabled. */ + bool flowControlEnabled() const; + + /** + * Sets the size of the window (in lines and columns of characters) + * used by this teletype. + */ + void setWindowSize(int lines, int cols); + + /** Returns the size of the window used by this teletype. See setWindowSize() */ + QSize windowSize() const; + + /** TODO Document me */ + void setErase(char erase); + + /** */ + char erase() const; + + /** + * Returns the process id of the teletype's current foreground + * process. This is the process which is currently reading + * input sent to the terminal via. sendData() + * + * If there is a problem reading the foreground process group, + * 0 will be returned. + */ + int foregroundProcessGroup() const; + + public slots: + + /** + * Put the pty into UTF-8 mode on systems which support it. + */ + void setUtf8Mode(bool on); + + /** + * Suspend or resume processing of data from the standard + * output of the terminal process. + * + * See K3Process::suspend() and K3Process::resume() + * + * @param lock If true, processing of output is suspended, + * otherwise processing is resumed. + */ + void lockPty(bool lock); + + /** + * Sends data to the process currently controlling the + * teletype ( whose id is returned by foregroundProcessGroup() ) + * + * @param buffer Pointer to the data to send. + * @param length Length of @p buffer. + */ + void sendData(const char* buffer, int length); + + signals: + + /** + * Emitted when a new block of data is received from + * the teletype. + * + * @param buffer Pointer to the data received. + * @param length Length of @p buffer + */ + void receivedData(const char* buffer, int length); + + protected: + void setupChildProcess(); + + private slots: + // called when data is received from the terminal process + void dataReceived(); + + private: + void init(); + + // takes a list of key=value pairs and adds them + // to the environment for the process + void addEnvironmentVariables(const QStringList& environment); + + int _windowColumns; + int _windowLines; + char _eraseChar; + bool _xonXoff; + bool _utf8; +}; + +} + +#endif // PTY_H diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..1801361 --- /dev/null +++ b/lib/README @@ -0,0 +1,7 @@ +lib.pro is a *.pro-file for qmake + +It produces static lib (libqtermwidget.a) only. +For creating shared lib (*.so) uncomment "dll" in "CONFIG" line in *.pro-file + +Library was tested both with HAVE_POSIX_OPENPT and HAVE_GETPT precompiler directives, +defined in "DEFINES" line. You should select variant which would be correct for your system. \ No newline at end of file diff --git a/lib/Screen.cpp b/lib/Screen.cpp new file mode 100644 index 0000000..acc5f81 --- /dev/null +++ b/lib/Screen.cpp @@ -0,0 +1,1360 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. + */ + +// Own +#include "Screen.h" + +// Standard +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include + +// KDE +//#include + +// Konsole +#include "konsole_wcwidth.h" +#include "TerminalCharacterDecoder.h" + +using namespace Konsole; + +//FIXME: this is emulation specific. Use false for xterm, true for ANSI. +//FIXME: see if we can get this from terminfo. +#define BS_CLEARS false + +//Macro to convert x,y position on screen to position within an image. +// +//Originally the image was stored as one large contiguous block of +//memory, so a position within the image could be represented as an +//offset from the beginning of the block. For efficiency reasons this +//is no longer the case. +//Many internal parts of this class still use this representation for parameters and so on, +//notably moveImage() and clearImage(). +//This macro converts from an X,Y position into an image offset. +#ifndef loc +#define loc(X,Y) ((Y)*columns+(X)) +#endif + + +Character Screen::defaultChar = Character(' ', + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), + CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), + DEFAULT_RENDITION); + +//#define REVERSE_WRAPPED_LINES // for wrapped line debug + + Screen::Screen(int l, int c) +: lines(l), + columns(c), + screenLines(new ImageLine[lines+1] ), + _scrolledLines(0), + _droppedLines(0), + history(new HistoryScrollNone()), + cuX(0), cuY(0), + currentRendition(0), + _topMargin(0), _bottomMargin(0), + selBegin(0), selTopLeft(0), selBottomRight(0), + blockSelectionMode(false), + effectiveForeground(CharacterColor()), effectiveBackground(CharacterColor()), effectiveRendition(0), + lastPos(-1) +{ + lineProperties.resize(lines+1); + for (int i=0;i _bottomMargin ? lines-1 : _bottomMargin; + cuX = qMin(columns-1,cuX); // nowrap! + cuY = qMin(stop,cuY+n); +} + +void Screen::cursorLeft(int n) + //=CUB +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-n); +} + +void Screen::cursorRight(int n) + //=CUF +{ + if (n == 0) n = 1; // Default + cuX = qMin(columns-1,cuX+n); +} + +void Screen::setMargins(int top, int bot) + //=STBM +{ + if (top == 0) top = 1; // Default + if (bot == 0) bot = lines; // Default + top = top - 1; // Adjust to internal lineno + bot = bot - 1; // Adjust to internal lineno + if ( !( 0 <= top && top < bot && bot < lines ) ) + { //Debug()<<" setRegion("< 0) + cuY -= 1; +} + +void Screen::nextLine() + //=NEL +{ + toStartOfLine(); index(); +} + +void Screen::eraseChars(int n) +{ + if (n == 0) n = 1; // Default + int p = qMax(0,qMin(cuX+n-1,columns-1)); + clearImage(loc(cuX,cuY),loc(p,cuY),' '); +} + +void Screen::deleteChars(int n) +{ + Q_ASSERT( n >= 0 ); + + // always delete at least one char + if (n == 0) + n = 1; + + // if cursor is beyond the end of the line there is nothing to do + if ( cuX >= screenLines[cuY].count() ) + return; + + if ( cuX+n > screenLines[cuY].count() ) + n = screenLines[cuY].count() - cuX; + + Q_ASSERT( n >= 0 ); + Q_ASSERT( cuX+n <= screenLines[cuY].count() ); + + screenLines[cuY].remove(cuX,n); +} + +void Screen::insertChars(int n) +{ + if (n == 0) n = 1; // Default + + if ( screenLines[cuY].size() < cuX ) + screenLines[cuY].resize(cuX); + + screenLines[cuY].insert(cuX,n,' '); + + if ( screenLines[cuY].count() > columns ) + screenLines[cuY].resize(columns); +} + +void Screen::deleteLines(int n) +{ + if (n == 0) n = 1; // Default + scrollUp(cuY,n); +} + +void Screen::insertLines(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(cuY,n); +} + +void Screen::setMode(int m) +{ + currentModes[m] = true; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = _topMargin; break; //FIXME: home + } +} + +void Screen::resetMode(int m) +{ + currentModes[m] = false; + switch(m) + { + case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home + } +} + +void Screen::saveMode(int m) +{ + savedModes[m] = currentModes[m]; +} + +void Screen::restoreMode(int m) +{ + currentModes[m] = savedModes[m]; +} + +bool Screen::getMode(int m) const +{ + return currentModes[m]; +} + +void Screen::saveCursor() +{ + savedState.cursorColumn = cuX; + savedState.cursorLine = cuY; + savedState.rendition = currentRendition; + savedState.foreground = currentForeground; + savedState.background = currentBackground; +} + +void Screen::restoreCursor() +{ + cuX = qMin(savedState.cursorColumn,columns-1); + cuY = qMin(savedState.cursorLine,lines-1); + currentRendition = savedState.rendition; + currentForeground = savedState.foreground; + currentBackground = savedState.background; + updateEffectiveRendition(); +} + +void Screen::resizeImage(int new_lines, int new_columns) +{ + if ((new_lines==lines) && (new_columns==columns)) return; + + if (cuY > new_lines-1) + { // attempt to preserve focus and lines + _bottomMargin = lines-1; //FIXME: margin lost + for (int i = 0; i < cuY-(new_lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + } + + // create new screen lines and copy from old to new + + ImageLine* newScreenLines = new ImageLine[new_lines+1]; + for (int i=0; i < qMin(lines-1,new_lines+1) ;i++) + newScreenLines[i]=screenLines[i]; + for (int i=lines;(i > 0) && (i 0) && (ir &= ~RE_TRANSPARENT; +} + +void Screen::updateEffectiveRendition() +{ + effectiveRendition = currentRendition; + if (currentRendition & RE_REVERSE) + { + effectiveForeground = currentBackground; + effectiveBackground = currentForeground; + } + else + { + effectiveForeground = currentForeground; + effectiveBackground = currentBackground; + } + + if (currentRendition & RE_BOLD) + effectiveForeground.toggleIntensive(); +} + +void Screen::copyFromHistory(Character* dest, int startLine, int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= history->getLines() ); + + for (int line = startLine; line < startLine + count; line++) + { + const int length = qMin(columns,history->getLineLen(line)); + const int destLineOffset = (line-startLine)*columns; + + history->getCells(line,0,length,dest + destLineOffset); + + for (int column = length; column < columns; column++) + dest[destLineOffset+column] = defaultChar; + + // invert selected text + if (selBegin !=-1) + { + for (int column = 0; column < columns; column++) + { + if (isSelected(column,line)) + { + reverseRendition(dest[destLineOffset + column]); + } + } + } + } +} + +void Screen::copyFromScreen(Character* dest , int startLine , int count) const +{ + Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines ); + + for (int line = startLine; line < (startLine+count) ; line++) + { + int srcLineStartIndex = line*columns; + int destLineStartIndex = (line-startLine)*columns; + + for (int column = 0; column < columns; column++) + { + int srcIndex = srcLineStartIndex + column; + int destIndex = destLineStartIndex + column; + + dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar); + + // invert selected text + if (selBegin != -1 && isSelected(column,line + history->getLines())) + reverseRendition(dest[destIndex]); + } + + } +} + +void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine - startLine + 1; + + Q_ASSERT( size >= mergedLines * columns ); + Q_UNUSED( size ); + + const int linesInHistoryBuffer = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; + + // copy lines from history buffer + if (linesInHistoryBuffer > 0) + copyFromHistory(dest,startLine,linesInHistoryBuffer); + + // copy lines from screen buffer + if (linesInScreenBuffer > 0) + copyFromScreen(dest + linesInHistoryBuffer*columns, + startLine + linesInHistoryBuffer - history->getLines(), + linesInScreenBuffer); + + // invert display when in screen mode + if (getMode(MODE_Screen)) + { + for (int i = 0; i < mergedLines*columns; i++) + reverseRendition(dest[i]); // for reverse display + } + + // mark the character at the current cursor position + int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer); + if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines) + dest[cursorIndex].rendition |= RE_CURSOR; +} + +QVector Screen::getLineProperties( int startLine , int endLine ) const +{ + Q_ASSERT( startLine >= 0 ); + Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); + + const int mergedLines = endLine-startLine+1; + const int linesInHistory = qBound(0,history->getLines()-startLine,mergedLines); + const int linesInScreen = mergedLines - linesInHistory; + + QVector result(mergedLines); + int index = 0; + + // copy properties for lines in history + for (int line = startLine; line < startLine + linesInHistory; line++) + { + //TODO Support for line properties other than wrapped lines + if (history->isWrappedLine(line)) + { + result[index] = (LineProperty)(result[index] | LINE_WRAPPED); + } + index++; + } + + // copy properties for lines in screen buffer + const int firstScreenLine = startLine + linesInHistory - history->getLines(); + for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++) + { + result[index]=lineProperties[line]; + index++; + } + + return result; +} + +void Screen::reset(bool clearScreen) +{ + setMode(MODE_Wrap ); saveMode(MODE_Wrap ); // wrap at end of margin + resetMode(MODE_Origin); saveMode(MODE_Origin); // position refere to [1,1] + resetMode(MODE_Insert); saveMode(MODE_Insert); // overstroke + setMode(MODE_Cursor); // cursor visible + resetMode(MODE_Screen); // screen not inverse + resetMode(MODE_NewLine); + + _topMargin=0; + _bottomMargin=lines-1; + + setDefaultRendition(); + saveCursor(); + + if ( clearScreen ) + clear(); +} + +void Screen::clear() +{ + clearEntireScreen(); + home(); +} + +void Screen::backspace() +{ + cuX = qMin(columns-1,cuX); // nowrap! + cuX = qMax(0,cuX-1); + + if (screenLines[cuY].size() < cuX+1) + screenLines[cuY].resize(cuX+1); + + if (BS_CLEARS) + screenLines[cuY][cuX].character = ' '; +} + +void Screen::tab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX < columns-1)) + { + cursorRight(1); + while((cuX < columns-1) && !tabStops[cuX]) + cursorRight(1); + n--; + } +} + +void Screen::backtab(int n) +{ + // note that TAB is a format effector (does not write ' '); + if (n == 0) n = 1; + while((n > 0) && (cuX > 0)) + { + cursorLeft(1); while((cuX > 0) && !tabStops[cuX]) cursorLeft(1); + n--; + } +} + +void Screen::clearTabStops() +{ + for (int i = 0; i < columns; i++) tabStops[i] = false; +} + +void Screen::changeTabStop(bool set) +{ + if (cuX >= columns) return; + tabStops[cuX] = set; +} + +void Screen::initTabStops() +{ + tabStops.resize(columns); + + // Arrg! The 1st tabstop has to be one longer than the other. + // i.e. the kids start counting from 0 instead of 1. + // Other programs might behave correctly. Be aware. + for (int i = 0; i < columns; i++) + tabStops[i] = (i%8 == 0 && i != 0); +} + +void Screen::newLine() +{ + if (getMode(MODE_NewLine)) + toStartOfLine(); + index(); +} + +void Screen::checkSelection(int from, int to) +{ + if (selBegin == -1) + return; + int scr_TL = loc(0, history->getLines()); + //Clear entire selection if it overlaps region [from, to] + if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) ) + clearSelection(); +} + +void Screen::displayCharacter(unsigned short c) +{ + // Note that VT100 does wrapping BEFORE putting the character. + // This has impact on the assumption of valid cursor positions. + // We indicate the fact that a newline has to be triggered by + // putting the cursor one right to the last column of the screen. + + int w = konsole_wcwidth(c); + if (w <= 0) + return; + + if (cuX+w > columns) { + if (getMode(MODE_Wrap)) { + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED); + nextLine(); + } + else + cuX = columns-w; + } + + // ensure current line vector has enough elements + int size = screenLines[cuY].size(); + if (size < cuX+w) + { + screenLines[cuY].resize(cuX+w); + } + + if (getMode(MODE_Insert)) insertChars(w); + + lastPos = loc(cuX,cuY); + + // check if selection is still valid. + checkSelection(lastPos, lastPos); + + Character& currentChar = screenLines[cuY][cuX]; + + currentChar.character = c; + currentChar.foregroundColor = effectiveForeground; + currentChar.backgroundColor = effectiveBackground; + currentChar.rendition = effectiveRendition; + + int i = 0; + int newCursorX = cuX + w--; + while(w) + { + i++; + + if ( screenLines[cuY].size() < cuX + i + 1 ) + screenLines[cuY].resize(cuX+i+1); + + Character& ch = screenLines[cuY][cuX + i]; + ch.character = 0; + ch.foregroundColor = effectiveForeground; + ch.backgroundColor = effectiveBackground; + ch.rendition = effectiveRendition; + + w--; + } + cuX = newCursorX; +} + +void Screen::compose(const QString& /*compose*/) +{ + Q_ASSERT( 0 /*Not implemented yet*/ ); + + /* if (lastPos == -1) + return; + + QChar c(image[lastPos].character); + compose.prepend(c); + //compose.compose(); ### FIXME! + image[lastPos].character = compose[0].unicode();*/ +} + +int Screen::scrolledLines() const +{ + return _scrolledLines; +} +int Screen::droppedLines() const +{ + return _droppedLines; +} +void Screen::resetDroppedLines() +{ + _droppedLines = 0; +} +void Screen::resetScrolledLines() +{ + _scrolledLines = 0; +} + +void Screen::scrollUp(int n) +{ + if (n == 0) n = 1; // Default + if (_topMargin == 0) addHistLine(); // history.history + scrollUp(_topMargin, n); +} + +QRect Screen::lastScrolledRegion() const +{ + return _lastScrolledRegion; +} + +void Screen::scrollUp(int from, int n) +{ + if (n <= 0 || from + n > _bottomMargin) return; + + _scrolledLines -= n; + _lastScrolledRegion = QRect(0,_topMargin,columns-1,(_bottomMargin-_topMargin)); + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + moveImage(loc(0,from),loc(0,from+n),loc(columns-1,_bottomMargin)); + clearImage(loc(0,_bottomMargin-n+1),loc(columns-1,_bottomMargin),' '); +} + +void Screen::scrollDown(int n) +{ + if (n == 0) n = 1; // Default + scrollDown(_topMargin, n); +} + +void Screen::scrollDown(int from, int n) +{ + _scrolledLines += n; + + //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. + if (n <= 0) + return; + if (from > _bottomMargin) + return; + if (from + n > _bottomMargin) + n = _bottomMargin - from; + moveImage(loc(0,from+n),loc(0,from),loc(columns-1,_bottomMargin-n)); + clearImage(loc(0,from),loc(columns-1,from+n-1),' '); +} + +void Screen::setCursorYX(int y, int x) +{ + setCursorY(y); setCursorX(x); +} + +void Screen::setCursorX(int x) +{ + if (x == 0) x = 1; // Default + x -= 1; // Adjust + cuX = qMax(0,qMin(columns-1, x)); +} + +void Screen::setCursorY(int y) +{ + if (y == 0) y = 1; // Default + y -= 1; // Adjust + cuY = qMax(0,qMin(lines -1, y + (getMode(MODE_Origin) ? _topMargin : 0) )); +} + +void Screen::home() +{ + cuX = 0; + cuY = 0; +} + +void Screen::toStartOfLine() +{ + cuX = 0; +} + +int Screen::getCursorX() const +{ + return cuX; +} + +int Screen::getCursorY() const +{ + return cuY; +} + +void Screen::clearImage(int loca, int loce, char c) +{ + int scr_TL=loc(0,history->getLines()); + //FIXME: check positions + + //Clear entire selection if it overlaps region to be moved... + if ( (selBottomRight > (loca+scr_TL) )&&(selTopLeft < (loce+scr_TL)) ) + { + clearSelection(); + } + + int topLine = loca/columns; + int bottomLine = loce/columns; + + Character clearCh(c,currentForeground,currentBackground,DEFAULT_RENDITION); + + //if the character being used to clear the area is the same as the + //default character, the affected lines can simply be shrunk. + bool isDefaultCh = (clearCh == Character()); + + for (int y=topLine;y<=bottomLine;y++) + { + lineProperties[y] = 0; + + int endCol = ( y == bottomLine) ? loce%columns : columns-1; + int startCol = ( y == topLine ) ? loca%columns : 0; + + QVector& line = screenLines[y]; + + if ( isDefaultCh && endCol == columns-1 ) + { + line.resize(startCol); + } + else + { + if (line.size() < endCol + 1) + line.resize(endCol+1); + + Character* data = line.data(); + for (int i=startCol;i<=endCol;i++) + data[i]=clearCh; + } + } +} + +void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) +{ + Q_ASSERT( sourceBegin <= sourceEnd ); + + int lines=(sourceEnd-sourceBegin)/columns; + + //move screen image and line properties: + //the source and destination areas of the image may overlap, + //so it matters that we do the copy in the right order - + //forwards if dest < sourceBegin or backwards otherwise. + //(search the web for 'memmove implementation' for details) + if (dest < sourceBegin) + { + for (int i=0;i<=lines;i++) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + else + { + for (int i=lines;i>=0;i--) + { + screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; + lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + } + } + + if (lastPos != -1) + { + int diff = dest - sourceBegin; // Scroll by this amount + lastPos += diff; + if ((lastPos < 0) || (lastPos >= (lines*columns))) + lastPos = -1; + } + + // Adjust selection to follow scroll. + if (selBegin != -1) + { + bool beginIsTL = (selBegin == selTopLeft); + int diff = dest - sourceBegin; // Scroll by this amount + int scr_TL=loc(0,history->getLines()); + int srca = sourceBegin+scr_TL; // Translate index from screen to global + int srce = sourceEnd+scr_TL; // Translate index from screen to global + int desta = srca+diff; + int deste = srce+diff; + + if ((selTopLeft >= srca) && (selTopLeft <= srce)) + selTopLeft += diff; + else if ((selTopLeft >= desta) && (selTopLeft <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if ((selBottomRight >= srca) && (selBottomRight <= srce)) + selBottomRight += diff; + else if ((selBottomRight >= desta) && (selBottomRight <= deste)) + selBottomRight = -1; // Clear selection (see below) + + if (selBottomRight < 0) + { + clearSelection(); + } + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } +} + +void Screen::clearToEndOfScreen() +{ + clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' '); +} + +void Screen::clearToBeginOfScreen() +{ + clearImage(loc(0,0),loc(cuX,cuY),' '); +} + +void Screen::clearEntireScreen() +{ + // Add entire screen to history + for (int i = 0; i < (lines-1); i++) + { + addHistLine(); scrollUp(0,1); + } + + clearImage(loc(0,0),loc(columns-1,lines-1),' '); +} + +/*! fill screen with 'E' + This is to aid screen alignment + */ + +void Screen::helpAlign() +{ + clearImage(loc(0,0),loc(columns-1,lines-1),'E'); +} + +void Screen::clearToEndOfLine() +{ + clearImage(loc(cuX,cuY),loc(columns-1,cuY),' '); +} + +void Screen::clearToBeginOfLine() +{ + clearImage(loc(0,cuY),loc(cuX,cuY),' '); +} + +void Screen::clearEntireLine() +{ + clearImage(loc(0,cuY),loc(columns-1,cuY),' '); +} + +void Screen::setRendition(int re) +{ + currentRendition |= re; + updateEffectiveRendition(); +} + +void Screen::resetRendition(int re) +{ + currentRendition &= ~re; + updateEffectiveRendition(); +} + +void Screen::setDefaultRendition() +{ + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); + currentRendition = DEFAULT_RENDITION; + updateEffectiveRendition(); +} + +void Screen::setForeColor(int space, int color) +{ + currentForeground = CharacterColor(space, color); + + if ( currentForeground.isValid() ) + updateEffectiveRendition(); + else + setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); +} + +void Screen::setBackColor(int space, int color) +{ + currentBackground = CharacterColor(space, color); + + if ( currentBackground.isValid() ) + updateEffectiveRendition(); + else + setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); +} + +void Screen::clearSelection() +{ + selBottomRight = -1; + selTopLeft = -1; + selBegin = -1; +} + +void Screen::getSelectionStart(int& column , int& line) const +{ + if ( selTopLeft != -1 ) + { + column = selTopLeft % columns; + line = selTopLeft / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::getSelectionEnd(int& column , int& line) const +{ + if ( selBottomRight != -1 ) + { + column = selBottomRight % columns; + line = selBottomRight / columns; + } + else + { + column = cuX + getHistLines(); + line = cuY + getHistLines(); + } +} +void Screen::setSelectionStart(const int x, const int y, const bool mode) +{ + selBegin = loc(x,y); + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) selBegin--; + + selBottomRight = selBegin; + selTopLeft = selBegin; + blockSelectionMode = mode; +} + +void Screen::setSelectionEnd( const int x, const int y) +{ + if (selBegin == -1) + return; + + int endPos = loc(x,y); + + if (endPos < selBegin) + { + selTopLeft = endPos; + selBottomRight = selBegin; + } + else + { + /* FIXME, HACK to correct for x too far to the right... */ + if (x == columns) + endPos--; + + selTopLeft = selBegin; + selBottomRight = endPos; + } + + // Normalize the selection in column mode + if (blockSelectionMode) + { + int topRow = selTopLeft / columns; + int topColumn = selTopLeft % columns; + int bottomRow = selBottomRight / columns; + int bottomColumn = selBottomRight % columns; + + selTopLeft = loc(qMin(topColumn,bottomColumn),topRow); + selBottomRight = loc(qMax(topColumn,bottomColumn),bottomRow); + } +} + +bool Screen::isSelected( const int x,const int y) const +{ + bool columnInSelection = true; + if (blockSelectionMode) + { + columnInSelection = x >= (selTopLeft % columns) && + x <= (selBottomRight % columns); + } + + int pos = loc(x,y); + return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; +} + +QString Screen::selectedText(bool preserveLineBreaks) const +{ + QString result; + QTextStream stream(&result, QIODevice::ReadWrite); + + PlainTextDecoder decoder; + decoder.begin(&stream); + writeSelectionToStream(&decoder , preserveLineBreaks); + decoder.end(); + + return result; +} + +bool Screen::isSelectionValid() const +{ + return selTopLeft >= 0 && selBottomRight >= 0; +} + +void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , + bool preserveLineBreaks) const +{ + if (!isSelectionValid()) + return; + writeToStream(decoder,selTopLeft,selBottomRight,preserveLineBreaks); +} + +void Screen::writeToStream(TerminalCharacterDecoder* decoder, + int startIndex, int endIndex, + bool preserveLineBreaks) const +{ + int top = startIndex / columns; + int left = startIndex % columns; + + int bottom = endIndex / columns; + int right = endIndex % columns; + + Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 ); + + for (int y=top;y<=bottom;y++) + { + int start = 0; + if ( y == top || blockSelectionMode ) start = left; + + int count = -1; + if ( y == bottom || blockSelectionMode ) count = right - start + 1; + + const bool appendNewLine = ( y != bottom ); + int copied = copyLineToStream( y, + start, + count, + decoder, + appendNewLine, + preserveLineBreaks ); + + // if the selection goes beyond the end of the last line then + // append a new line character. + // + // this makes it possible to 'select' a trailing new line character after + // the text on a line. + if ( y == bottom && + copied < count ) + { + Character newLineChar('\n'); + decoder->decodeLine(&newLineChar,1,0); + } + } +} + +int Screen::copyLineToStream(int line , + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const +{ + //buffer to hold characters for decoding + //the buffer is static to avoid initialising every + //element on each call to copyLineToStream + //(which is unnecessary since all elements will be overwritten anyway) + static const int MAX_CHARS = 1024; + static Character characterBuffer[MAX_CHARS]; + + assert( count < MAX_CHARS ); + + LineProperty currentLineProperties = 0; + + //determine if the line is in the history buffer or the screen image + if (line < history->getLines()) + { + const int lineLength = history->getLineLen(line); + + // ensure that start position is before end of line + start = qMin(start,qMax(0,lineLength-1)); + + // retrieve line from history buffer. It is assumed + // that the history buffer does not store trailing white space + // at the end of the line, so it does not need to be trimmed here + if (count == -1) + { + count = lineLength-start; + } + else + { + count = qMin(start+count,lineLength)-start; + } + + // safety checks + assert( start >= 0 ); + assert( count >= 0 ); + assert( (start+count) <= history->getLineLen(line) ); + + history->getCells(line,start,count,characterBuffer); + + if ( history->isWrappedLine(line) ) + currentLineProperties |= LINE_WRAPPED; + } + else + { + if ( count == -1 ) + count = columns - start; + + assert( count >= 0 ); + + const int screenLine = line-history->getLines(); + + Character* data = screenLines[screenLine].data(); + int length = screenLines[screenLine].count(); + + //retrieve line from screen image + for (int i=start;i < qMin(start+count,length);i++) + { + characterBuffer[i-start] = data[i]; + } + + // count cannot be any greater than length + count = qBound(0,count,length-start); + + Q_ASSERT( screenLine < lineProperties.count() ); + currentLineProperties |= lineProperties[screenLine]; + } + + // add new line character at end + const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || + !preserveLineBreaks; + + if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) ) + { + characterBuffer[count] = '\n'; + count++; + } + + //decode line and write to text stream + decoder->decodeLine( (Character*) characterBuffer , + count, currentLineProperties ); + + return count; +} + +void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const +{ + writeToStream(decoder,loc(0,fromLine),loc(columns-1,toLine)); +} + +void Screen::addHistLine() +{ + // add line to history buffer + // we have to take care about scrolling, too... + + if (hasScroll()) + { + int oldHistLines = history->getLines(); + + history->addCellsVector(screenLines[0]); + history->addLine( lineProperties[0] & LINE_WRAPPED ); + + int newHistLines = history->getLines(); + + bool beginIsTL = (selBegin == selTopLeft); + + // If the history is full, increment the count + // of dropped lines + if ( newHistLines == oldHistLines ) + _droppedLines++; + + // Adjust selection for the new point of reference + if (newHistLines > oldHistLines) + { + if (selBegin != -1) + { + selTopLeft += columns; + selBottomRight += columns; + } + } + + if (selBegin != -1) + { + // Scroll selection in history up + int top_BR = loc(0, 1+newHistLines); + + if (selTopLeft < top_BR) + selTopLeft -= columns; + + if (selBottomRight < top_BR) + selBottomRight -= columns; + + if (selBottomRight < 0) + clearSelection(); + else + { + if (selTopLeft < 0) + selTopLeft = 0; + } + + if (beginIsTL) + selBegin = selTopLeft; + else + selBegin = selBottomRight; + } + } + +} + +int Screen::getHistLines() const +{ + return history->getLines(); +} + +void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) +{ + clearSelection(); + + if ( copyPreviousScroll ) + history = t.scroll(history); + else + { + HistoryScroll* oldScroll = history; + history = t.scroll(0); + delete oldScroll; + } +} + +bool Screen::hasScroll() const +{ + return history->hasScroll(); +} + +const HistoryType& Screen::getScroll() const +{ + return history->getType(); +} + +void Screen::setLineProperty(LineProperty property , bool enable) +{ + if ( enable ) + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property); + else + lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property); +} +void Screen::fillWithDefaultChar(Character* dest, int count) +{ + for (int i=0;i + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef SCREEN_H +#define SCREEN_H + +// Qt +#include +#include +#include + +// Konsole +#include "Character.h" +#include "History.h" + +#define MODE_Origin 0 +#define MODE_Wrap 1 +#define MODE_Insert 2 +#define MODE_Screen 3 +#define MODE_Cursor 4 +#define MODE_NewLine 5 +#define MODES_SCREEN 6 + +namespace Konsole +{ + +class TerminalCharacterDecoder; + +/** + \brief An image of characters with associated attributes. + + The terminal emulation ( Emulation ) receives a serial stream of + characters from the program currently running in the terminal. + From this stream it creates an image of characters which is ultimately + rendered by the display widget ( TerminalDisplay ). Some types of emulation + may have more than one screen image. + + getImage() is used to retrieve the currently visible image + which is then used by the display widget to draw the output from the + terminal. + + The number of lines of output history which are kept in addition to the current + screen image depends on the history scroll being used to store the output. + The scroll is specified using setScroll() + The output history can be retrieved using writeToStream() + + The screen image has a selection associated with it, specified using + setSelectionStart() and setSelectionEnd(). The selected text can be retrieved + using selectedText(). When getImage() is used to retrieve the visible image, + characters which are part of the selection have their colours inverted. +*/ +class Screen +{ +public: + /** Construct a new screen image of size @p lines by @p columns. */ + Screen(int lines, int columns); + ~Screen(); + + // VT100/2 Operations + // Cursor Movement + + /** + * Move the cursor up by @p n lines. The cursor will stop at the + * top margin. + */ + void cursorUp(int n); + /** + * Move the cursor down by @p n lines. The cursor will stop at the + * bottom margin. + */ + void cursorDown(int n); + /** + * Move the cursor to the left by @p n columns. + * The cursor will stop at the first column. + */ + void cursorLeft(int n); + /** + * Move the cursor to the right by @p n columns. + * The cursor will stop at the right-most column. + */ + void cursorRight(int n); + /** Position the cursor on line @p y. */ + void setCursorY(int y); + /** Position the cursor at column @p x. */ + void setCursorX(int x); + /** Position the cursor at line @p y, column @p x. */ + void setCursorYX(int y, int x); + /** + * Sets the margins for scrolling the screen. + * + * @param topLine The top line of the new scrolling margin. + * @param bottomLine The bottom line of the new scrolling margin. + */ + void setMargins(int topLine , int bottomLine); + /** Returns the top line of the scrolling region. */ + int topMargin() const; + /** Returns the bottom line of the scrolling region. */ + int bottomMargin() const; + + /** + * Resets the scrolling margins back to the top and bottom lines + * of the screen. + */ + void setDefaultMargins(); + + /** + * Moves the cursor down one line, if the MODE_NewLine mode + * flag is enabled then the cursor is returned to the leftmost + * column first. + * + * Equivalent to NextLine() if the MODE_NewLine flag is set + * or index() otherwise. + */ + void newLine(); + /** + * Moves the cursor down one line and positions it at the beginning + * of the line. Equivalent to calling Return() followed by index() + */ + void nextLine(); + + /** + * Move the cursor down one line. If the cursor is on the bottom + * line of the scrolling region (as returned by bottomMargin()) the + * scrolling region is scrolled up by one line instead. + */ + void index(); + /** + * Move the cursor up one line. If the cursor is on the top line + * of the scrolling region (as returned by topMargin()) the scrolling + * region is scrolled down by one line instead. + */ + void reverseIndex(); + + /** + * Scroll the scrolling region of the screen up by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollUp(int n); + /** + * Scroll the scrolling region of the screen down by @p n lines. + * The scrolling region is initially the whole screen, but can be changed + * using setMargins() + */ + void scrollDown(int n); + /** + * Moves the cursor to the beginning of the current line. + * Equivalent to setCursorX(0) + */ + void toStartOfLine(); + /** + * Moves the cursor one column to the left and erases the character + * at the new cursor position. + */ + void backspace(); + /** Moves the cursor @p n tab-stops to the right. */ + void tab(int n = 1); + /** Moves the cursor @p n tab-stops to the left. */ + void backtab(int n); + + // Editing + + /** + * Erase @p n characters beginning from the current cursor position. + * This is equivalent to over-writing @p n characters starting with the current + * cursor position with spaces. + * If @p n is 0 then one character is erased. + */ + void eraseChars(int n); + /** + * Delete @p n characters beginning from the current cursor position. + * If @p n is 0 then one character is deleted. + */ + void deleteChars(int n); + /** + * Insert @p n blank characters beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one character is inserted. + */ + void insertChars(int n); + /** + * Removes @p n lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is removed. + */ + void deleteLines(int n); + /** + * Inserts @p lines beginning from the current cursor position. + * The position of the cursor is not altered. + * If @p n is 0 then one line is inserted. + */ + void insertLines(int n); + /** Clears all the tab stops. */ + void clearTabStops(); + /** Sets or removes a tab stop at the cursor's current column. */ + void changeTabStop(bool set); + + /** Resets (clears) the specified screen @p mode. */ + void resetMode(int mode); + /** Sets (enables) the specified screen @p mode. */ + void setMode(int mode); + /** + * Saves the state of the specified screen @p mode. It can be restored + * using restoreMode() + */ + void saveMode(int mode); + /** Restores the state of a screen @p mode saved by calling saveMode() */ + void restoreMode(int mode); + /** Returns whether the specified screen @p mode is enabled or not .*/ + bool getMode(int mode) const; + + /** + * Saves the current position and appearance (text color and style) of the cursor. + * It can be restored by calling restoreCursor() + */ + void saveCursor(); + /** Restores the position and appearance of the cursor. See saveCursor() */ + void restoreCursor(); + + /** Clear the whole screen, moving the current screen contents into the history first. */ + void clearEntireScreen(); + /** + * Clear the area of the screen from the current cursor position to the end of + * the screen. + */ + void clearToEndOfScreen(); + /** + * Clear the area of the screen from the current cursor position to the start + * of the screen. + */ + void clearToBeginOfScreen(); + /** Clears the whole of the line on which the cursor is currently positioned. */ + void clearEntireLine(); + /** Clears from the current cursor position to the end of the line. */ + void clearToEndOfLine(); + /** Clears from the current cursor position to the beginning of the line. */ + void clearToBeginOfLine(); + + /** Fills the entire screen with the letter 'E' */ + void helpAlign(); + + /** + * Enables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void setRendition(int rendition); + /** + * Disables the given @p rendition flag. Rendition flags control the appearance + * of characters on the screen. + * + * @see Character::rendition + */ + void resetRendition(int rendition); + + /** + * Sets the cursor's foreground color. + * @param space The color space used by the @p color argument + * @param color The new foreground color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setForeColor(int space, int color); + /** + * Sets the cursor's background color. + * @param space The color space used by the @p color argumnet. + * @param color The new background color. The meaning of this depends on + * the color @p space used. + * + * @see CharacterColor + */ + void setBackColor(int space, int color); + /** + * Resets the cursor's color back to the default and sets the + * character's rendition flags back to the default settings. + */ + void setDefaultRendition(); + + /** Returns the column which the cursor is positioned at. */ + int getCursorX() const; + /** Returns the line which the cursor is positioned on. */ + int getCursorY() const; + + /** Clear the entire screen and move the cursor to the home position. + * Equivalent to calling clearEntireScreen() followed by home(). + */ + void clear(); + /** + * Sets the position of the cursor to the 'home' position at the top-left + * corner of the screen (0,0) + */ + void home(); + /** + * Resets the state of the screen. This resets the various screen modes + * back to their default states. The cursor style and colors are reset + * (as if setDefaultRendition() had been called) + * + *
    + *
  • Line wrapping is enabled.
  • + *
  • Origin mode is disabled.
  • + *
  • Insert mode is disabled.
  • + *
  • Cursor mode is enabled. TODO Document me
  • + *
  • Screen mode is disabled. TODO Document me
  • + *
  • New line mode is disabled. TODO Document me
  • + *
+ * + * If @p clearScreen is true then the screen contents are erased entirely, + * otherwise they are unaltered. + */ + void reset(bool clearScreen = true); + + /** + * Displays a new character at the current cursor position. + * + * If the cursor is currently positioned at the right-edge of the screen and + * line wrapping is enabled then the character is added at the start of a new + * line below the current one. + * + * If the MODE_Insert screen mode is currently enabled then the character + * is inserted at the current cursor position, otherwise it will replace the + * character already at the current cursor position. + */ + void displayCharacter(unsigned short c); + + // Do composition with last shown character FIXME: Not implemented yet for KDE 4 + void compose(const QString& compose); + + /** + * Resizes the image to a new fixed size of @p new_lines by @p new_columns. + * In the case that @p new_columns is smaller than the current number of columns, + * existing lines are not truncated. This prevents characters from being lost + * if the terminal display is resized smaller and then larger again. + * + * The top and bottom margins are reset to the top and bottom of the new + * screen size. Tab stops are also reset and the current selection is + * cleared. + */ + void resizeImage(int new_lines, int new_columns); + + /** + * Returns the current screen image. + * The result is an array of Characters of size [getLines()][getColumns()] which + * must be freed by the caller after use. + * + * @param dest Buffer to copy the characters into + * @param size Size of @p dest in Characters + * @param startLine Index of first line to copy + * @param endLine Index of last line to copy + */ + void getImage( Character* dest , int size , int startLine , int endLine ) const; + + /** + * Returns the additional attributes associated with lines in the image. + * The most important attribute is LINE_WRAPPED which specifies that the + * line is wrapped, + * other attributes control the size of characters in the line. + */ + QVector getLineProperties( int startLine , int endLine ) const; + + + /** Return the number of lines. */ + int getLines() const + { return lines; } + /** Return the number of columns. */ + int getColumns() const + { return columns; } + /** Return the number of lines in the history buffer. */ + int getHistLines() const; + /** + * Sets the type of storage used to keep lines in the history. + * If @p copyPreviousScroll is true then the contents of the previous + * history buffer are copied into the new scroll. + */ + void setScroll(const HistoryType& , bool copyPreviousScroll = true); + /** Returns the type of storage used to keep lines in the history. */ + const HistoryType& getScroll() const; + /** + * Returns true if this screen keeps lines that are scrolled off the screen + * in a history buffer. + */ + bool hasScroll() const; + + /** + * Sets the start of the selection. + * + * @param column The column index of the first character in the selection. + * @param line The line index of the first character in the selection. + * @param blockSelectionMode True if the selection is in column mode. + */ + void setSelectionStart(const int column, const int line, const bool blockSelectionMode); + + /** + * Sets the end of the current selection. + * + * @param column The column index of the last character in the selection. + * @param line The line index of the last character in the selection. + */ + void setSelectionEnd(const int column, const int line); + + /** + * Retrieves the start of the selection or the cursor position if there + * is no selection. + */ + void getSelectionStart(int& column , int& line) const; + + /** + * Retrieves the end of the selection or the cursor position if there + * is no selection. + */ + void getSelectionEnd(int& column , int& line) const; + + /** Clears the current selection */ + void clearSelection(); + + /** + * Returns true if the character at (@p column, @p line) is part of the + * current selection. + */ + bool isSelected(const int column,const int line) const; + + /** + * Convenience method. Returns the currently selected text. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + QString selectedText(bool preserveLineBreaks) const; + + /** + * Copies part of the output to a stream. + * + * @param decoder A decoder which converts terminal characters into text + * @param fromLine The first line in the history to retrieve + * @param toLine The last line in the history to retrieve + */ + void writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const; + + /** + * Copies the selected characters, set using @see setSelBeginXY and @see setSelExtentXY + * into a stream. + * + * @param decoder A decoder which converts terminal characters into text. + * PlainTextDecoder is the most commonly used decoder which converts characters + * into plain text with no formatting. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + void writeSelectionToStream(TerminalCharacterDecoder* decoder , bool + preserveLineBreaks = true) const; + + /** + * Checks if the text between from and to is inside the current + * selection. If this is the case, the selection is cleared. The + * from and to are coordinates in the current viewable window. + * The loc(x,y) macro can be used to generate these values from a + * column,line pair. + * + * @param from The start of the area to check. + * @param to The end of the area to check + */ + void checkSelection(int from, int to); + + /** + * Sets or clears an attribute of the current line. + * + * @param property The attribute to set or clear + * Possible properties are: + * LINE_WRAPPED: Specifies that the line is wrapped. + * LINE_DOUBLEWIDTH: Specifies that the characters in the current line + * should be double the normal width. + * LINE_DOUBLEHEIGHT:Specifies that the characters in the current line + * should be double the normal height. + * Double-height lines are formed of two lines containing the same characters, + * with both having the LINE_DOUBLEHEIGHT attribute. + * This allows other parts of the code to work on the + * assumption that all lines are the same height. + * + * @param enable true to apply the attribute to the current line or false to remove it + */ + void setLineProperty(LineProperty property , bool enable); + + /** + * Returns the number of lines that the image has been scrolled up or down by, + * since the last call to resetScrolledLines(). + * + * a positive return value indicates that the image has been scrolled up, + * a negative return value indicates that the image has been scrolled down. + */ + int scrolledLines() const; + + /** + * Returns the region of the image which was last scrolled. + * + * This is the area of the image from the top margin to the + * bottom margin when the last scroll occurred. + */ + QRect lastScrolledRegion() const; + + /** + * Resets the count of the number of lines that the image has been scrolled up or down by, + * see scrolledLines() + */ + void resetScrolledLines(); + + /** + * Returns the number of lines of output which have been + * dropped from the history since the last call + * to resetDroppedLines() + * + * If the history is not unlimited then it will drop + * the oldest lines of output if new lines are added when + * it is full. + */ + int droppedLines() const; + + /** + * Resets the count of the number of lines dropped from + * the history. + */ + void resetDroppedLines(); + + /** + * Fills the buffer @p dest with @p count instances of the default (ie. blank) + * Character style. + */ + static void fillWithDefaultChar(Character* dest, int count); + +private: + + //copies a line of text from the screen or history into a stream using a + //specified character decoder. Returns the number of lines actually copied, + //which may be less than 'count' if (start+count) is more than the number of characters on + //the line + // + //line - the line number to copy, from 0 (the earliest line in the history) up to + // history->getLines() + lines - 1 + //start - the first column on the line to copy + //count - the number of characters on the line to copy + //decoder - a decoder which converts terminal characters (an Character array) into text + //appendNewLine - if true a new line character (\n) is appended to the end of the line + int copyLineToStream(int line, + int start, + int count, + TerminalCharacterDecoder* decoder, + bool appendNewLine, + bool preserveLineBreaks) const; + + //fills a section of the screen image with the character 'c' + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + void clearImage(int loca, int loce, char c); + + //move screen image between 'sourceBegin' and 'sourceEnd' to 'dest'. + //the parameters are specified as offsets from the start of the screen image. + //the loc(x,y) macro can be used to generate these values from a column,line pair. + // + //NOTE: moveImage() can only move whole lines + void moveImage(int dest, int sourceBegin, int sourceEnd); + // scroll up 'i' lines in current region, clearing the bottom 'i' lines + void scrollUp(int from, int i); + // scroll down 'i' lines in current region, clearing the top 'i' lines + void scrollDown(int from, int i); + + void addHistLine(); + + void initTabStops(); + + void updateEffectiveRendition(); + void reverseRendition(Character& p) const; + + bool isSelectionValid() const; + // copies text from 'startIndex' to 'endIndex' to a stream + // startIndex and endIndex are positions generated using the loc(x,y) macro + void writeToStream(TerminalCharacterDecoder* decoder, int startIndex, + int endIndex, bool preserveLineBreaks = true) const; + // copies 'count' lines from the screen buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the screen buffer + void copyFromScreen(Character* dest, int startLine, int count) const; + // copies 'count' lines from the history buffer into 'dest', + // starting from 'startLine', where 0 is the first line in the history + void copyFromHistory(Character* dest, int startLine, int count) const; + + + // screen image ---------------- + int lines; + int columns; + + typedef QVector ImageLine; // [0..columns] + ImageLine* screenLines; // [lines] + + int _scrolledLines; + QRect _lastScrolledRegion; + + int _droppedLines; + + QVarLengthArray lineProperties; + + // history buffer --------------- + HistoryScroll* history; + + // cursor location + int cuX; + int cuY; + + // cursor color and rendition info + CharacterColor currentForeground; + CharacterColor currentBackground; + quint8 currentRendition; + + // margins ---------------- + int _topMargin; + int _bottomMargin; + + // states ---------------- + int currentModes[MODES_SCREEN]; + int savedModes[MODES_SCREEN]; + + // ---------------------------- + + QBitArray tabStops; + + // selection ------------------- + int selBegin; // The first location selected. + int selTopLeft; // TopLeft Location. + int selBottomRight; // Bottom Right Location. + bool blockSelectionMode; // Column selection mode + + // effective colors and rendition ------------ + CharacterColor effectiveForeground; // These are derived from + CharacterColor effectiveBackground; // the cu_* variables above + quint8 effectiveRendition; // to speed up operation + + class SavedState + { + public: + SavedState() + : cursorColumn(0),cursorLine(0),rendition(0) {} + + int cursorColumn; + int cursorLine; + quint8 rendition; + CharacterColor foreground; + CharacterColor background; + }; + SavedState savedState; + + // last position where we added a character + int lastPos; + + static Character defaultChar; +}; + +} + +#endif // SCREEN_H diff --git a/lib/ScreenWindow.cpp b/lib/ScreenWindow.cpp new file mode 100644 index 0000000..1d3fadd --- /dev/null +++ b/lib/ScreenWindow.cpp @@ -0,0 +1,294 @@ +/* + Copyright (C) 2007 by Robert Knight + + 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. +*/ + +// Own +#include "ScreenWindow.h" + +// Qt +#include + +// Konsole +#include "Screen.h" + +using namespace Konsole; + +ScreenWindow::ScreenWindow(QObject* parent) + : QObject(parent) + , _windowBuffer(0) + , _windowBufferSize(0) + , _bufferNeedsUpdate(true) + , _windowLines(1) + , _currentLine(0) + , _trackOutput(true) + , _scrollCount(0) +{ +} +ScreenWindow::~ScreenWindow() +{ + delete[] _windowBuffer; +} +void ScreenWindow::setScreen(Screen* screen) +{ + Q_ASSERT( screen ); + + _screen = screen; +} + +Screen* ScreenWindow::screen() const +{ + return _screen; +} + +Character* ScreenWindow::getImage() +{ + // reallocate internal buffer if the window size has changed + int size = windowLines() * windowColumns(); + if (_windowBuffer == 0 || _windowBufferSize != size) + { + delete[] _windowBuffer; + _windowBufferSize = size; + _windowBuffer = new Character[size]; + _bufferNeedsUpdate = true; + } + + if (!_bufferNeedsUpdate) + return _windowBuffer; + + _screen->getImage(_windowBuffer,size, + currentLine(),endWindowLine()); + + // this window may look beyond the end of the screen, in which + // case there will be an unused area which needs to be filled + // with blank characters + fillUnusedArea(); + + _bufferNeedsUpdate = false; + return _windowBuffer; +} + +void ScreenWindow::fillUnusedArea() +{ + int screenEndLine = _screen->getHistLines() + _screen->getLines() - 1; + int windowEndLine = currentLine() + windowLines() - 1; + + int unusedLines = windowEndLine - screenEndLine; + int charsToFill = unusedLines * windowColumns(); + + Screen::fillWithDefaultChar(_windowBuffer + _windowBufferSize - charsToFill,charsToFill); +} + +// return the index of the line at the end of this window, or if this window +// goes beyond the end of the screen, the index of the line at the end +// of the screen. +// +// when passing a line number to a Screen method, the line number should +// never be more than endWindowLine() +// +int ScreenWindow::endWindowLine() const +{ + return qMin(currentLine() + windowLines() - 1, + lineCount() - 1); +} +QVector ScreenWindow::getLineProperties() +{ + QVector result = _screen->getLineProperties(currentLine(),endWindowLine()); + + if (result.count() != windowLines()) + result.resize(windowLines()); + + return result; +} + +QString ScreenWindow::selectedText( bool preserveLineBreaks ) const +{ + return _screen->selectedText( preserveLineBreaks ); +} + +void ScreenWindow::getSelectionStart( int& column , int& line ) +{ + _screen->getSelectionStart(column,line); + line -= currentLine(); +} +void ScreenWindow::getSelectionEnd( int& column , int& line ) +{ + _screen->getSelectionEnd(column,line); + line -= currentLine(); +} +void ScreenWindow::setSelectionStart( int column , int line , bool columnMode ) +{ + _screen->setSelectionStart( column , qMin(line + currentLine(),endWindowLine()) , columnMode); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +void ScreenWindow::setSelectionEnd( int column , int line ) +{ + _screen->setSelectionEnd( column , qMin(line + currentLine(),endWindowLine()) ); + + _bufferNeedsUpdate = true; + emit selectionChanged(); +} + +bool ScreenWindow::isSelected( int column , int line ) +{ + return _screen->isSelected( column , qMin(line + currentLine(),endWindowLine()) ); +} + +void ScreenWindow::clearSelection() +{ + _screen->clearSelection(); + + emit selectionChanged(); +} + +void ScreenWindow::setWindowLines(int lines) +{ + Q_ASSERT(lines > 0); + _windowLines = lines; +} +int ScreenWindow::windowLines() const +{ + return _windowLines; +} + +int ScreenWindow::windowColumns() const +{ + return _screen->getColumns(); +} + +int ScreenWindow::lineCount() const +{ + return _screen->getHistLines() + _screen->getLines(); +} + +int ScreenWindow::columnCount() const +{ + return _screen->getColumns(); +} + +QPoint ScreenWindow::cursorPosition() const +{ + QPoint position; + + position.setX( _screen->getCursorX() ); + position.setY( _screen->getCursorY() ); + + return position; +} + +int ScreenWindow::currentLine() const +{ + return qBound(0,_currentLine,lineCount()-windowLines()); +} + +void ScreenWindow::scrollBy( RelativeScrollMode mode , int amount ) +{ + if ( mode == ScrollLines ) + { + scrollTo( currentLine() + amount ); + } + else if ( mode == ScrollPages ) + { + scrollTo( currentLine() + amount * ( windowLines() / 2 ) ); + } +} + +bool ScreenWindow::atEndOfOutput() const +{ + return currentLine() == (lineCount()-windowLines()); +} + +void ScreenWindow::scrollTo( int line ) +{ + int maxCurrentLineNumber = lineCount() - windowLines(); + line = qBound(0,line,maxCurrentLineNumber); + + const int delta = line - _currentLine; + _currentLine = line; + + // keep track of number of lines scrolled by, + // this can be reset by calling resetScrollCount() + _scrollCount += delta; + + _bufferNeedsUpdate = true; + + emit scrolled(_currentLine); +} + +void ScreenWindow::setTrackOutput(bool trackOutput) +{ + _trackOutput = trackOutput; +} + +bool ScreenWindow::trackOutput() const +{ + return _trackOutput; +} + +int ScreenWindow::scrollCount() const +{ + return _scrollCount; +} + +void ScreenWindow::resetScrollCount() +{ + _scrollCount = 0; +} + +QRect ScreenWindow::scrollRegion() const +{ + bool equalToScreenSize = windowLines() == _screen->getLines(); + + if ( atEndOfOutput() && equalToScreenSize ) + return _screen->lastScrolledRegion(); + else + return QRect(0,0,windowColumns(),windowLines()); +} + +void ScreenWindow::notifyOutputChanged() +{ + // move window to the bottom of the screen and update scroll count + // if this window is currently tracking the bottom of the screen + if ( _trackOutput ) + { + _scrollCount -= _screen->scrolledLines(); + _currentLine = qMax(0,_screen->getHistLines() - (windowLines()-_screen->getLines())); + } + else + { + // if the history is not unlimited then it may + // have run out of space and dropped the oldest + // lines of output - in this case the screen + // window's current line number will need to + // be adjusted - otherwise the output will scroll + _currentLine = qMax(0,_currentLine - + _screen->droppedLines()); + + // ensure that the screen window's current position does + // not go beyond the bottom of the screen + _currentLine = qMin( _currentLine , _screen->getHistLines() ); + } + + _bufferNeedsUpdate = true; + + emit outputChanged(); +} + +//#include "ScreenWindow.moc" diff --git a/lib/ScreenWindow.h b/lib/ScreenWindow.h new file mode 100644 index 0000000..3ef2675 --- /dev/null +++ b/lib/ScreenWindow.h @@ -0,0 +1,259 @@ +/* + Copyright 2007-2008 by Robert Knight + + 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. +*/ + +#ifndef SCREENWINDOW_H +#define SCREENWINDOW_H + +// Qt +#include +#include +#include + +// Konsole +#include "Character.h" + +namespace Konsole +{ + +class Screen; + +/** + * Provides a window onto a section of a terminal screen. A terminal widget can then render + * the contents of the window and use the window to change the terminal screen's selection + * in response to mouse or keyboard input. + * + * A new ScreenWindow for a terminal session can be created by calling Emulation::createWindow() + * + * Use the scrollTo() method to scroll the window up and down on the screen. + * Use the getImage() method to retrieve the character image which is currently visible in the window. + * + * setTrackOutput() controls whether the window moves to the bottom of the associated screen when new + * lines are added to it. + * + * Whenever the output from the underlying screen is changed, the notifyOutputChanged() slot should + * be called. This in turn will update the window's position and emit the outputChanged() signal + * if necessary. + */ +class ScreenWindow : public QObject +{ +Q_OBJECT + +public: + /** + * Constructs a new screen window with the given parent. + * A screen must be specified by calling setScreen() before calling getImage() or getLineProperties(). + * + * You should not call this constructor directly, instead use the Emulation::createWindow() method + * to create a window on the emulation which you wish to view. This allows the emulation + * to notify the window when the associated screen has changed and synchronize selection updates + * between all views on a session. + */ + ScreenWindow(QObject* parent = 0); + virtual ~ScreenWindow(); + + /** Sets the screen which this window looks onto */ + void setScreen(Screen* screen); + /** Returns the screen which this window looks onto */ + Screen* screen() const; + + /** + * Returns the image of characters which are currently visible through this window + * onto the screen. + * + * The returned buffer is managed by the ScreenWindow instance and does not need to be + * deleted by the caller. + */ + Character* getImage(); + + /** + * Returns the line attributes associated with the lines of characters which + * are currently visible through this window + */ + QVector getLineProperties(); + + /** + * Returns the number of lines which the region of the window + * specified by scrollRegion() has been scrolled by since the last call + * to resetScrollCount(). scrollRegion() is in most cases the + * whole window, but will be a smaller area in, for example, applications + * which provide split-screen facilities. + * + * This is not guaranteed to be accurate, but allows views to optimize + * rendering by reducing the amount of costly text rendering that + * needs to be done when the output is scrolled. + */ + int scrollCount() const; + + /** + * Resets the count of scrolled lines returned by scrollCount() + */ + void resetScrollCount(); + + /** + * Returns the area of the window which was last scrolled, this is + * usually the whole window area. + * + * Like scrollCount(), this is not guaranteed to be accurate, + * but allows views to optimize rendering. + */ + QRect scrollRegion() const; + + /** + * Sets the start of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionStart( int column , int line , bool columnMode ); + /** + * Sets the end of the selection to the given @p line and @p column within + * the window. + */ + void setSelectionEnd( int column , int line ); + /** + * Retrieves the start of the selection within the window. + */ + void getSelectionStart( int& column , int& line ); + /** + * Retrieves the end of the selection within the window. + */ + void getSelectionEnd( int& column , int& line ); + /** + * Returns true if the character at @p line , @p column is part of the selection. + */ + bool isSelected( int column , int line ); + /** + * Clears the current selection + */ + void clearSelection(); + + /** Sets the number of lines in the window */ + void setWindowLines(int lines); + /** Returns the number of lines in the window */ + int windowLines() const; + /** Returns the number of columns in the window */ + int windowColumns() const; + + /** Returns the total number of lines in the screen */ + int lineCount() const; + /** Returns the total number of columns in the screen */ + int columnCount() const; + + /** Returns the index of the line which is currently at the top of this window */ + int currentLine() const; + + /** + * Returns the position of the cursor + * within the window. + */ + QPoint cursorPosition() const; + + /** + * Convenience method. Returns true if the window is currently at the bottom + * of the screen. + */ + bool atEndOfOutput() const; + + /** Scrolls the window so that @p line is at the top of the window */ + void scrollTo( int line ); + + /** Describes the units which scrollBy() moves the window by. */ + enum RelativeScrollMode + { + /** Scroll the window down by a given number of lines. */ + ScrollLines, + /** + * Scroll the window down by a given number of pages, where + * one page is windowLines() lines + */ + ScrollPages + }; + + /** + * Scrolls the window relative to its current position on the screen. + * + * @param mode Specifies whether @p amount refers to the number of lines or the number + * of pages to scroll. + * @param amount The number of lines or pages ( depending on @p mode ) to scroll by. If + * this number is positive, the view is scrolled down. If this number is negative, the view + * is scrolled up. + */ + void scrollBy( RelativeScrollMode mode , int amount ); + + /** + * Specifies whether the window should automatically move to the bottom + * of the screen when new output is added. + * + * If this is set to true, the window will be moved to the bottom of the associated screen ( see + * screen() ) when the notifyOutputChanged() method is called. + */ + void setTrackOutput(bool trackOutput); + /** + * Returns whether the window automatically moves to the bottom of the screen as + * new output is added. See setTrackOutput() + */ + bool trackOutput() const; + + /** + * Returns the text which is currently selected. + * + * @param preserveLineBreaks See Screen::selectedText() + */ + QString selectedText( bool preserveLineBreaks ) const; + +public slots: + /** + * Notifies the window that the contents of the associated terminal screen have changed. + * This moves the window to the bottom of the screen if trackOutput() is true and causes + * the outputChanged() signal to be emitted. + */ + void notifyOutputChanged(); + +signals: + /** + * Emitted when the contents of the associated terminal screen (see screen()) changes. + */ + void outputChanged(); + + /** + * Emitted when the screen window is scrolled to a different position. + * + * @param line The line which is now at the top of the window. + */ + void scrolled(int line); + + /** Emitted when the selection is changed. */ + void selectionChanged(); + +private: + int endWindowLine() const; + void fillUnusedArea(); + + Screen* _screen; // see setScreen() , screen() + Character* _windowBuffer; + int _windowBufferSize; + bool _bufferNeedsUpdate; + + int _windowLines; + int _currentLine; // see scrollTo() , currentLine() + bool _trackOutput; // see setTrackOutput() , trackOutput() + int _scrollCount; // count of lines which the window has been scrolled by since + // the last call to resetScrollCount() +}; + +} +#endif // SCREENWINDOW_H diff --git a/lib/SearchBar.cpp b/lib/SearchBar.cpp new file mode 100644 index 0000000..21e7325 --- /dev/null +++ b/lib/SearchBar.cpp @@ -0,0 +1,118 @@ +/* + Copyright 2013 Christian Surlykke + + 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. +*/ +#include +#include +#include +#include + +#include "SearchBar.h" + +SearchBar::SearchBar(QWidget *parent) : QWidget(parent) +{ + widget.setupUi(this); + connect(widget.closeButton, SIGNAL(clicked()), this, SLOT(hide())); + connect(widget.searchTextEdit, SIGNAL(textChanged(QString)), this, SIGNAL(searchCriteriaChanged())); + connect(widget.findPreviousButton, SIGNAL(clicked()), this, SIGNAL(findPrevious())); + connect(widget.findNextButton, SIGNAL(clicked()), this, SIGNAL(findNext())); + + connect(this, SIGNAL(searchCriteriaChanged()), this, SLOT(clearBackgroundColor())); + + QMenu *optionsMenu = new QMenu(widget.optionsButton); + widget.optionsButton->setMenu(optionsMenu); + + m_matchCaseMenuEntry = optionsMenu->addAction(tr("Match case")); + m_matchCaseMenuEntry->setCheckable(true); + m_matchCaseMenuEntry->setChecked(true); + connect(m_matchCaseMenuEntry, SIGNAL(toggled(bool)), this, SIGNAL(searchCriteriaChanged())); + + + m_useRegularExpressionMenuEntry = optionsMenu->addAction(tr("Regular expression")); + m_useRegularExpressionMenuEntry->setCheckable(true); + connect(m_useRegularExpressionMenuEntry, SIGNAL(toggled(bool)), this, SIGNAL(searchCriteriaChanged())); + + m_highlightMatchesMenuEntry = optionsMenu->addAction(tr("Higlight all matches")); + m_highlightMatchesMenuEntry->setCheckable(true); + m_highlightMatchesMenuEntry->setChecked(true); + connect(m_highlightMatchesMenuEntry, SIGNAL(toggled(bool)), this, SIGNAL(highlightMatchesChanged(bool))); +} + +SearchBar::~SearchBar() { +} + +QString SearchBar::searchText() +{ + return widget.searchTextEdit->text(); +} + + +bool SearchBar::useRegularExpression() +{ + return m_useRegularExpressionMenuEntry->isChecked(); +} + +bool SearchBar::matchCase() +{ + return m_matchCaseMenuEntry->isChecked(); +} + +bool SearchBar::highlightAllMatches() +{ + return m_highlightMatchesMenuEntry->isChecked(); +} + +void SearchBar::show() +{ + QWidget::show(); + widget.searchTextEdit->setFocus(); +} + +void SearchBar::noMatchFound() +{ + QPalette palette; + palette.setColor(widget.searchTextEdit->backgroundRole(), QColor(255, 128, 128)); + widget.searchTextEdit->setPalette(palette); +} + + +void SearchBar::keyReleaseEvent(QKeyEvent* keyEvent) +{ + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) + { + if (keyEvent->modifiers() == Qt::ShiftModifier) + { + findPrevious(); + } + else + { + findNext(); + } + } + else if (keyEvent->key() == Qt::Key_Escape) + { + hide(); + } +} + +void SearchBar::clearBackgroundColor() +{ + QPalette p; + p.setColor(QPalette::Base, Qt::white); + widget.searchTextEdit->setPalette(p); + +} \ No newline at end of file diff --git a/lib/SearchBar.h b/lib/SearchBar.h new file mode 100644 index 0000000..3eb751f --- /dev/null +++ b/lib/SearchBar.h @@ -0,0 +1,60 @@ +/* + Copyright 2013 Christian Surlykke + + 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. +*/ +#ifndef _SEARCHBAR_H +#define _SEARCHBAR_H + +#include + +#include "ui_SearchBar.h" +#include "HistorySearch.h" + +class SearchBar : public QWidget { + Q_OBJECT +public: + SearchBar(QWidget* parent = 0); + virtual ~SearchBar(); + virtual void show(); + QString searchText(); + bool useRegularExpression(); + bool matchCase(); + bool highlightAllMatches(); + +public slots: + void noMatchFound(); + +signals: + void searchCriteriaChanged(); + void highlightMatchesChanged(bool highlightMatches); + void findNext(); + void findPrevious(); + +protected: + virtual void keyReleaseEvent(QKeyEvent* keyEvent); + +private slots: + void clearBackgroundColor(); + +private: + Ui::SearchBar widget; + QAction *m_matchCaseMenuEntry; + QAction *m_useRegularExpressionMenuEntry; + QAction *m_highlightMatchesMenuEntry; +}; + +#endif /* _SEARCHBAR_H */ diff --git a/lib/SearchBar.ui b/lib/SearchBar.ui new file mode 100644 index 0000000..6e0c4e8 --- /dev/null +++ b/lib/SearchBar.ui @@ -0,0 +1,85 @@ + + + SearchBar + + + + 0 + 0 + 399 + 40 + + + + SearchBar + + + + + + X + + + + + + + + + + + + Find: + + + + + + + + + + < + + + + + + + + + + + + > + + + + + + + + + + + + ... + + + + + + + + QToolButton::InstantPopup + + + Qt::DownArrow + + + + + + + + diff --git a/lib/Session.cpp b/lib/Session.cpp new file mode 100644 index 0000000..e6eca88 --- /dev/null +++ b/lib/Session.cpp @@ -0,0 +1,1055 @@ +/* + This file is part of Konsole + + Copyright (C) 2006-2007 by Robert Knight + Copyright (C) 1997,1998 by Lars Doelle + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. +*/ + +// Own +#include "Session.h" + +// Standard +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Pty.h" +//#include "kptyprocess.h" +#include "TerminalDisplay.h" +#include "ShellCommand.h" +#include "Vt102Emulation.h" + +using namespace Konsole; + +int Session::lastSessionId = 0; + +Session::Session(QObject* parent) : + QObject(parent), + _shellProcess(0) + , _emulation(0) + , _monitorActivity(false) + , _monitorSilence(false) + , _notifiedActivity(false) + , _autoClose(true) + , _wantedClose(false) + , _silenceSeconds(10) + , _addToUtmp(false) // disabled by default because of a bug encountered on certain systems + // which caused Konsole to hang when closing a tab and then opening a new + // one. A 'QProcess destroyed while still running' warning was being + // printed to the terminal. Likely a problem in KPty::logout() + // or KPty::login() which uses a QProcess to start /usr/bin/utempter + , _flowControl(true) + , _fullScripting(false) + , _sessionId(0) +// , _zmodemBusy(false) +// , _zmodemProc(0) +// , _zmodemProgress(0) + , _hasDarkBackground(false) +{ + //prepare DBus communication +// new SessionAdaptor(this); + _sessionId = ++lastSessionId; +// QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this); + + //create teletype for I/O with shell process + _shellProcess = new Pty(); + + //create emulation backend + _emulation = new Vt102Emulation(); + + connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ), + this, SLOT( setUserTitle( int, const QString & ) ) ); + connect( _emulation, SIGNAL( stateSet(int) ), + this, SLOT( activityStateSet(int) ) ); +// connect( _emulation, SIGNAL( zmodemDetected() ), this , +// SLOT( fireZModemDetected() ) ); + connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ), + this, SIGNAL( changeTabTextColorRequest( int ) ) ); + connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString &)), + this, SIGNAL( profileChangeCommandReceived(const QString &)) ); + // TODO + // connect( _emulation,SIGNAL(imageSizeChanged(int,int)) , this , + // SLOT(onEmulationSizeChange(int,int)) ); + + //connect teletype to emulation backend + _shellProcess->setUtf8Mode(_emulation->utf8()); + + connect( _shellProcess,SIGNAL(receivedData(const char *,int)),this, + SLOT(onReceiveBlock(const char *,int)) ); + connect( _emulation,SIGNAL(sendData(const char *,int)),_shellProcess, + SLOT(sendData(const char *,int)) ); + connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) ); + connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) ); + + connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) ); + // not in kprocess anymore connect( _shellProcess,SIGNAL(done(int)), this, SLOT(done(int)) ); + + //setup timer for monitoring session activity + _monitorTimer = new QTimer(this); + _monitorTimer->setSingleShot(true); + connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone())); +} + +WId Session::windowId() const +{ + // Returns a window ID for this session which is used + // to set the WINDOWID environment variable in the shell + // process. + // + // Sessions can have multiple views or no views, which means + // that a single ID is not always going to be accurate. + // + // If there are no views, the window ID is just 0. If + // there are multiple views, then the window ID for the + // top-level window which contains the first view is + // returned + + if ( _views.count() == 0 ) { + return 0; + } else { + QWidget * window = _views.first(); + + Q_ASSERT( window ); + + while ( window->parentWidget() != 0 ) { + window = window->parentWidget(); + } + + return window->winId(); + } +} + +void Session::setDarkBackground(bool darkBackground) +{ + _hasDarkBackground = darkBackground; +} +bool Session::hasDarkBackground() const +{ + return _hasDarkBackground; +} +bool Session::isRunning() const +{ + return _shellProcess->state() == QProcess::Running; +} + +void Session::setCodec(QTextCodec * codec) +{ + emulation()->setCodec(codec); +} + +void Session::setProgram(const QString & program) +{ + _program = ShellCommand::expand(program); +} +void Session::setInitialWorkingDirectory(const QString & dir) +{ + _initialWorkingDir = ShellCommand::expand(dir); +} +void Session::setArguments(const QStringList & arguments) +{ + _arguments = ShellCommand::expand(arguments); +} + +QList Session::views() const +{ + return _views; +} + +void Session::addView(TerminalDisplay * widget) +{ + Q_ASSERT( !_views.contains(widget) ); + + _views.append(widget); + + if ( _emulation != 0 ) { + // connect emulation - view signals and slots + connect( widget , SIGNAL(keyPressedSignal(QKeyEvent *)) , _emulation , + SLOT(sendKeyEvent(QKeyEvent *)) ); + connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation , + SLOT(sendMouseEvent(int,int,int,int)) ); + connect( widget , SIGNAL(sendStringToEmu(const char *)) , _emulation , + SLOT(sendString(const char *)) ); + + // allow emulation to notify view when the foreground process + // indicates whether or not it is interested in mouse signals + connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget , + SLOT(setUsesMouse(bool)) ); + + widget->setUsesMouse( _emulation->programUsesMouse() ); + + widget->setScreenWindow(_emulation->createWindow()); + } + + //connect view signals and slots + QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this, + SLOT(onViewSizeChange(int,int))); + + QObject::connect( widget ,SIGNAL(destroyed(QObject *)) , this , + SLOT(viewDestroyed(QObject *)) ); +//slot for close + QObject::connect(this, SIGNAL(finished()), widget, SLOT(close())); + +} + +void Session::viewDestroyed(QObject * view) +{ + TerminalDisplay * display = (TerminalDisplay *)view; + + Q_ASSERT( _views.contains(display) ); + + removeView(display); +} + +void Session::removeView(TerminalDisplay * widget) +{ + _views.removeAll(widget); + + disconnect(widget,0,this,0); + + if ( _emulation != 0 ) { + // disconnect + // - key presses signals from widget + // - mouse activity signals from widget + // - string sending signals from widget + // + // ... and any other signals connected in addView() + disconnect( widget, 0, _emulation, 0); + + // disconnect state change signals emitted by emulation + disconnect( _emulation , 0 , widget , 0); + } + + // close the session automatically when the last view is removed + if ( _views.count() == 0 ) { + close(); + } +} + +void Session::run() +{ + //check that everything is in place to run the session + if (_program.isEmpty()) { + qDebug() << "Session::run() - program to run not set."; + } + else { + qDebug() << "Session::run() - program:" << _program; + } + + if (_arguments.isEmpty()) { + qDebug() << "Session::run() - no command line arguments specified."; + } + else { + qDebug() << "Session::run() - arguments:" << _arguments; + } + + // Upon a KPty error, there is no description on what that error was... + // Check to see if the given program is executable. + + + /* ok iam not exactly sure where _program comes from - however it was set to /bin/bash on my system + * Thats bad for BSD as its /usr/local/bin/bash there - its also bad for arch as its /usr/bin/bash there too! + * So i added a check to see if /bin/bash exists - if no then we use $SHELL - if that does not exist either, we fall back to /bin/sh + * As far as i know /bin/sh exists on every unix system.. You could also just put some ifdef __FREEBSD__ here but i think these 2 filechecks are worth + * their computing time on any system - especially with the problem on arch linux beeing there too. + */ + QString exec = QFile::encodeName(_program); + // if 'exec' is not specified, fall back to default shell. if that + // is not set then fall back to /bin/sh + + // here we expect full path. If there is no fullpath let's expect it's + // a custom shell (eg. python, etc.) available in the PATH. + if (exec.startsWith("/")) + { + QFile excheck(exec); + if ( exec.isEmpty() || !excheck.exists() ) { + exec = getenv("SHELL"); + } + excheck.setFileName(exec); + + if ( exec.isEmpty() || !excheck.exists() ) { + exec = "/bin/sh"; + } + } + + // _arguments sometimes contain ("") so isEmpty() + // or count() does not work as expected... + QString argsTmp(_arguments.join(" ").trimmed()); + QStringList arguments; + arguments << exec; + if (argsTmp.length()) + arguments << _arguments; + + QString cwd = QDir::currentPath(); + if (!_initialWorkingDir.isEmpty()) { + _shellProcess->setWorkingDirectory(_initialWorkingDir); + } else { + _shellProcess->setWorkingDirectory(cwd); + } + + _shellProcess->setFlowControlEnabled(_flowControl); + _shellProcess->setErase(_emulation->eraseChar()); + + // this is not strictly accurate use of the COLORFGBG variable. This does not + // tell the terminal exactly which colors are being used, but instead approximates + // the color scheme as "black on white" or "white on black" depending on whether + // the background color is deemed dark or not + QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15"; + + /* if we do all the checking if this shell exists then we use it ;) + * Dont know about the arguments though.. maybe youll need some more checking im not sure + * However this works on Arch and FreeBSD now. + */ + int result = _shellProcess->start(exec, + arguments, + _environment << backgroundColorHint, + windowId(), + _addToUtmp); + + if (result < 0) { + qDebug() << "CRASHED! result: " << result; + return; + } + + _shellProcess->setWriteable(false); // We are reachable via kwrited. + qDebug() << "started!"; + emit started(); +} + +void Session::setUserTitle( int what, const QString & caption ) +{ + //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) + bool modified = false; + + // (btw: what=0 changes _userTitle and icon, what=1 only icon, what=2 only _nameTitle + if ((what == 0) || (what == 2)) { + if ( _userTitle != caption ) { + _userTitle = caption; + modified = true; + } + } + + if ((what == 0) || (what == 1)) { + if ( _iconText != caption ) { + _iconText = caption; + modified = true; + } + } + + if (what == 11) { + QString colorString = caption.section(';',0,0); + qDebug() << __FILE__ << __LINE__ << ": setting background colour to " << colorString; + QColor backColor = QColor(colorString); + if (backColor.isValid()) { // change color via \033]11;Color\007 + if (backColor != _modifiedBackground) { + _modifiedBackground = backColor; + + // bail out here until the code to connect the terminal display + // to the changeBackgroundColor() signal has been written + // and tested - just so we don't forget to do this. + Q_ASSERT( 0 ); + + emit changeBackgroundColorRequest(backColor); + } + } + } + + if (what == 30) { + if ( _nameTitle != caption ) { + setTitle(Session::NameRole,caption); + return; + } + } + + if (what == 31) { + QString cwd=caption; + cwd=cwd.replace( QRegExp("^~"), QDir::homePath() ); + emit openUrlRequest(cwd); + } + + // change icon via \033]32;Icon\007 + if (what == 32) { + if ( _iconName != caption ) { + _iconName = caption; + + modified = true; + } + } + + if (what == 50) { + emit profileChangeCommandReceived(caption); + return; + } + + if ( modified ) { + emit titleChanged(); + } +} + +QString Session::userTitle() const +{ + return _userTitle; +} +void Session::setTabTitleFormat(TabTitleContext context , const QString & format) +{ + if ( context == LocalTabTitle ) { + _localTabTitleFormat = format; + } else if ( context == RemoteTabTitle ) { + _remoteTabTitleFormat = format; + } +} +QString Session::tabTitleFormat(TabTitleContext context) const +{ + if ( context == LocalTabTitle ) { + return _localTabTitleFormat; + } else if ( context == RemoteTabTitle ) { + return _remoteTabTitleFormat; + } + + return QString(); +} + +void Session::monitorTimerDone() +{ + //FIXME: The idea here is that the notification popup will appear to tell the user than output from + //the terminal has stopped and the popup will disappear when the user activates the session. + // + //This breaks with the addition of multiple views of a session. The popup should disappear + //when any of the views of the session becomes active + + + //FIXME: Make message text for this notification and the activity notification more descriptive. + if (_monitorSilence) { + emit silence(); + emit stateChanged(NOTIFYSILENCE); + } else { + emit stateChanged(NOTIFYNORMAL); + } + + _notifiedActivity=false; +} + +void Session::activityStateSet(int state) +{ + if (state==NOTIFYBELL) { + QString s; + s.sprintf("Bell in session '%s'",_nameTitle.toUtf8().data()); + + emit bellRequest( s ); + } else if (state==NOTIFYACTIVITY) { + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } + + if ( _monitorActivity ) { + //FIXME: See comments in Session::monitorTimerDone() + if (!_notifiedActivity) { + emit activity(); + _notifiedActivity=true; + } + } + } + + if ( state==NOTIFYACTIVITY && !_monitorActivity ) { + state = NOTIFYNORMAL; + } + if ( state==NOTIFYSILENCE && !_monitorSilence ) { + state = NOTIFYNORMAL; + } + + emit stateChanged(state); +} + +void Session::onViewSizeChange(int /*height*/, int /*width*/) +{ + updateTerminalSize(); +} +void Session::onEmulationSizeChange(int lines , int columns) +{ + setSize( QSize(lines,columns) ); +} + +void Session::updateTerminalSize() +{ + QListIterator viewIter(_views); + + int minLines = -1; + int minColumns = -1; + + // minimum number of lines and columns that views require for + // their size to be taken into consideration ( to avoid problems + // with new view widgets which haven't yet been set to their correct size ) + const int VIEW_LINES_THRESHOLD = 2; + const int VIEW_COLUMNS_THRESHOLD = 2; + + //select largest number of lines and columns that will fit in all visible views + while ( viewIter.hasNext() ) { + TerminalDisplay * view = viewIter.next(); + if ( view->isHidden() == false && + view->lines() >= VIEW_LINES_THRESHOLD && + view->columns() >= VIEW_COLUMNS_THRESHOLD ) { + minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() ); + minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() ); + } + } + + // backend emulation must have a _terminal of at least 1 column x 1 line in size + if ( minLines > 0 && minColumns > 0 ) { + _emulation->setImageSize( minLines , minColumns ); + _shellProcess->setWindowSize( minLines , minColumns ); + } +} + +void Session::refresh() +{ + // attempt to get the shell process to redraw the display + // + // this requires the program running in the shell + // to cooperate by sending an update in response to + // a window size change + // + // the window size is changed twice, first made slightly larger and then + // resized back to its normal size so that there is actually a change + // in the window size (some shells do nothing if the + // new and old sizes are the same) + // + // if there is a more 'correct' way to do this, please + // send an email with method or patches to konsole-devel@kde.org + + const QSize existingSize = _shellProcess->windowSize(); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1); + _shellProcess->setWindowSize(existingSize.height(),existingSize.width()); +} + +bool Session::sendSignal(int signal) +{ + int result = ::kill(_shellProcess->pid(),signal); + + if ( result == 0 ) + { + _shellProcess->waitForFinished(); + return true; + } + else + return false; +} + +void Session::close() +{ + _autoClose = true; + _wantedClose = true; + if (!_shellProcess->isRunning() || !sendSignal(SIGHUP)) { + // Forced close. + QTimer::singleShot(1, this, SIGNAL(finished())); + } +} + +void Session::sendText(const QString & text) const +{ + _emulation->sendText(text); +} + +Session::~Session() +{ + delete _emulation; + delete _shellProcess; +// delete _zmodemProc; +} + +void Session::setProfileKey(const QString & key) +{ + _profileKey = key; + emit profileChanged(key); +} +QString Session::profileKey() const +{ + return _profileKey; +} + +void Session::done(int exitStatus) +{ + if (!_autoClose) { + _userTitle = ("This session is done. Finished"); + emit titleChanged(); + return; + } + + QString message; + if (!_wantedClose || exitStatus != 0) { + + if (_shellProcess->exitStatus() == QProcess::NormalExit) { + message.sprintf("Session '%s' exited with status %d.", + _nameTitle.toUtf8().data(), exitStatus); + } else { + message.sprintf("Session '%s' crashed.", + _nameTitle.toUtf8().data()); + } + } + + if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit ) + message.sprintf("Session '%s' exited unexpectedly.", + _nameTitle.toUtf8().data()); + else + emit finished(); + +} + +Emulation * Session::emulation() const +{ + return _emulation; +} + +QString Session::keyBindings() const +{ + return _emulation->keyBindings(); +} + +QStringList Session::environment() const +{ + return _environment; +} + +void Session::setEnvironment(const QStringList & environment) +{ + _environment = environment; +} + +int Session::sessionId() const +{ + return _sessionId; +} + +void Session::setKeyBindings(const QString & id) +{ + _emulation->setKeyBindings(id); +} + +void Session::setTitle(TitleRole role , const QString & newTitle) +{ + if ( title(role) != newTitle ) { + if ( role == NameRole ) { + _nameTitle = newTitle; + } else if ( role == DisplayedTitleRole ) { + _displayTitle = newTitle; + } + + emit titleChanged(); + } +} + +QString Session::title(TitleRole role) const +{ + if ( role == NameRole ) { + return _nameTitle; + } else if ( role == DisplayedTitleRole ) { + return _displayTitle; + } else { + return QString(); + } +} + +void Session::setIconName(const QString & iconName) +{ + if ( iconName != _iconName ) { + _iconName = iconName; + emit titleChanged(); + } +} + +void Session::setIconText(const QString & iconText) +{ + _iconText = iconText; + //kDebug(1211)<<"Session setIconText " << _iconText; +} + +QString Session::iconName() const +{ + return _iconName; +} + +QString Session::iconText() const +{ + return _iconText; +} + +void Session::setHistoryType(const HistoryType & hType) +{ + _emulation->setHistory(hType); +} + +const HistoryType & Session::historyType() const +{ + return _emulation->history(); +} + +void Session::clearHistory() +{ + _emulation->clearHistory(); +} + +QStringList Session::arguments() const +{ + return _arguments; +} + +QString Session::program() const +{ + return _program; +} + +// unused currently +bool Session::isMonitorActivity() const +{ + return _monitorActivity; +} +// unused currently +bool Session::isMonitorSilence() const +{ + return _monitorSilence; +} + +void Session::setMonitorActivity(bool _monitor) +{ + _monitorActivity=_monitor; + _notifiedActivity=false; + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilence(bool _monitor) +{ + if (_monitorSilence==_monitor) { + return; + } + + _monitorSilence=_monitor; + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } else { + _monitorTimer->stop(); + } + + activityStateSet(NOTIFYNORMAL); +} + +void Session::setMonitorSilenceSeconds(int seconds) +{ + _silenceSeconds=seconds; + if (_monitorSilence) { + _monitorTimer->start(_silenceSeconds*1000); + } +} + +void Session::setAddToUtmp(bool set) +{ + _addToUtmp = set; +} + +void Session::setFlowControlEnabled(bool enabled) +{ + if (_flowControl == enabled) { + return; + } + + _flowControl = enabled; + + if (_shellProcess) { + _shellProcess->setFlowControlEnabled(_flowControl); + } + + emit flowControlEnabledChanged(enabled); +} +bool Session::flowControlEnabled() const +{ + return _flowControl; +} +//void Session::fireZModemDetected() +//{ +// if (!_zmodemBusy) +// { +// QTimer::singleShot(10, this, SIGNAL(zmodemDetected())); +// _zmodemBusy = true; +// } +//} + +//void Session::cancelZModem() +//{ +// _shellProcess->sendData("\030\030\030\030", 4); // Abort +// _zmodemBusy = false; +//} + +//void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list) +//{ +// _zmodemBusy = true; +// _zmodemProc = new KProcess(); +// _zmodemProc->setOutputChannelMode( KProcess::SeparateChannels ); +// +// *_zmodemProc << zmodem << "-v" << list; +// +// if (!dir.isEmpty()) +// _zmodemProc->setWorkingDirectory(dir); +// +// _zmodemProc->start(); +// +// connect(_zmodemProc,SIGNAL (readyReadStandardOutput()), +// this, SLOT(zmodemReadAndSendBlock())); +// connect(_zmodemProc,SIGNAL (readyReadStandardError()), +// this, SLOT(zmodemReadStatus())); +// connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)), +// this, SLOT(zmodemFinished())); +// +// disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) ); +// connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) ); +// +// _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, +// i18n("ZModem Progress")); +// +// connect(_zmodemProgress, SIGNAL(user1Clicked()), +// this, SLOT(zmodemDone())); +// +// _zmodemProgress->show(); +//} + +/*void Session::zmodemReadAndSendBlock() +{ + _zmodemProc->setReadChannel( QProcess::StandardOutput ); + QByteArray data = _zmodemProc->readAll(); + + if ( data.count() == 0 ) + return; + + _shellProcess->sendData(data.constData(),data.count()); +} +*/ +/* +void Session::zmodemReadStatus() +{ + _zmodemProc->setReadChannel( QProcess::StandardError ); + QByteArray msg = _zmodemProc->readAll(); + while(!msg.isEmpty()) + { + int i = msg.indexOf('\015'); + int j = msg.indexOf('\012'); + QByteArray txt; + if ((i != -1) && ((j == -1) || (i < j))) + { + msg = msg.mid(i+1); + } + else if (j != -1) + { + txt = msg.left(j); + msg = msg.mid(j+1); + } + else + { + txt = msg; + msg.truncate(0); + } + if (!txt.isEmpty()) + _zmodemProgress->addProgressText(QString::fromLocal8Bit(txt)); + } +} +*/ +/* +void Session::zmodemRcvBlock(const char *data, int len) +{ + QByteArray ba( data, len ); + + _zmodemProc->write( ba ); +} +*/ +/* +void Session::zmodemFinished() +{ + if (_zmodemProc) + { + delete _zmodemProc; + _zmodemProc = 0; + _zmodemBusy = false; + + disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) ); + connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) ); + + _shellProcess->sendData("\030\030\030\030", 4); // Abort + _shellProcess->sendData("\001\013\n", 3); // Try to get prompt back + _zmodemProgress->transferDone(); + } +} +*/ +void Session::onReceiveBlock( const char * buf, int len ) +{ + _emulation->receiveData( buf, len ); + emit receivedData( QString::fromLatin1( buf, len ) ); +} + +QSize Session::size() +{ + return _emulation->imageSize(); +} + +void Session::setSize(const QSize & size) +{ + if ((size.width() <= 1) || (size.height() <= 1)) { + return; + } + + emit resizeRequest(size); +} +int Session::foregroundProcessId() const +{ + return _shellProcess->foregroundProcessGroup(); +} +int Session::processId() const +{ + return _shellProcess->pid(); +} + +SessionGroup::SessionGroup() + : _masterMode(0) +{ +} +SessionGroup::~SessionGroup() +{ + // disconnect all + connectAll(false); +} +int SessionGroup::masterMode() const +{ + return _masterMode; +} +QList SessionGroup::sessions() const +{ + return _sessions.keys(); +} +bool SessionGroup::masterStatus(Session * session) const +{ + return _sessions[session]; +} + +void SessionGroup::addSession(Session * session) +{ + _sessions.insert(session,false); + + QListIterator masterIter(masters()); + + while ( masterIter.hasNext() ) { + connectPair(masterIter.next(),session); + } +} +void SessionGroup::removeSession(Session * session) +{ + setMasterStatus(session,false); + + QListIterator masterIter(masters()); + + while ( masterIter.hasNext() ) { + disconnectPair(masterIter.next(),session); + } + + _sessions.remove(session); +} +void SessionGroup::setMasterMode(int mode) +{ + _masterMode = mode; + + connectAll(false); + connectAll(true); +} +QList SessionGroup::masters() const +{ + return _sessions.keys(true); +} +void SessionGroup::connectAll(bool connect) +{ + QListIterator masterIter(masters()); + + while ( masterIter.hasNext() ) { + Session * master = masterIter.next(); + + QListIterator otherIter(_sessions.keys()); + while ( otherIter.hasNext() ) { + Session * other = otherIter.next(); + + if ( other != master ) { + if ( connect ) { + connectPair(master,other); + } else { + disconnectPair(master,other); + } + } + } + } +} +void SessionGroup::setMasterStatus(Session * session, bool master) +{ + bool wasMaster = _sessions[session]; + _sessions[session] = master; + + if ((!wasMaster && !master) + || (wasMaster && master)) { + return; + } + + QListIterator iter(_sessions.keys()); + while (iter.hasNext()) { + Session * other = iter.next(); + + if (other != session) { + if (master) { + connectPair(session, other); + } else { + disconnectPair(session, other); + } + } + } +} + +void SessionGroup::connectPair(Session * master , Session * other) +{ +// qDebug() << k_funcinfo; + + if ( _masterMode & CopyInputToAll ) { + qDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle(); + + connect( master->emulation() , SIGNAL(sendData(const char *,int)) , other->emulation() , + SLOT(sendString(const char *,int)) ); + } +} +void SessionGroup::disconnectPair(Session * master , Session * other) +{ +// qDebug() << k_funcinfo; + + if ( _masterMode & CopyInputToAll ) { + qDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle(); + + disconnect( master->emulation() , SIGNAL(sendData(const char *,int)) , other->emulation() , + SLOT(sendString(const char *,int)) ); + } +} + +//#include "moc_Session.cpp" diff --git a/lib/Session.h b/lib/Session.h new file mode 100644 index 0000000..b3a389f --- /dev/null +++ b/lib/Session.h @@ -0,0 +1,622 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright (C) 2007 by Robert Knight + Copyright (C) 1997,1998 by Lars Doelle + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. +*/ + +#ifndef SESSION_H +#define SESSION_H + + +#include +#include + +#include "History.h" + +class KProcess; + +namespace Konsole { + +class Emulation; +class Pty; +class TerminalDisplay; +//class ZModemDialog; + +/** + * Represents a terminal session consisting of a pseudo-teletype and a terminal emulation. + * The pseudo-teletype (or PTY) handles I/O between the terminal process and Konsole. + * The terminal emulation ( Emulation and subclasses ) processes the output stream from the + * PTY and produces a character image which is then shown on views connected to the session. + * + * Each Session can be connected to one or more views by using the addView() method. + * The attached views can then display output from the program running in the terminal + * or send input to the program in the terminal in the form of keypresses and mouse + * activity. + */ +class Session : public QObject { + Q_OBJECT + +public: + Q_PROPERTY(QString name READ nameTitle) + Q_PROPERTY(int processId READ processId) + Q_PROPERTY(QString keyBindings READ keyBindings WRITE setKeyBindings) + Q_PROPERTY(QSize size READ size WRITE setSize) + + /** + * Constructs a new session. + * + * To start the terminal process, call the run() method, + * after specifying the program and arguments + * using setProgram() and setArguments() + * + * If no program or arguments are specified explicitly, the Session + * falls back to using the program specified in the SHELL environment + * variable. + */ + Session(QObject* parent = 0); + virtual ~Session(); + + /** + * Returns true if the session is currently running. This will be true + * after run() has been called successfully. + */ + bool isRunning() const; + + /** + * Sets the profile associated with this session. + * + * @param profileKey A key which can be used to obtain the current + * profile settings from the SessionManager + */ + void setProfileKey(const QString & profileKey); + /** + * Returns the profile key associated with this session. + * This can be passed to the SessionManager to obtain the current + * profile settings. + */ + QString profileKey() const; + + /** + * Adds a new view for this session. + * + * The viewing widget will display the output from the terminal and + * input from the viewing widget (key presses, mouse activity etc.) + * will be sent to the terminal. + * + * Views can be removed using removeView(). The session is automatically + * closed when the last view is removed. + */ + void addView(TerminalDisplay * widget); + /** + * Removes a view from this session. When the last view is removed, + * the session will be closed automatically. + * + * @p widget will no longer display output from or send input + * to the terminal + */ + void removeView(TerminalDisplay * widget); + + /** + * Returns the views connected to this session + */ + QList views() const; + + /** + * Returns the terminal emulation instance being used to encode / decode + * characters to / from the process. + */ + Emulation * emulation() const; + + /** + * Returns the environment of this session as a list of strings like + * VARIABLE=VALUE + */ + QStringList environment() const; + /** + * Sets the environment for this session. + * @p environment should be a list of strings like + * VARIABLE=VALUE + */ + void setEnvironment(const QStringList & environment); + + /** Returns the unique ID for this session. */ + int sessionId() const; + + /** + * Return the session title set by the user (ie. the program running + * in the terminal), or an empty string if the user has not set a custom title + */ + QString userTitle() const; + + /** + * This enum describes the contexts for which separate + * tab title formats may be specified. + */ + enum TabTitleContext { + /** Default tab title format */ + LocalTabTitle, + /** + * Tab title format used session currently contains + * a connection to a remote computer (via SSH) + */ + RemoteTabTitle + }; + /** + * Sets the format used by this session for tab titles. + * + * @param context The context whoose format should be set. + * @param format The tab title format. This may be a mixture + * of plain text and dynamic elements denoted by a '%' character + * followed by a letter. (eg. %d for directory). The dynamic + * elements available depend on the @p context + */ + void setTabTitleFormat(TabTitleContext context , const QString & format); + /** Returns the format used by this session for tab titles. */ + QString tabTitleFormat(TabTitleContext context) const; + + + /** Returns the arguments passed to the shell process when run() is called. */ + QStringList arguments() const; + /** Returns the program name of the shell process started when run() is called. */ + QString program() const; + + /** + * Sets the command line arguments which the session's program will be passed when + * run() is called. + */ + void setArguments(const QStringList & arguments); + /** Sets the program to be executed when run() is called. */ + void setProgram(const QString & program); + + /** Returns the session's current working directory. */ + QString initialWorkingDirectory() { + return _initialWorkingDir; + } + + /** + * Sets the initial working directory for the session when it is run + * This has no effect once the session has been started. + */ + void setInitialWorkingDirectory( const QString & dir ); + + /** + * Sets the type of history store used by this session. + * Lines of output produced by the terminal are added + * to the history store. The type of history store + * used affects the number of lines which can be + * remembered before they are lost and the storage + * (in memory, on-disk etc.) used. + */ + void setHistoryType(const HistoryType & type); + /** + * Returns the type of history store used by this session. + */ + const HistoryType & historyType() const; + /** + * Clears the history store used by this session. + */ + void clearHistory(); + + /** + * Enables monitoring for activity in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYACTIVITY state flag when output is + * received from the terminal. + */ + void setMonitorActivity(bool); + /** Returns true if monitoring for activity is enabled. */ + bool isMonitorActivity() const; + + /** + * Enables monitoring for silence in the session. + * This will cause notifySessionState() to be emitted + * with the NOTIFYSILENCE state flag when output is not + * received from the terminal for a certain period of + * time, specified with setMonitorSilenceSeconds() + */ + void setMonitorSilence(bool); + /** + * Returns true if monitoring for inactivity (silence) + * in the session is enabled. + */ + bool isMonitorSilence() const; + /** See setMonitorSilence() */ + void setMonitorSilenceSeconds(int seconds); + + /** + * Sets the key bindings used by this session. The bindings + * specify how input key sequences are translated into + * the character stream which is sent to the terminal. + * + * @param id The name of the key bindings to use. The + * names of available key bindings can be determined using the + * KeyboardTranslatorManager class. + */ + void setKeyBindings(const QString & id); + /** Returns the name of the key bindings used by this session. */ + QString keyBindings() const; + + /** + * This enum describes the available title roles. + */ + enum TitleRole { + /** The name of the session. */ + NameRole, + /** The title of the session which is displayed in tabs etc. */ + DisplayedTitleRole + }; + + /** Sets the session's title for the specified @p role to @p title. */ + void setTitle(TitleRole role , const QString & title); + /** Returns the session's title for the specified @p role. */ + QString title(TitleRole role) const; + /** Convenience method used to read the name property. Returns title(Session::NameRole). */ + QString nameTitle() const { + return title(Session::NameRole); + } + + /** Sets the name of the icon associated with this session. */ + void setIconName(const QString & iconName); + /** Returns the name of the icon associated with this session. */ + QString iconName() const; + + /** Sets the text of the icon associated with this session. */ + void setIconText(const QString & iconText); + /** Returns the text of the icon associated with this session. */ + QString iconText() const; + + /** Specifies whether a utmp entry should be created for the pty used by this session. */ + void setAddToUtmp(bool); + + /** Sends the specified @p signal to the terminal process. */ + bool sendSignal(int signal); + + /** + * Specifies whether to close the session automatically when the terminal + * process terminates. + */ + void setAutoClose(bool b) { + _autoClose = b; + } + + /** + * Sets whether flow control is enabled for this terminal + * session. + */ + void setFlowControlEnabled(bool enabled); + + /** Returns whether flow control is enabled for this terminal session. */ + bool flowControlEnabled() const; + + /** + * Sends @p text to the current foreground terminal program. + */ + void sendText(const QString & text) const; + + /** + * Returns the process id of the terminal process. + * This is the id used by the system API to refer to the process. + */ + int processId() const; + + /** + * Returns the process id of the terminal's foreground process. + * This is initially the same as processId() but can change + * as the user starts other programs inside the terminal. + */ + int foregroundProcessId() const; + + /** Returns the terminal session's window size in lines and columns. */ + QSize size(); + /** + * Emits a request to resize the session to accommodate + * the specified window size. + * + * @param size The size in lines and columns to request. + */ + void setSize(const QSize & size); + + /** Sets the text codec used by this session's terminal emulation. */ + void setCodec(QTextCodec * codec); + + /** + * Sets whether the session has a dark background or not. The session + * uses this information to set the COLORFGBG variable in the process's + * environment, which allows the programs running in the terminal to determine + * whether the background is light or dark and use appropriate colors by default. + * + * This has no effect once the session is running. + */ + void setDarkBackground(bool darkBackground); + /** + * Returns true if the session has a dark background. + * See setDarkBackground() + */ + bool hasDarkBackground() const; + + /** + * Attempts to get the shell program to redraw the current display area. + * This can be used after clearing the screen, for example, to get the + * shell to redraw the prompt line. + */ + void refresh(); + +// void startZModem(const QString &rz, const QString &dir, const QStringList &list); +// void cancelZModem(); +// bool isZModemBusy() { return _zmodemBusy; } + +public slots: + + /** + * Starts the terminal session. + * + * This creates the terminal process and connects the teletype to it. + */ + void run(); + + /** + * Closes the terminal session. This sends a hangup signal + * (SIGHUP) to the terminal process and causes the done(Session*) + * signal to be emitted. + */ + void close(); + + /** + * Changes the session title or other customizable aspects of the terminal + * emulation display. For a list of what may be changed see the + * Emulation::titleChanged() signal. + */ + void setUserTitle( int, const QString & caption ); + +signals: + + /** Emitted when the terminal process starts. */ + void started(); + + /** + * Emitted when the terminal process exits. + */ + void finished(); + + /** + * Emitted when output is received from the terminal process. + */ + void receivedData( const QString & text ); + + /** Emitted when the session's title has changed. */ + void titleChanged(); + + /** Emitted when the session's profile has changed. */ + void profileChanged(const QString & profile); + + /** + * Emitted when the activity state of this session changes. + * + * @param state The new state of the session. This may be one + * of NOTIFYNORMAL, NOTIFYSILENCE or NOTIFYACTIVITY + */ + void stateChanged(int state); + + /** Emitted when a bell event occurs in the session. */ + void bellRequest( const QString & message ); + + /** + * Requests that the color the text for any tabs associated with + * this session should be changed; + * + * TODO: Document what the parameter does + */ + void changeTabTextColorRequest(int); + + /** + * Requests that the background color of views on this session + * should be changed. + */ + void changeBackgroundColorRequest(const QColor &); + + /** TODO: Document me. */ + void openUrlRequest(const QString & url); + + /** TODO: Document me. */ +// void zmodemDetected(); + + /** + * Emitted when the terminal process requests a change + * in the size of the terminal window. + * + * @param size The requested window size in terms of lines and columns. + */ + void resizeRequest(const QSize & size); + + /** + * Emitted when a profile change command is received from the terminal. + * + * @param text The text of the command. This is a string of the form + * "PropertyName=Value;PropertyName=Value ..." + */ + void profileChangeCommandReceived(const QString & text); + + /** + * Emitted when the flow control state changes. + * + * @param enabled True if flow control is enabled or false otherwise. + */ + void flowControlEnabledChanged(bool enabled); + + void silence(); + void activity(); + +private slots: + void done(int); + +// void fireZModemDetected(); + + void onReceiveBlock( const char * buffer, int len ); + void monitorTimerDone(); + + void onViewSizeChange(int height, int width); + void onEmulationSizeChange(int lines , int columns); + + void activityStateSet(int); + + //automatically detach views from sessions when view is destroyed + void viewDestroyed(QObject * view); + +// void zmodemReadStatus(); +// void zmodemReadAndSendBlock(); +// void zmodemRcvBlock(const char *data, int len); +// void zmodemFinished(); + +private: + + void updateTerminalSize(); + WId windowId() const; + + int _uniqueIdentifier; + + Pty *_shellProcess; + Emulation * _emulation; + + QList _views; + + bool _monitorActivity; + bool _monitorSilence; + bool _notifiedActivity; + bool _masterMode; + bool _autoClose; + bool _wantedClose; + QTimer * _monitorTimer; + + int _silenceSeconds; + + QString _nameTitle; + QString _displayTitle; + QString _userTitle; + + QString _localTabTitleFormat; + QString _remoteTabTitleFormat; + + QString _iconName; + QString _iconText; // as set by: echo -en '\033]1;IconText\007 + bool _addToUtmp; + bool _flowControl; + bool _fullScripting; + + QString _program; + QStringList _arguments; + + QStringList _environment; + int _sessionId; + + QString _initialWorkingDir; + + // ZModem +// bool _zmodemBusy; +// KProcess* _zmodemProc; +// ZModemDialog* _zmodemProgress; + + // Color/Font Changes by ESC Sequences + + QColor _modifiedBackground; // as set by: echo -en '\033]11;Color\007 + + QString _profileKey; + + bool _hasDarkBackground; + + static int lastSessionId; + +}; + +/** + * Provides a group of sessions which is divided into master and slave sessions. + * Activity in master sessions can be propagated to all sessions within the group. + * The type of activity which is propagated and method of propagation is controlled + * by the masterMode() flags. + */ +class SessionGroup : public QObject { + Q_OBJECT + +public: + /** Constructs an empty session group. */ + SessionGroup(); + /** Destroys the session group and removes all connections between master and slave sessions. */ + ~SessionGroup(); + + /** Adds a session to the group. */ + void addSession( Session * session ); + /** Removes a session from the group. */ + void removeSession( Session * session ); + + /** Returns the list of sessions currently in the group. */ + QList sessions() const; + + /** + * Sets whether a particular session is a master within the group. + * Changes or activity in the group's master sessions may be propagated + * to all the sessions in the group, depending on the current masterMode() + * + * @param session The session whoose master status should be changed. + * @param master True to make this session a master or false otherwise + */ + void setMasterStatus( Session * session , bool master ); + /** Returns the master status of a session. See setMasterStatus() */ + bool masterStatus( Session * session ) const; + + /** + * This enum describes the options for propagating certain activity or + * changes in the group's master sessions to all sessions in the group. + */ + enum MasterMode { + /** + * Any input key presses in the master sessions are sent to all + * sessions in the group. + */ + CopyInputToAll = 1 + }; + + /** + * Specifies which activity in the group's master sessions is propagated + * to all sessions in the group. + * + * @param mode A bitwise OR of MasterMode flags. + */ + void setMasterMode( int mode ); + /** + * Returns a bitwise OR of the active MasterMode flags for this group. + * See setMasterMode() + */ + int masterMode() const; + +private: + void connectPair(Session * master , Session * other); + void disconnectPair(Session * master , Session * other); + void connectAll(bool connect); + QList masters() const; + + // maps sessions to their master status + QHash _sessions; + + int _masterMode; +}; + +} + +#endif diff --git a/lib/ShellCommand.cpp b/lib/ShellCommand.cpp new file mode 100644 index 0000000..7440305 --- /dev/null +++ b/lib/ShellCommand.cpp @@ -0,0 +1,169 @@ +/* + Copyright (C) 2007 by Robert Knight + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. +*/ + +// Own +#include "ShellCommand.h" + +//some versions of gcc(4.3) require explicit include +#include + + +using namespace Konsole; + +// expands environment variables in 'text' +// function copied from kdelibs/kio/kio/kurlcompletion.cpp +static bool expandEnv(QString & text); + +ShellCommand::ShellCommand(const QString & fullCommand) +{ + bool inQuotes = false; + + QString builder; + + for ( int i = 0 ; i < fullCommand.count() ; i++ ) { + QChar ch = fullCommand[i]; + + const bool isLastChar = ( i == fullCommand.count() - 1 ); + const bool isQuote = ( ch == '\'' || ch == '\"' ); + + if ( !isLastChar && isQuote ) { + inQuotes = !inQuotes; + } else { + if ( (!ch.isSpace() || inQuotes) && !isQuote ) { + builder.append(ch); + } + + if ( (ch.isSpace() && !inQuotes) || ( i == fullCommand.count()-1 ) ) { + _arguments << builder; + builder.clear(); + } + } + } +} +ShellCommand::ShellCommand(const QString & command , const QStringList & arguments) +{ + _arguments = arguments; + + if ( !_arguments.isEmpty() ) { + _arguments[0] = command; + } +} +QString ShellCommand::fullCommand() const +{ + return _arguments.join(QChar(' ')); +} +QString ShellCommand::command() const +{ + if ( !_arguments.isEmpty() ) { + return _arguments[0]; + } else { + return QString(); + } +} +QStringList ShellCommand::arguments() const +{ + return _arguments; +} +bool ShellCommand::isRootCommand() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +bool ShellCommand::isAvailable() const +{ + Q_ASSERT(0); // not implemented yet + return false; +} +QStringList ShellCommand::expand(const QStringList & items) +{ + QStringList result; + + foreach( QString item , items ) + result << expand(item); + + return result; +} +QString ShellCommand::expand(const QString & text) +{ + QString result = text; + expandEnv(result); + return result; +} + +/* + * expandEnv + * + * Expand environment variables in text. Escaped '$' characters are ignored. + * Return true if any variables were expanded + */ +static bool expandEnv( QString & text ) +{ + // Find all environment variables beginning with '$' + // + int pos = 0; + + bool expanded = false; + + while ( (pos = text.indexOf(QLatin1Char('$'), pos)) != -1 ) { + + // Skip escaped '$' + // + if ( pos > 0 && text.at(pos-1) == QLatin1Char('\\') ) { + pos++; + } + // Variable found => expand + // + else { + // Find the end of the variable = next '/' or ' ' + // + int pos2 = text.indexOf( QLatin1Char(' '), pos+1 ); + int pos_tmp = text.indexOf( QLatin1Char('/'), pos+1 ); + + if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) { + pos2 = pos_tmp; + } + + if ( pos2 == -1 ) { + pos2 = text.length(); + } + + // Replace if the variable is terminated by '/' or ' ' + // and defined + // + if ( pos2 >= 0 ) { + int len = pos2 - pos; + QString key = text.mid( pos+1, len-1); + QString value = + QString::fromLocal8Bit( ::getenv(key.toLocal8Bit()) ); + + if ( !value.isEmpty() ) { + expanded = true; + text.replace( pos, len, value ); + pos = pos + value.length(); + } else { + pos = pos2; + } + } + } + } + + return expanded; +} diff --git a/lib/ShellCommand.h b/lib/ShellCommand.h new file mode 100644 index 0000000..3a5804a --- /dev/null +++ b/lib/ShellCommand.h @@ -0,0 +1,92 @@ +/* + Copyright (C) 2007 by Robert Knight + + Rewritten for QT4 by e_k , Copyright (C)2008 + + 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. +*/ + +#ifndef SHELLCOMMAND_H +#define SHELLCOMMAND_H + +// Qt +#include + +namespace Konsole { + +/** + * A class to parse and extract information about shell commands. + * + * ShellCommand can be used to: + * + *
    + *
  • Take a command-line (eg "/bin/sh -c /path/to/my/script") and split it + * into its component parts (eg. the command "/bin/sh" and the arguments + * "-c","/path/to/my/script") + *
  • + *
  • Take a command and a list of arguments and combine them to + * form a complete command line. + *
  • + *
  • Determine whether the binary specified by a command exists in the + * user's PATH. + *
  • + *
  • Determine whether a command-line specifies the execution of + * another command as the root user using su/sudo etc. + *
  • + *
+ */ +class ShellCommand { +public: + /** + * Constructs a ShellCommand from a command line. + * + * @param fullCommand The command line to parse. + */ + ShellCommand(const QString & fullCommand); + /** + * Constructs a ShellCommand with the specified @p command and @p arguments. + */ + ShellCommand(const QString & command , const QStringList & arguments); + + /** Returns the command. */ + QString command() const; + /** Returns the arguments. */ + QStringList arguments() const; + + /** + * Returns the full command line. + */ + QString fullCommand() const; + + /** Returns true if this is a root command. */ + bool isRootCommand() const; + /** Returns true if the program specified by @p command() exists. */ + bool isAvailable() const; + + /** Expands environment variables in @p text .*/ + static QString expand(const QString & text); + + /** Expands environment variables in each string in @p list. */ + static QStringList expand(const QStringList & items); + +private: + QStringList _arguments; +}; + +} + +#endif // SHELLCOMMAND_H + diff --git a/lib/TerminalCharacterDecoder.cpp b/lib/TerminalCharacterDecoder.cpp new file mode 100644 index 0000000..d5469f6 --- /dev/null +++ b/lib/TerminalCharacterDecoder.cpp @@ -0,0 +1,251 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser 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 Lesser 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. +*/ + +// Own +#include "TerminalCharacterDecoder.h" + +// Qt +#include + +// KDE +//#include + +// Konsole +#include "konsole_wcwidth.h" + +using namespace Konsole; +PlainTextDecoder::PlainTextDecoder() + : _output(0) + , _includeTrailingWhitespace(true) + , _recordLinePositions(false) +{ + +} +void PlainTextDecoder::setTrailingWhitespace(bool enable) +{ + _includeTrailingWhitespace = enable; +} +bool PlainTextDecoder::trailingWhitespace() const +{ + return _includeTrailingWhitespace; +} +void PlainTextDecoder::begin(QTextStream* output) +{ + _output = output; + if (!_linePositions.isEmpty()) + _linePositions.clear(); +} +void PlainTextDecoder::end() +{ + _output = 0; +} + +void PlainTextDecoder::setRecordLinePositions(bool record) +{ + _recordLinePositions = record; +} +QList PlainTextDecoder::linePositions() const +{ + return _linePositions; +} +void PlainTextDecoder::decodeLine(const Character* const characters, int count, LineProperty /*properties*/ + ) +{ + Q_ASSERT( _output ); + + if (_recordLinePositions && _output->string()) + { + int pos = _output->string()->count(); + _linePositions << pos; + } + + //TODO should we ignore or respect the LINE_WRAPPED line property? + + //note: we build up a QString and send it to the text stream rather writing into the text + //stream a character at a time because it is more efficient. + //(since QTextStream always deals with QStrings internally anyway) + QString plainText; + plainText.reserve(count); + + int outputCount = count; + + // if inclusion of trailing whitespace is disabled then find the end of the + // line + if ( !_includeTrailingWhitespace ) + { + for (int i = count-1 ; i >= 0 ; i--) + { + if ( characters[i].character != ' ' ) + break; + else + outputCount--; + } + } + + for (int i=0;i') + text.append(">"); + else + text.append(ch); + } + else + { + text.append(" "); //HTML truncates multiple spaces, so use a space marker instead + } + + } + + //close any remaining open inner spans + if ( _innerSpanOpen ) + closeSpan(text); + + //start new line + text.append("
"); + + *_output << text; +} +void HTMLDecoder::openSpan(QString& text , const QString& style) +{ + text.append( QString("").arg(style) ); +} + +void HTMLDecoder::closeSpan(QString& text) +{ + text.append(""); +} + +void HTMLDecoder::setColorTable(const ColorEntry* table) +{ + _colorTable = table; +} diff --git a/lib/TerminalCharacterDecoder.h b/lib/TerminalCharacterDecoder.h new file mode 100644 index 0000000..5bdc035 --- /dev/null +++ b/lib/TerminalCharacterDecoder.h @@ -0,0 +1,150 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2006-2008 by Robert Knight + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser 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 Lesser 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. +*/ + +#ifndef TERMINAL_CHARACTER_DECODER_H +#define TERMINAL_CHARACTER_DECODER_H + +#include "Character.h" + +#include + +class QTextStream; + +namespace Konsole +{ + +/** + * Base class for terminal character decoders + * + * The decoder converts lines of terminal characters which consist of a unicode character, foreground + * and background colours and other appearance-related properties into text strings. + * + * Derived classes may produce either plain text with no other colour or appearance information, or + * they may produce text which incorporates these additional properties. + */ +class TerminalCharacterDecoder +{ +public: + virtual ~TerminalCharacterDecoder() {} + + /** Begin decoding characters. The resulting text is appended to @p output. */ + virtual void begin(QTextStream* output) = 0; + /** End decoding. */ + virtual void end() = 0; + + /** + * Converts a line of terminal characters with associated properties into a text string + * and writes the string into an output QTextStream. + * + * @param characters An array of characters of length @p count. + * @param count The number of characters + * @param properties Additional properties which affect all characters in the line + */ + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties) = 0; +}; + +/** + * A terminal character decoder which produces plain text, ignoring colours and other appearance-related + * properties of the original characters. + */ +class PlainTextDecoder : public TerminalCharacterDecoder +{ +public: + PlainTextDecoder(); + + /** + * Set whether trailing whitespace at the end of lines should be included + * in the output. + * Defaults to true. + */ + void setTrailingWhitespace(bool enable); + /** + * Returns whether trailing whitespace at the end of lines is included + * in the output. + */ + bool trailingWhitespace() const; + /** + * Returns of character positions in the output stream + * at which new lines where added. Returns an empty if setTrackLinePositions() is false or if + * the output device is not a string. + */ + QList linePositions() const; + /** Enables recording of character positions at which new lines are added. See linePositions() */ + void setRecordLinePositions(bool record); + + virtual void begin(QTextStream* output); + virtual void end(); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + +private: + QTextStream* _output; + bool _includeTrailingWhitespace; + + bool _recordLinePositions; + QList _linePositions; +}; + +/** + * A terminal character decoder which produces pretty HTML markup + */ +class HTMLDecoder : public TerminalCharacterDecoder +{ +public: + /** + * Constructs an HTML decoder using a default black-on-white color scheme. + */ + HTMLDecoder(); + + /** + * Sets the colour table which the decoder uses to produce the HTML colour codes in its + * output + */ + void setColorTable( const ColorEntry* table ); + + virtual void decodeLine(const Character* const characters, + int count, + LineProperty properties); + + virtual void begin(QTextStream* output); + virtual void end(); + +private: + void openSpan(QString& text , const QString& style); + void closeSpan(QString& text); + + QTextStream* _output; + const ColorEntry* _colorTable; + bool _innerSpanOpen; + quint8 _lastRendition; + CharacterColor _lastForeColor; + CharacterColor _lastBackColor; + +}; + +} + +#endif diff --git a/lib/TerminalDisplay.cpp b/lib/TerminalDisplay.cpp new file mode 100644 index 0000000..c5997c7 --- /dev/null +++ b/lib/TerminalDisplay.cpp @@ -0,0 +1,3080 @@ +/* + This file is part of Konsole, a terminal emulator for KDE. + + Copyright 2006-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +// Own +#include "TerminalDisplay.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include + +// Konsole +//#include +#include "Filter.h" +#include "konsole_wcwidth.h" +#include "ScreenWindow.h" +#include "TerminalCharacterDecoder.h" + +using namespace Konsole; + +#ifndef loc +#define loc(X,Y) ((Y)*_columns+(X)) +#endif + +#define yMouseScroll 1 + +#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefgjijklmnopqrstuvwxyz" \ + "0123456789./+@" + +const ColorEntry Konsole::base_color_table[TABLE_COLORS] = +// The following are almost IBM standard color codes, with some slight +// gamma correction for the dim colors to compensate for bright X screens. +// It contains the 8 ansiterm/xterm colors in 2 intensities. +{ + // Fixme: could add faint colors here, also. + // normal + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red + ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow + ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta + ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White + // intensiv + ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1), + ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0), + ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0), + ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0), + ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0) +}; + +// scroll increment used when dragging selection at top/bottom of window. + +// static +bool TerminalDisplay::_antialiasText = true; +bool TerminalDisplay::HAVE_TRANSPARENCY = true; + +// we use this to force QPainter to display text in LTR mode +// more information can be found in: http://unicode.org/reports/tr9/ +const QChar LTR_OVERRIDE_CHAR( 0x202D ); + +/* ------------------------------------------------------------------------- */ +/* */ +/* Colors */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb) + + Code 0 1 2 3 4 5 6 7 + ----------- ------- ------- ------- ------- ------- ------- ------- ------- + ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White + IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White +*/ + +ScreenWindow* TerminalDisplay::screenWindow() const +{ + return _screenWindow; +} +void TerminalDisplay::setScreenWindow(ScreenWindow* window) +{ + // disconnect existing screen window if any + if ( _screenWindow ) + { + disconnect( _screenWindow , 0 , this , 0 ); + } + + _screenWindow = window; + + if ( window ) + { + +// TODO: Determine if this is an issue. +//#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?" + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) ); + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) ); + connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateFilters()) ); + connect( _screenWindow , SIGNAL(scrolled(int)) , this , SLOT(updateFilters()) ); + window->setWindowLines(_lines); + } +} + +const ColorEntry* TerminalDisplay::colorTable() const +{ + return _colorTable; +} +void TerminalDisplay::setBackgroundColor(const QColor& color) +{ + _colorTable[DEFAULT_BACK_COLOR].color = color; + QPalette p = palette(); + p.setColor( backgroundRole(), color ); + setPalette( p ); + + // Avoid propagating the palette change to the scroll bar + _scrollBar->setPalette( QApplication::palette() ); + + update(); +} +void TerminalDisplay::setForegroundColor(const QColor& color) +{ + _colorTable[DEFAULT_FORE_COLOR].color = color; + + update(); +} +void TerminalDisplay::setColorTable(const ColorEntry table[]) +{ + for (int i = 0; i < TABLE_COLORS; i++) + _colorTable[i] = table[i]; + + setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Font */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + The VT100 has 32 special graphical characters. The usual vt100 extended + xterm fonts have these at 0x00..0x1f. + + QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals + come in here as proper unicode characters. + + We treat non-iso10646 fonts as VT100 extended and do the requiered mapping + from unicode to 0x00..0x1f. The remaining translation is then left to the + QCodec. +*/ + +static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);} +static inline bool isLineCharString(const QString& string) +{ + return (string.length() > 0) && (isLineChar(string.at(0).unicode())); +} + + +// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i. + +unsigned short Konsole::vt100_graphics[32] = +{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15 + 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7 +}; + +void TerminalDisplay::fontChange(const QFont&) +{ + QFontMetrics fm(font()); + _fontHeight = fm.height() + _lineSpacing; + + // waba TerminalDisplay 1.123: + // "Base character width on widest ASCII character. This prevents too wide + // characters in the presence of double wide (e.g. Japanese) characters." + // Get the width from representative normal width characters + _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR)); + + _fixedFont = true; + + int fw = fm.width(REPCHAR[0]); + for(unsigned int i=1; i< strlen(REPCHAR); i++) + { + if (fw != fm.width(REPCHAR[i])) + { + _fixedFont = false; + break; + } + } + + if (_fontWidth < 1) + _fontWidth=1; + + _fontAscent = fm.ascent(); + + emit changedFontMetricSignal( _fontHeight, _fontWidth ); + propagateSize(); + update(); +} + +void TerminalDisplay::setVTFont(const QFont& f) +{ + QFont font = f; + + // This was originally set for OS X only: + // mac uses floats for font width specification. + // this ensures the same handling for all platforms + // but then there was revealed that various Linux distros + // have this problem too... + font.setStyleStrategy(QFont::ForceIntegerMetrics); + + QFontMetrics metrics(font); + + if ( !QFontInfo(font).fixedPitch() ) + { + qDebug() << "Using an unsupported variable-width font in the terminal. This may produce display errors."; + } + + if ( metrics.height() < height() && metrics.maxWidth() < width() ) + { + // hint that text should be drawn without anti-aliasing. + // depending on the user's font configuration, this may not be respected + if (!_antialiasText) + font.setStyleStrategy( QFont::NoAntialias ); + + // experimental optimization. Konsole assumes that the terminal is using a + // mono-spaced font, in which case kerning information should have an effect. + // Disabling kerning saves some computation when rendering text. + font.setKerning(false); + + QWidget::setFont(font); + fontChange(font); + } +} + +void TerminalDisplay::setFont(const QFont &) +{ + // ignore font change request if not coming from konsole itself +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Constructor / Destructor */ +/* */ +/* ------------------------------------------------------------------------- */ + +TerminalDisplay::TerminalDisplay(QWidget *parent) +:QWidget(parent) +,_screenWindow(0) +,_allowBell(true) +,_gridLayout(0) +,_fontHeight(1) +,_fontWidth(1) +,_fontAscent(1) +,_boldIntense(true) +,_lines(1) +,_columns(1) +,_usedLines(1) +,_usedColumns(1) +,_contentHeight(1) +,_contentWidth(1) +,_image(0) +,_randomSeed(0) +,_resizing(false) +,_terminalSizeHint(false) +,_terminalSizeStartup(true) +,_bidiEnabled(false) +,_actSel(0) +,_wordSelectionMode(false) +,_lineSelectionMode(false) +,_preserveLineBreaks(false) +,_columnSelectionMode(false) +,_scrollbarLocation(NoScrollBar) +,_wordCharacters(":@-./_~") +,_bellMode(SystemBeepBell) +,_blinking(false) +,_hasBlinker(false) +,_cursorBlinking(false) +,_hasBlinkingCursor(false) +,_allowBlinkingText(true) +,_ctrlDrag(false) +,_tripleClickMode(SelectWholeLine) +,_isFixedSize(false) +,_possibleTripleClick(false) +,_resizeWidget(0) +,_resizeTimer(0) +,_flowControlWarningEnabled(false) +,_outputSuspendedLabel(0) +,_lineSpacing(0) +,_colorsInverted(false) +,_blendColor(qRgba(0,0,0,0xff)) +,_filterChain(new TerminalImageFilterChain()) +,_cursorShape(BlockCursor) +,mMotionAfterPasting(NoMoveScreenWindow) +{ + // terminal applications are not designed with Right-To-Left in mind, + // so the layout is forced to Left-To-Right + setLayoutDirection(Qt::LeftToRight); + + // The offsets are not yet calculated. + // Do not calculate these too often to be more smoothly when resizing + // konsole in opaque mode. + _topMargin = DEFAULT_TOP_MARGIN; + _leftMargin = DEFAULT_LEFT_MARGIN; + + // create scroll bar for scrolling output up and down + // set the scroll bar's slider to occupy the whole area of the scroll bar initially + _scrollBar = new QScrollBar(this); + setScroll(0,0); + _scrollBar->setCursor( Qt::ArrowCursor ); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, + SLOT(scrollBarPositionChanged(int))); + // qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar + // check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position) + _scrollBar->hide(); + + // setup timers for blinking cursor and text + _blinkTimer = new QTimer(this); + connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent())); + _blinkCursorTimer = new QTimer(this); + connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent())); + +// KCursor::setAutoHideCursor( this, true ); + + setUsesMouse(true); + setColorTable(base_color_table); + setMouseTracking(true); + + // Enable drag and drop + setAcceptDrops(true); // attempt + dragInfo.state = diNone; + + setFocusPolicy( Qt::WheelFocus ); + + // enable input method support + setAttribute(Qt::WA_InputMethodEnabled, true); + + // this is an important optimization, it tells Qt + // that TerminalDisplay will handle repainting its entire area. + setAttribute(Qt::WA_OpaquePaintEvent); + + _gridLayout = new QGridLayout(this); + _gridLayout->setContentsMargins(0, 0, 0, 0); + + setLayout( _gridLayout ); + + new AutoScrollHandler(this); +} + +TerminalDisplay::~TerminalDisplay() +{ + disconnect(_blinkTimer); + disconnect(_blinkCursorTimer); + qApp->removeEventFilter( this ); + + delete[] _image; + + delete _gridLayout; + delete _outputSuspendedLabel; + delete _filterChain; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Display Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/** + A table for emulating the simple (single width) unicode drawing chars. + It represents the 250x - 257x glyphs. If it's zero, we can't use it. + if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered + 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. + + Then, the pixels basically have the following interpretation: + _|||_ + -...- + -...- + -...- + _|||_ + +where _ = none + | = vertical line. + - = horizontal line. + */ + + +enum LineEncode +{ + TopL = (1<<1), + TopC = (1<<2), + TopR = (1<<3), + + LeftT = (1<<5), + Int11 = (1<<6), + Int12 = (1<<7), + Int13 = (1<<8), + RightT = (1<<9), + + LeftC = (1<<10), + Int21 = (1<<11), + Int22 = (1<<12), + Int23 = (1<<13), + RightC = (1<<14), + + LeftB = (1<<15), + Int31 = (1<<16), + Int32 = (1<<17), + Int33 = (1<<18), + RightB = (1<<19), + + BotL = (1<<21), + BotC = (1<<22), + BotR = (1<<23) +}; + +#include "LineFont.h" + +static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code) +{ + //Calculate cell midpoints, end points. + int cx = x + w/2; + int cy = y + h/2; + int ex = x + w - 1; + int ey = y + h - 1; + + quint32 toDraw = LineChars[code]; + + //Top _lines: + if (toDraw & TopL) + paint.drawLine(cx-1, y, cx-1, cy-2); + if (toDraw & TopC) + paint.drawLine(cx, y, cx, cy-2); + if (toDraw & TopR) + paint.drawLine(cx+1, y, cx+1, cy-2); + + //Bot _lines: + if (toDraw & BotL) + paint.drawLine(cx-1, cy+2, cx-1, ey); + if (toDraw & BotC) + paint.drawLine(cx, cy+2, cx, ey); + if (toDraw & BotR) + paint.drawLine(cx+1, cy+2, cx+1, ey); + + //Left _lines: + if (toDraw & LeftT) + paint.drawLine(x, cy-1, cx-2, cy-1); + if (toDraw & LeftC) + paint.drawLine(x, cy, cx-2, cy); + if (toDraw & LeftB) + paint.drawLine(x, cy+1, cx-2, cy+1); + + //Right _lines: + if (toDraw & RightT) + paint.drawLine(cx+2, cy-1, ex, cy-1); + if (toDraw & RightC) + paint.drawLine(cx+2, cy, ex, cy); + if (toDraw & RightB) + paint.drawLine(cx+2, cy+1, ex, cy+1); + + //Intersection points. + if (toDraw & Int11) + paint.drawPoint(cx-1, cy-1); + if (toDraw & Int12) + paint.drawPoint(cx, cy-1); + if (toDraw & Int13) + paint.drawPoint(cx+1, cy-1); + + if (toDraw & Int21) + paint.drawPoint(cx-1, cy); + if (toDraw & Int22) + paint.drawPoint(cx, cy); + if (toDraw & Int23) + paint.drawPoint(cx+1, cy); + + if (toDraw & Int31) + paint.drawPoint(cx-1, cy+1); + if (toDraw & Int32) + paint.drawPoint(cx, cy+1); + if (toDraw & Int33) + paint.drawPoint(cx+1, cy+1); + +} + +void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str, + const Character* attributes) +{ + const QPen& currentPen = painter.pen(); + + if ( (attributes->rendition & RE_BOLD) && _boldIntense ) + { + QPen boldPen(currentPen); + boldPen.setWidth(3); + painter.setPen( boldPen ); + } + + for (int i=0 ; i < str.length(); i++) + { + uchar code = str[i].cell(); + if (LineChars[code]) + drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code); + } + + painter.setPen( currentPen ); +} + +void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape) +{ + _cursorShape = shape; +} +TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const +{ + return _cursorShape; +} +void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color) +{ + if (useForegroundColor) + _cursorColor = QColor(); // an invalid color means that + // the foreground color of the + // current character should + // be used + + else + _cursorColor = color; +} +QColor TerminalDisplay::keyboardCursorColor() const +{ + return _cursorColor; +} + +void TerminalDisplay::setOpacity(qreal opacity) +{ + QColor color(_blendColor); + color.setAlphaF(opacity); + + // enable automatic background filling to prevent the display + // flickering if there is no transparency + /*if ( color.alpha() == 255 ) + { + setAutoFillBackground(true); + } + else + { + setAutoFillBackground(false); + }*/ + + _blendColor = color.rgba(); +} + +void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting ) +{ + // the area of the widget showing the contents of the terminal display is drawn + // using the background color from the color scheme set with setColorTable() + // + // the area of the widget behind the scroll-bar is drawn using the background + // brush from the scroll-bar's palette, to give the effect of the scroll-bar + // being outside of the terminal display and visual consistency with other KDE + // applications. + // + QRect scrollBarArea = _scrollBar->isVisible() ? + rect.intersected(_scrollBar->geometry()) : + QRect(); + QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea); + QRect contentsRect = contentsRegion.boundingRect(); + + if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting ) + { + QColor color(backgroundColor); + color.setAlpha(qAlpha(_blendColor)); + + painter.save(); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(contentsRect, color); + painter.restore(); + } + else + painter.fillRect(contentsRect, backgroundColor); + + painter.fillRect(scrollBarArea,_scrollBar->palette().background()); +} + +void TerminalDisplay::drawCursor(QPainter& painter, + const QRect& rect, + const QColor& foregroundColor, + const QColor& /*backgroundColor*/, + bool& invertCharacterColor) +{ + QRect cursorRect = rect; + cursorRect.setHeight(_fontHeight - _lineSpacing - 1); + + if (!_cursorBlinking) + { + if ( _cursorColor.isValid() ) + painter.setPen(_cursorColor); + else + painter.setPen(foregroundColor); + + if ( _cursorShape == BlockCursor ) + { + // draw the cursor outline, adjusting the area so that + // it is draw entirely inside 'rect' + int penWidth = qMax(1,painter.pen().width()); + + painter.drawRect(cursorRect.adjusted(penWidth/2, + penWidth/2, + - penWidth/2 - penWidth%2, + - penWidth/2 - penWidth%2)); + if ( hasFocus() ) + { + painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor); + + if ( !_cursorColor.isValid() ) + { + // invert the colour used to draw the text to ensure that the character at + // the cursor position is readable + invertCharacterColor = true; + } + } + } + else if ( _cursorShape == UnderlineCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.bottom(), + cursorRect.right(), + cursorRect.bottom()); + else if ( _cursorShape == IBeamCursor ) + painter.drawLine(cursorRect.left(), + cursorRect.top(), + cursorRect.left(), + cursorRect.bottom()); + + } +} + +void TerminalDisplay::drawCharacters(QPainter& painter, + const QRect& rect, + const QString& text, + const Character* style, + bool invertCharacterColor) +{ + // don't draw text which is currently blinking + if ( _blinking && (style->rendition & RE_BLINK) ) + return; + + // setup bold and underline + bool useBold; + ColorEntry::FontWeight weight = style->fontWeight(_colorTable); + if (weight == ColorEntry::UseCurrentFormat) + useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold(); + else + useBold = (weight == ColorEntry::Bold) ? true : false; + bool useUnderline = style->rendition & RE_UNDERLINE || font().underline(); + + QFont font = painter.font(); + if ( font.bold() != useBold + || font.underline() != useUnderline ) + { + font.setBold(useBold); + font.setUnderline(useUnderline); + painter.setFont(font); + } + + // setup pen + const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor ); + const QColor color = textColor.color(_colorTable); + QPen pen = painter.pen(); + if ( pen.color() != color ) + { + pen.setColor(color); + painter.setPen(color); + } + + // draw text + if ( isLineCharString(text) ) + drawLineCharString(painter,rect.x(),rect.y(),text,style); + else + { + // the drawText(rect,flags,string) overload is used here with null flags + // instead of drawText(rect,string) because the (rect,string) overload causes + // the application's default layout direction to be used instead of + // the widget-specific layout direction, which should always be + // Qt::LeftToRight for this widget + // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2 + if (_bidiEnabled) + painter.drawText(rect,0,text); + else +#if QT_VERSION >= 0x040800 + painter.drawText(rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + text); +#else + painter.drawText(rect, 0, LTR_OVERRIDE_CHAR + text); +#endif + } +} + +void TerminalDisplay::drawTextFragment(QPainter& painter , + const QRect& rect, + const QString& text, + const Character* style) +{ + painter.save(); + + // setup painter + const QColor foregroundColor = style->foregroundColor.color(_colorTable); + const QColor backgroundColor = style->backgroundColor.color(_colorTable); + + // draw background if different from the display's background color + if ( backgroundColor != palette().background().color() ) + drawBackground(painter,rect,backgroundColor, + false /* do not use transparency */); + + // draw cursor shape if the current character is the cursor + // this may alter the foreground and background colors + bool invertCharacterColor = false; + if ( style->rendition & RE_CURSOR ) + drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor); + + // draw text + drawCharacters(painter,rect,text,style,invertCharacterColor); + + painter.restore(); +} + +void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; } +uint TerminalDisplay::randomSeed() const { return _randomSeed; } + +#if 0 +/*! + Set XIM Position +*/ +void TerminalDisplay::setCursorPos(const int curx, const int cury) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int xpos, ypos; + ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent; + xpos = _leftMargin + tLx + _fontWidth*curx; + //setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ??? + // fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos); + _cursorLine = cury; + _cursorCol = curx; +} +#endif + +// scrolls the image by 'lines', down if lines > 0 or up otherwise. +// +// the terminal emulation keeps track of the scrolling of the character +// image as it receives input, and when the view is updated, it calls scrollImage() +// with the final scroll amount. this improves performance because scrolling the +// display is much cheaper than re-rendering all the text for the +// part of the image which has moved up or down. +// Instead only new lines have to be drawn +void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion) +{ + // if the flow control warning is enabled this will interfere with the + // scrolling optimizations and cause artifacts. the simple solution here + // is to just disable the optimization whilst it is visible + if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() ) + return; + + // constrain the region to the display + // the bottom of the region is capped to the number of lines in the display's + // internal image - 2, so that the height of 'region' is strictly less + // than the height of the internal image. + QRect region = screenWindowRegion; + region.setBottom( qMin(region.bottom(),this->_lines-2) ); + + // return if there is nothing to do + if ( lines == 0 + || _image == 0 + || !region.isValid() + || (region.top() + abs(lines)) >= region.bottom() + || this->_lines <= region.height() ) return; + + // hide terminal size label to prevent it being scrolled + if (_resizeWidget && _resizeWidget->isVisible()) + _resizeWidget->hide(); + + // Note: With Qt 4.4 the left edge of the scrolled area must be at 0 + // to get the correct (newly exposed) part of the widget repainted. + // + // The right edge must be before the left edge of the scroll bar to + // avoid triggering a repaint of the entire widget, the distance is + // given by SCROLLBAR_CONTENT_GAP + // + // Set the QT_FLUSH_PAINT environment variable to '1' before starting the + // application to monitor repainting. + // + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width(); + const int SCROLLBAR_CONTENT_GAP = 1; + QRect scrollRect; + if ( _scrollbarLocation == ScrollBarLeft ) + { + scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP); + scrollRect.setRight(width()); + } + else + { + scrollRect.setLeft(0); + scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); + } + void* firstCharPos = &_image[ region.top() * this->_columns ]; + void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ]; + + int top = _topMargin + (region.top() * _fontHeight); + int linesToMove = region.height() - abs(lines); + int bytesToMove = linesToMove * + this->_columns * + sizeof(Character); + + Q_ASSERT( linesToMove > 0 ); + Q_ASSERT( bytesToMove > 0 ); + + //scroll internal image + if ( lines > 0 ) + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)lastCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + Q_ASSERT( (lines*this->_columns) < _imageSize ); + + //scroll internal image down + memmove( firstCharPos , lastCharPos , bytesToMove ); + + //set region of display to scroll + scrollRect.setTop(top); + } + else + { + // check that the memory areas that we are going to move are valid + Q_ASSERT( (char*)firstCharPos + bytesToMove < + (char*)(_image + (this->_lines * this->_columns)) ); + + //scroll internal image up + memmove( lastCharPos , firstCharPos , bytesToMove ); + + //set region of the display to scroll + scrollRect.setTop(top + abs(lines) * _fontHeight); + } + scrollRect.setHeight(linesToMove * _fontHeight ); + + Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty()); + + //scroll the display vertically to match internal _image + scroll( 0 , _fontHeight * (-lines) , scrollRect ); +} + +QRegion TerminalDisplay::hotSpotRegion() const +{ + QRegion region; + foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() ) + { + QRect r; + if (hotSpot->startLine()==hotSpot->endLine()) { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } else { + r.setLeft(hotSpot->startColumn()); + r.setTop(hotSpot->startLine()); + r.setRight(_columns); + r.setBottom(hotSpot->startLine()); + region |= imageToWidget(r);; + for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) { + r.setLeft(0); + r.setTop(line); + r.setRight(_columns); + r.setBottom(line); + region |= imageToWidget(r);; + } + r.setLeft(0); + r.setTop(hotSpot->endLine()); + r.setRight(hotSpot->endColumn()); + r.setBottom(hotSpot->endLine()); + region |= imageToWidget(r);; + } + } + return region; +} + +void TerminalDisplay::processFilters() +{ + if (!_screenWindow) + return; + + QRegion preUpdateHotSpots = hotSpotRegion(); + + // use _screenWindow->getImage() here rather than _image because + // other classes may call processFilters() when this display's + // ScreenWindow emits a scrolled() signal - which will happen before + // updateImage() is called on the display and therefore _image is + // out of date at this point + _filterChain->setImage( _screenWindow->getImage(), + _screenWindow->windowLines(), + _screenWindow->windowColumns(), + _screenWindow->getLineProperties() ); + _filterChain->process(); + + QRegion postUpdateHotSpots = hotSpotRegion(); + + update( preUpdateHotSpots | postUpdateHotSpots ); +} + +void TerminalDisplay::updateImage() +{ + if ( !_screenWindow ) + return; + + // optimization - scroll the existing image where possible and + // avoid expensive text drawing for parts of the image that + // can simply be moved up or down + scrollImage( _screenWindow->scrollCount() , + _screenWindow->scrollRegion() ); + _screenWindow->resetScrollCount(); + + if (!_image) { + // Create _image. + // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first. + updateImageSize(); + } + + Character* const newimg = _screenWindow->getImage(); + int lines = _screenWindow->windowLines(); + int columns = _screenWindow->windowColumns(); + + setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() ); + + Q_ASSERT( this->_usedLines <= this->_lines ); + Q_ASSERT( this->_usedColumns <= this->_columns ); + + int y,x,len; + + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + _hasBlinker = false; + + CharacterColor cf; // undefined + CharacterColor _clipboard; // undefined + int cr = -1; // undefined + + const int linesToUpdate = qMin(this->_lines, qMax(0,lines )); + const int columnsToUpdate = qMin(this->_columns,qMax(0,columns)); + + QChar *disstrU = new QChar[columnsToUpdate]; + char *dirtyMask = new char[columnsToUpdate+2]; + QRegion dirtyRegion; + + // debugging variable, this records the number of lines that are found to + // be 'dirty' ( ie. have changed from the old _image to the new _image ) and + // which therefore need to be repainted + int dirtyLineCount = 0; + + for (y = 0; y < linesToUpdate; ++y) + { + const Character* currentLine = &_image[y*this->_columns]; + const Character* const newLine = &newimg[y*columns]; + + bool updateLine = false; + + // The dirty mask indicates which characters need repainting. We also + // mark surrounding neighbours dirty, in case the character exceeds + // its cell boundaries + memset(dirtyMask, 0, columnsToUpdate+2); + + for( x = 0 ; x < columnsToUpdate ; ++x) + { + if ( newLine[x] != currentLine[x] ) + { + dirtyMask[x] = true; + } + } + + if (!_resizing) // not while _resizing, we're expecting a paintEvent + for (x = 0; x < columnsToUpdate; ++x) + { + _hasBlinker |= (newLine[x].rendition & RE_BLINK); + + // Start drawing if this character or the next one differs. + // We also take the next one into account to handle the situation + // where characters exceed their cell width. + if (dirtyMask[x]) + { + quint16 c = newLine[x+0].character; + if ( !c ) + continue; + int p = 0; + disstrU[p++] = c; //fontMap(c); + bool lineDraw = isLineChar(c); + bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0); + cr = newLine[x].rendition; + _clipboard = newLine[x].backgroundColor; + if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor; + int lln = columnsToUpdate - x; + for (len = 1; len < lln; ++len) + { + const Character& ch = newLine[x+len]; + + if (!ch.character) + continue; // Skip trailing part of multi-col chars. + + bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0); + + if ( ch.foregroundColor != cf || + ch.backgroundColor != _clipboard || + ch.rendition != cr || + !dirtyMask[x+len] || + isLineChar(c) != lineDraw || + nextIsDoubleWidth != doubleWidth ) + break; + + disstrU[p++] = c; //fontMap(c); + } + + QString unistr(disstrU, p); + + bool saveFixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + + updateLine = true; + + _fixedFont = saveFixedFont; + x += len - 1; + } + + } + + //both the top and bottom halves of double height _lines must always be redrawn + //although both top and bottom halves contain the same characters, only + //the top one is actually + //drawn. + if (_lineProperties.count() > y) + updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT); + + // if the characters on the line are different in the old and the new _image + // then this line must be repainted. + if (updateLine) + { + dirtyLineCount++; + + // add the area occupied by this line to the region which needs to be + // repainted + QRect dirtyRect = QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*y , + _fontWidth * columnsToUpdate , + _fontHeight ); + + dirtyRegion |= dirtyRect; + } + + // replace the line of characters in the old _image with the + // current line of the new _image + memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character)); + } + + // if the new _image is smaller than the previous _image, then ensure that the area + // outside the new _image is cleared + if ( linesToUpdate < _usedLines ) + { + dirtyRegion |= QRect( _leftMargin+tLx , + _topMargin+tLy+_fontHeight*linesToUpdate , + _fontWidth * this->_columns , + _fontHeight * (_usedLines-linesToUpdate) ); + } + _usedLines = linesToUpdate; + + if ( columnsToUpdate < _usedColumns ) + { + dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth , + _topMargin+tLy , + _fontWidth * (_usedColumns-columnsToUpdate) , + _fontHeight * this->_lines ); + } + _usedColumns = columnsToUpdate; + + dirtyRegion |= _inputMethodData.previousPreeditRect; + + // update the parts of the display which have changed + update(dirtyRegion); + + if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( TEXT_BLINK_DELAY ); + if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; } + delete[] dirtyMask; + delete[] disstrU; + +} + +void TerminalDisplay::showResizeNotification() +{ + if (_terminalSizeHint && isVisible()) + { + if (_terminalSizeStartup) { + _terminalSizeStartup=false; + return; + } + if (!_resizeWidget) + { + _resizeWidget = new QLabel("Size: XXX x XXX", this); + _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width("Size: XXX x XXX")); + _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height()); + _resizeWidget->setAlignment(Qt::AlignCenter); + + _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"); + + _resizeTimer = new QTimer(this); + _resizeTimer->setSingleShot(true); + connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide())); + } + QString sizeStr = QString("Size: %1 x %2").arg(_columns).arg(_lines); + _resizeWidget->setText(sizeStr); + _resizeWidget->move((width()-_resizeWidget->width())/2, + (height()-_resizeWidget->height())/2+20); + _resizeWidget->show(); + _resizeTimer->start(1000); + } +} + +void TerminalDisplay::setBlinkingCursor(bool blink) +{ + _hasBlinkingCursor=blink; + + if (blink && !_blinkCursorTimer->isActive()) + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + + if (!blink && _blinkCursorTimer->isActive()) + { + _blinkCursorTimer->stop(); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } +} + +void TerminalDisplay::setBlinkingTextEnabled(bool blink) +{ + _allowBlinkingText = blink; + + if (blink && !_blinkTimer->isActive()) + _blinkTimer->start(TEXT_BLINK_DELAY); + + if (!blink && _blinkTimer->isActive()) + { + _blinkTimer->stop(); + _blinking = false; + } +} + +void TerminalDisplay::focusOutEvent(QFocusEvent*) +{ + emit termLostFocus(); + // trigger a repaint of the cursor so that it is both visible (in case + // it was hidden during blinking) + // and drawn in a focused out state + _cursorBlinking = false; + updateCursor(); + + _blinkCursorTimer->stop(); + if (_blinking) + blinkEvent(); + + _blinkTimer->stop(); +} +void TerminalDisplay::focusInEvent(QFocusEvent*) +{ + emit termGetFocus(); + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(); + } + updateCursor(); + + if (_hasBlinker) + _blinkTimer->start(); +} + +void TerminalDisplay::paintEvent( QPaintEvent* pe ) +{ + QPainter paint(this); + + foreach (const QRect &rect, (pe->region() & contentsRect()).rects()) + { + drawBackground(paint,rect,palette().background().color(), + true /* use opacity setting */); + drawContents(paint, rect); + } + drawInputMethodPreeditString(paint,preeditRect()); + paintFilters(paint); +} + +QPoint TerminalDisplay::cursorPosition() const +{ + if (_screenWindow) + return _screenWindow->cursorPosition(); + else + return QPoint(0,0); +} + +QRect TerminalDisplay::preeditRect() const +{ + const int preeditLength = string_width(_inputMethodData.preeditString); + + if ( preeditLength == 0 ) + return QRect(); + + return QRect(_leftMargin + _fontWidth*cursorPosition().x(), + _topMargin + _fontHeight*cursorPosition().y(), + _fontWidth*preeditLength, + _fontHeight); +} + +void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect) +{ + if ( _inputMethodData.preeditString.isEmpty() ) + return; + + const QPoint cursorPos = cursorPosition(); + + bool invertColors = false; + const QColor background = _colorTable[DEFAULT_BACK_COLOR].color; + const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color; + const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())]; + + drawBackground(painter,rect,background,true); + drawCursor(painter,rect,foreground,background,invertColors); + drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors); + + _inputMethodData.previousPreeditRect = rect; +} + +FilterChain* TerminalDisplay::filterChain() const +{ + return _filterChain; +} + +void TerminalDisplay::paintFilters(QPainter& painter) +{ + // get color of character under mouse and use it to draw + // lines for filters + QPoint cursorPos = mapFromGlobal(QCursor::pos()); + int cursorLine; + int cursorColumn; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition( cursorPos , cursorLine , cursorColumn ); + Character cursorCharacter = _image[loc(cursorColumn,cursorLine)]; + + painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) ); + + // iterate over hotspots identified by the display's currently active filters + // and draw appropriate visuals to indicate the presence of the hotspot + + QList spots = _filterChain->hotSpots(); + QListIterator iter(spots); + while (iter.hasNext()) + { + Filter::HotSpot* spot = iter.next(); + + QRegion region; + if ( spot->type() == Filter::HotSpot::Link ) { + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, + spot->startLine()*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight - 1 ); + region |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + (_columns-1)*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + region |= r; + } + r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, + spot->endLine()*_fontHeight + 1, + (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + region |= r; + } + } + + for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ ) + { + int startColumn = 0; + int endColumn = _columns-1; // TODO use number of _columns which are actually + // occupied on this line rather than the width of the + // display in _columns + + // ignore whitespace at the end of the lines + while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 ) + endColumn--; + + // increment here because the column which we want to set 'endColumn' to + // is the first whitespace character at the end of the line + endColumn++; + + if ( line == spot->startLine() ) + startColumn = spot->startColumn(); + if ( line == spot->endLine() ) + endColumn = spot->endColumn(); + + // subtract one pixel from + // the right and bottom so that + // we do not overdraw adjacent + // hotspots + // + // subtracting one pixel from all sides also prevents an edge case where + // moving the mouse outside a link could still leave it underlined + // because the check below for the position of the cursor + // finds it on the border of the target area + QRect r; + r.setCoords( startColumn*_fontWidth + 1 + scrollBarWidth, + line*_fontHeight + 1, + endColumn*_fontWidth - 1 + scrollBarWidth, + (line+1)*_fontHeight - 1 ); + // Underline link hotspots + if ( spot->type() == Filter::HotSpot::Link ) + { + QFontMetrics metrics(font()); + + // find the baseline (which is the invisible line that the characters in the font sit on, + // with some having tails dangling below) + int baseline = r.bottom() - metrics.descent(); + // find the position of the underline below that + int underlinePos = baseline + metrics.underlinePos(); + if ( region.contains( mapFromGlobal(QCursor::pos()) ) ){ + painter.drawLine( r.left() , underlinePos , + r.right() , underlinePos ); + } + } + // Marker hotspots simply have a transparent rectanglular shape + // drawn on top of them + else if ( spot->type() == Filter::HotSpot::Marker ) + { + //TODO - Do not use a hardcoded colour for this + painter.fillRect(r,QBrush(QColor(255,0,0,120))); + } + } + } +} +void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect) +{ + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + + int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth)); + int luy = qMin(_usedLines-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight)); + int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth)); + int rly = qMin(_usedLines-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight)); + + const int bufferSize = _usedColumns; + QString unistr; + unistr.reserve(bufferSize); + for (int y = luy; y <= rly; y++) + { + quint16 c = _image[loc(lux,y)].character; + int x = lux; + if(!c && x) + x--; // Search for start of multi-column character + for (; x <= rlx; x++) + { + int len = 1; + int p = 0; + + // reset our buffer to the maximal size + unistr.resize(bufferSize); + QChar *disstrU = unistr.data(); + + // is this a single character or a sequence of characters ? + if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR ) + { + // sequence of characters + ushort extendedCharLength = 0; + ushort* chars = ExtendedCharTable::instance + .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength); + for ( int index = 0 ; index < extendedCharLength ; index++ ) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = chars[index]; + } + } + else + { + // single character + c = _image[loc(x,y)].character; + if (c) + { + Q_ASSERT( p < bufferSize ); + disstrU[p++] = c; //fontMap(c); + } + } + + bool lineDraw = isLineChar(c); + bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0); + CharacterColor currentForeground = _image[loc(x,y)].foregroundColor; + CharacterColor currentBackground = _image[loc(x,y)].backgroundColor; + quint8 currentRendition = _image[loc(x,y)].rendition; + + while (x+len <= rlx && + _image[loc(x+len,y)].foregroundColor == currentForeground && + _image[loc(x+len,y)].backgroundColor == currentBackground && + _image[loc(x+len,y)].rendition == currentRendition && + (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth && + isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment! + { + if (c) + disstrU[p++] = c; //fontMap(c); + if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition + len++; // Skip trailing part of multi-column character + len++; + } + if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character)) + len++; // Adjust for trailing part of multi-column character + + bool save__fixedFont = _fixedFont; + if (lineDraw) + _fixedFont = false; + if (doubleWidth) + _fixedFont = false; + unistr.resize(p); + + // Create a text scaling matrix for double width and double height lines. + QMatrix textScale; + + if (y < _lineProperties.size()) + { + if (_lineProperties[y] & LINE_DOUBLEWIDTH) + textScale.scale(2,1); + + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + textScale.scale(1,2); + } + + //Apply text scaling matrix. + paint.setWorldMatrix(textScale, true); + + //calculate the area in which the text will be drawn + QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight); + + //move the calculated area to take account of scaling applied to the painter. + //the position of the area from the origin (0,0) is scaled + //by the opposite of whatever + //transformation has been applied to the painter. this ensures that + //painting does actually start from textArea.topLeft() + //(instead of textArea.topLeft() * painter-scale) + textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) ); + + //paint text fragment + drawTextFragment( paint, + textArea, + unistr, + &_image[loc(x,y)] ); //, + //0, + //!_isPrinting ); + + _fixedFont = save__fixedFont; + + //reset back to single-width, single-height _lines + paint.setWorldMatrix(textScale.inverted(), true); + + if (y < _lineProperties.size()-1) + { + //double-height _lines are represented by two adjacent _lines + //containing the same characters + //both _lines will have the LINE_DOUBLEHEIGHT attribute. + //If the current line has the LINE_DOUBLEHEIGHT attribute, + //we can therefore skip the next line + if (_lineProperties[y] & LINE_DOUBLEHEIGHT) + y++; + } + + x += len - 1; + } + } +} + +void TerminalDisplay::blinkEvent() +{ + if (!_allowBlinkingText) return; + + _blinking = !_blinking; + + //TODO: Optimize to only repaint the areas of the widget + // where there is blinking text + // rather than repainting the whole widget. + update(); +} + +QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const +{ + QRect result; + result.setLeft( _leftMargin + _fontWidth * imageArea.left() ); + result.setTop( _topMargin + _fontHeight * imageArea.top() ); + result.setWidth( _fontWidth * imageArea.width() ); + result.setHeight( _fontHeight * imageArea.height() ); + + return result; +} + +void TerminalDisplay::updateCursor() +{ + QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) ); + update(cursorRect); +} + +void TerminalDisplay::blinkCursorEvent() +{ + _cursorBlinking = !_cursorBlinking; + updateCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Resizing */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::resizeEvent(QResizeEvent*) +{ + updateImageSize(); + processFilters(); +} + +void TerminalDisplay::propagateSize() +{ + if (_isFixedSize) + { + setSize(_columns, _lines); + QWidget::setFixedSize(sizeHint()); + parentWidget()->adjustSize(); + parentWidget()->setFixedSize(parentWidget()->sizeHint()); + return; + } + if (_image) + updateImageSize(); +} + +void TerminalDisplay::updateImageSize() +{ + Character* oldimg = _image; + int oldlin = _lines; + int oldcol = _columns; + + makeImage(); + + // copy the old image to reduce flicker + int lines = qMin(oldlin,_lines); + int columns = qMin(oldcol,_columns); + + if (oldimg) + { + for (int line = 0; line < lines; line++) + { + memcpy((void*)&_image[_columns*line], + (void*)&oldimg[oldcol*line],columns*sizeof(Character)); + } + delete[] oldimg; + } + + if (_screenWindow) + _screenWindow->setWindowLines(_lines); + + _resizing = (oldlin!=_lines) || (oldcol!=_columns); + + if ( _resizing ) + { + showResizeNotification(); + emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent + } + + _resizing = false; +} + +//showEvent and hideEvent are reimplemented here so that it appears to other classes that the +//display has been resized when the display is hidden or shown. +// +//TODO: Perhaps it would be better to have separate signals for show and hide instead of using +//the same signal as the one for a content size change +void TerminalDisplay::showEvent(QShowEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} +void TerminalDisplay::hideEvent(QHideEvent*) +{ + emit changedContentSizeSignal(_contentHeight,_contentWidth); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Scrollbar */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::scrollBarPositionChanged(int) +{ + if ( !_screenWindow ) + return; + + _screenWindow->scrollTo( _scrollBar->value() ); + + // if the thumb has been moved to the bottom of the _scrollBar then set + // the display to automatically track new output, + // that is, scroll down automatically + // to how new _lines as they are added + const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum()); + _screenWindow->setTrackOutput( atEndOfOutput ); + + updateImage(); +} + +void TerminalDisplay::setScroll(int cursor, int slines) +{ + // update _scrollBar if the range or value has changed, + // otherwise return + // + // setting the range or value of a _scrollBar will always trigger + // a repaint, so it should be avoided if it is not necessary + if ( _scrollBar->minimum() == 0 && + _scrollBar->maximum() == (slines - _lines) && + _scrollBar->value() == cursor ) + { + return; + } + + disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); + _scrollBar->setRange(0,slines - _lines); + _scrollBar->setSingleStep(1); + _scrollBar->setPageStep(_lines); + _scrollBar->setValue(cursor); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); +} + +void TerminalDisplay::scrollToEnd() +{ + disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); + _scrollBar->setValue( _scrollBar->maximum() ); + connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int))); + + _screenWindow->scrollTo( _scrollBar->value() + 1 ); + _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() ); +} + +void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position) +{ + if (_scrollbarLocation == position) + return; + + if ( position == NoScrollBar ) + _scrollBar->hide(); + else + _scrollBar->show(); + + _topMargin = _leftMargin = 1; + _scrollbarLocation = position; + + propagateSize(); + update(); +} + +void TerminalDisplay::mousePressEvent(QMouseEvent* ev) +{ + if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) { + mouseTripleClickEvent(ev); + return; + } + + if ( !contentsRect().contains(ev->pos()) ) return; + + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + QPoint pos = QPoint(charColumn,charLine); + + if ( ev->button() == Qt::LeftButton) + { + _lineSelectionMode = false; + _wordSelectionMode = false; + + emit isBusySelecting(true); // Keep it steady... + // Drag only when the Control key is hold + bool selected = false; + + // The receiver of the testIsSelected() signal will adjust + // 'selected' accordingly. + //emit testIsSelected(pos.x(), pos.y(), selected); + + selected = _screenWindow->isSelected(pos.x(),pos.y()); + + if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) { + // The user clicked inside selected text + dragInfo.state = diPending; + dragInfo.start = ev->pos(); + } + else { + // No reason to ever start a drag event + dragInfo.state = diNone; + + _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) ); + _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier); + + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + { + _screenWindow->clearSelection(); + + //emit clearSelectionSignal(); + pos.ry() += _scrollBar->value(); + _iPntSel = _pntSel = pos; + _actSel = 1; // left mouse button pressed but nothing selected yet. + + } + else + { + emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + + Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn); + if (spot && spot->type() == Filter::HotSpot::Link) + spot->activate("open-action"); + } + } + else if ( ev->button() == Qt::MidButton ) + { + if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) ) + emitSelection(true,ev->modifiers() & Qt::ControlModifier); + else + emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + else if ( ev->button() == Qt::RightButton ) + { + if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) + emit configureRequest(ev->pos()); + else + emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } +} + +QList TerminalDisplay::filterActions(const QPoint& position) +{ + int charLine, charColumn; + getCharacterPosition(position,charLine,charColumn); + + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + + return spot ? spot->actions() : QList(); +} + +void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev) +{ + int charLine = 0; + int charColumn = 0; + int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + // handle filters + // change link hot-spot appearance on mouse-over + Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn); + if ( spot && spot->type() == Filter::HotSpot::Link) + { + QRegion previousHotspotArea = _mouseOverHotspotArea; + _mouseOverHotspotArea = QRegion(); + QRect r; + if (spot->startLine()==spot->endLine()) { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight - 1 ); + _mouseOverHotspotArea |= r; + } else { + r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, + spot->startLine()*_fontHeight, + _columns*_fontWidth - 1 + scrollBarWidth, + (spot->startLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) { + r.setCoords( 0*_fontWidth + scrollBarWidth, + line*_fontHeight, + _columns*_fontWidth + scrollBarWidth, + (line+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + r.setCoords( 0*_fontWidth + scrollBarWidth, + spot->endLine()*_fontHeight, + spot->endColumn()*_fontWidth + scrollBarWidth, + (spot->endLine()+1)*_fontHeight ); + _mouseOverHotspotArea |= r; + } + // display tooltips when mousing over links + // TODO: Extend this to work with filter types other than links + const QString& tooltip = spot->tooltip(); + if ( !tooltip.isEmpty() ) + { + QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea.boundingRect() ); + } + + update( _mouseOverHotspotArea | previousHotspotArea ); + } + else if ( !_mouseOverHotspotArea.isEmpty() ) + { + update( _mouseOverHotspotArea ); + // set hotspot area to an invalid rectangle + _mouseOverHotspotArea = QRegion(); + } + + // for auto-hiding the cursor, we need mouseTracking + if (ev->buttons() == Qt::NoButton ) return; + + // if the terminal is interested in mouse movements + // then emit a mouse movement signal, unless the shift + // key is being held down, which overrides this. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + int button = 3; + if (ev->buttons() & Qt::LeftButton) + button = 0; + if (ev->buttons() & Qt::MidButton) + button = 1; + if (ev->buttons() & Qt::RightButton) + button = 2; + + + emit mouseSignal( button, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum(), + 1 ); + + return; + } + + if (dragInfo.state == diPending) + { + // we had a mouse down, but haven't confirmed a drag yet + // if the mouse has moved sufficiently, we will confirm + +// int distance = KGlobalSettings::dndEventDelay(); + int distance = QApplication::startDragDistance(); + if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance || + ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance) + { + // we've left the drag square, we can start a real drag operation now + emit isBusySelecting(false); // Ok.. we can breath again. + + _screenWindow->clearSelection(); + doDrag(); + } + return; + } + else if (dragInfo.state == diDragging) + { + // this isn't technically needed because mouseMoveEvent is suppressed during + // Qt drag operations, replaced by dragMoveEvent + return; + } + + if (_actSel == 0) return; + + // don't extend selection while pasting + if (ev->buttons() & Qt::MidButton) return; + + extendSelection( ev->pos() ); +} + +void TerminalDisplay::extendSelection( const QPoint& position ) +{ + QPoint pos = position; + + if ( !_screenWindow ) + return; + + //if ( !contentsRect().contains(ev->pos()) ) return; + QPoint tL = contentsRect().topLeft(); + int tLx = tL.x(); + int tLy = tL.y(); + int scroll = _scrollBar->value(); + + // we're in the process of moving the mouse with the left button pressed + // the mouse cursor will kept caught within the bounds of the text in + // this widget. + + int linesBeyondWidget = 0; + + QRect textBounds(tLx + _leftMargin, + tLy + _topMargin, + _usedColumns*_fontWidth-1, + _usedLines*_fontHeight-1); + + // Adjust position within text area bounds. + QPoint oldpos = pos; + + pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) ); + pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) ); + + if ( oldpos.y() > textBounds.bottom() ) + { + linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward + } + if ( oldpos.y() < textBounds.top() ) + { + linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight; + _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history + } + + int charColumn = 0; + int charLine = 0; + getCharacterPosition(pos,charLine,charColumn); + + QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight); + QPoint ohere; + QPoint _iPntSelCorr = _iPntSel; + _iPntSelCorr.ry() -= _scrollBar->value(); + QPoint _pntSelCorr = _pntSel; + _pntSelCorr.ry() -= _scrollBar->value(); + bool swapping = false; + + if ( _wordSelectionMode ) + { + // Extend to word boundaries + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + i = loc(left.x(),left.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} } + } + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i].character); + while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; + } + else + { + here = right; ohere = left; + } + ohere.rx()++; + } + + if ( _lineSelectionMode ) + { + // Extend to complete line + bool above_not_below = ( here.y() < _iPntSelCorr.y() ); + + QPoint above = above_not_below ? here : _iPntSelCorr; + QPoint below = above_not_below ? _iPntSelCorr : here; + + while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) ) + above.ry()--; + while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) ) + below.ry()++; + + above.setX(0); + below.setX(_usedColumns-1); + + // Pick which is start (ohere) and which is extension (here) + if ( above_not_below ) + { + here = above; ohere = below; + } + else + { + here = below; ohere = above; + } + + QPoint newSelBegin = QPoint( ohere.x(), ohere.y() ); + swapping = !(_tripleSelBegin==newSelBegin); + _tripleSelBegin = newSelBegin; + + ohere.rx()++; + } + + int offset = 0; + if ( !_wordSelectionMode && !_lineSelectionMode ) + { + int i; + QChar selClass; + + bool left_not_right = ( here.y() < _iPntSelCorr.y() || + ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) ); + bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() || + ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) ); + swapping = left_not_right != old_left_not_right; + + // Find left (left_not_right ? from here : from start) + QPoint left = left_not_right ? here : _iPntSelCorr; + + // Find left (left_not_right ? from start : from here) + QPoint right = left_not_right ? _iPntSelCorr : here; + if ( right.x() > 0 && !_columnSelectionMode ) + { + i = loc(right.x(),right.y()); + if (i>=0 && i<=_imageSize) { + selClass = charClass(_image[i-1].character); + /* if (selClass == ' ') + { + while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) && + !(_lineProperties[right.y()] & LINE_WRAPPED)) + { i++; right.rx()++; } + if (right.x() < _usedColumns-1) + right = left_not_right ? _iPntSelCorr : here; + else + right.rx()++; // will be balanced later because of offset=-1; + }*/ + } + } + + // Pick which is start (ohere) and which is extension (here) + if ( left_not_right ) + { + here = left; ohere = right; offset = 0; + } + else + { + here = right; ohere = left; offset = -1; + } + } + + if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved + + if (here == ohere) return; // It's not left, it's not right. + + if ( _actSel < 2 || swapping ) + { + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true ); + } + else + { + _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false ); + } + + } + + _actSel = 2; // within selection + _pntSel = here; + _pntSel.ry() += _scrollBar->value(); + + if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode ) + { + _screenWindow->setSelectionEnd( here.x() , here.y() ); + } + else + { + _screenWindow->setSelectionEnd( here.x()+offset , here.y() ); + } + +} + +void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) + return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + + if ( ev->button() == Qt::LeftButton) + { + emit isBusySelecting(false); + if(dragInfo.state == diPending) + { + // We had a drag event pending but never confirmed. Kill selection + _screenWindow->clearSelection(); + //emit clearSelectionSignal(); + } + else + { + if ( _actSel > 1 ) + { + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _actSel = 0; + + //FIXME: emits a release event even if the mouse is + // outside the range. The procedure used in `mouseMoveEvent' + // applies here, too. + + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + emit mouseSignal( 3, // release + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0); + } + dragInfo.state = diNone; + } + + + if ( !_mouseMarks && + ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) + || ev->button() == Qt::MidButton) ) + { + emit mouseSignal( 3, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const +{ + column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth; + line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight; + + if ( line < 0 ) + line = 0; + if ( column < 0 ) + column = 0; + + if ( line >= _usedLines ) + line = _usedLines-1; + + // the column value returned can be equal to _usedColumns, which + // is the position just after the last character displayed in a line. + // + // this is required so that the user can select characters in the right-most + // column (or left-most for right-to-left input) + if ( column > _usedColumns ) + column = _usedColumns; +} + +void TerminalDisplay::updateFilters() +{ + if ( !_screenWindow ) + return; + + processFilters(); +} + +void TerminalDisplay::updateLineProperties() +{ + if ( !_screenWindow ) + return; + + _lineProperties = _screenWindow->getLineProperties(); +} + +void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev) +{ + if ( ev->button() != Qt::LeftButton) return; + if ( !_screenWindow ) return; + + int charLine = 0; + int charColumn = 0; + + getCharacterPosition(ev->pos(),charLine,charColumn); + + QPoint pos(charColumn,charLine); + + // pass on double click as two clicks. + if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) + { + // Send just _ONE_ click event, since the first click of the double click + // was already sent by the click handler + emit mouseSignal( 0, + pos.x()+1, + pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(), + 0 ); // left button + return; + } + + _screenWindow->clearSelection(); + QPoint bgnSel = pos; + QPoint endSel = pos; + int i = loc(bgnSel.x(),bgnSel.y()); + _iPntSel = bgnSel; + _iPntSel.ry() += _scrollBar->value(); + + _wordSelectionMode = true; + + // find word boundaries... + QChar selClass = charClass(_image[i].character); + { + // find the start of the word + int x = bgnSel.x(); + while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) )) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_usedColumns-1; + bgnSel.ry()--; + } + } + + bgnSel.setX(x); + _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false ); + + // find the end of the word + i = loc( endSel.x(), endSel.y() ); + x = endSel.x(); + while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) )) + && charClass(_image[i+1].character) == selClass ) + { + i++; + if (x<_usedColumns-1) + x++; + else + { + x=0; + endSel.ry()++; + } + } + + endSel.setX(x); + + // In word selection mode don't select @ (64) if at end of word. + if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) ) + endSel.setX( x - 1 ); + + + _actSel = 2; // within selection + + _screenWindow->setSelectionEnd( endSel.x() , endSel.y() ); + + setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); + } + + _possibleTripleClick=true; + + QTimer::singleShot(QApplication::doubleClickInterval(),this, + SLOT(tripleClickTimeout())); +} + +void TerminalDisplay::wheelEvent( QWheelEvent* ev ) +{ + if (ev->orientation() != Qt::Vertical) + return; + + // if the terminal program is not interested mouse events + // then send the event to the scrollbar if the slider has room to move + // or otherwise send simulated up / down key presses to the terminal program + // for the benefit of programs such as 'less' + if ( _mouseMarks ) + { + bool canScroll = _scrollBar->maximum() > 0; + if (canScroll) + _scrollBar->event(ev); + else + { + // assume that each Up / Down key event will cause the terminal application + // to scroll by one line. + // + // to get a reasonable scrolling speed, scroll by one line for every 5 degrees + // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees, + // giving a scroll of 3 lines + int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down; + + // QWheelEvent::delta() gives rotation in eighths of a degree + int wheelDegrees = ev->delta() / 8; + int linesToScroll = abs(wheelDegrees) / 5; + + QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier); + + for (int i=0;ipos() , charLine , charColumn ); + + emit mouseSignal( ev->delta() > 0 ? 4 : 5, + charColumn + 1, + charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , + 0); + } +} + +void TerminalDisplay::tripleClickTimeout() +{ + _possibleTripleClick=false; +} + +void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev) +{ + if ( !_screenWindow ) return; + + int charLine; + int charColumn; + getCharacterPosition(ev->pos(),charLine,charColumn); + _iPntSel = QPoint(charColumn,charLine); + + _screenWindow->clearSelection(); + + _lineSelectionMode = true; + _wordSelectionMode = false; + + _actSel = 2; // within selection + emit isBusySelecting(true); // Keep it steady... + + while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + _iPntSel.ry()--; + + if (_tripleClickMode == SelectForwardsFromCursor) { + // find word boundary start + int i = loc(_iPntSel.x(),_iPntSel.y()); + QChar selClass = charClass(_image[i].character); + int x = _iPntSel.x(); + + while ( ((x>0) || + (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) ) + ) + && charClass(_image[i-1].character) == selClass ) + { + i--; + if (x>0) + x--; + else + { + x=_columns-1; + _iPntSel.ry()--; + } + } + + _screenWindow->setSelectionStart( x , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( x, _iPntSel.y() ); + } + else if (_tripleClickMode == SelectWholeLine) { + _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false ); + _tripleSelBegin = QPoint( 0, _iPntSel.y() ); + } + + while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) ) + _iPntSel.ry()++; + + _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() ); + + setSelection(_screenWindow->selectedText(_preserveLineBreaks)); + + _iPntSel.ry() += _scrollBar->value(); +} + + +bool TerminalDisplay::focusNextPrevChild( bool next ) +{ + if (next) + return false; // This disables changing the active part in konqueror + // when pressing Tab + return QWidget::focusNextPrevChild( next ); +} + + +QChar TerminalDisplay::charClass(QChar qch) const +{ + if ( qch.isSpace() ) return ' '; + + if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) ) + return 'a'; + + return qch; +} + +void TerminalDisplay::setWordCharacters(const QString& wc) +{ + _wordCharacters = wc; +} + +void TerminalDisplay::setUsesMouse(bool on) +{ + _mouseMarks = on; + setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor ); +} +bool TerminalDisplay::usesMouse() const +{ + return _mouseMarks; +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Clipboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +#undef KeyPress + +void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn) +{ + if ( !_screenWindow ) + return; + + // Paste Clipboard by simulating keypress events + QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : + QClipboard::Clipboard); + if(appendReturn) + text.append("\r"); + if ( ! text.isEmpty() ) + { + text.replace('\n', '\r'); + QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text); + emit keyPressedSignal(&e); // expose as a big fat keypress event + + _screenWindow->clearSelection(); + } +} + +void TerminalDisplay::setSelection(const QString& t) +{ + QApplication::clipboard()->setText(t, QClipboard::Selection); +} + +void TerminalDisplay::copyClipboard() +{ + if ( !_screenWindow ) + return; + + QString text = _screenWindow->selectedText(_preserveLineBreaks); + if (!text.isEmpty()) + QApplication::clipboard()->setText(text); +} + +void TerminalDisplay::pasteClipboard() +{ + emitSelection(false,false); +} + +void TerminalDisplay::pasteSelection() +{ + emitSelection(true,false); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Keyboard */ +/* */ +/* ------------------------------------------------------------------------- */ + +void TerminalDisplay::setFlowControlWarningEnabled( bool enable ) +{ + _flowControlWarningEnabled = enable; + + // if the dialog is currently visible and the flow control warning has + // been disabled then hide the dialog + if (!enable) + outputSuspended(false); +} + +void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action) +{ + mMotionAfterPasting = action; +} + +int TerminalDisplay::motionAfterPasting() +{ + return mMotionAfterPasting; +} + +void TerminalDisplay::keyPressEvent( QKeyEvent* event ) +{ + bool emitKeyPressSignal = true; + + // Keyboard-based navigation + if ( event->modifiers() == Qt::ShiftModifier ) + { + bool update = true; + + if ( event->key() == Qt::Key_PageUp ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 ); + } + else if ( event->key() == Qt::Key_PageDown ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 ); + } + else if ( event->key() == Qt::Key_Up ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 ); + } + else if ( event->key() == Qt::Key_Down ) + { + _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 ); + } + else if ( event->key() == Qt::Key_End) + { + scrollToEnd(); + } + else if ( event->key() == Qt::Key_Home) + { + _screenWindow->scrollTo(0); + } + else + update = false; + + if ( update ) + { + _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() ); + + updateLineProperties(); + updateImage(); + + // do not send key press to terminal + emitKeyPressSignal = false; + } + } + + _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't + // know where the current selection is. + + if (_hasBlinkingCursor) + { + _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); + if (_cursorBlinking) + blinkCursorEvent(); + else + _cursorBlinking = false; + } + + if ( emitKeyPressSignal ) + { + emit keyPressedSignal(event); + + if(event->modifiers().testFlag(Qt::ShiftModifier) + || event->modifiers().testFlag(Qt::ControlModifier) + || event->modifiers().testFlag(Qt::AltModifier)) + { + switch(mMotionAfterPasting) + { + case MoveStartScreenWindow: + _screenWindow->scrollTo(0); + break; + case MoveEndScreenWindow: + scrollToEnd(); + break; + case NoMoveScreenWindow: + break; + } + } + else + { + scrollToEnd(); + } + } + + event->accept(); +} + +void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event ) +{ + QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString()); + emit keyPressedSignal(&keyEvent); + + _inputMethodData.preeditString = event->preeditString(); + update(preeditRect() | _inputMethodData.previousPreeditRect); + + event->accept(); +} +QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const +{ + const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0); + switch ( query ) + { + case Qt::ImMicroFocus: + return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1)); + break; + case Qt::ImFont: + return font(); + break; + case Qt::ImCursorPosition: + // return the cursor position within the current line + return cursorPos.x(); + break; + case Qt::ImSurroundingText: + { + // return the text from the current line + QString lineText; + QTextStream stream(&lineText); + PlainTextDecoder decoder; + decoder.begin(&stream); + decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]); + decoder.end(); + return lineText; + } + break; + case Qt::ImCurrentSelection: + return QString(); + break; + default: + break; + } + + return QVariant(); +} + +bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent) +{ + int modifiers = keyEvent->modifiers(); + + // When a possible shortcut combination is pressed, + // emit the overrideShortcutCheck() signal to allow the host + // to decide whether the terminal should override it or not. + if (modifiers != Qt::NoModifier) + { + int modifierCount = 0; + unsigned int currentModifier = Qt::ShiftModifier; + + while (currentModifier <= Qt::KeypadModifier) + { + if (modifiers & currentModifier) + modifierCount++; + currentModifier <<= 1; + } + if (modifierCount < 2) + { + bool override = false; + emit overrideShortcutCheck(keyEvent,override); + if (override) + { + keyEvent->accept(); + return true; + } + } + } + + // Override any of the following shortcuts because + // they are needed by the terminal + int keyCode = keyEvent->key() | modifiers; + switch ( keyCode ) + { + // list is taken from the QLineEdit::event() code + case Qt::Key_Tab: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + keyEvent->accept(); + return true; + } + return false; +} + +bool TerminalDisplay::event(QEvent* event) +{ + bool eventHandled = false; + switch (event->type()) + { + case QEvent::ShortcutOverride: + eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event); + break; + case QEvent::PaletteChange: + case QEvent::ApplicationPaletteChange: + _scrollBar->setPalette( QApplication::palette() ); + break; + default: + break; + } + return eventHandled ? true : QWidget::event(event); +} + +void TerminalDisplay::setBellMode(int mode) +{ + _bellMode=mode; +} + +void TerminalDisplay::enableBell() +{ + _allowBell = true; +} + +void TerminalDisplay::bell(const QString& message) +{ + if (_bellMode==NoBell) return; + + //limit the rate at which bells can occur + //...mainly for sound effects where rapid bells in sequence + //produce a horrible noise + if ( _allowBell ) + { + _allowBell = false; + QTimer::singleShot(500,this,SLOT(enableBell())); + + if (_bellMode==SystemBeepBell) + { + QApplication::beep(); + } + else if (_bellMode==NotifyBell) + { + emit notifyBell(message); + } + else if (_bellMode==VisualBell) + { + swapColorTable(); + QTimer::singleShot(200,this,SLOT(swapColorTable())); + } + } +} + +void TerminalDisplay::selectionChanged() +{ + emit copyAvailable(_screenWindow->selectedText(false).isEmpty() == false); +} + +void TerminalDisplay::swapColorTable() +{ + ColorEntry color = _colorTable[1]; + _colorTable[1]=_colorTable[0]; + _colorTable[0]= color; + _colorsInverted = !_colorsInverted; + update(); +} + +void TerminalDisplay::clearImage() +{ + // We initialize _image[_imageSize] too. See makeImage() + for (int i = 0; i <= _imageSize; i++) + { + _image[i].character = ' '; + _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_FORE_COLOR); + _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, + DEFAULT_BACK_COLOR); + _image[i].rendition = DEFAULT_RENDITION; + } +} + +void TerminalDisplay::calcGeometry() +{ + _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height()); + switch(_scrollbarLocation) + { + case NoScrollBar : + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN; + break; + case ScrollBarLeft : + _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width(); + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topLeft()); + break; + case ScrollBarRight: + _leftMargin = DEFAULT_LEFT_MARGIN; + _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width(); + _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0)); + break; + } + + _topMargin = DEFAULT_TOP_MARGIN; + _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1; + + if (!_isFixedSize) + { + // ensure that display is always at least one column wide + _columns = qMax(1,_contentWidth / _fontWidth); + _usedColumns = qMin(_usedColumns,_columns); + + // ensure that display is always at least one line high + _lines = qMax(1,_contentHeight / _fontHeight); + _usedLines = qMin(_usedLines,_lines); + } +} + +void TerminalDisplay::makeImage() +{ + calcGeometry(); + + // confirm that array will be of non-zero size, since the painting code + // assumes a non-zero array length + Q_ASSERT( _lines > 0 && _columns > 0 ); + Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns ); + + _imageSize=_lines*_columns; + + // We over-commit one character so that we can be more relaxed in dealing with + // certain boundary conditions: _image[_imageSize] is a valid but unused position + _image = new Character[_imageSize+1]; + + clearImage(); +} + +// calculate the needed size, this must be synced with calcGeometry() +void TerminalDisplay::setSize(int columns, int lines) +{ + int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width(); + int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN; + int verticalMargin = 2 * DEFAULT_TOP_MARGIN; + + QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) , + verticalMargin + (lines * _fontHeight) ); + + if ( newSize != size() ) + { + _size = newSize; + updateGeometry(); + } +} + +void TerminalDisplay::setFixedSize(int cols, int lins) +{ + _isFixedSize = true; + + //ensure that display is at least one line by one column in size + _columns = qMax(1,cols); + _lines = qMax(1,lins); + _usedColumns = qMin(_usedColumns,_columns); + _usedLines = qMin(_usedLines,_lines); + + if (_image) + { + delete[] _image; + makeImage(); + } + setSize(cols, lins); + QWidget::setFixedSize(_size); +} + +QSize TerminalDisplay::sizeHint() const +{ + return _size; +} + + +/* --------------------------------------------------------------------- */ +/* */ +/* Drag & Drop */ +/* */ +/* --------------------------------------------------------------------- */ + +void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasFormat("text/plain")) + event->acceptProposedAction(); + if (event->mimeData()->urls().count()); + event->acceptProposedAction(); +} + +void TerminalDisplay::dropEvent(QDropEvent* event) +{ + //KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); + QList urls = event->mimeData()->urls(); + + QString dropText; + if (!urls.isEmpty()) + { + // TODO/FIXME: escape or quote pasted things if neccessary... + qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please"; + for ( int i = 0 ; i < urls.count() ; i++ ) + { + //KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 ); + QUrl url = urls[i]; + + QString urlText; + + if (url.isLocalFile()) + urlText = url.path(); + else + urlText = url.toString(); + + // in future it may be useful to be able to insert file names with drag-and-drop + // without quoting them (this only affects paths with spaces in) + //urlText = KShell::quoteArg(urlText); + + dropText += urlText; + + if ( i != urls.count()-1 ) + dropText += ' '; + } + } + else + { + dropText = event->mimeData()->text(); + } + + emit sendStringToEmu(dropText.toLocal8Bit()); +} + +void TerminalDisplay::doDrag() +{ + dragInfo.state = diDragging; + dragInfo.dragObject = new QDrag(this); + QMimeData *mimeData = new QMimeData; + mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection)); + dragInfo.dragObject->setMimeData(mimeData); + dragInfo.dragObject->start(Qt::CopyAction); + // Don't delete the QTextDrag object. Qt will delete it when it's done with it. +} + +void TerminalDisplay::outputSuspended(bool suspended) +{ + //create the label when this function is first called + if (!_outputSuspendedLabel) + { + //This label includes a link to an English language website + //describing the 'flow control' (Xon/Xoff) feature found in almost + //all terminal emulators. + //If there isn't a suitable article available in the target language the link + //can simply be removed. + _outputSuspendedLabel = new QLabel( tr("Output has been " + "suspended" + " by pressing Ctrl+S." + " Press Ctrl+Q to resume."), + this ); + + QPalette palette(_outputSuspendedLabel->palette()); + //KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground); + _outputSuspendedLabel->setPalette(palette); + _outputSuspendedLabel->setAutoFillBackground(true); + _outputSuspendedLabel->setBackgroundRole(QPalette::Base); + _outputSuspendedLabel->setFont(QApplication::font()); + _outputSuspendedLabel->setContentsMargins(5, 5, 5, 5); + + //enable activation of "Xon/Xoff" link in label + _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | + Qt::LinksAccessibleByKeyboard); + _outputSuspendedLabel->setOpenExternalLinks(true); + _outputSuspendedLabel->setVisible(false); + + _gridLayout->addWidget(_outputSuspendedLabel); + _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding, + QSizePolicy::Expanding), + 1,0); + + } + + _outputSuspendedLabel->setVisible(suspended); +} + +uint TerminalDisplay::lineSpacing() const +{ + return _lineSpacing; +} + +void TerminalDisplay::setLineSpacing(uint i) +{ + _lineSpacing = i; + setVTFont(font()); // Trigger an update. +} + +AutoScrollHandler::AutoScrollHandler(QWidget* parent) +: QObject(parent) +, _timerId(0) +{ + parent->installEventFilter(this); +} +void AutoScrollHandler::timerEvent(QTimerEvent* event) +{ + if (event->timerId() != _timerId) + return; + + QMouseEvent mouseEvent( QEvent::MouseMove, + widget()->mapFromGlobal(QCursor::pos()), + Qt::NoButton, + Qt::LeftButton, + Qt::NoModifier); + + QApplication::sendEvent(widget(),&mouseEvent); +} +bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event) +{ + Q_ASSERT( watched == parent() ); + Q_UNUSED( watched ); + + QMouseEvent* mouseEvent = (QMouseEvent*)event; + switch (event->type()) + { + case QEvent::MouseMove: + { + bool mouseInWidget = widget()->rect().contains(mouseEvent->pos()); + + if (mouseInWidget) + { + if (_timerId) + killTimer(_timerId); + _timerId = 0; + } + else + { + if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton)) + _timerId = startTimer(100); + } + break; + } + case QEvent::MouseButtonRelease: + if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton)) + { + killTimer(_timerId); + _timerId = 0; + } + break; + default: + break; + }; + + return false; +} + +//#include "TerminalDisplay.moc" diff --git a/lib/TerminalDisplay.h b/lib/TerminalDisplay.h new file mode 100644 index 0000000..c32d799 --- /dev/null +++ b/lib/TerminalDisplay.h @@ -0,0 +1,857 @@ +/* + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef TERMINALDISPLAY_H +#define TERMINALDISPLAY_H + +// Qt +#include +#include +#include + +// Konsole +#include "Filter.h" +#include "Character.h" +//#include "konsole_export.h" +#define KONSOLEPRIVATE_EXPORT + +class QDrag; +class QDragEnterEvent; +class QDropEvent; +class QLabel; +class QTimer; +class QEvent; +class QGridLayout; +class QKeyEvent; +class QScrollBar; +class QShowEvent; +class QHideEvent; +class QTimerEvent; +class QWidget; + +//class KMenu; + +namespace Konsole +{ + + enum MotionAfterPasting + { + // No move screenwindow after pasting + NoMoveScreenWindow = 0, + // Move start of screenwindow after pasting + MoveStartScreenWindow = 1, + // Move end of screenwindow after pasting + MoveEndScreenWindow = 2 + }; + + +extern unsigned short vt100_graphics[32]; + +class ScreenWindow; + +/** + * A widget which displays output from a terminal emulation and sends input keypresses and mouse activity + * to the terminal. + * + * When the terminal emulation receives new output from the program running in the terminal, + * it will update the display by calling updateImage(). + * + * TODO More documentation + */ +class KONSOLEPRIVATE_EXPORT TerminalDisplay : public QWidget +{ + Q_OBJECT + +public: + /** Constructs a new terminal display widget with the specified parent. */ + TerminalDisplay(QWidget *parent=0); + virtual ~TerminalDisplay(); + + /** Returns the terminal color palette used by the display. */ + const ColorEntry* colorTable() const; + /** Sets the terminal color palette used by the display. */ + void setColorTable(const ColorEntry table[]); + /** + * Sets the seed used to generate random colors for the display + * (in color schemes that support them). + */ + void setRandomSeed(uint seed); + /** + * Returns the seed used to generate random colors for the display + * (in color schemes that support them). + */ + uint randomSeed() const; + + /** Sets the opacity of the terminal display. */ + void setOpacity(qreal opacity); + + /** + * This enum describes the location where the scroll bar is positioned in the display widget. + */ + enum ScrollBarPosition + { + /** Do not show the scroll bar. */ + NoScrollBar=0, + /** Show the scroll bar on the left side of the display. */ + ScrollBarLeft=1, + /** Show the scroll bar on the right side of the display. */ + ScrollBarRight=2 + }; + /** + * Specifies whether the terminal display has a vertical scroll bar, and if so whether it + * is shown on the left or right side of the display. + */ + void setScrollBarPosition(ScrollBarPosition position); + + /** + * Sets the current position and range of the display's scroll bar. + * + * @param cursor The position of the scroll bar's thumb. + * @param lines The maximum value of the scroll bar. + */ + void setScroll(int cursor, int lines); + + /** + * Scroll to the bottom of the terminal (reset scrolling). + */ + void scrollToEnd(); + + /** + * Returns the display's filter chain. When the image for the display is updated, + * the text is passed through each filter in the chain. Each filter can define + * hotspots which correspond to certain strings (such as URLs or particular words). + * Depending on the type of the hotspots created by the filter ( returned by Filter::Hotspot::type() ) + * the view will draw visual cues such as underlines on mouse-over for links or translucent + * rectangles for markers. + * + * To add a new filter to the view, call: + * viewWidget->filterChain()->addFilter( filterObject ); + */ + FilterChain* filterChain() const; + + /** + * Updates the filters in the display's filter chain. This will cause + * the hotspots to be updated to match the current image. + * + * WARNING: This function can be expensive depending on the + * image size and number of filters in the filterChain() + * + * TODO - This API does not really allow efficient usage. Revise it so + * that the processing can be done in a better way. + * + * eg: + * - Area of interest may be known ( eg. mouse cursor hovering + * over an area ) + */ + void processFilters(); + + /** + * Returns a list of menu actions created by the filters for the content + * at the given @p position. + */ + QList filterActions(const QPoint& position); + + /** Returns true if the cursor is set to blink or false otherwise. */ + bool blinkingCursor() { return _hasBlinkingCursor; } + /** Specifies whether or not the cursor blinks. */ + void setBlinkingCursor(bool blink); + + /** Specifies whether or not text can blink. */ + void setBlinkingTextEnabled(bool blink); + + void setCtrlDrag(bool enable) { _ctrlDrag=enable; } + bool ctrlDrag() { return _ctrlDrag; } + + /** + * This enum describes the methods for selecting text when + * the user triple-clicks within the display. + */ + enum TripleClickMode + { + /** Select the whole line underneath the cursor. */ + SelectWholeLine, + /** Select from the current cursor position to the end of the line. */ + SelectForwardsFromCursor + }; + /** Sets how the text is selected when the user triple clicks within the display. */ + void setTripleClickMode(TripleClickMode mode) { _tripleClickMode = mode; } + /** See setTripleClickSelectionMode() */ + TripleClickMode tripleClickMode() { return _tripleClickMode; } + + void setLineSpacing(uint); + uint lineSpacing() const; + + void emitSelection(bool useXselection,bool appendReturn); + + /** + * This enum describes the available shapes for the keyboard cursor. + * See setKeyboardCursorShape() + */ + enum KeyboardCursorShape + { + /** A rectangular block which covers the entire area of the cursor character. */ + BlockCursor, + /** + * A single flat line which occupies the space at the bottom of the cursor + * character's area. + */ + UnderlineCursor, + /** + * An cursor shaped like the capital letter 'I', similar to the IBeam + * cursor used in Qt/KDE text editors. + */ + IBeamCursor + }; + /** + * Sets the shape of the keyboard cursor. This is the cursor drawn + * at the position in the terminal where keyboard input will appear. + * + * In addition the terminal display widget also has a cursor for + * the mouse pointer, which can be set using the QWidget::setCursor() + * method. + * + * Defaults to BlockCursor + */ + void setKeyboardCursorShape(KeyboardCursorShape shape); + /** + * Returns the shape of the keyboard cursor. See setKeyboardCursorShape() + */ + KeyboardCursorShape keyboardCursorShape() const; + + /** + * Sets the color used to draw the keyboard cursor. + * + * The keyboard cursor defaults to using the foreground color of the character + * underneath it. + * + * @param useForegroundColor If true, the cursor color will change to match + * the foreground color of the character underneath it as it is moved, in this + * case, the @p color parameter is ignored and the color of the character + * under the cursor is inverted to ensure that it is still readable. + * @param color The color to use to draw the cursor. This is only taken into + * account if @p useForegroundColor is false. + */ + void setKeyboardCursorColor(bool useForegroundColor , const QColor& color); + + /** + * Returns the color of the keyboard cursor, or an invalid color if the keyboard + * cursor color is set to change according to the foreground color of the character + * underneath it. + */ + QColor keyboardCursorColor() const; + + /** + * Returns the number of lines of text which can be displayed in the widget. + * + * This will depend upon the height of the widget and the current font. + * See fontHeight() + */ + int lines() { return _lines; } + /** + * Returns the number of characters of text which can be displayed on + * each line in the widget. + * + * This will depend upon the width of the widget and the current font. + * See fontWidth() + */ + int columns() { return _columns; } + + /** + * Returns the height of the characters in the font used to draw the text in the display. + */ + int fontHeight() { return _fontHeight; } + /** + * Returns the width of the characters in the display. + * This assumes the use of a fixed-width font. + */ + int fontWidth() { return _fontWidth; } + + void setSize(int cols, int lins); + void setFixedSize(int cols, int lins); + + // reimplemented + QSize sizeHint() const; + + /** + * Sets which characters, in addition to letters and numbers, + * are regarded as being part of a word for the purposes + * of selecting words in the display by double clicking on them. + * + * The word boundaries occur at the first and last characters which + * are either a letter, number, or a character in @p wc + * + * @param wc An array of characters which are to be considered parts + * of a word ( in addition to letters and numbers ). + */ + void setWordCharacters(const QString& wc); + /** + * Returns the characters which are considered part of a word for the + * purpose of selecting words in the display with the mouse. + * + * @see setWordCharacters() + */ + QString wordCharacters() { return _wordCharacters; } + + /** + * Sets the type of effect used to alert the user when a 'bell' occurs in the + * terminal session. + * + * The terminal session can trigger the bell effect by calling bell() with + * the alert message. + */ + void setBellMode(int mode); + /** + * Returns the type of effect used to alert the user when a 'bell' occurs in + * the terminal session. + * + * See setBellMode() + */ + int bellMode() { return _bellMode; } + + /** + * This enum describes the different types of sounds and visual effects which + * can be used to alert the user when a 'bell' occurs in the terminal + * session. + */ + enum BellMode + { + /** A system beep. */ + SystemBeepBell=0, + /** + * KDE notification. This may play a sound, show a passive popup + * or perform some other action depending on the user's settings. + */ + NotifyBell=1, + /** A silent, visual bell (eg. inverting the display's colors briefly) */ + VisualBell=2, + /** No bell effects */ + NoBell=3 + }; + + void setSelection(const QString &t); + + /** + * Reimplemented. Has no effect. Use setVTFont() to change the font + * used to draw characters in the display. + */ + virtual void setFont(const QFont &); + + /** Returns the font used to draw characters in the display */ + QFont getVTFont() { return font(); } + + /** + * Sets the font used to draw the display. Has no effect if @p font + * is larger than the size of the display itself. + */ + void setVTFont(const QFont& font); + + /** + * Specified whether anti-aliasing of text in the terminal display + * is enabled or not. Defaults to enabled. + */ + static void setAntialias( bool antialias ) { _antialiasText = antialias; } + /** + * Returns true if anti-aliasing of text in the terminal is enabled. + */ + static bool antialias() { return _antialiasText; } + + /** + * Specifies whether characters with intense colors should be rendered + * as bold. Defaults to true. + */ + void setBoldIntense(bool value) { _boldIntense = value; } + /** + * Returns true if characters with intense colors are rendered in bold. + */ + bool getBoldIntense() { return _boldIntense; } + + /** + * Sets whether or not the current height and width of the + * terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + void setTerminalSizeHint(bool on) { _terminalSizeHint=on; } + /** + * Returns whether or not the current height and width of + * the terminal in lines and columns is displayed whilst the widget + * is being resized. + */ + bool terminalSizeHint() { return _terminalSizeHint; } + /** + * Sets whether the terminal size display is shown briefly + * after the widget is first shown. + * + * See setTerminalSizeHint() , isTerminalSizeHint() + */ + void setTerminalSizeStartup(bool on) { _terminalSizeStartup=on; } + + /** + * Sets the status of the BiDi rendering inside the terminal display. + * Defaults to disabled. + */ + void setBidiEnabled(bool set) { _bidiEnabled=set; } + /** + * Returns the status of the BiDi rendering in this widget. + */ + bool isBidiEnabled() { return _bidiEnabled; } + + /** + * Sets the terminal screen section which is displayed in this widget. + * When updateImage() is called, the display fetches the latest character image from the + * the associated terminal screen window. + * + * In terms of the model-view paradigm, the ScreenWindow is the model which is rendered + * by the TerminalDisplay. + */ + void setScreenWindow( ScreenWindow* window ); + /** Returns the terminal screen section which is displayed in this widget. See setScreenWindow() */ + ScreenWindow* screenWindow() const; + + static bool HAVE_TRANSPARENCY; + + void setMotionAfterPasting(MotionAfterPasting action); + int motionAfterPasting(); + + // maps a point on the widget to the position ( ie. line and column ) + // of the character at that point. + void getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const; + +public slots: + + /** + * Causes the terminal display to fetch the latest character image from the associated + * terminal screen ( see setScreenWindow() ) and redraw the display. + */ + void updateImage(); + + /** Essentially calles processFilters(). + */ + void updateFilters(); + + /** + * Causes the terminal display to fetch the latest line status flags from the + * associated terminal screen ( see setScreenWindow() ). + */ + void updateLineProperties(); + + /** Copies the selected text to the clipboard. */ + void copyClipboard(); + /** + * Pastes the content of the clipboard into the + * display. + */ + void pasteClipboard(); + /** + * Pastes the content of the selection into the + * display. + */ + void pasteSelection(); + + /** + * Changes whether the flow control warning box should be shown when the flow control + * stop key (Ctrl+S) are pressed. + */ + void setFlowControlWarningEnabled(bool enabled); + /** + * Returns true if the flow control warning box is enabled. + * See outputSuspended() and setFlowControlWarningEnabled() + */ + bool flowControlWarningEnabled() const + { return _flowControlWarningEnabled; } + + /** + * Causes the widget to display or hide a message informing the user that terminal + * output has been suspended (by using the flow control key combination Ctrl+S) + * + * @param suspended True if terminal output has been suspended and the warning message should + * be shown or false to indicate that terminal output has been resumed and that + * the warning message should disappear. + */ + void outputSuspended(bool suspended); + + /** + * Sets whether the program whoose output is being displayed in the view + * is interested in mouse events. + * + * If this is set to true, mouse signals will be emitted by the view when the user clicks, drags + * or otherwise moves the mouse inside the view. + * The user interaction needed to create selections will also change, and the user will be required + * to hold down the shift key to create a selection or perform other mouse activities inside the + * view area - since the program running in the terminal is being allowed to handle normal mouse + * events itself. + * + * @param usesMouse Set to true if the program running in the terminal is interested in mouse events + * or false otherwise. + */ + void setUsesMouse(bool usesMouse); + + /** See setUsesMouse() */ + bool usesMouse() const; + + /** + * Shows a notification that a bell event has occurred in the terminal. + * TODO: More documentation here + */ + void bell(const QString& message); + + /** + * Sets the background of the display to the specified color. + * @see setColorTable(), setForegroundColor() + */ + void setBackgroundColor(const QColor& color); + + /** + * Sets the text of the display to the specified color. + * @see setColorTable(), setBackgroundColor() + */ + void setForegroundColor(const QColor& color); + + void selectionChanged(); + +signals: + + /** + * Emitted when the user presses a key whilst the terminal widget has focus. + */ + void keyPressedSignal(QKeyEvent *e); + + /** + * A mouse event occurred. + * @param button The mouse button (0 for left button, 1 for middle button, 2 for right button, 3 for release) + * @param column The character column where the event occurred + * @param line The character row where the event occurred + * @param eventType The type of event. 0 for a mouse press / release or 1 for mouse motion + */ + void mouseSignal(int button, int column, int line, int eventType); + void changedFontMetricSignal(int height, int width); + void changedContentSizeSignal(int height, int width); + + /** + * Emitted when the user right clicks on the display, or right-clicks with the Shift + * key held down if usesMouse() is true. + * + * This can be used to display a context menu. + */ + void configureRequest(const QPoint& position); + + /** + * When a shortcut which is also a valid terminal key sequence is pressed while + * the terminal widget has focus, this signal is emitted to allow the host to decide + * whether the shortcut should be overridden. + * When the shortcut is overridden, the key sequence will be sent to the terminal emulation instead + * and the action associated with the shortcut will not be triggered. + * + * @p override is set to false by default and the shortcut will be triggered as normal. + */ + void overrideShortcutCheck(QKeyEvent* keyEvent,bool& override); + + void isBusySelecting(bool); + void sendStringToEmu(const char*); + + // qtermwidget signals + void copyAvailable(bool); + void termGetFocus(); + void termLostFocus(); + + void notifyBell(const QString&); + +protected: + virtual bool event( QEvent * ); + + virtual void paintEvent( QPaintEvent * ); + + virtual void showEvent(QShowEvent*); + virtual void hideEvent(QHideEvent*); + virtual void resizeEvent(QResizeEvent*); + + virtual void fontChange(const QFont &font); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void mouseDoubleClickEvent(QMouseEvent* ev); + virtual void mousePressEvent( QMouseEvent* ); + virtual void mouseReleaseEvent( QMouseEvent* ); + virtual void mouseMoveEvent( QMouseEvent* ); + virtual void extendSelection( const QPoint& pos ); + virtual void wheelEvent( QWheelEvent* ); + + virtual bool focusNextPrevChild( bool next ); + + // drag and drop + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dropEvent(QDropEvent* event); + void doDrag(); + enum DragState { diNone, diPending, diDragging }; + + struct _dragInfo { + DragState state; + QPoint start; + QDrag *dragObject; + } dragInfo; + + // classifies the 'ch' into one of three categories + // and returns a character to indicate which category it is in + // + // - A space (returns ' ') + // - Part of a word (returns 'a') + // - Other characters (returns the input character) + QChar charClass(QChar ch) const; + + void clearImage(); + + void mouseTripleClickEvent(QMouseEvent* ev); + + // reimplemented + virtual void inputMethodEvent ( QInputMethodEvent* event ); + virtual QVariant inputMethodQuery( Qt::InputMethodQuery query ) const; + +protected slots: + + void scrollBarPositionChanged(int value); + void blinkEvent(); + void blinkCursorEvent(); + + //Renables bell noises and visuals. Used to disable further bells for a short period of time + //after emitting the first in a sequence of bell events. + void enableBell(); + +private slots: + + void swapColorTable(); + void tripleClickTimeout(); // resets possibleTripleClick + +private: + + // -- Drawing helpers -- + + // divides the part of the display specified by 'rect' into + // fragments according to their colors and styles and calls + // drawTextFragment() to draw the fragments + void drawContents(QPainter &paint, const QRect &rect); + // draws a section of text, all the text in this section + // has a common color and style + void drawTextFragment(QPainter& painter, const QRect& rect, + const QString& text, const Character* style); + // draws the background for a text fragment + // if useOpacitySetting is true then the color's alpha value will be set to + // the display's transparency (set with setOpacity()), otherwise the background + // will be drawn fully opaque + void drawBackground(QPainter& painter, const QRect& rect, const QColor& color, + bool useOpacitySetting); + // draws the cursor character + void drawCursor(QPainter& painter, const QRect& rect , const QColor& foregroundColor, + const QColor& backgroundColor , bool& invertColors); + // draws the characters or line graphics in a text fragment + void drawCharacters(QPainter& painter, const QRect& rect, const QString& text, + const Character* style, bool invertCharacterColor); + // draws a string of line graphics + void drawLineCharString(QPainter& painter, int x, int y, + const QString& str, const Character* attributes); + + // draws the preedit string for input methods + void drawInputMethodPreeditString(QPainter& painter , const QRect& rect); + + // -- + + // maps an area in the character image to an area on the widget + QRect imageToWidget(const QRect& imageArea) const; + + // the area where the preedit string for input methods will be draw + QRect preeditRect() const; + + // shows a notification window in the middle of the widget indicating the terminal's + // current size in columns and lines + void showResizeNotification(); + + // scrolls the image by a number of lines. + // 'lines' may be positive ( to scroll the image down ) + // or negative ( to scroll the image up ) + // 'region' is the part of the image to scroll - currently only + // the top, bottom and height of 'region' are taken into account, + // the left and right are ignored. + void scrollImage(int lines , const QRect& region); + + void calcGeometry(); + void propagateSize(); + void updateImageSize(); + void makeImage(); + + void paintFilters(QPainter& painter); + + // returns a region covering all of the areas of the widget which contain + // a hotspot + QRegion hotSpotRegion() const; + + // returns the position of the cursor in columns and lines + QPoint cursorPosition() const; + + // redraws the cursor + void updateCursor(); + + bool handleShortcutOverrideEvent(QKeyEvent* event); + + // the window onto the terminal screen which this display + // is currently showing. + QPointer _screenWindow; + + bool _allowBell; + + QGridLayout* _gridLayout; + + bool _fixedFont; // has fixed pitch + int _fontHeight; // height + int _fontWidth; // width + int _fontAscent; // ascend + bool _boldIntense; // Whether intense colors should be rendered with bold font + + int _leftMargin; // offset + int _topMargin; // offset + + int _lines; // the number of lines that can be displayed in the widget + int _columns; // the number of columns that can be displayed in the widget + + int _usedLines; // the number of lines that are actually being used, this will be less + // than 'lines' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _usedColumns; // the number of columns that are actually being used, this will be less + // than 'columns' if the character image provided with setImage() is smaller + // than the maximum image size which can be displayed + + int _contentHeight; + int _contentWidth; + Character* _image; // [lines][columns] + // only the area [usedLines][usedColumns] in the image contains valid data + + int _imageSize; + QVector _lineProperties; + + ColorEntry _colorTable[TABLE_COLORS]; + uint _randomSeed; + + bool _resizing; + bool _terminalSizeHint; + bool _terminalSizeStartup; + bool _bidiEnabled; + bool _mouseMarks; + + QPoint _iPntSel; // initial selection point + QPoint _pntSel; // current selection point + QPoint _tripleSelBegin; // help avoid flicker + int _actSel; // selection state + bool _wordSelectionMode; + bool _lineSelectionMode; + bool _preserveLineBreaks; + bool _columnSelectionMode; + + QClipboard* _clipboard; + QScrollBar* _scrollBar; + ScrollBarPosition _scrollbarLocation; + QString _wordCharacters; + int _bellMode; + + bool _blinking; // hide text in paintEvent + bool _hasBlinker; // has characters to blink + bool _cursorBlinking; // hide cursor in paintEvent + bool _hasBlinkingCursor; // has blinking cursor enabled + bool _allowBlinkingText; // allow text to blink + bool _ctrlDrag; // require Ctrl key for drag + TripleClickMode _tripleClickMode; + bool _isFixedSize; //Columns / lines are locked. + QTimer* _blinkTimer; // active when hasBlinker + QTimer* _blinkCursorTimer; // active when hasBlinkingCursor + + //QMenu* _drop; + QString _dropText; + int _dndFileCount; + + bool _possibleTripleClick; // is set in mouseDoubleClickEvent and deleted + // after QApplication::doubleClickInterval() delay + + + QLabel* _resizeWidget; + QTimer* _resizeTimer; + + bool _flowControlWarningEnabled; + + //widgets related to the warning message that appears when the user presses Ctrl+S to suspend + //terminal output - informing them what has happened and how to resume output + QLabel* _outputSuspendedLabel; + + uint _lineSpacing; + + bool _colorsInverted; // true during visual bell + + QSize _size; + + QRgb _blendColor; + + // list of filters currently applied to the display. used for links and + // search highlight + TerminalImageFilterChain* _filterChain; + QRegion _mouseOverHotspotArea; + + KeyboardCursorShape _cursorShape; + + // custom cursor color. if this is invalid then the foreground + // color of the character under the cursor is used + QColor _cursorColor; + + + MotionAfterPasting mMotionAfterPasting; + + struct InputMethodData + { + QString preeditString; + QRect previousPreeditRect; + }; + InputMethodData _inputMethodData; + + static bool _antialiasText; // do we antialias or not + + //the delay in milliseconds between redrawing blinking text + static const int TEXT_BLINK_DELAY = 500; + static const int DEFAULT_LEFT_MARGIN = 1; + static const int DEFAULT_TOP_MARGIN = 1; + +public: + static void setTransparencyEnabled(bool enable) + { + HAVE_TRANSPARENCY = enable; + } +}; + +class AutoScrollHandler : public QObject +{ +Q_OBJECT + +public: + AutoScrollHandler(QWidget* parent); +protected: + virtual void timerEvent(QTimerEvent* event); + virtual bool eventFilter(QObject* watched,QEvent* event); +private: + QWidget* widget() const { return static_cast(parent()); } + int _timerId; +}; + +} + +#endif // TERMINALDISPLAY_H diff --git a/lib/Vt102Emulation.cpp b/lib/Vt102Emulation.cpp new file mode 100644 index 0000000..cb92363 --- /dev/null +++ b/lib/Vt102Emulation.cpp @@ -0,0 +1,1237 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +// Own +#include "Vt102Emulation.h" + +// XKB +//#include + +// this allows konsole to be compiled without XKB and XTEST extensions +// even though it might be available on a particular system. +#if defined(AVOID_XKB) + #undef HAVE_XKB +#endif + +#if defined(HAVE_XKB) + void scrolllock_set_off(); + void scrolllock_set_on(); +#endif + +// Standard +#include +#include +#include + +// Qt +#include +#include +#include + +// KDE +//#include +//#include + +// Konsole +#include "KeyboardTranslator.h" +#include "Screen.h" + + +using namespace Konsole; + +Vt102Emulation::Vt102Emulation() + : Emulation(), + _titleUpdateTimer(new QTimer(this)) +{ + _titleUpdateTimer->setSingleShot(true); + QObject::connect(_titleUpdateTimer , SIGNAL(timeout()) , this , SLOT(updateTitle())); + + initTokenizer(); + reset(); +} + +Vt102Emulation::~Vt102Emulation() +{} + +void Vt102Emulation::clearEntireScreen() +{ + _currentScreen->clearEntireScreen(); + bufferedUpdate(); +} + +void Vt102Emulation::reset() +{ + resetTokenizer(); + resetModes(); + resetCharset(0); + _screen[0]->reset(); + resetCharset(1); + _screen[1]->reset(); + setCodec(LocaleCodec); + + bufferedUpdate(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Processing the incoming byte stream */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* Incoming Bytes Event pipeline + + This section deals with decoding the incoming character stream. + Decoding means here, that the stream is first separated into `tokens' + which are then mapped to a `meaning' provided as operations by the + `Screen' class or by the emulation class itself. + + The pipeline proceeds as follows: + + - Tokenizing the ESC codes (onReceiveChar) + - VT100 code page translation of plain characters (applyCharset) + - Interpretation of ESC codes (processToken) + + The escape codes and their meaning are described in the + technical reference of this program. +*/ + +// Tokens ------------------------------------------------------------------ -- + +/* + Since the tokens are the central notion if this section, we've put them + in front. They provide the syntactical elements used to represent the + terminals operations as byte sequences. + + They are encodes here into a single machine word, so that we can later + switch over them easily. Depending on the token itself, additional + argument variables are filled with parameter values. + + The tokens are defined below: + + - CHR - Printable characters (32..255 but DEL (=127)) + - CTL - Control characters (0..31 but ESC (= 27), DEL) + - ESC - Escape codes of the form + - ESC_DE - Escape codes of the form C + - CSI_PN - Escape codes of the form '[' {Pn} ';' {Pn} C + - CSI_PS - Escape codes of the form '[' {Pn} ';' ... C + - CSI_PR - Escape codes of the form '[' '?' {Pn} ';' ... C + - CSI_PE - Escape codes of the form '[' '!' {Pn} ';' ... C + - VT52 - VT52 escape codes + - + - 'Y'{Pc}{Pc} + - XTE_HA - Xterm window/terminal attribute commands + of the form `]' {Pn} `;' {Text} + (Note that these are handled differently to the other formats) + + The last two forms allow list of arguments. Since the elements of + the lists are treated individually the same way, they are passed + as individual tokens to the interpretation. Further, because the + meaning of the parameters are names (althought represented as numbers), + they are includes within the token ('N'). + +*/ + +#define TY_CONSTRUCT(T,A,N) ( ((((int)N) & 0xffff) << 16) | ((((int)A) & 0xff) << 8) | (((int)T) & 0xff) ) + +#define TY_CHR( ) TY_CONSTRUCT(0,0,0) +#define TY_CTL(A ) TY_CONSTRUCT(1,A,0) +#define TY_ESC(A ) TY_CONSTRUCT(2,A,0) +#define TY_ESC_CS(A,B) TY_CONSTRUCT(3,A,B) +#define TY_ESC_DE(A ) TY_CONSTRUCT(4,A,0) +#define TY_CSI_PS(A,N) TY_CONSTRUCT(5,A,N) +#define TY_CSI_PN(A ) TY_CONSTRUCT(6,A,0) +#define TY_CSI_PR(A,N) TY_CONSTRUCT(7,A,N) + +#define TY_VT52(A) TY_CONSTRUCT(8,A,0) +#define TY_CSI_PG(A) TY_CONSTRUCT(9,A,0) +#define TY_CSI_PE(A) TY_CONSTRUCT(10,A,0) + +#define MAX_ARGUMENT 4096 + +// Tokenizer --------------------------------------------------------------- -- + +/* The tokenizer's state + + The state is represented by the buffer (tokenBuffer, tokenBufferPos), + and accompanied by decoded arguments kept in (argv,argc). + Note that they are kept internal in the tokenizer. +*/ + +void Vt102Emulation::resetTokenizer() +{ + tokenBufferPos = 0; + argc = 0; + argv[0] = 0; + argv[1] = 0; +} + +void Vt102Emulation::addDigit(int digit) +{ + if (argv[argc] < MAX_ARGUMENT) + argv[argc] = 10*argv[argc] + digit; +} + +void Vt102Emulation::addArgument() +{ + argc = qMin(argc+1,MAXARGS-1); + argv[argc] = 0; +} + +void Vt102Emulation::addToCurrentToken(int cc) +{ + tokenBuffer[tokenBufferPos] = cc; + tokenBufferPos = qMin(tokenBufferPos+1,MAX_TOKEN_LENGTH-1); +} + +// Character Class flags used while decoding + +#define CTL 1 // Control character +#define CHR 2 // Printable character +#define CPN 4 // TODO: Document me +#define DIG 8 // Digit +#define SCS 16 // TODO: Document me +#define GRP 32 // TODO: Document me +#define CPS 64 // Character which indicates end of window resize + // escape sequence '\e[8;;t' + +void Vt102Emulation::initTokenizer() +{ + int i; + quint8* s; + for(i = 0;i < 256; ++i) + charClass[i] = 0; + for(i = 0;i < 32; ++i) + charClass[i] |= CTL; + for(i = 32;i < 256; ++i) + charClass[i] |= CHR; + for(s = (quint8*)"@ABCDGHILMPSTXZcdfry"; *s; ++s) + charClass[*s] |= CPN; + // resize = \e[8;;t + for(s = (quint8*)"t"; *s; ++s) + charClass[*s] |= CPS; + for(s = (quint8*)"0123456789"; *s; ++s) + charClass[*s] |= DIG; + for(s = (quint8*)"()+*%"; *s; ++s) + charClass[*s] |= SCS; + for(s = (quint8*)"()+*#[]%"; *s; ++s) + charClass[*s] |= GRP; + + resetTokenizer(); +} + +/* Ok, here comes the nasty part of the decoder. + + Instead of keeping an explicit state, we deduce it from the + token scanned so far. It is then immediately combined with + the current character to form a scanning decision. + + This is done by the following defines. + + - P is the length of the token scanned so far. + - L (often P-1) is the position on which contents we base a decision. + - C is a character or a group of characters (taken from 'charClass'). + + - 'cc' is the current character + - 's' is a pointer to the start of the token buffer + - 'p' is the current position within the token buffer + + Note that they need to applied in proper order. +*/ + +#define lec(P,L,C) (p == (P) && s[(L)] == (C)) +#define lun( ) (p == 1 && cc >= 32 ) +#define les(P,L,C) (p == (P) && s[L] < 256 && (charClass[s[(L)]] & (C)) == (C)) +#define eec(C) (p >= 3 && cc == (C)) +#define ees(C) (p >= 3 && cc < 256 && (charClass[cc] & (C)) == (C)) +#define eps(C) (p >= 3 && s[2] != '?' && s[2] != '!' && s[2] != '>' && cc < 256 && (charClass[cc] & (C)) == (C)) +#define epp( ) (p >= 3 && s[2] == '?') +#define epe( ) (p >= 3 && s[2] == '!') +#define egt( ) (p >= 3 && s[2] == '>') +#define Xpe (tokenBufferPos >= 2 && tokenBuffer[1] == ']') +#define Xte (Xpe && cc == 7 ) +#define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C) && !Xte) + +#define ESC 27 +#define CNTL(c) ((c)-'@') + +// process an incoming unicode character +void Vt102Emulation::receiveChar(int cc) +{ + if (cc == 127) + return; //VT100: ignore. + + if (ces(CTL)) + { + // DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100 + // This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do + // of course. Guess this originates from a weakly layered handling of the X-on + // X-off protocol, which comes really below this level. + if (cc == CNTL('X') || cc == CNTL('Z') || cc == ESC) + resetTokenizer(); //VT100: CAN or SUB + if (cc != ESC) + { + processToken(TY_CTL(cc+'@' ),0,0); + return; + } + } + // advance the state + addToCurrentToken(cc); + + int* s = tokenBuffer; + int p = tokenBufferPos; + + if (getMode(MODE_Ansi)) + { + if (lec(1,0,ESC)) { return; } + if (lec(1,0,ESC+128)) { s[0] = ESC; receiveChar('['); return; } + if (les(2,1,GRP)) { return; } + if (Xte ) { processWindowAttributeChange(); resetTokenizer(); return; } + if (Xpe ) { return; } + if (lec(3,2,'?')) { return; } + if (lec(3,2,'>')) { return; } + if (lec(3,2,'!')) { return; } + if (lun( )) { processToken( TY_CHR(), applyCharset(cc), 0); resetTokenizer(); return; } + if (lec(2,0,ESC)) { processToken( TY_ESC(s[1]), 0, 0); resetTokenizer(); return; } + if (les(3,1,SCS)) { processToken( TY_ESC_CS(s[1],s[2]), 0, 0); resetTokenizer(); return; } + if (lec(3,1,'#')) { processToken( TY_ESC_DE(s[2]), 0, 0); resetTokenizer(); return; } + if (eps( CPN)) { processToken( TY_CSI_PN(cc), argv[0],argv[1]); resetTokenizer(); return; } + + // resize = \e[8;;t + if (eps(CPS)) + { + processToken( TY_CSI_PS(cc, argv[0]), argv[1], argv[2]); + resetTokenizer(); + return; + } + + if (epe( )) { processToken( TY_CSI_PE(cc), 0, 0); resetTokenizer(); return; } + if (ees(DIG)) { addDigit(cc-'0'); return; } + if (eec(';')) { addArgument(); return; } + for (int i=0;i<=argc;i++) + { + if (epp()) + processToken( TY_CSI_PR(cc,argv[i]), 0, 0); + else if (egt()) + processToken( TY_CSI_PG(cc), 0, 0); // spec. case for ESC]>0c or ESC]>c + else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 2) + { + // ESC[ ... 48;2;;; ... m -or- ESC[ ... 38;2;;; ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i+1] << 8) | argv[i+2]); + i += 2; + } + else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 5) + { + // ESC[ ... 48;5; ... m -or- ESC[ ... 38;5; ... m + i += 2; + processToken( TY_CSI_PS(cc, argv[i-2]), COLOR_SPACE_256, argv[i]); + } + else + processToken( TY_CSI_PS(cc,argv[i]), 0, 0); + } + resetTokenizer(); + } + else + { + // VT52 Mode + if (lec(1,0,ESC)) + return; + if (les(1,0,CHR)) + { + processToken( TY_CHR(), s[0], 0); + resetTokenizer(); + return; + } + if (lec(2,1,'Y')) + return; + if (lec(3,1,'Y')) + return; + if (p < 4) + { + processToken( TY_VT52(s[1] ), 0, 0); + resetTokenizer(); + return; + } + processToken( TY_VT52(s[1]), s[2], s[3]); + resetTokenizer(); + return; + } +} +void Vt102Emulation::processWindowAttributeChange() +{ + // Describes the window or terminal session attribute to change + // See Session::UserTitleChange for possible values + int attributeToChange = 0; + int i; + for (i = 2; i < tokenBufferPos && + tokenBuffer[i] >= '0' && + tokenBuffer[i] <= '9'; i++) + { + attributeToChange = 10 * attributeToChange + (tokenBuffer[i]-'0'); + } + + if (tokenBuffer[i] != ';') + { + reportDecodingError(); + return; + } + + QString newValue; + newValue.reserve(tokenBufferPos-i-2); + for (int j = 0; j < tokenBufferPos-i-2; j++) + newValue[j] = tokenBuffer[i+1+j]; + + _pendingTitleUpdates[attributeToChange] = newValue; + _titleUpdateTimer->start(20); +} + +void Vt102Emulation::updateTitle() +{ + QListIterator iter( _pendingTitleUpdates.keys() ); + while (iter.hasNext()) { + int arg = iter.next(); + emit titleChanged( arg , _pendingTitleUpdates[arg] ); + } + _pendingTitleUpdates.clear(); +} + +// Interpreting Codes --------------------------------------------------------- + +/* + Now that the incoming character stream is properly tokenized, + meaning is assigned to them. These are either operations of + the current _screen, or of the emulation class itself. + + The token to be interpreteted comes in as a machine word + possibly accompanied by two parameters. + + Likewise, the operations assigned to, come with up to two + arguments. One could consider to make up a proper table + from the function below. + + The technical reference manual provides more information + about this mapping. +*/ + +void Vt102Emulation::processToken(int token, int p, int q) +{ + switch (token) + { + + case TY_CHR( ) : _currentScreen->displayCharacter (p ); break; //UTF16 + + // 127 DEL : ignored on input + + case TY_CTL('@' ) : /* NUL: ignored */ break; + case TY_CTL('A' ) : /* SOH: ignored */ break; + case TY_CTL('B' ) : /* STX: ignored */ break; + case TY_CTL('C' ) : /* ETX: ignored */ break; + case TY_CTL('D' ) : /* EOT: ignored */ break; + case TY_CTL('E' ) : reportAnswerBack ( ); break; //VT100 + case TY_CTL('F' ) : /* ACK: ignored */ break; + case TY_CTL('G' ) : emit stateSet(NOTIFYBELL); + break; //VT100 + case TY_CTL('H' ) : _currentScreen->backspace ( ); break; //VT100 + case TY_CTL('I' ) : _currentScreen->tab ( ); break; //VT100 + case TY_CTL('J' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('K' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('L' ) : _currentScreen->newLine ( ); break; //VT100 + case TY_CTL('M' ) : _currentScreen->toStartOfLine ( ); break; //VT100 + + case TY_CTL('N' ) : useCharset ( 1); break; //VT100 + case TY_CTL('O' ) : useCharset ( 0); break; //VT100 + + case TY_CTL('P' ) : /* DLE: ignored */ break; + case TY_CTL('Q' ) : /* DC1: XON continue */ break; //VT100 + case TY_CTL('R' ) : /* DC2: ignored */ break; + case TY_CTL('S' ) : /* DC3: XOFF halt */ break; //VT100 + case TY_CTL('T' ) : /* DC4: ignored */ break; + case TY_CTL('U' ) : /* NAK: ignored */ break; + case TY_CTL('V' ) : /* SYN: ignored */ break; + case TY_CTL('W' ) : /* ETB: ignored */ break; + case TY_CTL('X' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('Y' ) : /* EM : ignored */ break; + case TY_CTL('Z' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 + case TY_CTL('[' ) : /* ESC: cannot be seen here. */ break; + case TY_CTL('\\' ) : /* FS : ignored */ break; + case TY_CTL(']' ) : /* GS : ignored */ break; + case TY_CTL('^' ) : /* RS : ignored */ break; + case TY_CTL('_' ) : /* US : ignored */ break; + + case TY_ESC('D' ) : _currentScreen->index ( ); break; //VT100 + case TY_ESC('E' ) : _currentScreen->nextLine ( ); break; //VT100 + case TY_ESC('H' ) : _currentScreen->changeTabStop (true ); break; //VT100 + case TY_ESC('M' ) : _currentScreen->reverseIndex ( ); break; //VT100 + case TY_ESC('Z' ) : reportTerminalType ( ); break; + case TY_ESC('c' ) : reset ( ); break; + + case TY_ESC('n' ) : useCharset ( 2); break; + case TY_ESC('o' ) : useCharset ( 3); break; + case TY_ESC('7' ) : saveCursor ( ); break; + case TY_ESC('8' ) : restoreCursor ( ); break; + + case TY_ESC('=' ) : setMode (MODE_AppKeyPad); break; + case TY_ESC('>' ) : resetMode (MODE_AppKeyPad); break; + case TY_ESC('<' ) : setMode (MODE_Ansi ); break; //VT100 + + case TY_ESC_CS('(', '0') : setCharset (0, '0'); break; //VT100 + case TY_ESC_CS('(', 'A') : setCharset (0, 'A'); break; //VT100 + case TY_ESC_CS('(', 'B') : setCharset (0, 'B'); break; //VT100 + + case TY_ESC_CS(')', '0') : setCharset (1, '0'); break; //VT100 + case TY_ESC_CS(')', 'A') : setCharset (1, 'A'); break; //VT100 + case TY_ESC_CS(')', 'B') : setCharset (1, 'B'); break; //VT100 + + case TY_ESC_CS('*', '0') : setCharset (2, '0'); break; //VT100 + case TY_ESC_CS('*', 'A') : setCharset (2, 'A'); break; //VT100 + case TY_ESC_CS('*', 'B') : setCharset (2, 'B'); break; //VT100 + + case TY_ESC_CS('+', '0') : setCharset (3, '0'); break; //VT100 + case TY_ESC_CS('+', 'A') : setCharset (3, 'A'); break; //VT100 + case TY_ESC_CS('+', 'B') : setCharset (3, 'B'); break; //VT100 + + case TY_ESC_CS('%', 'G') : setCodec (Utf8Codec ); break; //LINUX + case TY_ESC_CS('%', '@') : setCodec (LocaleCodec ); break; //LINUX + + case TY_ESC_DE('3' ) : /* Double height line, top half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('4' ) : /* Double height line, bottom half */ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); + break; + case TY_ESC_DE('5' ) : /* Single width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , false); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('6' ) : /* Double width, single height line*/ + _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true); + _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); + break; + case TY_ESC_DE('8' ) : _currentScreen->helpAlign ( ); break; + +// resize = \e[8;;t + case TY_CSI_PS('t', 8) : setImageSize( q /* columns */, p /* lines */ ); break; + +// change tab text color : \e[28;t color: 0-16,777,215 + case TY_CSI_PS('t', 28) : emit changeTabTextColorRequest ( p ); break; + + case TY_CSI_PS('K', 0) : _currentScreen->clearToEndOfLine ( ); break; + case TY_CSI_PS('K', 1) : _currentScreen->clearToBeginOfLine ( ); break; + case TY_CSI_PS('K', 2) : _currentScreen->clearEntireLine ( ); break; + case TY_CSI_PS('J', 0) : _currentScreen->clearToEndOfScreen ( ); break; + case TY_CSI_PS('J', 1) : _currentScreen->clearToBeginOfScreen ( ); break; + case TY_CSI_PS('J', 2) : _currentScreen->clearEntireScreen ( ); break; + case TY_CSI_PS('J', 3) : clearHistory(); break; + case TY_CSI_PS('g', 0) : _currentScreen->changeTabStop (false ); break; //VT100 + case TY_CSI_PS('g', 3) : _currentScreen->clearTabStops ( ); break; //VT100 + case TY_CSI_PS('h', 4) : _currentScreen-> setMode (MODE_Insert ); break; + case TY_CSI_PS('h', 20) : setMode (MODE_NewLine ); break; + case TY_CSI_PS('i', 0) : /* IGNORE: attached printer */ break; //VT100 + case TY_CSI_PS('l', 4) : _currentScreen-> resetMode (MODE_Insert ); break; + case TY_CSI_PS('l', 20) : resetMode (MODE_NewLine ); break; + case TY_CSI_PS('s', 0) : saveCursor ( ); break; + case TY_CSI_PS('u', 0) : restoreCursor ( ); break; + + case TY_CSI_PS('m', 0) : _currentScreen->setDefaultRendition ( ); break; + case TY_CSI_PS('m', 1) : _currentScreen-> setRendition (RE_BOLD ); break; //VT100 + case TY_CSI_PS('m', 4) : _currentScreen-> setRendition (RE_UNDERLINE); break; //VT100 + case TY_CSI_PS('m', 5) : _currentScreen-> setRendition (RE_BLINK ); break; //VT100 + case TY_CSI_PS('m', 7) : _currentScreen-> setRendition (RE_REVERSE ); break; + case TY_CSI_PS('m', 10) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 11) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 12) : /* IGNORED: mapping related */ break; //LINUX + case TY_CSI_PS('m', 22) : _currentScreen->resetRendition (RE_BOLD ); break; + case TY_CSI_PS('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE); break; + case TY_CSI_PS('m', 25) : _currentScreen->resetRendition (RE_BLINK ); break; + case TY_CSI_PS('m', 27) : _currentScreen->resetRendition (RE_REVERSE ); break; + + case TY_CSI_PS('m', 30) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 31) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 32) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 33) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 34) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 35) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 36) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 37) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 38) : _currentScreen->setForeColor (p, q); break; + + case TY_CSI_PS('m', 39) : _currentScreen->setForeColor (COLOR_SPACE_DEFAULT, 0); break; + + case TY_CSI_PS('m', 40) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 0); break; + case TY_CSI_PS('m', 41) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 1); break; + case TY_CSI_PS('m', 42) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 2); break; + case TY_CSI_PS('m', 43) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 3); break; + case TY_CSI_PS('m', 44) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 4); break; + case TY_CSI_PS('m', 45) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 5); break; + case TY_CSI_PS('m', 46) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 6); break; + case TY_CSI_PS('m', 47) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 7); break; + + case TY_CSI_PS('m', 48) : _currentScreen->setBackColor (p, q); break; + + case TY_CSI_PS('m', 49) : _currentScreen->setBackColor (COLOR_SPACE_DEFAULT, 1); break; + + case TY_CSI_PS('m', 90) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 91) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 92) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 93) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 94) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 95) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 96) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 97) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('m', 100) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 8); break; + case TY_CSI_PS('m', 101) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 9); break; + case TY_CSI_PS('m', 102) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 10); break; + case TY_CSI_PS('m', 103) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 11); break; + case TY_CSI_PS('m', 104) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 12); break; + case TY_CSI_PS('m', 105) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 13); break; + case TY_CSI_PS('m', 106) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 14); break; + case TY_CSI_PS('m', 107) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 15); break; + + case TY_CSI_PS('n', 5) : reportStatus ( ); break; + case TY_CSI_PS('n', 6) : reportCursorPosition ( ); break; + case TY_CSI_PS('q', 0) : /* IGNORED: LEDs off */ break; //VT100 + case TY_CSI_PS('q', 1) : /* IGNORED: LED1 on */ break; //VT100 + case TY_CSI_PS('q', 2) : /* IGNORED: LED2 on */ break; //VT100 + case TY_CSI_PS('q', 3) : /* IGNORED: LED3 on */ break; //VT100 + case TY_CSI_PS('q', 4) : /* IGNORED: LED4 on */ break; //VT100 + case TY_CSI_PS('x', 0) : reportTerminalParms ( 2); break; //VT100 + case TY_CSI_PS('x', 1) : reportTerminalParms ( 3); break; //VT100 + + case TY_CSI_PN('@' ) : _currentScreen->insertChars (p ); break; + case TY_CSI_PN('A' ) : _currentScreen->cursorUp (p ); break; //VT100 + case TY_CSI_PN('B' ) : _currentScreen->cursorDown (p ); break; //VT100 + case TY_CSI_PN('C' ) : _currentScreen->cursorRight (p ); break; //VT100 + case TY_CSI_PN('D' ) : _currentScreen->cursorLeft (p ); break; //VT100 + case TY_CSI_PN('G' ) : _currentScreen->setCursorX (p ); break; //LINUX + case TY_CSI_PN('H' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('I' ) : _currentScreen->tab (p ); break; + case TY_CSI_PN('L' ) : _currentScreen->insertLines (p ); break; + case TY_CSI_PN('M' ) : _currentScreen->deleteLines (p ); break; + case TY_CSI_PN('P' ) : _currentScreen->deleteChars (p ); break; + case TY_CSI_PN('S' ) : _currentScreen->scrollUp (p ); break; + case TY_CSI_PN('T' ) : _currentScreen->scrollDown (p ); break; + case TY_CSI_PN('X' ) : _currentScreen->eraseChars (p ); break; + case TY_CSI_PN('Z' ) : _currentScreen->backtab (p ); break; + case TY_CSI_PN('c' ) : reportTerminalType ( ); break; //VT100 + case TY_CSI_PN('d' ) : _currentScreen->setCursorY (p ); break; //LINUX + case TY_CSI_PN('f' ) : _currentScreen->setCursorYX (p, q); break; //VT100 + case TY_CSI_PN('r' ) : setMargins (p, q); break; //VT100 + case TY_CSI_PN('y' ) : /* IGNORED: Confidence test */ break; //VT100 + + case TY_CSI_PR('h', 1) : setMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('l', 1) : resetMode (MODE_AppCuKeys); break; //VT100 + case TY_CSI_PR('s', 1) : saveMode (MODE_AppCuKeys); break; //FIXME + case TY_CSI_PR('r', 1) : restoreMode (MODE_AppCuKeys); break; //FIXME + + case TY_CSI_PR('l', 2) : resetMode (MODE_Ansi ); break; //VT100 + + case TY_CSI_PR('h', 3) : setMode (MODE_132Columns);break; //VT100 + case TY_CSI_PR('l', 3) : resetMode (MODE_132Columns);break; //VT100 + + case TY_CSI_PR('h', 4) : /* IGNORED: soft scrolling */ break; //VT100 + case TY_CSI_PR('l', 4) : /* IGNORED: soft scrolling */ break; //VT100 + + case TY_CSI_PR('h', 5) : _currentScreen-> setMode (MODE_Screen ); break; //VT100 + case TY_CSI_PR('l', 5) : _currentScreen-> resetMode (MODE_Screen ); break; //VT100 + + case TY_CSI_PR('h', 6) : _currentScreen-> setMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('l', 6) : _currentScreen-> resetMode (MODE_Origin ); break; //VT100 + case TY_CSI_PR('s', 6) : _currentScreen-> saveMode (MODE_Origin ); break; //FIXME + case TY_CSI_PR('r', 6) : _currentScreen->restoreMode (MODE_Origin ); break; //FIXME + + case TY_CSI_PR('h', 7) : _currentScreen-> setMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('l', 7) : _currentScreen-> resetMode (MODE_Wrap ); break; //VT100 + case TY_CSI_PR('s', 7) : _currentScreen-> saveMode (MODE_Wrap ); break; //FIXME + case TY_CSI_PR('r', 7) : _currentScreen->restoreMode (MODE_Wrap ); break; //FIXME + + case TY_CSI_PR('h', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('l', 8) : /* IGNORED: autorepeat off */ break; //VT100 + case TY_CSI_PR('s', 8) : /* IGNORED: autorepeat on */ break; //VT100 + case TY_CSI_PR('r', 8) : /* IGNORED: autorepeat off */ break; //VT100 + + case TY_CSI_PR('h', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('l', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('s', 9) : /* IGNORED: interlace */ break; //VT100 + case TY_CSI_PR('r', 9) : /* IGNORED: interlace */ break; //VT100 + + case TY_CSI_PR('h', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('l', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('s', 12) : /* IGNORED: Cursor blink */ break; //att610 + case TY_CSI_PR('r', 12) : /* IGNORED: Cursor blink */ break; //att610 + + case TY_CSI_PR('h', 25) : setMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('l', 25) : resetMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('s', 25) : saveMode (MODE_Cursor ); break; //VT100 + case TY_CSI_PR('r', 25) : restoreMode (MODE_Cursor ); break; //VT100 + + case TY_CSI_PR('h', 40) : setMode(MODE_Allow132Columns ); break; // XTERM + case TY_CSI_PR('l', 40) : resetMode(MODE_Allow132Columns ); break; // XTERM + + case TY_CSI_PR('h', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('l', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('s', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + case TY_CSI_PR('r', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM + + case TY_CSI_PR('h', 47) : setMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('l', 47) : resetMode (MODE_AppScreen); break; //VT100 + case TY_CSI_PR('s', 47) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 47) : restoreMode (MODE_AppScreen); break; //XTERM + + case TY_CSI_PR('h', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('l', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('s', 67) : /* IGNORED: DECBKM */ break; //XTERM + case TY_CSI_PR('r', 67) : /* IGNORED: DECBKM */ break; //XTERM + + // XTerm defines the following modes: + // SET_VT200_MOUSE 1000 + // SET_VT200_HIGHLIGHT_MOUSE 1001 + // SET_BTN_EVENT_MOUSE 1002 + // SET_ANY_EVENT_MOUSE 1003 + // + + //Note about mouse modes: + //There are four mouse modes which xterm-compatible terminals can support - 1000,1001,1002,1003 + //Konsole currently supports mode 1000 (basic mouse press and release) and mode 1002 (dragging the mouse). + //TODO: Implementation of mouse modes 1001 (something called hilight tracking) and + //1003 (a slight variation on dragging the mouse) + // + + case TY_CSI_PR('h', 1000) : setMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('l', 1000) : resetMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('s', 1000) : saveMode (MODE_Mouse1000); break; //XTERM + case TY_CSI_PR('r', 1000) : restoreMode (MODE_Mouse1000); break; //XTERM + + case TY_CSI_PR('h', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('l', 1001) : resetMode (MODE_Mouse1001); break; //XTERM + case TY_CSI_PR('s', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + case TY_CSI_PR('r', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM + + case TY_CSI_PR('h', 1002) : setMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('l', 1002) : resetMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('s', 1002) : saveMode (MODE_Mouse1002); break; //XTERM + case TY_CSI_PR('r', 1002) : restoreMode (MODE_Mouse1002); break; //XTERM + + case TY_CSI_PR('h', 1003) : setMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('l', 1003) : resetMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('s', 1003) : saveMode (MODE_Mouse1003); break; //XTERM + case TY_CSI_PR('r', 1003) : restoreMode (MODE_Mouse1003); break; //XTERM + + case TY_CSI_PR('h', 1034) : /* IGNORED: 8bitinput activation */ break; //XTERM + + case TY_CSI_PR('h', 1047) : setMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1047) : _screen[1]->clearEntireScreen(); resetMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('s', 1047) : saveMode (MODE_AppScreen); break; //XTERM + case TY_CSI_PR('r', 1047) : restoreMode (MODE_AppScreen); break; //XTERM + + //FIXME: Unitoken: save translations + case TY_CSI_PR('h', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('l', 1048) : restoreCursor ( ); break; //XTERM + case TY_CSI_PR('s', 1048) : saveCursor ( ); break; //XTERM + case TY_CSI_PR('r', 1048) : restoreCursor ( ); break; //XTERM + + //FIXME: every once new sequences like this pop up in xterm. + // Here's a guess of what they could mean. + case TY_CSI_PR('h', 1049) : saveCursor(); _screen[1]->clearEntireScreen(); setMode(MODE_AppScreen); break; //XTERM + case TY_CSI_PR('l', 1049) : resetMode(MODE_AppScreen); restoreCursor(); break; //XTERM + + //FIXME: weird DEC reset sequence + case TY_CSI_PE('p' ) : /* IGNORED: reset ( ) */ break; + + //FIXME: when changing between vt52 and ansi mode evtl do some resetting. + case TY_VT52('A' ) : _currentScreen->cursorUp ( 1); break; //VT52 + case TY_VT52('B' ) : _currentScreen->cursorDown ( 1); break; //VT52 + case TY_VT52('C' ) : _currentScreen->cursorRight ( 1); break; //VT52 + case TY_VT52('D' ) : _currentScreen->cursorLeft ( 1); break; //VT52 + + case TY_VT52('F' ) : setAndUseCharset (0, '0'); break; //VT52 + case TY_VT52('G' ) : setAndUseCharset (0, 'B'); break; //VT52 + + case TY_VT52('H' ) : _currentScreen->setCursorYX (1,1 ); break; //VT52 + case TY_VT52('I' ) : _currentScreen->reverseIndex ( ); break; //VT52 + case TY_VT52('J' ) : _currentScreen->clearToEndOfScreen ( ); break; //VT52 + case TY_VT52('K' ) : _currentScreen->clearToEndOfLine ( ); break; //VT52 + case TY_VT52('Y' ) : _currentScreen->setCursorYX (p-31,q-31 ); break; //VT52 + case TY_VT52('Z' ) : reportTerminalType ( ); break; //VT52 + case TY_VT52('<' ) : setMode (MODE_Ansi ); break; //VT52 + case TY_VT52('=' ) : setMode (MODE_AppKeyPad); break; //VT52 + case TY_VT52('>' ) : resetMode (MODE_AppKeyPad); break; //VT52 + + case TY_CSI_PG('c' ) : reportSecondaryAttributes( ); break; //VT100 + + default: + reportDecodingError(); + break; + }; +} + +void Vt102Emulation::clearScreenAndSetColumns(int columnCount) +{ + setImageSize(_currentScreen->getLines(),columnCount); + clearEntireScreen(); + setDefaultMargins(); + _currentScreen->setCursorYX(0,0); +} + +void Vt102Emulation::sendString(const char* s , int length) +{ + if ( length >= 0 ) + emit sendData(s,length); + else + emit sendData(s,strlen(s)); +} + +void Vt102Emulation::reportCursorPosition() +{ + char tmp[20]; + sprintf(tmp,"\033[%d;%dR",_currentScreen->getCursorY()+1,_currentScreen->getCursorX()+1); + sendString(tmp); +} + +void Vt102Emulation::reportTerminalType() +{ + // Primary device attribute response (Request was: ^[[0c or ^[[c (from TT321 Users Guide)) + // VT220: ^[[?63;1;2;3;6;7;8c (list deps on emul. capabilities) + // VT100: ^[[?1;2c + // VT101: ^[[?1;0c + // VT102: ^[[?6v + if (getMode(MODE_Ansi)) + sendString("\033[?1;2c"); // I'm a VT100 + else + sendString("\033/Z"); // I'm a VT52 +} + +void Vt102Emulation::reportSecondaryAttributes() +{ + // Seconday device attribute response (Request was: ^[[>0c or ^[[>c) + if (getMode(MODE_Ansi)) + sendString("\033[>0;115;0c"); // Why 115? ;) + else + sendString("\033/Z"); // FIXME I don't think VT52 knows about it but kept for + // konsoles backward compatibility. +} + +void Vt102Emulation::reportTerminalParms(int p) +// DECREPTPARM +{ + char tmp[100]; + sprintf(tmp,"\033[%d;1;1;112;112;1;0x",p); // not really true. + sendString(tmp); +} + +void Vt102Emulation::reportStatus() +{ + sendString("\033[0n"); //VT100. Device status report. 0 = Ready. +} + +void Vt102Emulation::reportAnswerBack() +{ + // FIXME - Test this with VTTEST + // This is really obsolete VT100 stuff. + const char* ANSWER_BACK = ""; + sendString(ANSWER_BACK); +} + +/*! + `cx',`cy' are 1-based. + `eventType' indicates the button pressed (0-2) + or a general mouse release (3). + + eventType represents the kind of mouse action that occurred: + 0 = Mouse button press or release + 1 = Mouse drag +*/ + +void Vt102Emulation::sendMouseEvent( int cb, int cx, int cy , int eventType ) +{ + if (cx < 1 || cy < 1) + return; + + // normal buttons are passed as 0x20 + button, + // mouse wheel (buttons 4,5) as 0x5c + button + if (cb >= 4) + cb += 0x3c; + + //Mouse motion handling + if ((getMode(MODE_Mouse1002) || getMode(MODE_Mouse1003)) && eventType == 1) + cb += 0x20; //add 32 to signify motion event + + char command[20]; + sprintf(command,"\033[M%c%c%c",cb+0x20,cx+0x20,cy+0x20); + sendString(command); +} + +void Vt102Emulation::sendText( const QString& text ) +{ + if (!text.isEmpty()) + { + QKeyEvent event(QEvent::KeyPress, + 0, + Qt::NoModifier, + text); + sendKeyEvent(&event); // expose as a big fat keypress event + } +} +void Vt102Emulation::sendKeyEvent( QKeyEvent* event ) +{ + Qt::KeyboardModifiers modifiers = event->modifiers(); + KeyboardTranslator::States states = KeyboardTranslator::NoState; + + // get current states + if (getMode(MODE_NewLine) ) states |= KeyboardTranslator::NewLineState; + if (getMode(MODE_Ansi) ) states |= KeyboardTranslator::AnsiState; + if (getMode(MODE_AppCuKeys)) states |= KeyboardTranslator::CursorKeysState; + if (getMode(MODE_AppScreen)) states |= KeyboardTranslator::AlternateScreenState; + if (getMode(MODE_AppKeyPad) && (modifiers & Qt::KeypadModifier)) + states |= KeyboardTranslator::ApplicationKeypadState; + + // check flow control state + if (modifiers & Qt::ControlModifier) + { + if (event->key() == Qt::Key_S) + emit flowControlKeyPressed(true); + else if (event->key() == Qt::Key_Q) + emit flowControlKeyPressed(false); + } + + // lookup key binding + if ( _keyTranslator ) + { + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + event->key() , + modifiers, + states ); + + // send result to terminal + QByteArray textToSend; + + // special handling for the Alt (aka. Meta) modifier. pressing + // Alt+[Character] results in Esc+[Character] being sent + // (unless there is an entry defined for this particular combination + // in the keyboard modifier) + bool wantsAltModifier = entry.modifiers() & entry.modifierMask() & Qt::AltModifier; + bool wantsAnyModifier = entry.state() & + entry.stateMask() & KeyboardTranslator::AnyModifierState; + + if ( modifiers & Qt::AltModifier && !(wantsAltModifier || wantsAnyModifier) + && !event->text().isEmpty() ) + { + textToSend.prepend("\033"); + } + + if ( entry.command() != KeyboardTranslator::NoCommand ) + { + if (entry.command() & KeyboardTranslator::EraseCommand) + textToSend += eraseChar(); + + // TODO command handling + } + else if ( !entry.text().isEmpty() ) + { + textToSend += _codec->fromUnicode(entry.text(true,modifiers)); + } + else if((modifiers & Qt::ControlModifier) && event->key() >= 0x40 && event->key() < 0x5f) { + textToSend += (event->key() & 0x1f); + } + else if(event->key() == Qt::Key_Tab) { + textToSend += 0x09; + } + else if (event->key() == Qt::Key_PageUp) { + textToSend += "\033[5~"; + } + else if (event->key() == Qt::Key_PageDown) { + textToSend += "\033[6~"; + } + else { + textToSend += _codec->fromUnicode(event->text()); + } + + sendData( textToSend.constData() , textToSend.length() ); + } + else + { + // print an error message to the terminal if no key translator has been + // set + QString translatorError = tr("No keyboard translator available. " + "The information needed to convert key presses " + "into characters to send to the terminal " + "is missing."); + reset(); + receiveData( translatorError.toUtf8().constData() , translatorError.count() ); + } +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* VT100 Charsets */ +/* */ +/* ------------------------------------------------------------------------- */ + +// Character Set Conversion ------------------------------------------------ -- + +/* + The processing contains a VT100 specific code translation layer. + It's still in use and mainly responsible for the line drawing graphics. + + These and some other glyphs are assigned to codes (0x5f-0xfe) + normally occupied by the latin letters. Since this codes also + appear within control sequences, the extra code conversion + does not permute with the tokenizer and is placed behind it + in the pipeline. It only applies to tokens, which represent + plain characters. + + This conversion it eventually continued in TerminalDisplay.C, since + it might involve VT100 enhanced fonts, which have these + particular glyphs allocated in (0x00-0x1f) in their code page. +*/ + +#define CHARSET _charset[_currentScreen==_screen[1]] + +// Apply current character map. + +unsigned short Vt102Emulation::applyCharset(unsigned short c) +{ + if (CHARSET.graphic && 0x5f <= c && c <= 0x7e) return vt100_graphics[c-0x5f]; + if (CHARSET.pound && c == '#' ) return 0xa3; //This mode is obsolete + return c; +} + +/* + "Charset" related part of the emulation state. + This configures the VT100 charset filter. + + While most operation work on the current _screen, + the following two are different. +*/ + +void Vt102Emulation::resetCharset(int scrno) +{ + _charset[scrno].cu_cs = 0; + strncpy(_charset[scrno].charset,"BBBB",4); + _charset[scrno].sa_graphic = false; + _charset[scrno].sa_pound = false; + _charset[scrno].graphic = false; + _charset[scrno].pound = false; +} + +void Vt102Emulation::setCharset(int n, int cs) // on both screens. +{ + _charset[0].charset[n&3] = cs; useCharset(_charset[0].cu_cs); + _charset[1].charset[n&3] = cs; useCharset(_charset[1].cu_cs); +} + +void Vt102Emulation::setAndUseCharset(int n, int cs) +{ + CHARSET.charset[n&3] = cs; + useCharset(n&3); +} + +void Vt102Emulation::useCharset(int n) +{ + CHARSET.cu_cs = n&3; + CHARSET.graphic = (CHARSET.charset[n&3] == '0'); + CHARSET.pound = (CHARSET.charset[n&3] == 'A'); //This mode is obsolete +} + +void Vt102Emulation::setDefaultMargins() +{ + _screen[0]->setDefaultMargins(); + _screen[1]->setDefaultMargins(); +} + +void Vt102Emulation::setMargins(int t, int b) +{ + _screen[0]->setMargins(t, b); + _screen[1]->setMargins(t, b); +} + +void Vt102Emulation::saveCursor() +{ + CHARSET.sa_graphic = CHARSET.graphic; + CHARSET.sa_pound = CHARSET.pound; //This mode is obsolete + // we are not clear about these + //sa_charset = charsets[cScreen->_charset]; + //sa_charset_num = cScreen->_charset; + _currentScreen->saveCursor(); +} + +void Vt102Emulation::restoreCursor() +{ + CHARSET.graphic = CHARSET.sa_graphic; + CHARSET.pound = CHARSET.sa_pound; //This mode is obsolete + _currentScreen->restoreCursor(); +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Mode Operations */ +/* */ +/* ------------------------------------------------------------------------- */ + +/* + Some of the emulations state is either added to the state of the screens. + + This causes some scoping problems, since different emulations choose to + located the mode either to the current _screen or to both. + + For strange reasons, the extend of the rendition attributes ranges over + all screens and not over the actual _screen. + + We decided on the precise precise extend, somehow. +*/ + +// "Mode" related part of the state. These are all booleans. + +void Vt102Emulation::resetModes() +{ + // MODE_Allow132Columns is not reset here + // to match Xterm's behaviour (see Xterm's VTReset() function) + + resetMode(MODE_132Columns); saveMode(MODE_132Columns); + resetMode(MODE_Mouse1000); saveMode(MODE_Mouse1000); + resetMode(MODE_Mouse1001); saveMode(MODE_Mouse1001); + resetMode(MODE_Mouse1002); saveMode(MODE_Mouse1002); + resetMode(MODE_Mouse1003); saveMode(MODE_Mouse1003); + + resetMode(MODE_AppScreen); saveMode(MODE_AppScreen); + resetMode(MODE_AppCuKeys); saveMode(MODE_AppCuKeys); + resetMode(MODE_AppKeyPad); saveMode(MODE_AppKeyPad); + resetMode(MODE_NewLine); + setMode(MODE_Ansi); +} + +void Vt102Emulation::setMode(int m) +{ + _currentModes.mode[m] = true; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(132); + else + _currentModes.mode[m] = false; + break; + case MODE_Mouse1000: + case MODE_Mouse1001: + case MODE_Mouse1002: + case MODE_Mouse1003: + emit programUsesMouseChanged(false); + break; + + case MODE_AppScreen : _screen[1]->clearSelection(); + setScreen(1); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->setMode(m); + _screen[1]->setMode(m); + } +} + +void Vt102Emulation::resetMode(int m) +{ + _currentModes.mode[m] = false; + switch (m) + { + case MODE_132Columns: + if (getMode(MODE_Allow132Columns)) + clearScreenAndSetColumns(80); + break; + case MODE_Mouse1000 : + case MODE_Mouse1001 : + case MODE_Mouse1002 : + case MODE_Mouse1003 : + emit programUsesMouseChanged(true); + break; + + case MODE_AppScreen : + _screen[0]->clearSelection(); + setScreen(0); + break; + } + if (m < MODES_SCREEN || m == MODE_NewLine) + { + _screen[0]->resetMode(m); + _screen[1]->resetMode(m); + } +} + +void Vt102Emulation::saveMode(int m) +{ + _savedModes.mode[m] = _currentModes.mode[m]; +} + +void Vt102Emulation::restoreMode(int m) +{ + if (_savedModes.mode[m]) + setMode(m); + else + resetMode(m); +} + +bool Vt102Emulation::getMode(int m) +{ + return _currentModes.mode[m]; +} + +char Vt102Emulation::eraseChar() const +{ + KeyboardTranslator::Entry entry = _keyTranslator->findEntry( + Qt::Key_Backspace, + 0, + 0); + if ( entry.text().count() > 0 ) + return entry.text()[0]; + else + return '\b'; +} + +// print contents of the scan buffer +static void hexdump(int* s, int len) +{ int i; + for (i = 0; i < len; i++) + { + if (s[i] == '\\') + printf("\\\\"); + else + if ((s[i]) > 32 && s[i] < 127) + printf("%c",s[i]); + else + printf("\\%04x(hex)",s[i]); + } +} + +void Vt102Emulation::reportDecodingError() +{ + if (tokenBufferPos == 0 || ( tokenBufferPos == 1 && (tokenBuffer[0] & 0xff) >= 32) ) + return; + printf("Undecodable sequence: "); + hexdump(tokenBuffer,tokenBufferPos); + printf("\n"); +} + +//#include "Vt102Emulation.moc" + diff --git a/lib/Vt102Emulation.h b/lib/Vt102Emulation.h new file mode 100644 index 0000000..504d941 --- /dev/null +++ b/lib/Vt102Emulation.h @@ -0,0 +1,191 @@ +/* + This file is part of Konsole, an X terminal. + + Copyright 2007-2008 by Robert Knight + Copyright 1997,1998 by Lars Doelle + + 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. +*/ + +#ifndef VT102EMULATION_H +#define VT102EMULATION_H + +// Standard Library +#include + +// Qt +#include +#include +#include + +// Konsole +#include "Emulation.h" +#include "Screen.h" + +#define MODE_AppScreen (MODES_SCREEN+0) // Mode #1 +#define MODE_AppCuKeys (MODES_SCREEN+1) // Application cursor keys (DECCKM) +#define MODE_AppKeyPad (MODES_SCREEN+2) // +#define MODE_Mouse1000 (MODES_SCREEN+3) // Send mouse X,Y position on press and release +#define MODE_Mouse1001 (MODES_SCREEN+4) // Use Hilight mouse tracking +#define MODE_Mouse1002 (MODES_SCREEN+5) // Use cell motion mouse tracking +#define MODE_Mouse1003 (MODES_SCREEN+6) // Use all motion mouse tracking +#define MODE_Ansi (MODES_SCREEN+7) // Use US Ascii for character sets G0-G3 (DECANM) +#define MODE_132Columns (MODES_SCREEN+8) // 80 <-> 132 column mode switch (DECCOLM) +#define MODE_Allow132Columns (MODES_SCREEN+9) // Allow DECCOLM mode +#define MODE_total (MODES_SCREEN+10) + +namespace Konsole +{ + +struct CharCodes +{ + // coding info + char charset[4]; // + int cu_cs; // actual charset. + bool graphic; // Some VT100 tricks + bool pound ; // Some VT100 tricks + bool sa_graphic; // saved graphic + bool sa_pound; // saved pound +}; + +/** + * Provides an xterm compatible terminal emulation based on the DEC VT102 terminal. + * A full description of this terminal can be found at http://vt100.net/docs/vt102-ug/ + * + * In addition, various additional xterm escape sequences are supported to provide + * features such as mouse input handling. + * See http://rtfm.etla.org/xterm/ctlseq.html for a description of xterm's escape + * sequences. + * + */ +class Vt102Emulation : public Emulation +{ +Q_OBJECT + +public: + /** Constructs a new emulation */ + Vt102Emulation(); + ~Vt102Emulation(); + + // reimplemented from Emulation + virtual void clearEntireScreen(); + virtual void reset(); + virtual char eraseChar() const; + +public slots: + // reimplemented from Emulation + virtual void sendString(const char*,int length = -1); + virtual void sendText(const QString& text); + virtual void sendKeyEvent(QKeyEvent*); + virtual void sendMouseEvent(int buttons, int column, int line, int eventType); + +protected: + // reimplemented from Emulation + virtual void setMode(int mode); + virtual void resetMode(int mode); + virtual void receiveChar(int cc); + +private slots: + //causes changeTitle() to be emitted for each (int,QString) pair in pendingTitleUpdates + //used to buffer multiple title updates + void updateTitle(); + +private: + unsigned short applyCharset(unsigned short c); + void setCharset(int n, int cs); + void useCharset(int n); + void setAndUseCharset(int n, int cs); + void saveCursor(); + void restoreCursor(); + void resetCharset(int scrno); + + void setMargins(int top, int bottom); + //set margins for all screens back to their defaults + void setDefaultMargins(); + + // returns true if 'mode' is set or false otherwise + bool getMode (int mode); + // saves the current boolean value of 'mode' + void saveMode (int mode); + // restores the boolean value of 'mode' + void restoreMode(int mode); + // resets all modes + // (except MODE_Allow132Columns) + void resetModes(); + + void resetTokenizer(); + #define MAX_TOKEN_LENGTH 80 + void addToCurrentToken(int cc); + int tokenBuffer[MAX_TOKEN_LENGTH]; //FIXME: overflow? + int tokenBufferPos; +#define MAXARGS 15 + void addDigit(int dig); + void addArgument(); + int argv[MAXARGS]; + int argc; + void initTokenizer(); + + // Set of flags for each of the ASCII characters which indicates + // what category they fall into (printable character, control, digit etc.) + // for the purposes of decoding terminal output + int charClass[256]; + + void reportDecodingError(); + + void processToken(int code, int p, int q); + void processWindowAttributeChange(); + + void reportTerminalType(); + void reportSecondaryAttributes(); + void reportStatus(); + void reportAnswerBack(); + void reportCursorPosition(); + void reportTerminalParms(int p); + + void onScrollLock(); + void scrollLock(const bool lock); + + // clears the screen and resizes it to the specified + // number of columns + void clearScreenAndSetColumns(int columnCount); + + CharCodes _charset[2]; + + class TerminalState + { + public: + // Initializes all modes to false + TerminalState() + { memset(&mode,false,MODE_total * sizeof(bool)); } + + bool mode[MODE_total]; + }; + + TerminalState _currentModes; + TerminalState _savedModes; + + //hash table and timer for buffering calls to the session instance + //to update the name of the session + //or window title. + //these calls occur when certain escape sequences are seen in the + //output from the terminal + QHash _pendingTitleUpdates; + QTimer* _titleUpdateTimer; +}; + +} + +#endif // VT102EMULATION_H diff --git a/lib/color-schemes/BlackOnLightYellow.schema b/lib/color-schemes/BlackOnLightYellow.schema new file mode 100644 index 0000000..251a696 --- /dev/null +++ b/lib/color-schemes/BlackOnLightYellow.schema @@ -0,0 +1,42 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Black on Light Yellow + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 221 1 0 # regular background color (Light Yellow) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 221 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/BlackOnRandomLight.colorscheme b/lib/color-schemes/BlackOnRandomLight.colorscheme new file mode 100644 index 0000000..4d6f831 --- /dev/null +++ b/lib/color-schemes/BlackOnRandomLight.colorscheme @@ -0,0 +1,104 @@ +[Background] +Bold=false +Color=247,247,214 +Transparency=true +MaxRandomHue=340 + +[BackgroundIntense] +Bold=false +Color=255,255,221 +Transparency=true + +[Color0] +Bold=false +Color=0,0,0 +Transparency=false + +[Color0Intense] +Bold=false +Color=104,104,104 +Transparency=false + +[Color1] +Bold=false +Color=178,24,24 +Transparency=false + +[Color1Intense] +Bold=false +Color=255,84,84 +Transparency=false + +[Color2] +Bold=false +Color=24,178,24 +Transparency=false + +[Color2Intense] +Bold=false +Color=84,255,84 +Transparency=false + +[Color3] +Bold=false +Color=178,104,24 +Transparency=false + +[Color3Intense] +Bold=false +Color=255,255,84 +Transparency=false + +[Color4] +Bold=false +Color=24,24,178 +Transparency=false + +[Color4Intense] +Bold=false +Color=84,84,255 +Transparency=false + +[Color5] +Bold=false +Color=178,24,178 +Transparency=false + +[Color5Intense] +Bold=false +Color=255,84,255 +Transparency=false + +[Color6] +Bold=false +Color=24,178,178 +Transparency=false + +[Color6Intense] +Bold=false +Color=84,255,255 +Transparency=false + +[Color7] +Bold=false +Color=178,178,178 +Transparency=false + +[Color7Intense] +Bold=false +Color=255,255,255 +Transparency=false + +[Foreground] +Bold=false +Color=0,0,0 +Transparency=false + +[ForegroundIntense] +Bold=true +Color=0,0,0 +Transparency=false + +[General] +Description=Black on Random Light +Opacity=1 diff --git a/lib/color-schemes/BlackOnWhite.schema b/lib/color-schemes/BlackOnWhite.schema new file mode 100644 index 0000000..11853e6 --- /dev/null +++ b/lib/color-schemes/BlackOnWhite.schema @@ -0,0 +1,42 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Black on White + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 255 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/DarkPastels.colorscheme b/lib/color-schemes/DarkPastels.colorscheme new file mode 100644 index 0000000..fdcb02a --- /dev/null +++ b/lib/color-schemes/DarkPastels.colorscheme @@ -0,0 +1,103 @@ +[Background] +Bold=false +Color=44,44,44 +Transparency=false + +[BackgroundIntense] +Bold=true +Color=44,44,44 +Transparency=false + +[Color0] +Bold=false +Color=63,63,63 +Transparency=false + +[Color0Intense] +Bold=true +Color=112,144,128 +Transparency=false + +[Color1] +Bold=false +Color=112,80,80 +Transparency=false + +[Color1Intense] +Bold=true +Color=220,163,163 +Transparency=false + +[Color2] +Bold=false +Color=96,180,138 +Transparency=false + +[Color2Intense] +Bold=true +Color=114,213,163 +Transparency=false + +[Color3] +Bold=false +Color=223,175,143 +Transparency=false + +[Color3Intense] +Bold=true +Color=240,223,175 +Transparency=false + +[Color4] +Bold=false +Color=154,184,215 +Transparency=false + +[Color4Intense] +Bold=true +Color=148,191,243 +Transparency=false + +[Color5] +Bold=false +Color=220,140,195 +Transparency=false + +[Color5Intense] +Bold=true +Color=236,147,211 +Transparency=false + +[Color6] +Bold=false +Color=140,208,211 +Transparency=false + +[Color6Intense] +Bold=true +Color=147,224,227 +Transparency=false + +[Color7] +Bold=false +Color=220,220,204 +Transparency=false + +[Color7Intense] +Bold=true +Color=255,255,255 +Transparency=false + +[Foreground] +Bold=false +Color=220,220,204 +Transparency=false + +[ForegroundIntense] +Bold=true +Color=220,220,204 +Transparency=false + +[General] +Description=Dark Pastels +Opacity=1 diff --git a/lib/color-schemes/GreenOnBlack.colorscheme b/lib/color-schemes/GreenOnBlack.colorscheme new file mode 100644 index 0000000..4d55b3a --- /dev/null +++ b/lib/color-schemes/GreenOnBlack.colorscheme @@ -0,0 +1,104 @@ + +[Background] +Bold=false +Color=0,0,0 +Transparency=false + +[BackgroundIntense] +Bold=false +Color=0,0,0 +Transparency=false + +[Color0] +Bold=false +Color=0,0,0 +Transparency=false + +[Color0Intense] +Bold=false +Color=104,104,104 +Transparency=false + +[Color1] +Bold=false +Color=250,75,75 +Transparency=false + +[Color1Intense] +Bold=false +Color=255,84,84 +Transparency=false + +[Color2] +Bold=false +Color=24,178,24 +Transparency=false + +[Color2Intense] +Bold=false +Color=84,255,84 +Transparency=false + +[Color3] +Bold=false +Color=178,104,24 +Transparency=false + +[Color3Intense] +Bold=false +Color=255,255,84 +Transparency=false + +[Color4] +Bold=false +Color=92,167,251 +Transparency=false + +[Color4Intense] +Bold=false +Color=84,84,255 +Transparency=false + +[Color5] +Bold=false +Color=225,30,225 +Transparency=false + +[Color5Intense] +Bold=false +Color=255,84,255 +Transparency=false + +[Color6] +Bold=false +Color=24,178,178 +Transparency=false + +[Color6Intense] +Bold=false +Color=84,255,255 +Transparency=false + +[Color7] +Bold=false +Color=178,178,178 +Transparency=false + +[Color7Intense] +Bold=false +Color=255,255,255 +Transparency=false + +[Foreground] +Bold=false +Color=24,240,24 +Transparency=false + +[ForegroundIntense] +Bold=true +Color=24,240,24 +Transparency=false + +[General] +Description=Green on Black +Opacity=1 diff --git a/lib/color-schemes/Linux.colorscheme b/lib/color-schemes/Linux.colorscheme new file mode 100644 index 0000000..c9afb14 --- /dev/null +++ b/lib/color-schemes/Linux.colorscheme @@ -0,0 +1,100 @@ +[Background] +Bold=false +Color=0,0,0 + +[BackgroundIntense] +Bold=false +Color=104,104,104 + +[Color0] +Bold=false +Color=0,0,0 + + +[Color0Intense] +Bold=false +Color=104,104,104 + + +[Color1] +Bold=false +Color=178,24,24 + + +[Color1Intense] +Bold=false +Color=255,84,84 + + +[Color2] +Bold=false +Color=24,178,24 + + +[Color2Intense] +Bold=false +Color=84,255,84 + + +[Color3] +Bold=false +Color=178,104,24 + + +[Color3Intense] +Bold=false +Color=255,255,84 + + +[Color4] +Bold=false +Color=24,24,178 + + +[Color4Intense] +Bold=false +Color=84,84,255 + + +[Color5] +Bold=false +Color=178,24,178 + + +[Color5Intense] +Bold=false +Color=255,84,255 + + +[Color6] +Bold=false +Color=24,178,178 + + +[Color6Intense] +Bold=false +Color=84,255,255 + + +[Color7] +Bold=false +Color=178,178,178 + + +[Color7Intense] +Bold=false +Color=255,255,255 + + +[Foreground] +Bold=false +Color=178,178,178 + + +[ForegroundIntense] +Bold=false +Color=255,255,255 + + +[General] +Description=Linux Colors diff --git a/lib/color-schemes/WhiteOnBlack.schema b/lib/color-schemes/WhiteOnBlack.schema new file mode 100644 index 0000000..05c5bc0 --- /dev/null +++ b/lib/color-schemes/WhiteOnBlack.schema @@ -0,0 +1,42 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title White on Black + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 255 255 255 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 1 # intensive foreground color +color 11 0 0 0 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/color-schemes.qrc b/lib/color-schemes/color-schemes.qrc new file mode 100644 index 0000000..a1be0f3 --- /dev/null +++ b/lib/color-schemes/color-schemes.qrc @@ -0,0 +1,24 @@ + + + BlackOnLightYellow.schema + BlackOnRandomLight.colorscheme + Linux.colorscheme + BlackOnWhite.schema + DarkPastels.colorscheme + GreenOnBlack.colorscheme + WhiteOnBlack.schema + historic/vim.schema + historic/Transparent.schema + historic/Transparent_MC.schema + historic/Linux.schema + historic/Transparent_darkbg.schema + historic/GreenTint.schema + historic/Transparent_lightbg.schema + historic/LightPicture.schema + historic/DarkPicture.schema + historic/syscolor.schema + historic/XTerm.schema + historic/BlackOnLightColor.schema + historic/GreenTint_MC.schema + historic/GreenOnBlack.schema + diff --git a/lib/color-schemes/historic/BlackOnLightColor.schema b/lib/color-schemes/historic/BlackOnLightColor.schema new file mode 100644 index 0000000..92e598a --- /dev/null +++ b/lib/color-schemes/historic/BlackOnLightColor.schema @@ -0,0 +1,42 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Black on Light Color + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +rcolor 1 30 255 1 0 # regular background color (Light Color) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 221 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/DarkPicture.schema b/lib/color-schemes/historic/DarkPicture.schema new file mode 100644 index 0000000..78ab3df --- /dev/null +++ b/lib/color-schemes/historic/DarkPicture.schema @@ -0,0 +1,44 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Marble + +image tile Blkmarble.jpg + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 255 255 255 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 1 # intensive foreground color +color 11 0 0 0 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Example.Schema b/lib/color-schemes/historic/Example.Schema new file mode 100644 index 0000000..8611d44 --- /dev/null +++ b/lib/color-schemes/historic/Example.Schema @@ -0,0 +1,47 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Ugly 1 + +# add a wallpaper, if you like. Second word one of { tile,center,full } + +image tile /opt/kde/share/wallpapers/dancy_pants.jpg + + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 255 0 0 0 0 # regular color 1 Red +color 4 0 255 0 0 0 # regular color 2 Green +color 5 255 255 0 0 0 # regular color 3 Yellow +color 6 0 0 255 0 0 # regular color 4 Blue +color 7 255 0 255 0 0 # regular color 5 Magenta +color 8 0 255 255 0 0 # regular color 6 Cyan +color 9 255 255 255 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 255 1 1 # intensive background color + +color 12 0 0 0 0 1 # intensive color 0 +color 13 255 0 0 0 1 # intensive color 1 +color 14 0 255 0 0 1 # intensive color 2 +color 15 255 255 0 0 1 # intensive color 3 +color 16 0 0 255 0 1 # intensive color 4 +color 17 255 0 255 0 1 # intensive color 5 +color 18 0 255 255 0 1 # intensive color 6 +color 19 255 255 255 0 1 # intensive color 7 diff --git a/lib/color-schemes/historic/GreenOnBlack.schema b/lib/color-schemes/historic/GreenOnBlack.schema new file mode 100644 index 0000000..8f19c5b --- /dev/null +++ b/lib/color-schemes/historic/GreenOnBlack.schema @@ -0,0 +1,42 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Green on Black + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 24 240 24 0 0 # regular foreground color (Green) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 24 240 24 0 1 # intensive foreground color +color 11 0 0 0 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/GreenTint.schema b/lib/color-schemes/historic/GreenTint.schema new file mode 100644 index 0000000..2786678 --- /dev/null +++ b/lib/color-schemes/historic/GreenTint.schema @@ -0,0 +1,49 @@ +# linux color schema for konsole + +title Green Tint + +transparency 0.3 0 150 0 + +# FIXME +# +# The flaw in this schema is that "blick" comes out on the +# Linux console as intensive background, really. +# Since this is not used in clients you'll hardly notice +# it in practice. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 178 178 178 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/GreenTint_MC.schema b/lib/color-schemes/historic/GreenTint_MC.schema new file mode 100644 index 0000000..954755e --- /dev/null +++ b/lib/color-schemes/historic/GreenTint_MC.schema @@ -0,0 +1,49 @@ +# linux color schema for konsole + +title Green Tint with Transparent MC + +transparency 0.3 0 150 0 + +# FIXME +# +# The flaw in this schema is that "blick" comes out on the +# Linux console as intensive background, really. +# Since this is not used in clients you'll hardly notice +# it in practice. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 178 178 178 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 0 0 0 1 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/LightPicture.schema b/lib/color-schemes/historic/LightPicture.schema new file mode 100644 index 0000000..6acd7a8 --- /dev/null +++ b/lib/color-schemes/historic/LightPicture.schema @@ -0,0 +1,44 @@ +# example scheme for konsole + +# the title is to appear in the menu. + +title Paper + +image tile Paper01.jpg + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 255 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Linux.schema b/lib/color-schemes/historic/Linux.schema new file mode 100644 index 0000000..a2515d9 --- /dev/null +++ b/lib/color-schemes/historic/Linux.schema @@ -0,0 +1,47 @@ +# linux color schema for konsole + +title Linux Colors + +# FIXME +# +# The flaw in this schema is that "blick" comes out on the +# Linux console as intensive background, really. +# Since this is not used in clients you'll hardly notice +# it in practice. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 178 178 178 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/README.Schema b/lib/color-schemes/historic/README.Schema new file mode 100644 index 0000000..f737c14 --- /dev/null +++ b/lib/color-schemes/historic/README.Schema @@ -0,0 +1,132 @@ +[README.Schema] + +The schemata offered in the Options/Schema menu are +taken from from configurations files with a *.schema +pattern either located in $KDEDIR/share/apps/konsole +or ~/.kde/share/apps/konsole. + +Schemata allow to configure the color set that konsole +uses, together with some more information on rendition +processing. + +Syntax + + File + :: { [Line] ['#' Comment] '\n' } + + Line + :: "title" Title + :: "image" Usage PathToPictureFile + :: "transparency" Fade Red Green Blue + :: "color" Slot Red Green Blue Transparent Bold + :: "rcolor" Slot Saturation Value Transparent Bold + :: "sysfg" Slot Transparent Bold + :: "sysbg" Slot Transparent Bold + +Meaning + + - Title is the text to appear in the Option/Schema menu. + It should be unique among all other schemata therefore. + + - The "image" clause allows to place an image on the + konsole's background. + + - Usage can be either + - "tile" - the image is tilewise replicated. + - "center" - the image is centered. + - "full" - the image is stretched to fit the window size. + + - The Path of the picture can both be relative + (to kde wallpapers) or absolute. + + When a schema uses a background image (or transparency) + one has to make at least one color slot transparent to + achive any visible effect. Please read below about the + "Transparent" field in color,sysbg,sysfg. + + - The "transparency" clause picks and uses the background + of the desktop as if it where an image together with + a fade effect. This effect will fade the background + to the specified color. + + The "Fade" is a real value between 0 and 1, indicating + the strength of the fade. A value of 0 will not change + the image, a value of 1 will make it the fade color + everywhere, and in between. This will make the "glas" + of the window be of the color given in the clause and + being more(1) or less(0) intransparent. + + - The remaining clauses (color,sysbg,sysfg) are used + to setup konsoles rendition system. + + To this end, konsole offers 20 color slots. + + Slot Meaning + ----- -------------------------- + 0 regular foreground color + 1 regular background color + 2-9 regular bgr color 0-7 + 10 intensive foreground color + 11 intensive background color + 12-19 intensive bgr color 0-7 + + The traditional meaning of the "bgr" color codes + has a bitwise interpretation of an additive three + primary color scheme inherited from early EGA + color terminals. + + Color Bits Colors + ----- ---- ------- + 0 000 Black + 1 001 Red + 2 010 Green + 3 011 Yellow + 4 100 Blue + 5 101 Magenta + 6 110 Cyan + 7 111 White + + One may or may not stick to this tradition. + Konsole allows to assign colors freely to slots. + + The slots fall apart into two groups, regular + and intensive colors. The later are used when + BOLD rendition is used by the client. + + Each of the groups have a default fore- and + background color and the said bgr colors. + Normal terminal processing will simply use + the default colors. + + The color desired for a slot is indicated + in the Red Green Blue fields of the color + clause. Use the sysfg or the sysbg clause + to indicate the default fore and background + colors of the desktop. + + To specify randomized color for a slot use + the clause rcolor. The two parameters to it + being Saturation - the amount of colour, + and Value, the darkness of the colour. + + To use transparency/images and to simulate + the behavior of the xterm, one can supply + two additional tags to each slot: + - Transparent (0/1) meaning to show the + background picture, if any. + - Bold (0/1) to render characters bold. + + +If you know about the escape codes, you might have +noticed that intensive and bold rendition are sort +of confused. This is inherited by the xterm which +konsole is simulating. + +One can use the colortest.sh script supplied +with the konsole source distribution to test +a schema. + +The schema installed with konsole are more or +less demonstrations and not really beauty, +beside the Linux.schema, perhaps, which is +made after the VGA colors. diff --git a/lib/color-schemes/historic/README.default.Schema b/lib/color-schemes/historic/README.default.Schema new file mode 100644 index 0000000..e024e5a --- /dev/null +++ b/lib/color-schemes/historic/README.default.Schema @@ -0,0 +1,44 @@ +# default scheme for konsole (only here for documentation purposes) + +# the title is to appear in the menu. + +title Konsole Defaults + +# image tile /opt/kde/share/wallpapers/gray2.jpg + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 255 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Transparent.schema b/lib/color-schemes/historic/Transparent.schema new file mode 100644 index 0000000..41f8dc0 --- /dev/null +++ b/lib/color-schemes/historic/Transparent.schema @@ -0,0 +1,49 @@ +# linux color schema for konsole + +title Transparent Konsole + +transparency 0.35 0 0 0 + +# FIXME +# +# The flaw in this schema is that "blick" comes out on the +# Linux console as intensive background, really. +# Since this is not used in clients you'll hardly notice +# it in practice. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 178 178 178 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Transparent_MC.schema b/lib/color-schemes/historic/Transparent_MC.schema new file mode 100644 index 0000000..8991b9a --- /dev/null +++ b/lib/color-schemes/historic/Transparent_MC.schema @@ -0,0 +1,51 @@ +# linux color schema for konsole + +title Transparent for MC + +transparency 0.35 0 0 0 + +# FIXME +# +# The flaw in this schema is that "blick" comes out on the +# Linux console as intensive background, really. +# Since this is not used in clients you'll hardly notice +# it in practice. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 178 178 178 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +#color 6 24 24 178 0 0 # regular color 4 Blue +color 6 0 0 0 1 0 # regular color 4 Blue + +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Transparent_darkbg.schema b/lib/color-schemes/historic/Transparent_darkbg.schema new file mode 100644 index 0000000..61792fa --- /dev/null +++ b/lib/color-schemes/historic/Transparent_darkbg.schema @@ -0,0 +1,42 @@ +# linux color schema for konsole + +title Transparent, Dark Background + +transparency 0.75 0 0 0 + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 255 255 255 0 0 # regular foreground color (White) +color 1 0 0 0 1 0 # regular background color (Black) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 255 255 255 0 0 # intensive foreground color +color 11 104 104 104 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/Transparent_lightbg.schema b/lib/color-schemes/historic/Transparent_lightbg.schema new file mode 100644 index 0000000..ce201f2 --- /dev/null +++ b/lib/color-schemes/historic/Transparent_lightbg.schema @@ -0,0 +1,51 @@ +# linux color schema for konsole + +title Transparent, Light Background + +transparency 0.1 0 0 0 + +# This is a schema for very light backgrounds. It makes some +# hacks about the colors to make Midnight Commander transparent +# and with suitable colors. + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 50 50 50 0 0 # regular foreground color (DarkGray) +color 1 200 200 200 1 0 # regular background color (White) + +# color 2 0 0 0 0 0 # regular color 0 Black +color 2 200 200 200 1 0 # regular background color (White) +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +#color 6 24 24 178 0 0 # regular color 4 Blue +color 6 0 0 0 1 0 # regular color 4 Blue +# Blue is transparent, to make MC transparent + +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +# color 9 178 178 178 0 0 # regular color 7 White +color 9 50 50 50 0 0 # regular foreground color (DarkGray) + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 0 # intensive foreground color +color 11 255 255 255 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/XTerm.schema b/lib/color-schemes/historic/XTerm.schema new file mode 100644 index 0000000..3ff787d --- /dev/null +++ b/lib/color-schemes/historic/XTerm.schema @@ -0,0 +1,46 @@ +# xterm color schema for konsole + +# xterm colors can be configured (almost) like +# konsole colors can. This is the uncustomized +# xterm schema. +# Please refere to your local xterm setup files +# if this schema differs. + +title XTerm Colors + +# foreground colors ------------------------------- + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 205 0 0 0 0 # regular color 1 Red +color 4 0 205 0 0 0 # regular color 2 Green +color 5 205 205 0 0 0 # regular color 3 Yellow +color 6 0 0 205 0 0 # regular color 4 Blue +color 7 205 0 205 0 0 # regular color 5 Magenta +color 8 0 205 205 0 0 # regular color 6 Cyan +color 9 229 229 229 0 0 # regular color 7 White + +# intensive colors ------------------------------------------- + +# for some strange reason, intensive colors are bold, also. + +color 10 77 77 77 0 1 # intensive foreground color +color 11 255 255 255 1 1 # intensive background color + +color 12 77 77 77 0 1 # intensive color 0 +color 13 255 0 0 0 1 # intensive color 1 +color 14 0 255 0 0 1 # intensive color 2 +color 15 255 255 0 0 1 # intensive color 3 +color 16 0 0 255 0 1 # intensive color 4 +color 17 255 0 255 0 1 # intensive color 5 +color 18 0 255 255 0 1 # intensive color 6 +color 19 255 255 255 0 1 # intensive color 7 diff --git a/lib/color-schemes/historic/syscolor.schema b/lib/color-schemes/historic/syscolor.schema new file mode 100644 index 0000000..a9a65ea --- /dev/null +++ b/lib/color-schemes/historic/syscolor.schema @@ -0,0 +1,44 @@ +# schema that uses system colors + +# the title is to appear in the menu. + +title System Colors + +# image none + +# foreground colors + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | | | +# V V--color--V V V + +sysfg 0 0 0 # regular foreground color (system) +sysbg 1 1 0 # regular background color (system) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 178 24 24 0 0 # regular color 1 Red +color 4 24 178 24 0 0 # regular color 2 Green +color 5 178 104 24 0 0 # regular color 3 Yellow +color 6 24 24 178 0 0 # regular color 4 Blue +color 7 178 24 178 0 0 # regular color 5 Magenta +color 8 24 178 178 0 0 # regular color 6 Cyan +color 9 178 178 178 0 0 # regular color 7 White + +# intensive colors + +# instead of changing the colors, we've flaged the text to become bold + +color 10 0 0 0 0 1 # intensive foreground color +color 11 255 255 255 1 0 # intensive background color + +color 12 104 104 104 0 0 # intensive color 0 +color 13 255 84 84 0 0 # intensive color 1 +color 14 84 255 84 0 0 # intensive color 2 +color 15 255 255 84 0 0 # intensive color 3 +color 16 84 84 255 0 0 # intensive color 4 +color 17 255 84 255 0 0 # intensive color 5 +color 18 84 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/color-schemes/historic/vim.schema b/lib/color-schemes/historic/vim.schema new file mode 100644 index 0000000..f29e3f7 --- /dev/null +++ b/lib/color-schemes/historic/vim.schema @@ -0,0 +1,40 @@ +# VIM-recommended color schema for konsole + +# VIM (VI improved) in "help xiterm" recommends these colors for xterm. + +title VIM Colors + +# foreground colors ------------------------------- + +# note that the default background color is flagged +# to become transparent when an image is present. + +# slot transparent bold +# | red grn blu | | +# V V--color--V V V + +color 0 0 0 0 0 0 # regular foreground color (Black) +color 1 255 255 255 1 0 # regular background color (White) + +color 2 0 0 0 0 0 # regular color 0 Black +color 3 192 0 0 0 0 # regular color 1 Red +color 4 0 128 0 0 0 # regular color 2 Green +color 5 128 128 0 0 0 # regular color 3 Yellow +color 6 0 0 192 0 0 # regular color 4 Blue +color 7 192 0 192 0 0 # regular color 5 Magenta +color 8 0 128 128 0 0 # regular color 6 Cyan +color 9 192 192 192 0 0 # regular color 7 White + +# intensive colors ------------------------------------------- + +color 10 77 77 77 0 1 # intensive foreground color +color 11 255 255 255 1 1 # intensive background color + +color 12 128 128 128 0 0 # intensive color 0 +color 13 255 96 96 0 0 # intensive color 1 +color 14 0 255 0 0 0 # intensive color 2 +color 15 255 255 0 0 0 # intensive color 3 +color 16 128 128 255 0 0 # intensive color 4 +color 17 255 64 255 0 0 # intensive color 5 +color 18 0 255 255 0 0 # intensive color 6 +color 19 255 255 255 0 0 # intensive color 7 diff --git a/lib/default.keytab b/lib/default.keytab new file mode 100644 index 0000000..f84293d --- /dev/null +++ b/lib/default.keytab @@ -0,0 +1,128 @@ +# [README.default.Keytab] Buildin Keyboard Table +# +# To customize your keyboard, copy this file to something +# ending with .keytab and change it to meet you needs. +# Please read the README.KeyTab and the README.keyboard +# in this case. +# +# -------------------------------------------------------------- + +keyboard "Default (XFree 4)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\E" + +key Tab -Shift : "\t" +key Tab +Shift+Ansi : "\E[Z" +key Tab +Shift-Ansi : "\t" +key Backtab +Ansi : "\E[Z" +key Backtab -Ansi : "\t" + +key Return-Shift-NewLine : "\r" +key Return-Shift+NewLine : "\r\n" + +key Return+Shift : "\EOM" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# Arrow keys in VT52 mode +# shift up/down are reserved for scrolling. +# shift left/right are reserved for switching between tabs (this is hardcoded). + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# Arrow keys in ANSI mode with Application - and Normal Cursor Mode) + +key Up -Shift-AnyMod+Ansi+AppCuKeys : "\EOA" +key Down -Shift-AnyMod+Ansi+AppCuKeys : "\EOB" +key Right -Shift-AnyMod+Ansi+AppCuKeys : "\EOC" +key Left -Shift-AnyMod+Ansi+AppCuKeys : "\EOD" + +key Up -Shift-AnyMod+Ansi-AppCuKeys : "\E[A" +key Down -Shift-AnyMod+Ansi-AppCuKeys : "\E[B" +key Right -Shift-AnyMod+Ansi-AppCuKeys : "\E[C" +key Left -Shift-AnyMod+Ansi-AppCuKeys : "\E[D" + +key Up -Shift+AnyMod+Ansi : "\E[1;*A" +key Down -Shift+AnyMod+Ansi : "\E[1;*B" +key Right -Shift+AnyMod+Ansi : "\E[1;*C" +key Left -Shift+AnyMod+Ansi : "\E[1;*D" + +# other grey PC keys + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Home -AnyMod -AppCuKeys : "\E[H" +key End -AnyMod -AppCuKeys : "\E[F" +key Home -AnyMod +AppCuKeys : "\EOH" +key End -AnyMod +AppCuKeys : "\EOF" +key Home +AnyMod : "\E[1;*H" +key End +AnyMod : "\E[1;*F" + +key Insert -AnyMod : "\E[2~" +key Delete -AnyMod : "\E[3~" +key Insert +AnyMod : "\E[2;*~" +key Delete +AnyMod : "\E[3;*~" + +key Prior -Shift-AnyMod : "\E[5~" +key Next -Shift-AnyMod : "\E[6~" +key Prior -Shift+AnyMod : "\E[5;*~" +key Next -Shift+AnyMod : "\E[6;*~" + +# Function keys +key F1 -AnyMod : "\EOP" +key F2 -AnyMod : "\EOQ" +key F3 -AnyMod : "\EOR" +key F4 -AnyMod : "\EOS" +key F5 -AnyMod : "\E[15~" +key F6 -AnyMod : "\E[17~" +key F7 -AnyMod : "\E[18~" +key F8 -AnyMod : "\E[19~" +key F9 -AnyMod : "\E[20~" +key F10 -AnyMod : "\E[21~" +key F11 -AnyMod : "\E[23~" +key F12 -AnyMod : "\E[24~" + +key F1 +AnyMod : "\EO*P" +key F2 +AnyMod : "\EO*Q" +key F3 +AnyMod : "\EO*R" +key F4 +AnyMod : "\EO*S" +key F5 +AnyMod : "\E[15;*~" +key F6 +AnyMod : "\E[17;*~" +key F7 +AnyMod : "\E[18;*~" +key F8 +AnyMod : "\E[19;*~" +key F9 +AnyMod : "\E[20;*~" +key F10 +AnyMod : "\E[21;*~" +key F11 +AnyMod : "\E[23;*~" +key F12 +AnyMod : "\E[24;*~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift-AppScreen : scrollLineUp +key Prior +Shift-AppScreen : scrollPageUp +key Down +Shift-AppScreen : scrollLineDown +key Next +Shift-AppScreen : scrollPageDown + +key ScrollLock : scrollLock + +# keypad characters are not offered differently by Qt. diff --git a/lib/designer/qtermwidget.png b/lib/designer/qtermwidget.png new file mode 100644 index 0000000..7b9abcc Binary files /dev/null and b/lib/designer/qtermwidget.png differ diff --git a/lib/designer/qtermwidgetplugin.cpp b/lib/designer/qtermwidgetplugin.cpp new file mode 100644 index 0000000..bb9ad91 --- /dev/null +++ b/lib/designer/qtermwidgetplugin.cpp @@ -0,0 +1,102 @@ + +#include "qtermwidgetplugin.h" + +#include + +#include "qtermwidget.h" + + +QTermWidgetPlugin::QTermWidgetPlugin(QObject *parent) + : QObject(parent), initialized(false) +{ + Q_INIT_RESOURCE(qtermwidgetplugin); +} + + +QTermWidgetPlugin::~QTermWidgetPlugin() +{ +} + + +void QTermWidgetPlugin::initialize(QDesignerFormEditorInterface * /* core */) +{ + initialized = true; +} + + +bool QTermWidgetPlugin::isInitialized() const +{ + return initialized; +} + + +QWidget *QTermWidgetPlugin::createWidget(QWidget *parent) +{ + return new QTermWidget(0, parent); +} + + +QString QTermWidgetPlugin::name() const +{ + return "QTermWidget"; +} + + +QString QTermWidgetPlugin::group() const +{ + return "Input Widgets"; +} + + +QIcon QTermWidgetPlugin::icon() const +{ + return QIcon(":qtermwidget.png"); +} + + +QString QTermWidgetPlugin::toolTip() const +{ + return "QTermWidget component/widget"; +} + + +QString QTermWidgetPlugin::whatsThis() const +{ + return "Qt based terminal emulator"; +} + + +bool QTermWidgetPlugin::isContainer() const +{ + return false; +} + + +QString QTermWidgetPlugin::domXml() const +{ + return "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 400\n" + " 200\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; +} + + +QString QTermWidgetPlugin::includeFile() const +{ + return "qtermwidget.h"; +} + + +Q_EXPORT_PLUGIN2(QTermWidgetPlugin, QTermWidgetPlugin) diff --git a/lib/designer/qtermwidgetplugin.h b/lib/designer/qtermwidgetplugin.h new file mode 100644 index 0000000..8a216de --- /dev/null +++ b/lib/designer/qtermwidgetplugin.h @@ -0,0 +1,33 @@ + +#ifndef QTERMWIDGETPLUGIN_H +#define QTERMWIDGETPLUGIN_H + +#include + + +class QTermWidgetPlugin : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) + +public: + QTermWidgetPlugin(QObject *parent = 0); + virtual ~QTermWidgetPlugin(); + + bool isContainer() const; + bool isInitialized() const; + QIcon icon() const; + QString domXml() const; + QString group() const; + QString includeFile() const; + QString name() const; + QString toolTip() const; + QString whatsThis() const; + QWidget *createWidget(QWidget *parent); + void initialize(QDesignerFormEditorInterface *core); + +private: + bool initialized; +}; + +#endif diff --git a/lib/designer/qtermwidgetplugin.qrc b/lib/designer/qtermwidgetplugin.qrc new file mode 100644 index 0000000..fbf222c --- /dev/null +++ b/lib/designer/qtermwidgetplugin.qrc @@ -0,0 +1,6 @@ + + + + qtermwidget.png + + diff --git a/lib/kb-layouts/README b/lib/kb-layouts/README new file mode 100644 index 0000000..4e94e5e --- /dev/null +++ b/lib/kb-layouts/README @@ -0,0 +1,72 @@ +[README.KeyTab] + +The keytabs offered in the Options/Keyboard menu are +taken from from configurations files with a *.keytab +pattern either located in $KDEDIR/share/apps/konsole +or ~/.kde/share/apps/konsole. + +Keytabs allow to configure the behavior of konsole +on keyboard events, especially for functions keys. +Please have a look into the README.keyboard file, too. + +The syntax is that each entry has the form : + + "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) + +Keynames are those defined in with the +"Qt::Key_" prefix removed. + +Mode names are: + + - Shift : Shift Key pressed + - Alt : Alt Key pressed + - Control : Control Key pressed + + ( The VT100 emulation has modes that can affect the + sequences emitted by certain keys. These modes are + under control of the client program. + + - Newline : effects Return and Enter key. + - Application : effects Up and Down key. + - Ansi : effects Up and Down key (This is for VT52, really). + + Since sending a state to a program that has set the state + itself is positivly wrong and obsolete design, better forget + about this nasty detail. I may well remove this "feature" + in a future clean up round. ) + + A "+" preceeding a Modename means the Key is pressed. + A "-" preceeding a Modename means the Key is not pressed. + If no mode is given it means don't care. + + Note that the combination of Key and Modes (set/reset) + has to be unique. This means, that + + key A + Shift : "A" + key A : "a" + + will not accept the small letter "a" rule as expected, + one has to add a "- Shift" to the last clause. Use + the stdout/stderr dianostics of konsole when modifying + keytabs to find problems like this. + +Operations are + + - scrollUpLine : scroll up one line in the history log + - scrollUpPage : scroll up one page in the history log + - scrollDownLine : scroll down one line in the history log + - scrollDownPage : scroll down one page in the history log + - emitClipboard : "paste" the current clipboard + - emitSelection : "paste" the current selection + +Strings have the syntax of C strings, +one may use the following escapes: + + - \E - escape + - \\ - backslash + - \" - double quote + - \t - tab + - \r - return + - \n - newline + - \b - backspace + - \xHH - where HH are two hex digits diff --git a/lib/kb-layouts/default.keytab b/lib/kb-layouts/default.keytab new file mode 100644 index 0000000..aebd8cf --- /dev/null +++ b/lib/kb-layouts/default.keytab @@ -0,0 +1,169 @@ +# [README.default.Keytab] Default Keyboard Table +# +# To customize your keyboard, copy this file to something +# ending with .keytab and change it to meet you needs. +# Please read the README.KeyTab and the README.keyboard +# in this case. +# +# -------------------------------------------------------------- + +keyboard "Default (XFree 4)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\E" + +key Tab -Shift : "\t" +key Tab +Shift+Ansi : "\E[Z" +key Tab +Shift-Ansi : "\t" +key Backtab +Ansi : "\E[Z" +key Backtab -Ansi : "\t" + +key Return-Shift-NewLine : "\r" +key Return-Shift+NewLine : "\r\n" + +key Return+Shift : "\EOM" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# Arrow keys in VT52 mode +# shift up/down are reserved for scrolling. +# shift left/right are reserved for switching between tabs (this is hardcoded). + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# Arrow keys in ANSI mode with Application - and Normal Cursor Mode) + +key Up -Shift-AnyMod+Ansi+AppCuKeys : "\EOA" +key Down -Shift-AnyMod+Ansi+AppCuKeys : "\EOB" +key Right -Shift-AnyMod+Ansi+AppCuKeys : "\EOC" +key Left -Shift-AnyMod+Ansi+AppCuKeys : "\EOD" + +key Up -Shift-AnyMod+Ansi-AppCuKeys : "\E[A" +key Down -Shift-AnyMod+Ansi-AppCuKeys : "\E[B" +key Right -Shift-AnyMod+Ansi-AppCuKeys : "\E[C" +key Left -Shift-AnyMod+Ansi-AppCuKeys : "\E[D" + +key Up -Shift+AnyMod+Ansi : "\E[1;*A" +key Down -Shift+AnyMod+Ansi : "\E[1;*B" +key Right -Shift+AnyMod+Ansi : "\E[1;*C" +key Left -Shift+AnyMod+Ansi : "\E[1;*D" + +# Keypad keys with NumLock ON +# (see "Numeric Keypad" section at http://www.nw.com/nw/WWW/products/wizcon/vt100.html ) +# +# Not enabled for now because it breaks the keypad in Vim. +# +#key 0 +KeyPad+AppKeyPad : "\EOp" +#key 1 +KeyPad+AppKeyPad : "\EOq" +#key 2 +KeyPad+AppKeyPad : "\EOr" +#key 3 +KeyPad+AppKeyPad : "\EOs" +#key 4 +KeyPad+AppKeyPad : "\EOt" +#key 5 +KeyPad+AppKeyPad : "\EOu" +#key 6 +KeyPad+AppKeyPad : "\EOv" +#key 7 +KeyPad+AppKeyPad : "\EOw" +#key 8 +KeyPad+AppKeyPad : "\EOx" +#key 9 +KeyPad+AppKeyPad : "\EOy" +#key + +KeyPad+AppKeyPad : "\EOl" +#key - +KeyPad+AppKeyPad : "\EOm" +#key . +KeyPad+AppKeyPad : "\EOn" +#key * +KeyPad+AppKeyPad : "\EOM" +#key Enter +KeyPad+AppKeyPad : "\r" + +# Keypad keys with NumLock Off +key Up -Shift+Ansi+AppCuKeys+KeyPad : "\EOA" +key Down -Shift+Ansi+AppCuKeys+KeyPad : "\EOB" +key Right -Shift+Ansi+AppCuKeys+KeyPad : "\EOC" +key Left -Shift+Ansi+AppCuKeys+KeyPad : "\EOD" + +key Up -Shift+Ansi-AppCuKeys+KeyPad : "\E[A" +key Down -Shift+Ansi-AppCuKeys+KeyPad : "\E[B" +key Right -Shift+Ansi-AppCuKeys+KeyPad : "\E[C" +key Left -Shift+Ansi-AppCuKeys+KeyPad : "\E[D" + +key Home +AppCuKeys+KeyPad : "\EOH" +key End +AppCuKeys+KeyPad : "\EOF" +key Home -AppCuKeys+KeyPad : "\E[H" +key End -AppCuKeys+KeyPad : "\E[F" + +key Insert +KeyPad : "\E[2~" +key Delete +KeyPad : "\E[3~" +key Prior -Shift+KeyPad : "\E[5~" +key Next -Shift+KeyPad : "\E[6~" + +# other grey PC keys + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Home -AnyMod-AppCuKeys : "\E[H" +key End -AnyMod-AppCuKeys : "\E[F" +key Home -AnyMod+AppCuKeys : "\EOH" +key End -AnyMod+AppCuKeys : "\EOF" +key Home +AnyMod : "\E[1;*H" +key End +AnyMod : "\E[1;*F" + +key Insert -AnyMod : "\E[2~" +key Delete -AnyMod : "\E[3~" +key Insert +AnyMod : "\E[2;*~" +key Delete +AnyMod : "\E[3;*~" + +key Prior -Shift-AnyMod : "\E[5~" +key Next -Shift-AnyMod : "\E[6~" +key Prior -Shift+AnyMod : "\E[5;*~" +key Next -Shift+AnyMod : "\E[6;*~" + +# Function keys +key F1 -AnyMod : "\EOP" +key F2 -AnyMod : "\EOQ" +key F3 -AnyMod : "\EOR" +key F4 -AnyMod : "\EOS" +key F5 -AnyMod : "\E[15~" +key F6 -AnyMod : "\E[17~" +key F7 -AnyMod : "\E[18~" +key F8 -AnyMod : "\E[19~" +key F9 -AnyMod : "\E[20~" +key F10 -AnyMod : "\E[21~" +key F11 -AnyMod : "\E[23~" +key F12 -AnyMod : "\E[24~" + +key F1 +AnyMod : "\EO*P" +key F2 +AnyMod : "\EO*Q" +key F3 +AnyMod : "\EO*R" +key F4 +AnyMod : "\EO*S" +key F5 +AnyMod : "\E[15;*~" +key F6 +AnyMod : "\E[17;*~" +key F7 +AnyMod : "\E[18;*~" +key F8 +AnyMod : "\E[19;*~" +key F9 +AnyMod : "\E[20;*~" +key F10 +AnyMod : "\E[21;*~" +key F11 +AnyMod : "\E[23;*~" +key F12 +AnyMod : "\E[24;*~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift-AppScreen : scrollLineUp +key Prior +Shift-AppScreen : scrollPageUp +key Down +Shift-AppScreen : scrollLineDown +key Next +Shift-AppScreen : scrollPageDown + +key ScrollLock : scrollLock + diff --git a/lib/kb-layouts/historic/vt100.keytab b/lib/kb-layouts/historic/vt100.keytab new file mode 100644 index 0000000..dec49ba --- /dev/null +++ b/lib/kb-layouts/historic/vt100.keytab @@ -0,0 +1,133 @@ +# [vt100.keytab] Konsole Keyboard Table (VT100 keys) +# +# -------------------------------------------------------------- + +keyboard "vt100 (historical)" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" + +# VT100 can add an extra \n after return. +# The NewLine mode is set by an escape sequence. + +key Return-NewLine : "\r" +key Return+NewLine : "\r\n" + +# Some desperately try to save the ^H. + +key Backspace : "\x7f" +key Delete : "\E[3~" + +# These codes are for the VT52 mode of VT100 +# The Ansi mode (i.e. VT100 mode) is set by +# an escape sequence + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# VT100 emits a mode bit together +# with the arrow keys.The AppCuKeys +# mode is set by an escape sequence. + +key Up -Shift+Ansi+AppCuKeys : "\EOA" +key Down -Shift+Ansi+AppCuKeys : "\EOB" +key Right-Shift+Ansi+AppCuKeys : "\EOC" +key Left -Shift+Ansi+AppCuKeys : "\EOD" + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +# function keys (FIXME: make pf1-pf4) + +key F1 : "\E[11~" +key F2 : "\E[12~" +key F3 : "\E[13~" +key F4 : "\E[14~" +key F5 : "\E[15~" + +key F6 : "\E[17~" +key F7 : "\E[18~" +key F8 : "\E[19~" +key F9 : "\E[20~" +key F10 : "\E[21~" +key F11 : "\E[23~" +key F12 : "\E[24~" + +key Home : "\E[H" +key End : "\E[F" + +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" +key Insert-Shift : "\E[2~" + +# Keypad-Enter. See comment on Return above. + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Space +Control : "\x00" + +# some of keys are used by konsole. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + + +#---------------------------------------------------------- + +# keypad characters as offered by Qt +# cannot be recognized as such. + +#---------------------------------------------------------- + +# Following other strings as emitted by konsole. diff --git a/lib/kb-layouts/historic/x11r5.keytab b/lib/kb-layouts/historic/x11r5.keytab new file mode 100644 index 0000000..75ba06e --- /dev/null +++ b/lib/kb-layouts/historic/x11r5.keytab @@ -0,0 +1,71 @@ +# [x11r5.Keytab] Keyboard Table for X11 R5 + +keyboard "XTerm (XFree 3.x.x)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\E" +key Tab : "\t" + +key Return : "\r" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# cursor keys + +key Up -Shift : "\EOA" +key Down -Shift : "\EOB" +key Right -Shift : "\EOC" +key Left -Shift : "\EOD" + +# other grey PC keys + +key Enter : "\r" + +key Home : "\E[1~" +key Insert-Shift : "\E[2~" +key Delete : "\E[3~" +key End : "\E[4~" +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" + +# function keys + +key F1 : "\E[11~" +key F2 : "\E[12~" +key F3 : "\E[13~" +key F4 : "\E[14~" +key F5 : "\E[15~" +key F6 : "\E[17~" +key F7 : "\E[18~" +key F8 : "\E[19~" +key F9 : "\E[20~" +key F10 : "\E[21~" +key F11 : "\E[23~" +key F12 : "\E[24~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +# keypad characters are not offered differently by Qt. diff --git a/lib/kb-layouts/kb-layouts.qrc b/lib/kb-layouts/kb-layouts.qrc new file mode 100644 index 0000000..ce85691 --- /dev/null +++ b/lib/kb-layouts/kb-layouts.qrc @@ -0,0 +1,10 @@ + + + linux.keytab + solaris.keytab + macbook.keytab + default.keytab + vt420pc.keytab + historic/x11r5.keytab + historic/vt100.keytab + diff --git a/lib/kb-layouts/linux.keytab b/lib/kb-layouts/linux.keytab new file mode 100644 index 0000000..94a39fb --- /dev/null +++ b/lib/kb-layouts/linux.keytab @@ -0,0 +1,138 @@ +# [linux.keytab] Konsole Keyboard Table (Linux console keys) +# +# -------------------------------------------------------------- + +# NOT TESTED, MAY NEED SOME CLEANUPS +keyboard "Linux console" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" + +# VT100 can add an extra \n after return. +# The NewLine mode is set by an escape sequence. + +key Return-NewLine : "\r" +key Return+NewLine : "\r\n" + +# Some desperately try to save the ^H. + +key Backspace : "\x7f" +key Delete : "\E[3~" + +# These codes are for the VT52 mode of VT100 +# The Ansi mode (i.e. VT100 mode) is set by +# an escape sequence + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# VT100 emits a mode bit together +# with the arrow keys.The AppCuKeys +# mode is set by an escape sequence. + +key Up -Shift+Ansi+AppCuKeys : "\EOA" +key Down -Shift+Ansi+AppCuKeys : "\EOB" +key Right-Shift+Ansi+AppCuKeys : "\EOC" +key Left -Shift+Ansi+AppCuKeys : "\EOD" + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +key Up -Shift+AnyMod+Ansi : "\E[1;*A" +key Down -Shift+AnyMod+Ansi : "\E[1;*B" +key Right -Shift+AnyMod+Ansi : "\E[1;*C" +key Left -Shift+AnyMod+Ansi : "\E[1;*D" + +# linux functions keys F1-F5 differ from xterm + +key F1 : "\E[[A" +key F2 : "\E[[B" +key F3 : "\E[[C" +key F4 : "\E[[D" +key F5 : "\E[[E" + +key F6 : "\E[17~" +key F7 : "\E[18~" +key F8 : "\E[19~" +key F9 : "\E[20~" +key F10 : "\E[21~" +key F11 : "\E[23~" +key F12 : "\E[24~" + +key Home : "\E[1~" +key End : "\E[4~" + +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" +key Insert-Shift : "\E[2~" + +# Keypad-Enter. See comment on Return above. + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Space +Control : "\x00" + +# some of keys are used by konsole. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +#---------------------------------------------------------- + +# keypad characters as offered by Qt +# cannot be recognized as such. + +#---------------------------------------------------------- + +# Following other strings as emitted by konsole. diff --git a/lib/kb-layouts/macbook.keytab b/lib/kb-layouts/macbook.keytab new file mode 100644 index 0000000..adbc784 --- /dev/null +++ b/lib/kb-layouts/macbook.keytab @@ -0,0 +1,175 @@ +# [README.default.Keytab] Buildin Keyboard Table +# +# To customize your keyboard, copy this file to something +# ending with .keytab and change it to meet you needs. +# Please read the README.KeyTab and the README.keyboard +# in this case. +# +# -------------------------------------------------------------- + +keyboard "Default (XFree 4)" + +# -------------------------------------------------------------- +# +# Note that this particular table is a "risc" version made to +# ease customization without bothering with obsolete details. +# See VT100.keytab for the more hairy stuff. +# +# -------------------------------------------------------------- + +# common keys + +key Escape : "\x1b" + +#key Control : "^" + +key Tab -Shift : "\t" +key Tab +Shift+Ansi : "\E[Z" +key Tab +Shift-Ansi : "\t" +key Backtab +Ansi : "\E[Z" +key Backtab -Ansi : "\t" + +key Return-Shift-NewLine : "\r" +key Return-Shift+NewLine : "\r\n" + +key Return+Shift : "\EOM" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x7f" + +# Arrow keys in VT52 mode +# shift up/down are reserved for scrolling. +# shift left/right are reserved for switching between tabs (this is hardcoded). + + +# Command + C +# on mac - Control=Command, Meta=Ctrl +# do not use Control+C for interrupt signal - it's used for "Copy to clipboard" +#key Control +C : "\x03" +key Meta +C: "\x03" + + +# Arrow keys in ANSI mode with Application - and Normal Cursor Mode) + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +key Up -Ansi : "\E[1;*A" +key Down -Ansi : "\E[1;*B" +key Right -Ansi : "\E[1;*C" +key Left -Ansi : "\E[1;*D" + +#key Up -Shift-Ansi : "\EA" +#key Down -Shift-Ansi : "\EB" +#key Right-Shift-Ansi : "\EC" +#key Left -Shift-Ansi : "\ED" + +#key Up -Shift-AnyMod+Ansi-AppCuKeys : "\E[A" +#key Down -Shift-AnyMod+Ansi-AppCuKeys : "\E[B" +#key Right -Shift-AnyMod+Ansi-AppCuKeys : "\E[C" +#key Left -Shift-AnyMod+Ansi-AppCuKeys : "\E[D" + +#key Up -Shift-AnyMod+Ansi-AppCuKeys : "\EOA" +#key Down -Shift-AnyMod+Ansi-AppCuKeys : "\EOB" +#key Right -Shift-AnyMod+Ansi-AppCuKeys : "\EOC" +#key Left -Shift-AnyMod+Ansi-AppCuKeys : "\EOD" + +#key Up -Shift-AnyMod+Ansi : "\E[1;*A" +#key Down -Shift-AnyMod+Ansi : "\E[1;*B" +#key Right -Shift-AnyMod+Ansi : "\E[1;*C" +#key Left -Shift-AnyMod+Ansi : "\E[1;*D" + +# other grey PC keys + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Home -AnyMod -AppCuKeys : "\E[H" +key End -AnyMod -AppCuKeys : "\E[F" +key Home -AnyMod +AppCuKeys : "\EOH" +key End -AnyMod +AppCuKeys : "\EOF" +key Home +AnyMod : "\E[1;*H" +key End +AnyMod : "\E[1;*F" + +key Insert -AnyMod : "\E[2~" +key Delete -AnyMod : "\E[3~" +key Insert +AnyMod : "\E[2;*~" +key Delete +AnyMod : "\E[3;*~" + +key Prior -Shift-AnyMod : "\E[5~" +key Next -Shift-AnyMod : "\E[6~" +key Prior -Shift+AnyMod : "\E[5;*~" +key Next -Shift+AnyMod : "\E[6;*~" + +# Function keys +#key F1 -AnyMod : "\EOP" +#key F2 -AnyMod : "\EOQ" +#key F3 -AnyMod : "\EOR" +#key F4 -AnyMod : "\EOS" +#define ALT_KP_0 "\033Op" +#define ALT_KP_1 "\033Oq" +#define ALT_KP_2 "\033Or" +#define ALT_KP_3 "\033Os" +#define ALT_KP_4 "\033Ot" +#define ALT_KP_5 "\033Ou" +#define ALT_KP_6 "\033Ov" +#define ALT_KP_7 "\033Ow" +#define ALT_KP_8 "\033Ox" +#define ALT_KP_9 "\033Oy" + +key F1 -AnyMod : "\EOP" +key F2 -AnyMod : "\EOQ" +key F3 -AnyMod : "\EOR" +key F4 -AnyMod : "\EOS" +key F5 -AnyMod : "\EOT" +key F6 -AnyMod : "\EOU" +key F7 -AnyMod : "\EOV" +key F8 -AnyMod : "\EOW" +key F9 -AnyMod : "\EOX" +key F10 -AnyMod : "\EOY" + +#key F5 -AnyMod : "\E[15~" +#key F6 -AnyMod : "\E[17~" +#key F7 -AnyMod : "\E[18~" +#key F8 -AnyMod : "\E[19~" +#key F9 -AnyMod : "\E[20~" +#key F10 -AnyMod : "\E[21~" +#key F11 -AnyMod : "\E[23~" +#key F12 -AnyMod : "\E[24~" + +#key F1 +AnyMod : "\EO*P" +#key F2 +AnyMod : "\EO*Q" +#key F3 +AnyMod : "\EO*R" +#key F4 +AnyMod : "\EO*S" +#key F5 +AnyMod : "\E[15;*~" +#key F6 +AnyMod : "\E[17;*~" +#key F7 +AnyMod : "\E[18;*~" +#key F8 +AnyMod : "\E[19;*~" +#key F9 +AnyMod : "\E[20;*~" +#key F10 +AnyMod : "\E[21;*~" +#key F11 +AnyMod : "\E[23;*~" +#key F12 +AnyMod : "\E[24;*~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +key Up +Shift-AppScreen : scrollLineUp +key Prior +Shift-AppScreen : scrollPageUp +key Down +Shift-AppScreen : scrollLineDown +key Next +Shift-AppScreen : scrollPageDown + +#key Up +Shift : scrollLineUp +#key Prior +Shift : scrollPageUp +#key Down +Shift : scrollLineDown +#key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +# keypad characters are not offered differently by Qt. diff --git a/lib/kb-layouts/solaris.keytab b/lib/kb-layouts/solaris.keytab new file mode 100644 index 0000000..0739edf --- /dev/null +++ b/lib/kb-layouts/solaris.keytab @@ -0,0 +1,108 @@ +# [solaris.keytab] Konsole Keyboard Table +# + +keyboard "Solaris console" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" + +key Return-Alt : "\r" +key Return+Alt : "\E\r" + +# Backspace and Delete codes are preserving CTRL-H. + +key Backspace : "\x08" +#key Delete : "\x7F" + +# cursor keys + +key Up -Shift : "\EOA" +key Down -Shift : "\EOB" +key Right -Shift : "\EOC" +key Left -Shift : "\EOD" + +# other grey PC keys + +key Enter : "\r" + +key Home : "\E[1~" +key Insert-Shift : "\E[2~" +key Delete : "\E[3~" +key End : "\E[4~" +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" + +# function keys + +key F1 : "\E[11~" +key F2 : "\E[12~" +key F3 : "\E[13~" +key F4 : "\E[14~" +key F5 : "\E[15~" +key F6 : "\E[17~" +key F7 : "\E[18~" +key F8 : "\E[19~" +key F9 : "\E[20~" +key F10 : "\E[21~" +key F11 : "\E[23~" +key F12 : "\E[24~" + +# Work around dead keys + +key Space +Control : "\x00" + +# Some keys are used by konsole to cause operations. +# The scroll* operations refer to the history buffer. + +#key Left +Shift : prevSession +#key Right +Shift : nextSession +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown +#key Insert+Shift : emitSelection + +# keypad characters are not offered differently by Qt. diff --git a/lib/kb-layouts/vt420pc.keytab b/lib/kb-layouts/vt420pc.keytab new file mode 100644 index 0000000..ee6aa9a --- /dev/null +++ b/lib/kb-layouts/vt420pc.keytab @@ -0,0 +1,168 @@ +# +# NOTE: This keyboard binding is not installed because it +# apparently doesn't work with actual VT420 systems +# (see BUG:170220) +# +# [vt420pc.keytab] Konsole Keyboard Table (VT420pc keys) +# adapted by ferdinand gassauer f.gassauer@aon.at +# Nov 2000 +# +################################################################ +# +# The escape sequences emmited by the +# keys Shift+F1 to Shift+F12 might not fit your needs +# +################# IMPORTANT NOTICE ############################# +# the key bindings (Kcontrol -> look and feel -> keybindgs) +# overrule the settings in this file. The key bindings might be +# changed by the user WITHOUT notification of the maintainer of +# the keytab file. Konsole will not work as expected by +# the maintainer of the keytab file. +################################################################ +# +# -------------------------------------------------------------- + +keyboard "DEC VT420 Terminal" + +# -------------------------------------------------------------- +# +# This configuration table allows to customize the +# meaning of the keys. +# +# The syntax is that each entry has the form : +# +# "key" Keyname { ("+"|"-") Modename } ":" (String|Operation) +# +# Keynames are those defined in with the +# "Qt::Key_" removed. (We'd better insert the list here) +# +# Mode names are : +# +# - Shift +# - Alt +# - Control +# +# The VT100 emulation has two modes that can affect the +# sequences emitted by certain keys. These modes are +# under control of the client program. +# +# - Newline : effects Return and Enter key. +# - Application : effects Up and Down key. +# +# - Ansi : effects Up and Down key (This is for VT52, really). +# +# Operations are +# +# - scrollUpLine +# - scrollUpPage +# - scrollDownLine +# - scrollDownPage +# +# - emitSelection +# +# If the key is not found here, the text of the +# key event as provided by QT is emitted, possibly +# preceeded by ESC if the Alt key is pressed. +# +# -------------------------------------------------------------- + +key Escape : "\E" +key Tab : "\t" +key Backtab: "\E[Z" + +# VT100 can add an extra \n after return. +# The NewLine mode is set by an escape sequence. + +key Return-NewLine : "\r" +key Return+NewLine : "\r\n" + +# Some desperately try to save the ^H. +# may be not everyone wants this + +key Backspace : "\x08" # Control H +key Delete : "\x7f" + +# These codes are for the VT420pc +# The Ansi mode (i.e. VT100 mode) is set by +# an escape sequence + +key Up -Shift-Ansi : "\EA" +key Down -Shift-Ansi : "\EB" +key Right-Shift-Ansi : "\EC" +key Left -Shift-Ansi : "\ED" + +# VT100 emits a mode bit together +# with the arrow keys.The AppCuKeys +# mode is set by an escape sequence. + +key Up -Shift+Ansi+AppCuKeys : "\EOA" +key Down -Shift+Ansi+AppCuKeys : "\EOB" +key Right-Shift+Ansi+AppCuKeys : "\EOC" +key Left -Shift+Ansi+AppCuKeys : "\EOD" + +key Up -Shift+Ansi-AppCuKeys : "\E[A" +key Down -Shift+Ansi-AppCuKeys : "\E[B" +key Right-Shift+Ansi-AppCuKeys : "\E[C" +key Left -Shift+Ansi-AppCuKeys : "\E[D" + +# function keys + +key F1 -Shift : "\E[11~" +key F2 -Shift : "\E[12~" +key F3 -Shift : "\E[13~" +key F4 -Shift : "\E[14~" +key F5 -Shift : "\E[15~" +key F6 -Shift : "\E[17~" +key F7 -Shift : "\E[18~" +key F8 -Shift : "\E[19~" +key F9 -Shift : "\E[20~" +key F10-Shift : "\E[21~" +key F11-Shift : "\E[23~" +key F12-Shift : "\E[24~" +# +# Shift F1-F12 +# +key F1 +Shift : "\E[11;2~" +key F2 +Shift : "\E[12;2~" +key F3 +Shift : "\E[13;2~" +key F4 +Shift : "\E[14;2~" +key F5 +Shift : "\E[15;2~" +key F6 +Shift : "\E[17;2~" +key F7 +Shift : "\E[18;2~" +key F8 +Shift : "\E[19;2~" +key F9 +Shift : "\E[20;2~" +key F10+Shift : "\E[21;2~" +key F11+Shift : "\E[23;2~" +key F12+Shift : "\E[24;2~" + +key Home : "\E[H" +key End : "\E[F" + +key Prior -Shift : "\E[5~" +key Next -Shift : "\E[6~" +key Insert-Shift : "\E[2~" + +# Keypad-Enter. See comment on Return above. + +key Enter+NewLine : "\r\n" +key Enter-NewLine : "\r" + +key Space +Control : "\x00" + +# some of keys are used by konsole. + +key Up +Shift : scrollLineUp +key Prior +Shift : scrollPageUp +key Down +Shift : scrollLineDown +key Next +Shift : scrollPageDown + +key ScrollLock : scrollLock + +#---------------------------------------------------------- + +# keypad characters as offered by Qt +# cannot be recognized as such. + +#---------------------------------------------------------- + +# Following other strings as emitted by konsole. diff --git a/lib/konsole_wcwidth.cpp b/lib/konsole_wcwidth.cpp new file mode 100644 index 0000000..20162ea --- /dev/null +++ b/lib/konsole_wcwidth.cpp @@ -0,0 +1,226 @@ +/* $XFree86: xc/programs/xterm/wcwidth.character,v 1.3 2001/07/29 22:08:16 tsi Exp $ */ +/* + * This is an implementation of wcwidth() and wcswidth() as defined in + * "The Single UNIX Specification, Version 2, The Open Group, 1997" + * + * + * Markus Kuhn -- 2001-01-12 -- public domain + */ + +#include + +#include "konsole_wcwidth.h" + +struct interval { + unsigned short first; + unsigned short last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(quint16 ucs, const struct interval * table, int max) +{ + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) { + return 0; + } + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) { + min = mid + 1; + } else if (ucs < table[mid].first) { + max = mid - 1; + } else { + return 1; + } + } + + return 0; +} + + +/* The following functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * FullWidth (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that quint16 characters are encoded + * in ISO 10646. + */ + +int konsole_wcwidth(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + static const struct interval combining[] = { + { 0x0300, 0x034E }, { 0x0360, 0x0362 }, { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, + { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x0901, 0x0902 }, { 0x093C, 0x093C }, + { 0x0941, 0x0948 }, { 0x094D, 0x094D }, { 0x0951, 0x0954 }, + { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, + { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, + { 0x0A02, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, + { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, + { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, + { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 }, + { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, + { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, + { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, + { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, + { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, + { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, + { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, + { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, + { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, + { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, + { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, + { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, + { 0x1160, 0x11FF }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, { 0x180B, 0x180E }, { 0x18A9, 0x18A9 }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x206A, 0x206F }, + { 0x20D0, 0x20E3 }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, + { 0xFB1E, 0xFB1E }, { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, + { 0xFFF9, 0xFFFB } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) { + return 0; + } + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { + return -1; + } + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) { + return 0; + } + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) /* do not compare UINT16 with 0x20000 || + (ucs >= 0x20000 && ucs <= 0x2ffff) */)); +} + +#if 0 +/* + * The following function is the same as konsole_wcwidth(), except that + * spacing characters in the East Asian Ambiguous (A) category as + * defined in Unicode Technical Report #11 have a column width of 2. + * This experimental variant might be useful for users of CJK legacy + * encodings who want to migrate to UCS. It is not otherwise + * recommended for general use. + */ +int konsole_wcwidth_cjk(quint16 ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AD, 0x00AD }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014A }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, { 0x02CD, 0x02CD }, + { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, { 0x02DD, 0x02DD }, + { 0x0391, 0x03A1 }, { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, + { 0x03C3, 0x03C9 }, { 0x0401, 0x0401 }, { 0x0410, 0x044F }, + { 0x0451, 0x0451 }, { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, + { 0x2018, 0x2019 }, { 0x201C, 0x201D }, { 0x2020, 0x2021 }, + { 0x2025, 0x2027 }, { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, + { 0x2035, 0x2035 }, { 0x203B, 0x203B }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2121, 0x2122 }, { 0x2126, 0x2126 }, + { 0x212B, 0x212B }, { 0x2154, 0x2155 }, { 0x215B, 0x215B }, + { 0x215E, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21D2, 0x21D2 }, { 0x21D4, 0x21D4 }, + { 0x2200, 0x2200 }, { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, + { 0x220B, 0x220B }, { 0x220F, 0x220F }, { 0x2211, 0x2211 }, + { 0x2215, 0x2215 }, { 0x221A, 0x221A }, { 0x221D, 0x2220 }, + { 0x2223, 0x2223 }, { 0x2225, 0x2225 }, { 0x2227, 0x222C }, + { 0x222E, 0x222E }, { 0x2234, 0x2237 }, { 0x223C, 0x223D }, + { 0x2248, 0x2248 }, { 0x224C, 0x224C }, { 0x2252, 0x2252 }, + { 0x2260, 0x2261 }, { 0x2264, 0x2267 }, { 0x226A, 0x226B }, + { 0x226E, 0x226F }, { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, + { 0x2295, 0x2295 }, { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, + { 0x22BF, 0x22BF }, { 0x2312, 0x2312 }, { 0x2460, 0x24BF }, + { 0x24D0, 0x24E9 }, { 0x2500, 0x254B }, { 0x2550, 0x2574 }, + { 0x2580, 0x258F }, { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, + { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, + { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, + { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, + { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, + { 0x260E, 0x260F }, { 0x261C, 0x261C }, { 0x261E, 0x261E }, + { 0x2640, 0x2640 }, { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, + { 0x2663, 0x2665 }, { 0x2667, 0x266A }, { 0x266C, 0x266D }, + { 0x266F, 0x266F }, { 0x300A, 0x300B }, { 0x301A, 0x301B }, + { 0xE000, 0xF8FF }, { 0xFFFD, 0xFFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) { + return 2; + } + + return konsole_wcwidth(ucs); +} +#endif + +// single byte char: +1, multi byte char: +2 +int string_width( const QString & txt ) +{ + int w = 0; + for ( int i = 0; i < txt.length(); ++i ) { + w += konsole_wcwidth( txt[ i ].unicode() ); + } + return w; +} diff --git a/lib/konsole_wcwidth.h b/lib/konsole_wcwidth.h new file mode 100644 index 0000000..30f30ef --- /dev/null +++ b/lib/konsole_wcwidth.h @@ -0,0 +1,25 @@ +/* $XFree86: xc/programs/xterm/wcwidth.h,v 1.2 2001/06/18 19:09:27 dickey Exp $ */ + +/* Markus Kuhn -- 2001-01-12 -- public domain */ +/* Adaptions for KDE by Waldo Bastian */ +/* + Rewritten for QT4 by e_k +*/ + + +#ifndef _KONSOLE_WCWIDTH_H_ +#define _KONSOLE_WCWIDTH_H_ + +// Qt +#include + +class QString; + +int konsole_wcwidth(quint16 ucs); +#if 0 +int konsole_wcwidth_cjk(Q_UINT16 ucs); +#endif + +int string_width( const QString & txt ); + +#endif diff --git a/lib/kprocess.cpp b/lib/kprocess.cpp new file mode 100644 index 0000000..73264a6 --- /dev/null +++ b/lib/kprocess.cpp @@ -0,0 +1,412 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kprocess.h" + +#include + +#ifdef Q_OS_WIN +# include +#else +# include +# include +#endif + +#ifndef Q_OS_WIN +# define STD_OUTPUT_HANDLE 1 +# define STD_ERROR_HANDLE 2 +#endif + +#ifdef _WIN32_WCE +#include +#endif + +void KProcessPrivate::writeAll(const QByteArray &buf, int fd) +{ +#ifdef Q_OS_WIN +#ifndef _WIN32_WCE + HANDLE h = GetStdHandle(fd); + if (h) { + DWORD wr; + WriteFile(h, buf.data(), buf.size(), &wr, 0); + } +#else + fwrite(buf.data(), 1, buf.size(), (FILE*)fd); +#endif +#else + int off = 0; + do { + int ret = ::write(fd, buf.data() + off, buf.size() - off); + if (ret < 0) { + if (errno != EINTR) + return; + } else { + off += ret; + } + } while (off < buf.size()); +#endif +} + +void KProcessPrivate::forwardStd(KProcess::ProcessChannel good, int fd) +{ + Q_Q(KProcess); + + QProcess::ProcessChannel oc = q->readChannel(); + q->setReadChannel(good); + writeAll(q->readAll(), fd); + q->setReadChannel(oc); +} + +void KProcessPrivate::_k_forwardStdout() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardOutput, STD_OUTPUT_HANDLE); +#else + forwardStd(KProcess::StandardOutput, (int)stdout); +#endif +} + +void KProcessPrivate::_k_forwardStderr() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardError, STD_ERROR_HANDLE); +#else + forwardStd(KProcess::StandardError, (int)stderr); +#endif +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KProcess::KProcess(QObject *parent) : + QProcess(parent), + d_ptr(new KProcessPrivate) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::KProcess(KProcessPrivate *d, QObject *parent) : + QProcess(parent), + d_ptr(d) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::~KProcess() +{ + delete d_ptr; +} + +void KProcess::setOutputChannelMode(OutputChannelMode mode) +{ + Q_D(KProcess); + + d->outputChannelMode = mode; + disconnect(this, SIGNAL(readyReadStandardOutput())); + disconnect(this, SIGNAL(readyReadStandardError())); + switch (mode) { + case OnlyStdoutChannel: + connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr())); + break; + case OnlyStderrChannel: + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout())); + break; + default: + QProcess::setProcessChannelMode((ProcessChannelMode)mode); + return; + } + QProcess::setProcessChannelMode(QProcess::SeparateChannels); +} + +KProcess::OutputChannelMode KProcess::outputChannelMode() const +{ + Q_D(const KProcess); + + return d->outputChannelMode; +} + +void KProcess::setNextOpenMode(QIODevice::OpenMode mode) +{ + Q_D(KProcess); + + d->openMode = mode; +} + +#define DUMMYENV "_KPROCESS_DUMMY_=" + +void KProcess::clearEnvironment() +{ + setEnvironment(QStringList() << QString::fromLatin1(DUMMYENV)); +} + +void KProcess::setEnv(const QString &name, const QString &value, bool overwrite) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + if (overwrite) { + *it = fname.append(value); + setEnvironment(env); + } + return; + } + env.append(fname.append(value)); + setEnvironment(env); +} + +void KProcess::unsetEnv(const QString &name) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + env.erase(it); + if (env.isEmpty()) + env.append(QString::fromLatin1(DUMMYENV)); + setEnvironment(env); + return; + } +} + +void KProcess::setProgram(const QString &exe, const QStringList &args) +{ + Q_D(KProcess); + + d->prog = exe; + d->args = args; +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setProgram(const QStringList &argv) +{ + Q_D(KProcess); + + Q_ASSERT( !argv.isEmpty() ); + d->args = argv; + d->prog = d->args.takeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +KProcess &KProcess::operator<<(const QString &arg) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + d->prog = arg; + else + d->args << arg; + return *this; +} + +KProcess &KProcess::operator<<(const QStringList &args) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + setProgram(args); + else + d->args << args; + return *this; +} + +void KProcess::clearProgram() +{ + Q_D(KProcess); + + d->prog.clear(); + d->args.clear(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +#if 0 +void KProcess::setShellCommand(const QString &cmd) +{ + Q_D(KProcess); + + KShell::Errors err; + d->args = KShell::splitArgs( + cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err); + if (err == KShell::NoError && !d->args.isEmpty()) { + d->prog = KStandardDirs::findExe(d->args[0]); + if (!d->prog.isEmpty()) { + d->args.removeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif + return; + } + } + + d->args.clear(); + +#ifdef Q_OS_UNIX +// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh +# if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) + // If /bin/sh is a symlink, we can be pretty sure that it points to a + // POSIX shell - the original bourne shell is about the only non-POSIX + // shell still in use and it is always installed natively as /bin/sh. + d->prog = QFile::symLinkTarget(QString::fromLatin1("/bin/sh")); + if (d->prog.isEmpty()) { + // Try some known POSIX shells. + d->prog = KStandardDirs::findExe(QString::fromLatin1("ksh")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("ash")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("bash")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("zsh")); + if (d->prog.isEmpty()) + // We're pretty much screwed, to be honest ... + d->prog = QString::fromLatin1("/bin/sh"); + } + } + } + } +# else + d->prog = QString::fromLatin1("/bin/sh"); +# endif + + d->args << QString::fromLatin1("-c") << cmd; +#else // Q_OS_UNIX + // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and + // KShell::joinArgs() may generate these for security reasons. + setEnv(PERCENT_VARIABLE, QLatin1String("%")); + +#ifndef _WIN32_WCE + WCHAR sysdir[MAX_PATH + 1]; + UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1); + d->prog = QString::fromUtf16((const ushort *) sysdir, size); + d->prog += QLatin1String("\\cmd.exe"); + setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"')); +#else + d->prog = QLatin1String("\\windows\\cmd.exe"); + setNativeArguments(QLatin1String("/S /C \"") + cmd + QLatin1Char('"')); +#endif +#endif +} +#endif +QStringList KProcess::program() const +{ + Q_D(const KProcess); + + QStringList argv = d->args; + argv.prepend(d->prog); + return argv; +} + +void KProcess::start() +{ + Q_D(KProcess); + + QProcess::start(d->prog, d->args, d->openMode); +} + +int KProcess::execute(int msecs) +{ + start(); + if (!waitForFinished(msecs)) { + kill(); + waitForFinished(-1); + return -2; + } + return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; +} + +// static +int KProcess::execute(const QString &exe, const QStringList &args, int msecs) +{ + KProcess p; + p.setProgram(exe, args); + return p.execute(msecs); +} + +// static +int KProcess::execute(const QStringList &argv, int msecs) +{ + KProcess p; + p.setProgram(argv); + return p.execute(msecs); +} + +int KProcess::startDetached() +{ + Q_D(KProcess); + + qint64 pid; + if (!QProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QString &exe, const QStringList &args) +{ + qint64 pid; + if (!QProcess::startDetached(exe, args, QString(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QStringList &argv) +{ + QStringList args = argv; + QString prog = args.takeFirst(); + return startDetached(prog, args); +} + +int KProcess::pid() const +{ +#ifdef Q_OS_UNIX + return (int) QProcess::pid(); +#else + return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; +#endif +} + diff --git a/lib/kprocess.h b/lib/kprocess.h new file mode 100644 index 0000000..2d3bcca --- /dev/null +++ b/lib/kprocess.h @@ -0,0 +1,372 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_H +#define KPROCESS_H + +//#include + +#include + +class KProcessPrivate; + +/** + * \class KProcess kprocess.h + * + * Child process invocation, monitoring and control. + * + * This class extends QProcess by some useful functionality, overrides + * some defaults with saner values and wraps parts of the API into a more + * accessible one. + * This is the preferred way of spawning child processes in KDE; don't + * use QProcess directly. + * + * @author Oswald Buddenhagen + **/ +class KProcess : public QProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KProcess) + +public: + + /** + * Modes in which the output channels can be opened. + */ + enum OutputChannelMode { + SeparateChannels = QProcess::SeparateChannels, + /**< Standard output and standard error are handled by KProcess + as separate channels */ + MergedChannels = QProcess::MergedChannels, + /**< Standard output and standard error are handled by KProcess + as one channel */ + ForwardedChannels = QProcess::ForwardedChannels, + /**< Both standard output and standard error are forwarded + to the parent process' respective channel */ + OnlyStdoutChannel, + /**< Only standard output is handled; standard error is forwarded */ + OnlyStderrChannel /**< Only standard error is handled; standard output is forwarded */ + }; + + /** + * Constructor + */ + explicit KProcess(QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KProcess(); + + /** + * Set how to handle the output channels of the child process. + * + * The default is ForwardedChannels, which is unlike in QProcess. + * Do not request more than you actually handle, as this output is + * simply lost otherwise. + * + * This function must be called before starting the process. + * + * @param mode the output channel handling mode + */ + void setOutputChannelMode(OutputChannelMode mode); + + /** + * Query how the output channels of the child process are handled. + * + * @return the output channel handling mode + */ + OutputChannelMode outputChannelMode() const; + + /** + * Set the QIODevice open mode the process will be opened in. + * + * This function must be called before starting the process, obviously. + * + * @param mode the open mode. Note that this mode is automatically + * "reduced" according to the channel modes and redirections. + * The default is QIODevice::ReadWrite. + */ + void setNextOpenMode(QIODevice::OpenMode mode); + + /** + * Adds the variable @p name to the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + * @param value the new value for the environment variable + * @param overwrite if @c false and the environment variable is already + * set, the old value will be preserved + */ + void setEnv(const QString &name, const QString &value, bool overwrite = true); + + /** + * Removes the variable @p name from the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + */ + void unsetEnv(const QString &name); + + /** + * Empties the process' environment. + * + * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added + * on *NIX. + * + * This function must be called before starting the process. + */ + void clearEnvironment(); + + /** + * Set the program and the command line arguments. + * + * This function must be called before starting the process, obviously. + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + */ + void setProgram(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + */ + void setProgram(const QStringList &argv); + + /** + * Append an element to the command line argument list for this process. + * + * If no executable is set yet, it will be set instead. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * \code + * KProcess p; + * p << "ls" << "-l" << "/usr/local/bin"; + * ... + * \endcode + * + * This function must be called before starting the process, obviously. + * + * @param arg the argument to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QString& arg); + + /** + * @overload + * + * @param args the arguments to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QStringList& args); + + /** + * Clear the program and command line argument list. + */ + void clearProgram(); + + /** + * Set a command to execute through a shell (a POSIX sh on *NIX + * and cmd.exe on Windows). + * + * Using this for anything but user-supplied commands is usually a bad + * idea, as the command's syntax depends on the platform. + * Redirections including pipes, etc. are better handled by the + * respective functions provided by QProcess. + * + * If KProcess determines that the command does not really need a + * shell, it will trasparently execute it without one for performance + * reasons. + * + * This function must be called before starting the process, obviously. + * + * @param cmd the command to execute through a shell. + * The caller must make sure that all filenames etc. are properly + * quoted when passed as argument. Failure to do so often results in + * serious security holes. See KShell::quoteArg(). + */ + void setShellCommand(const QString &cmd); + + /** + * Obtain the currently set program and arguments. + * + * @return a list, the first element being the program, the remaining ones + * being command line arguments to the program. + */ + QStringList program() const; + + /** + * Start the process. + * + * @see QProcess::start(const QString &, const QStringList &, OpenMode) + */ + void start(); + + /** + * Start the process, wait for it to finish, and return the exit code. + * + * This method is roughly equivalent to the sequence: + * + * start(); + * waitForFinished(msecs); + * return exitCode(); + * + * + * Unlike the other execute() variants this method is not static, + * so the process can be parametrized properly and talked to. + * + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + int execute(int msecs = -1); + + /** + * @overload + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QStringList &argv, int msecs = -1); + + /** + * Start the process and detach from it. See QProcess::startDetached() + * for details. + * + * Unlike the other startDetached() variants this method is not static, + * so the process can be parametrized properly. + * @note Currently, only the setProgram()/setShellCommand() and + * setWorkingDirectory() parametrizations are supported. + * + * The KProcess object may be re-used immediately after calling this + * function. + * + * @return the PID of the started process or 0 on error + */ + int startDetached(); + + /** + * @overload + * + * @param exe the program to start + * @param args the command line arguments for the program, + * one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to start and the command line arguments + * for the program, one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QStringList &argv); + + /** + * Obtain the process' ID as known to the system. + * + * Unlike with QProcess::pid(), this is a real PID also on Windows. + * + * This function can be called only while the process is running. + * It cannot be applied to detached processes. + * + * @return the process ID + */ + int pid() const; + +protected: + /** + * @internal + */ + KProcess(KProcessPrivate *d, QObject *parent); + + /** + * @internal + */ + KProcessPrivate * const d_ptr; + +private: + // hide those + using QProcess::setReadChannelMode; + using QProcess::readChannelMode; + using QProcess::setProcessChannelMode; + using QProcess::processChannelMode; + + Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout()) + Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr()) +}; + +/* ----------- kprocess_p.h ---------------- */ +class KProcessPrivate { + + Q_DECLARE_PUBLIC(KProcess) + +protected: + KProcessPrivate() : + openMode(QIODevice::ReadWrite) + { + } + void writeAll(const QByteArray &buf, int fd); + void forwardStd(KProcess::ProcessChannel good, int fd); + void _k_forwardStdout(); + void _k_forwardStderr(); + + QString prog; + QStringList args; + KProcess::OutputChannelMode outputChannelMode; + QIODevice::OpenMode openMode; + + KProcess *q_ptr; +}; +/* ------------------------------------------- */ +#endif + diff --git a/lib/kpty.cpp b/lib/kpty.cpp new file mode 100644 index 0000000..1039e00 --- /dev/null +++ b/lib/kpty.cpp @@ -0,0 +1,701 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2002 Waldo Bastian + Copyright (C) 2002-2003,2007 Oswald Buddenhagen + + Rewritten for QT4 by e_k , Copyright (C)2008 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kpty_p.h" + +#include + + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#define HAVE_LOGIN +#define HAVE_LIBUTIL_H +#endif + +#ifdef __sgi +#define __svr4__ +#endif + +#ifdef __osf__ +#define _OSF_SOURCE +#include +#endif + +#ifdef _AIX +#define _ALL_SOURCE +#endif + +// __USE_XOPEN isn't defined by default in ICC +// (needed for ptsname(), grantpt() and unlockpt()) +#ifdef __INTEL_COMPILER +# ifndef __USE_XOPEN +# define __USE_XOPEN +# endif +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_PTY_H) +# include +#endif + +#ifdef HAVE_LIBUTIL_H +# include +#elif defined(HAVE_UTIL_H) +# include +#endif + +#ifdef HAVE_UTEMPTER +extern "C" { +# include +} +#else +# include +# ifdef HAVE_UTMPX +# include +# endif +# if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE) +# define _PATH_UTMPX _UTMPX_FILE +# endif +# ifdef HAVE_UPDWTMPX +# if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE) +# define _PATH_WTMPX _WTMPX_FILE +# endif +# endif +#endif + +/* for HP-UX (some versions) the extern C is needed, and for other + platforms it doesn't hurt */ +extern "C" { +#include +#if defined(HAVE_TERMIO_H) +# include // struct winsize on some systems +#endif +} + +#if defined (_HPUX_SOURCE) +# define _TERMIOS_INCLUDED +# include +#endif + +#ifdef HAVE_SYS_STROPTS_H +# include // Defines I_PUSH +# define _NEW_TTY_CTRL +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__Lynx__) || defined (__CYGWIN__) +# define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode) +# else +# define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode) +# endif +#endif + +#if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) || defined (__bsdi__) || defined(__APPLE__) || defined (__DragonFly__) +# define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode) +#else +# if defined(_HPUX_SOURCE) || defined(__CYGWIN__) +# define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode) +# else +# define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode) +# endif +#endif + +//#include +//#include // findExe + +// not defined on HP-UX for example +#ifndef CTRL +# define CTRL(x) ((x) & 037) +#endif + +#define TTY_GROUP "tty" + +/////////////////////// +// private functions // +/////////////////////// + +////////////////// +// private data // +////////////////// + +KPtyPrivate::KPtyPrivate(KPty* parent) : + masterFd(-1), slaveFd(-1), ownMaster(true), q_ptr(parent) +{ +} + +KPtyPrivate::~KPtyPrivate() +{ +} + +bool KPtyPrivate::chownpty(bool) +{ +// return !QProcess::execute(KStandardDirs::findExe("kgrantpty"), +// QStringList() << (grant?"--grant":"--revoke") << QString::number(masterFd)); + return true; +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPty::KPty() : + d_ptr(new KPtyPrivate(this)) +{ +} + +KPty::KPty(KPtyPrivate *d) : + d_ptr(d) +{ + d_ptr->q_ptr = this; +} + +KPty::~KPty() +{ + close(); + delete d_ptr; +} + +bool KPty::open() +{ + Q_D(KPty); + + if (d->masterFd >= 0) + return true; + + d->ownMaster = true; + + QByteArray ptyName; + + // Find a master pty that we can open //////////////////////////////// + + // Because not all the pty animals are created equal, they want to + // be opened by several different methods. + + // We try, as we know them, one by one. + +#ifdef HAVE_OPENPTY + + char ptsn[PATH_MAX]; + if (::openpty( &d->masterFd, &d->slaveFd, ptsn, 0, 0)) { + d->masterFd = -1; + d->slaveFd = -1; + qWarning(175) << "Can't open a pseudo teletype"; + return false; + } + d->ttyName = ptsn; + +#else + +#ifdef HAVE__GETPTY // irix + + char *ptsn = _getpty(&d->masterFd, O_RDWR|O_NOCTTY, S_IRUSR|S_IWUSR, 0); + if (ptsn) { + d->ttyName = ptsn; + goto grantedpt; + } + +#elif defined(HAVE_PTSNAME) || defined(TIOCGPTN) + +#ifdef HAVE_POSIX_OPENPT + d->masterFd = ::posix_openpt(O_RDWR|O_NOCTTY); +#elif defined(HAVE_GETPT) + d->masterFd = ::getpt(); +#elif defined(PTM_DEVICE) + d->masterFd = ::open(PTM_DEVICE, O_RDWR|O_NOCTTY); +#else +# error No method to open a PTY master detected. +#endif + if (d->masterFd >= 0) { +#ifdef HAVE_PTSNAME + char *ptsn = ptsname(d->masterFd); + if (ptsn) { + d->ttyName = ptsn; +#else + int ptyno; + if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) { + d->ttyName = QByteArray("/dev/pts/") + QByteArray::number(ptyno); +#endif +#ifdef HAVE_GRANTPT + if (!grantpt(d->masterFd)) { + goto grantedpt; + } +#else + + goto gotpty; +#endif + } + ::close(d->masterFd); + d->masterFd = -1; + } +#endif // HAVE_PTSNAME || TIOCGPTN + + // Linux device names, FIXME: Trouble on other systems? + for (const char * s3 = "pqrstuvwxyzabcde"; *s3; s3++) { + for (const char * s4 = "0123456789abcdef"; *s4; s4++) { + ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toUtf8(); + d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toUtf8(); + + d->masterFd = ::open(ptyName.data(), O_RDWR); + if (d->masterFd >= 0) { +#ifdef Q_OS_SOLARIS + /* Need to check the process group of the pty. + * If it exists, then the slave pty is in use, + * and we need to get another one. + */ + int pgrp_rtn; + if (ioctl(d->masterFd, TIOCGPGRP, &pgrp_rtn) == 0 || errno != EIO) { + ::close(d->masterFd); + d->masterFd = -1; + continue; + } +#endif /* Q_OS_SOLARIS */ + if (!access(d->ttyName.data(),R_OK|W_OK)) { // checks availability based on permission bits + if (!geteuid()) { + struct group * p = getgrnam(TTY_GROUP); + if (!p) { + p = getgrnam("wheel"); + } + gid_t gid = p ? p->gr_gid : getgid (); + + if (!chown(d->ttyName.data(), getuid(), gid)) { + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IWGRP); + } + } + goto gotpty; + } + ::close(d->masterFd); + d->masterFd = -1; + } + } + } + + qWarning() << "Can't open a pseudo teletype"; + return false; + +gotpty: + struct stat st; + if (stat(d->ttyName.data(), &st)) { + return false; // this just cannot happen ... *cough* Yeah right, I just + // had it happen when pty #349 was allocated. I guess + // there was some sort of leak? I only had a few open. + } + if (((st.st_uid != getuid()) || + (st.st_mode & (S_IRGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH))) && + !d->chownpty(true)) { + qWarning() + << "chownpty failed for device " << ptyName << "::" << d->ttyName + << "\nThis means the communication can be eavesdropped." << endl; + } + +#if defined (HAVE__GETPTY) || defined (HAVE_GRANTPT) +grantedpt: +#endif + +#ifdef HAVE_REVOKE + revoke(d->ttyName.data()); +#endif + +#ifdef HAVE_UNLOCKPT + unlockpt(d->masterFd); +#elif defined(TIOCSPTLCK) + int flag = 0; + ioctl(d->masterFd, TIOCSPTLCK, &flag); +#endif + + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) { + qWarning() << "Can't open slave pseudo teletype"; + ::close(d->masterFd); + d->masterFd = -1; + return false; + } + +#if (defined(__svr4__) || defined(__sgi__)) + // Solaris + ioctl(d->slaveFd, I_PUSH, "ptem"); + ioctl(d->slaveFd, I_PUSH, "ldterm"); +#endif + +#endif /* HAVE_OPENPTY */ + + fcntl(d->masterFd, F_SETFD, FD_CLOEXEC); + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + + return true; +} + +bool KPty::open(int fd) +{ +#if !defined(HAVE_PTSNAME) && !defined(TIOCGPTN) + qWarning() << "Unsupported attempt to open pty with fd" << fd; + return false; +#else + Q_D(KPty); + + if (d->masterFd >= 0) { + qWarning() << "Attempting to open an already open pty"; + return false; + } + + d->ownMaster = false; + +# ifdef HAVE_PTSNAME + char *ptsn = ptsname(fd); + if (ptsn) { + d->ttyName = ptsn; +# else + int ptyno; + if (!ioctl(fd, TIOCGPTN, &ptyno)) { + char buf[32]; + sprintf(buf, "/dev/pts/%d", ptyno); + d->ttyName = buf; +# endif + } else { + qWarning() << "Failed to determine pty slave device for fd" << fd; + return false; + } + + d->masterFd = fd; + if (!openSlave()) { + + d->masterFd = -1; + return false; + } + + return true; +#endif +} + +void KPty::closeSlave() +{ + Q_D(KPty); + + if (d->slaveFd < 0) { + return; + } + ::close(d->slaveFd); + d->slaveFd = -1; +} + +bool KPty::openSlave() +{ + Q_D(KPty); + + if (d->slaveFd >= 0) + return true; + if (d->masterFd < 0) { + qDebug() << "Attempting to open pty slave while master is closed"; + return false; + } + //d->slaveFd = KDE_open(d->ttyName.data(), O_RDWR | O_NOCTTY); + d->slaveFd = ::open(d->ttyName.data(), O_RDWR | O_NOCTTY); + if (d->slaveFd < 0) { + qDebug() << "Can't open slave pseudo teletype"; + return false; + } + fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC); + return true; +} + +void KPty::close() +{ + Q_D(KPty); + + if (d->masterFd < 0) { + return; + } + closeSlave(); + // don't bother resetting unix98 pty, it will go away after closing master anyway. + if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) { + if (!geteuid()) { + struct stat st; + if (!stat(d->ttyName.data(), &st)) { + chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1); + chmod(d->ttyName.data(), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + } + } else { + fcntl(d->masterFd, F_SETFD, 0); + d->chownpty(false); + } + } + ::close(d->masterFd); + d->masterFd = -1; +} + +void KPty::setCTty() +{ + Q_D(KPty); + + // Setup job control ////////////////////////////////// + + // Become session leader, process group leader, + // and get rid of the old controlling terminal. + setsid(); + + // make our slave pty the new controlling terminal. +#ifdef TIOCSCTTY + ioctl(d->slaveFd, TIOCSCTTY, 0); +#else + // __svr4__ hack: the first tty opened after setsid() becomes controlling tty + ::close(::open(d->ttyName, O_WRONLY, 0)); +#endif + + // make our new process group the foreground group on the pty + int pgrp = getpid(); +#if defined(_POSIX_VERSION) || defined(__svr4__) + tcsetpgrp(d->slaveFd, pgrp); +#elif defined(TIOCSPGRP) + ioctl(d->slaveFd, TIOCSPGRP, (char *)&pgrp); +#endif +} + +void KPty::login(const char * user, const char * remotehost) +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + addToUtmp(d->ttyName, remotehost, d->masterFd); + Q_UNUSED(user); +#else +# ifdef HAVE_UTMPX + struct utmpx l_struct; +# else + struct utmp l_struct; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + // note: strncpy without terminators _is_ correct here. man 4 utmp + + if (user) { + strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name)); + } + + if (remotehost) { + strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host)); +# endif + } + +# ifndef __GLIBC__ + Q_D(KPty); + const char * str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) { + str_ptr += 5; + } + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); +# ifdef HAVE_STRUCT_UTMP_UT_ID + strncpy(l_struct.ut_id, + str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), + sizeof(l_struct.ut_id)); +# endif +# endif + +# ifdef HAVE_UTMPX + gettimeofday(&l_struct.ut_tv, 0); +# else + l_struct.ut_time = time(0); +# endif + +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::loginx(&l_struct); +# else + ::login(&l_struct); +# endif +# else +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + l_struct.ut_type = USER_PROCESS; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_PID + l_struct.ut_pid = getpid(); +# ifdef HAVE_STRUCT_UTMP_UT_SESSION + l_struct.ut_session = getsid(0); +# endif +# endif +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + pututxline(&l_struct); + endutxent(); +# ifdef HAVE_UPDWTMPX + updwtmpx(_PATH_WTMPX, &l_struct); +# endif +# else + utmpname(_PATH_UTMP); + setutent(); + pututline(&l_struct); + endutent(); + updwtmp(_PATH_WTMP, &l_struct); +# endif +# endif +#endif +} + +void KPty::logout() +{ +#ifdef HAVE_UTEMPTER + Q_D(KPty); + + removeLineFromUtmp(d->ttyName, d->masterFd); +#else + Q_D(KPty); + + const char *str_ptr = d->ttyName.data(); + if (!memcmp(str_ptr, "/dev/", 5)) { + str_ptr += 5; + } +# ifdef __GLIBC__ + else { + const char * sl_ptr = strrchr(str_ptr, '/'); + if (sl_ptr) { + str_ptr = sl_ptr + 1; + } + } +# endif +# ifdef HAVE_LOGIN +# ifdef HAVE_LOGINX + ::logoutx(str_ptr, 0, DEAD_PROCESS); +# else + ::logout(str_ptr); +# endif +# else +# ifdef HAVE_UTMPX + struct utmpx l_struct, *ut; +# else + struct utmp l_struct, *ut; +# endif + memset(&l_struct, 0, sizeof(l_struct)); + + strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line)); + +# ifdef HAVE_UTMPX + utmpxname(_PATH_UTMPX); + setutxent(); + if ((ut = getutxline(&l_struct))) { +# else + utmpname(_PATH_UTMP); + setutent(); + if ((ut = getutline(&l_struct))) { +# endif + memset(ut->ut_name, 0, sizeof(*ut->ut_name)); + memset(ut->ut_host, 0, sizeof(*ut->ut_host)); +# ifdef HAVE_STRUCT_UTMP_UT_SYSLEN + ut->ut_syslen = 0; +# endif +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + ut->ut_type = DEAD_PROCESS; +# endif +# ifdef HAVE_UTMPX + gettimeofday(&ut->ut_tv, 0); + pututxline(ut); + } + endutxent(); +# else + ut->ut_time = time(0); + pututline(ut); +} +endutent(); +# endif +# endif +#endif +} + +// XXX Supposedly, tc[gs]etattr do not work with the master on Solaris. +// Please verify. + +bool KPty::tcGetAttr(struct ::termios * ttmode) const +{ + Q_D(const KPty); + + return _tcgetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::tcSetAttr(struct ::termios * ttmode) +{ + Q_D(KPty); + + return _tcsetattr(d->masterFd, ttmode) == 0; +} + +bool KPty::setWinSize(int lines, int columns) +{ + Q_D(KPty); + + struct winsize winSize; + memset(&winSize, 0, sizeof(winSize)); + winSize.ws_row = (unsigned short)lines; + winSize.ws_col = (unsigned short)columns; + return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0; +} + +bool KPty::setEcho(bool echo) +{ + struct ::termios ttmode; + if (!tcGetAttr(&ttmode)) { + return false; + } + if (!echo) { + ttmode.c_lflag &= ~ECHO; + } else { + ttmode.c_lflag |= ECHO; + } + return tcSetAttr(&ttmode); +} + +const char * KPty::ttyName() const +{ + Q_D(const KPty); + + return d->ttyName.data(); +} + +int KPty::masterFd() const +{ + Q_D(const KPty); + + return d->masterFd; +} + +int KPty::slaveFd() const +{ + Q_D(const KPty); + + return d->slaveFd; +} diff --git a/lib/kpty.h b/lib/kpty.h new file mode 100644 index 0000000..24457a5 --- /dev/null +++ b/lib/kpty.h @@ -0,0 +1,191 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen + + Rewritten for QT4 by e_k , Copyright (C)2008 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_h +#define kpty_h + +#include + +struct KPtyPrivate; +struct termios; + +/** + * Provides primitives for opening & closing a pseudo TTY pair, assigning the + * controlling TTY, utmp registration and setting various terminal attributes. + */ +class KPty { + Q_DECLARE_PRIVATE(KPty) + +public: + + /** + * Constructor + */ + KPty(); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + ~KPty(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + bool open(); + + bool open(int fd); + + /** + * Close the pty master/slave pair. + */ + void close(); + + /** + * Close the pty slave descriptor. + * + * When creating the pty, KPty also opens the slave and keeps it open. + * Consequently the master will never receive an EOF notification. + * Usually this is the desired behavior, as a closed pty slave can be + * reopened any time - unlike a pipe or socket. However, in some cases + * pipe-alike behavior might be desired. + * + * After this function was called, slaveFd() and setCTty() cannot be + * used. + */ + void closeSlave(); + bool openSlave(); + + /** + * Creates a new session and process group and makes this pty the + * controlling tty. + */ + void setCTty(); + + /** + * Creates an utmp entry for the tty. + * This function must be called after calling setCTty and + * making this pty the stdin. + * @param user the user to be logged on + * @param remotehost the host from which the login is coming. This is + * @em not the local host. For remote logins it should be the hostname + * of the client. For local logins from inside an X session it should + * be the name of the X display. Otherwise it should be empty. + */ + void login(const char * user = 0, const char * remotehost = 0); + + /** + * Removes the utmp entry for this tty. + */ + void logout(); + + /** + * Wrapper around tcgetattr(3). + * + * This function can be used only while the PTY is open. + * You will need an #include <termios.h> to do anything useful + * with it. + * + * @param ttmode a pointer to a termios structure. + * Note: when declaring ttmode, @c struct @c ::termios must be used - + * without the '::' some version of HP-UX thinks, this declares + * the struct in your class, in your method. + * @return @c true on success, false otherwise + */ + bool tcGetAttr(struct ::termios * ttmode) const; + + /** + * Wrapper around tcsetattr(3) with mode TCSANOW. + * + * This function can be used only while the PTY is open. + * + * @param ttmode a pointer to a termios structure. + * @return @c true on success, false otherwise. Note that success means + * that @em at @em least @em one attribute could be set. + */ + bool tcSetAttr(struct ::termios * ttmode); + + /** + * Change the logical (screen) size of the pty. + * The default is 24 lines by 80 columns. + * + * This function can be used only while the PTY is open. + * + * @param lines the number of rows + * @param columns the number of columns + * @return @c true on success, false otherwise + */ + bool setWinSize(int lines, int columns); + + /** + * Set whether the pty should echo input. + * + * Echo is on by default. + * If the output of automatically fed (non-interactive) PTY clients + * needs to be parsed, disabling echo often makes it much simpler. + * + * This function can be used only while the PTY is open. + * + * @param echo true if input should be echoed. + * @return @c true on success, false otherwise + */ + bool setEcho(bool echo); + + /** + * @return the name of the slave pty device. + * + * This function should be called only while the pty is open. + */ + const char * ttyName() const; + + /** + * @return the file descriptor of the master pty + * + * This function should be called only while the pty is open. + */ + int masterFd() const; + + /** + * @return the file descriptor of the slave pty + * + * This function should be called only while the pty slave is open. + */ + int slaveFd() const; + +protected: + /** + * @internal + */ + KPty(KPtyPrivate * d); + + /** + * @internal + */ + KPtyPrivate * const d_ptr; +}; + +#endif + diff --git a/lib/kpty_p.h b/lib/kpty_p.h new file mode 100644 index 0000000..3f6bb88 --- /dev/null +++ b/lib/kpty_p.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2003,2007 Oswald Buddenhagen + + Rewritten for QT4 by e_k , Copyright (C)2008 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kpty_p_h +#define kpty_p_h + +#include "kpty.h" + +#include + +struct KPtyPrivate { + + Q_DECLARE_PUBLIC(KPty) + + KPtyPrivate(KPty* parent); + virtual ~KPtyPrivate(); + +#ifndef HAVE_OPENPTY + bool chownpty(bool grant); +#endif + + int masterFd; + int slaveFd; + bool ownMaster:1; + + QByteArray ttyName; + + KPty *q_ptr; +}; + +#endif diff --git a/lib/kptydevice.cpp b/lib/kptydevice.cpp new file mode 100644 index 0000000..37ecce8 --- /dev/null +++ b/lib/kptydevice.cpp @@ -0,0 +1,422 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + + This file is part of the KDE libraries + Copyright (C) 2007 Oswald Buddenhagen + Copyright (C) 2010 KDE e.V. + Author Adriaan de Groot + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kptydevice.h" +#include "kpty_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_FILIO_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) + // "the other end's output queue size" - kinda braindead, huh? +# define PTY_BYTES_AVAILABLE TIOCOUTQ +#elif defined(TIOCINQ) + // "our end's input queue size" +# define PTY_BYTES_AVAILABLE TIOCINQ +#else + // likewise. more generic ioctl (theoretically) +# define PTY_BYTES_AVAILABLE FIONREAD +#endif + + + + +////////////////// +// private data // +////////////////// + +// Lifted from Qt. I don't think they would mind. ;) +// Re-lift again from Qt whenever a proper replacement for pthread_once appears +static void qt_ignore_sigpipe() +{ + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &noaction, 0); + } +} + +#define NO_INTR(ret,func) do { ret = func; } while (ret < 0 && errno == EINTR) + +bool KPtyDevicePrivate::_k_canRead() +{ + Q_Q(KPtyDevice); + qint64 readBytes = 0; + +#ifdef Q_OS_IRIX // this should use a config define, but how to check it? + size_t available; +#else + int available; +#endif + if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *) &available)) { +#ifdef Q_OS_SOLARIS + // A Pty is a STREAMS module, and those can be activated + // with 0 bytes available. This happens either when ^C is + // pressed, or when an application does an explicit write(a,b,0) + // which happens in experiments fairly often. When 0 bytes are + // available, you must read those 0 bytes to clear the STREAMS + // module, but we don't want to hit the !readBytes case further down. + if (!available) { + char c; + // Read the 0-byte STREAMS message + NO_INTR(readBytes, read(q->masterFd(), &c, 0)); + // Should return 0 bytes read; -1 is error + if (readBytes < 0) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } + return true; + } +#endif + + char *ptr = readBuffer.reserve(available); +#ifdef Q_OS_SOLARIS + // Even if available > 0, it is possible for read() + // to return 0 on Solaris, due to 0-byte writes in the stream. + // Ignore them and keep reading until we hit *some* data. + // In Solaris it is possible to have 15 bytes available + // and to (say) get 0, 0, 6, 0 and 9 bytes in subsequent reads. + // Because the stream is set to O_NONBLOCK in finishOpen(), + // an EOF read will return -1. + readBytes = 0; + while (!readBytes) +#endif + // Useless block braces except in Solaris + { + NO_INTR(readBytes, read(q->masterFd(), ptr, available)); + } + if (readBytes < 0) { + readBuffer.unreserve(available); + q->setErrorString("Error reading from PTY"); + return false; + } + readBuffer.unreserve(available - readBytes); // *should* be a no-op + } + + if (!readBytes) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } else { + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + return true; + } +} + +bool KPtyDevicePrivate::_k_canWrite() +{ + Q_Q(KPtyDevice); + + writeNotifier->setEnabled(false); + if (writeBuffer.isEmpty()) + return false; + + qt_ignore_sigpipe(); + int wroteBytes; + NO_INTR(wroteBytes, + write(q->masterFd(), + writeBuffer.readPointer(), writeBuffer.readSize())); + if (wroteBytes < 0) { + q->setErrorString("Error writing to PTY"); + return false; + } + writeBuffer.free(wroteBytes); + + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(wroteBytes); + emittedBytesWritten = false; + } + + if (!writeBuffer.isEmpty()) + writeNotifier->setEnabled(true); + return true; +} + +#ifndef timeradd +// Lifted from GLIBC +# define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +# define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +bool KPtyDevicePrivate::doWait(int msecs, bool reading) +{ + Q_Q(KPtyDevice); +#ifndef __linux__ + struct timeval etv; +#endif + struct timeval tv, *tvp; + + if (msecs < 0) + tvp = 0; + else { + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; +#ifndef __linux__ + gettimeofday(&etv, 0); + timeradd(&tv, &etv, &etv); +#endif + tvp = &tv; + } + + while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (readNotifier->isEnabled()) + FD_SET(q->masterFd(), &rfds); + if (!writeBuffer.isEmpty()) + FD_SET(q->masterFd(), &wfds); + +#ifndef __linux__ + if (tvp) { + gettimeofday(&tv, 0); + timersub(&etv, &tv, &tv); + if (tv.tv_sec < 0) + tv.tv_sec = tv.tv_usec = 0; + } +#endif + + switch (select(q->masterFd() + 1, &rfds, &wfds, 0, tvp)) { + case -1: + if (errno == EINTR) + break; + return false; + case 0: + q->setErrorString("PTY operation timed out"); + return false; + default: + if (FD_ISSET(q->masterFd(), &rfds)) { + bool canRead = _k_canRead(); + if (reading && canRead) + return true; + } + if (FD_ISSET(q->masterFd(), &wfds)) { + bool canWrite = _k_canWrite(); + if (!reading) + return canWrite; + } + break; + } + } + return false; +} + +void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode) +{ + Q_Q(KPtyDevice); + + q->QIODevice::open(mode); + fcntl(q->masterFd(), F_SETFL, O_NONBLOCK); + readBuffer.clear(); + readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q); + writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q); + QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead())); + QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite())); + readNotifier->setEnabled(true); +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPtyDevice::KPtyDevice(QObject *parent) : + QIODevice(parent), + KPty(new KPtyDevicePrivate(this)) +{ +} + +KPtyDevice::~KPtyDevice() +{ + close(); +} + +bool KPtyDevice::open(OpenMode mode) +{ + Q_D(KPtyDevice); + + if (masterFd() >= 0) + return true; + + if (!KPty::open()) { + setErrorString("Error opening PTY"); + return false; + } + + d->finishOpen(mode); + + return true; +} + +bool KPtyDevice::open(int fd, OpenMode mode) +{ + Q_D(KPtyDevice); + + if (!KPty::open(fd)) { + setErrorString("Error opening PTY"); + return false; + } + + d->finishOpen(mode); + + return true; +} + +void KPtyDevice::close() +{ + Q_D(KPtyDevice); + + if (masterFd() < 0) + return; + + delete d->readNotifier; + delete d->writeNotifier; + + QIODevice::close(); + + KPty::close(); +} + +bool KPtyDevice::isSequential() const +{ + return true; +} + +bool KPtyDevice::canReadLine() const +{ + Q_D(const KPtyDevice); + return QIODevice::canReadLine() || d->readBuffer.canReadLine(); +} + +bool KPtyDevice::atEnd() const +{ + Q_D(const KPtyDevice); + return QIODevice::atEnd() && d->readBuffer.isEmpty(); +} + +qint64 KPtyDevice::bytesAvailable() const +{ + Q_D(const KPtyDevice); + return QIODevice::bytesAvailable() + d->readBuffer.size(); +} + +qint64 KPtyDevice::bytesToWrite() const +{ + Q_D(const KPtyDevice); + return d->writeBuffer.size(); +} + +bool KPtyDevice::waitForReadyRead(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, true); +} + +bool KPtyDevice::waitForBytesWritten(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, false); +} + +void KPtyDevice::setSuspended(bool suspended) +{ + Q_D(KPtyDevice); + d->readNotifier->setEnabled(!suspended); +} + +bool KPtyDevice::isSuspended() const +{ + Q_D(const KPtyDevice); + return !d->readNotifier->isEnabled(); +} + +// protected +qint64 KPtyDevice::readData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.read(data, (int)qMin(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::readLineData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.readLine(data, (int)qMin(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::writeData(const char *data, qint64 len) +{ + Q_D(KPtyDevice); + Q_ASSERT(len <= KMAXINT); + + d->writeBuffer.write(data, len); + d->writeNotifier->setEnabled(true); + return len; +} diff --git a/lib/kptydevice.h b/lib/kptydevice.h new file mode 100644 index 0000000..3ef8d17 --- /dev/null +++ b/lib/kptydevice.h @@ -0,0 +1,361 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef kptydev_h +#define kptydev_h + +#include "kpty_p.h" + +#include + +#define KMAXINT ((int)(~0U >> 1)) + +struct KPtyDevicePrivate; +class QSocketNotifier; + +#define Q_DECLARE_PRIVATE_MI(Class, SuperClass) \ + inline Class##Private* d_func() { return reinterpret_cast(SuperClass::d_ptr); } \ + inline const Class##Private* d_func() const { return reinterpret_cast(SuperClass::d_ptr); } \ + friend class Class##Private; + +/** + * Encapsulates KPty into a QIODevice, so it can be used with Q*Stream, etc. + */ +class KPtyDevice : public QIODevice, public KPty { + Q_OBJECT + Q_DECLARE_PRIVATE_MI(KPtyDevice, KPty) + +public: + + /** + * Constructor + */ + KPtyDevice(QObject *parent = 0); + + /** + * Destructor: + * + * If the pty is still open, it will be closed. Note, however, that + * an utmp registration is @em not undone. + */ + virtual ~KPtyDevice(); + + /** + * Create a pty master/slave pair. + * + * @return true if a pty pair was successfully opened + */ + virtual bool open(OpenMode mode = ReadWrite | Unbuffered); + + /** + * Open using an existing pty master. The ownership of the fd + * remains with the caller, i.e., close() will not close the fd. + * + * This is useful if you wish to attach a secondary "controller" to an + * existing pty device such as a terminal widget. + * Note that you will need to use setSuspended() on both devices to + * control which one gets the incoming data from the pty. + * + * @param fd an open pty master file descriptor. + * @param mode the device mode to open the pty with. + * @return true if a pty pair was successfully opened + */ + bool open(int fd, OpenMode mode = ReadWrite | Unbuffered); + + /** + * Close the pty master/slave pair. + */ + virtual void close(); + + /** + * Sets whether the KPtyDevice monitors the pty for incoming data. + * + * When the KPtyDevice is suspended, it will no longer attempt to buffer + * data that becomes available from the pty and it will not emit any + * signals. + * + * Do not use on closed ptys. + * After a call to open(), the pty is not suspended. If you need to + * ensure that no data is read, call this function before the main loop + * is entered again (i.e., immediately after opening the pty). + */ + void setSuspended(bool suspended); + + /** + * Returns true if the KPtyDevice is not monitoring the pty for incoming + * data. + * + * Do not use on closed ptys. + * + * See setSuspended() + */ + bool isSuspended() const; + + /** + * @return always true + */ + virtual bool isSequential() const; + + /** + * @reimp + */ + bool canReadLine() const; + + /** + * @reimp + */ + bool atEnd() const; + + /** + * @reimp + */ + qint64 bytesAvailable() const; + + /** + * @reimp + */ + qint64 bytesToWrite() const; + + bool waitForBytesWritten(int msecs = -1); + bool waitForReadyRead(int msecs = -1); + + +Q_SIGNALS: + /** + * Emitted when EOF is read from the PTY. + * + * Data may still remain in the buffers. + */ + void readEof(); + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 readLineData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + +private: + Q_PRIVATE_SLOT(d_func(), bool _k_canRead()) + Q_PRIVATE_SLOT(d_func(), bool _k_canWrite()) +}; + +///////////////////////////////////////////////////// +// Helper. Remove when QRingBuffer becomes public. // +///////////////////////////////////////////////////// + +#include +#include + +#define CHUNKSIZE 4096 + +class KRingBuffer +{ +public: + KRingBuffer() + { + clear(); + } + + void clear() + { + buffers.clear(); + QByteArray tmp; + tmp.resize(CHUNKSIZE); + buffers << tmp; + head = tail = 0; + totalSize = 0; + } + + inline bool isEmpty() const + { + return buffers.count() == 1 && !tail; + } + + inline int size() const + { + return totalSize; + } + + inline int readSize() const + { + return (buffers.count() == 1 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const + { + Q_ASSERT(totalSize > 0); + return buffers.first().constData() + head; + } + + void free(int bytes) + { + totalSize -= bytes; + Q_ASSERT(totalSize >= 0); + + forever { + int nbs = readSize(); + + if (bytes < nbs) { + head += bytes; + if (head == tail && buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + } + break; + } + + bytes -= nbs; + if (buffers.count() == 1) { + buffers.first().resize(CHUNKSIZE); + head = tail = 0; + break; + } + + buffers.removeFirst(); + head = 0; + } + } + + char *reserve(int bytes) + { + totalSize += bytes; + + char *ptr; + if (tail + bytes <= buffers.last().size()) { + ptr = buffers.last().data() + tail; + tail += bytes; + } else { + buffers.last().resize(tail); + QByteArray tmp; + tmp.resize(qMax(CHUNKSIZE, bytes)); + ptr = tmp.data(); + buffers << tmp; + tail = bytes; + } + return ptr; + } + + // release a trailing part of the last reservation + inline void unreserve(int bytes) + { + totalSize -= bytes; + tail -= bytes; + } + + inline void write(const char *data, int len) + { + memcpy(reserve(len), data, len); + } + + // Find the first occurrence of c and return the index after it. + // If c is not found until maxLength, maxLength is returned, provided + // it is smaller than the buffer size. Otherwise -1 is returned. + int indexAfter(char c, int maxLength = KMAXINT) const + { + int index = 0; + int start = head; + QLinkedList::ConstIterator it = buffers.begin(); + forever { + if (!maxLength) + return index; + if (index == size()) + return -1; + const QByteArray &buf = *it; + ++it; + int len = qMin((it == buffers.end() ? tail : buf.size()) - start, + maxLength); + const char *ptr = buf.data() + start; + if (const char *rptr = (const char *)memchr(ptr, c, len)) + return index + (rptr - ptr) + 1; + index += len; + maxLength -= len; + start = 0; + } + } + + inline int lineSize(int maxLength = KMAXINT) const + { + return indexAfter('\n', maxLength); + } + + inline bool canReadLine() const + { + return lineSize() != -1; + } + + int read(char *data, int maxLength) + { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readPointer(); + int bs = qMin(bytesToRead - readSoFar, readSize()); + memcpy(data + readSoFar, ptr, bs); + readSoFar += bs; + free(bs); + } + return readSoFar; + } + + int readLine(char *data, int maxLength) + { + return read(data, lineSize(qMin(maxLength, size()))); + } + +private: + QLinkedList buffers; + int head, tail; + int totalSize; +}; + +struct KPtyDevicePrivate : public KPtyPrivate { + + Q_DECLARE_PUBLIC(KPtyDevice) + + KPtyDevicePrivate(KPty* parent) : + KPtyPrivate(parent), + emittedReadyRead(false), emittedBytesWritten(false), + readNotifier(0), writeNotifier(0) + { + } + + bool _k_canRead(); + bool _k_canWrite(); + + bool doWait(int msecs, bool reading); + void finishOpen(QIODevice::OpenMode mode); + + bool emittedReadyRead; + bool emittedBytesWritten; + QSocketNotifier *readNotifier; + QSocketNotifier *writeNotifier; + KRingBuffer readBuffer; + KRingBuffer writeBuffer; +}; + +#endif + diff --git a/lib/kptyprocess.cpp b/lib/kptyprocess.cpp new file mode 100644 index 0000000..078770b --- /dev/null +++ b/lib/kptyprocess.cpp @@ -0,0 +1,129 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "kptyprocess.h" +#include "kprocess.h" +#include "kptydevice.h" + +#include +#include + +KPtyProcess::KPtyProcess(QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::KPtyProcess(int ptyMasterFd, QObject *parent) : + KProcess(new KPtyProcessPrivate, parent) +{ + Q_D(KPtyProcess); + + d->pty = new KPtyDevice(this); + d->pty->open(ptyMasterFd); + connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(_k_onStateChanged(QProcess::ProcessState))); +} + +KPtyProcess::~KPtyProcess() +{ + Q_D(KPtyProcess); + + if (state() != QProcess::NotRunning && d->addUtmp) { + d->pty->logout(); + disconnect(SIGNAL(stateChanged(QProcess::ProcessState)), + this, SLOT(_k_onStateChanged(QProcess::ProcessState))); + } + delete d->pty; +} + +void KPtyProcess::setPtyChannels(PtyChannels channels) +{ + Q_D(KPtyProcess); + + d->ptyChannels = channels; +} + +KPtyProcess::PtyChannels KPtyProcess::ptyChannels() const +{ + Q_D(const KPtyProcess); + + return d->ptyChannels; +} + +void KPtyProcess::setUseUtmp(bool value) +{ + Q_D(KPtyProcess); + + d->addUtmp = value; +} + +bool KPtyProcess::isUseUtmp() const +{ + Q_D(const KPtyProcess); + + return d->addUtmp; +} + +KPtyDevice *KPtyProcess::pty() const +{ + Q_D(const KPtyProcess); + + return d->pty; +} + +void KPtyProcess::setupChildProcess() +{ + Q_D(KPtyProcess); + + d->pty->setCTty(); + +#if 0 + if (d->addUtmp) + d->pty->login(KUser(KUser::UseRealUserID).loginName().toLocal8Bit().data(), qgetenv("DISPLAY")); +#endif + if (d->ptyChannels & StdinChannel) + dup2(d->pty->slaveFd(), 0); + + if (d->ptyChannels & StdoutChannel) + dup2(d->pty->slaveFd(), 1); + + if (d->ptyChannels & StderrChannel) + dup2(d->pty->slaveFd(), 2); + + KProcess::setupChildProcess(); +} + +//#include "kptyprocess.moc" diff --git a/lib/kptyprocess.h b/lib/kptyprocess.h new file mode 100644 index 0000000..15e4de4 --- /dev/null +++ b/lib/kptyprocess.h @@ -0,0 +1,178 @@ +/* + * This file is a part of QTerminal - http://gitorious.org/qterminal + * + * This file was un-linked from KDE and modified + * by Maxim Bourmistrov + * + */ + +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPTYPROCESS_H +#define KPTYPROCESS_H + +#include "kprocess.h" +#include "kptydevice.h" + +#include + +class KPtyDevice; + +struct KPtyProcessPrivate; + +/** + * This class extends KProcess by support for PTYs (pseudo TTYs). + * + * The PTY is opened as soon as the class is instantiated. Verify that + * it was opened successfully by checking that pty()->masterFd() is not -1. + * + * The PTY is always made the process' controlling TTY. + * Utmp registration and connecting the stdio handles to the PTY are optional. + * + * No attempt to integrate with QProcess' waitFor*() functions was made, + * for it is impossible. Note that execute() does not work with the PTY, too. + * Use the PTY device's waitFor*() functions or use it asynchronously. + * + * @author Oswald Buddenhagen + */ +class KPtyProcess : public KProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KPtyProcess) + +public: + enum PtyChannelFlag { + NoChannels = 0, /**< The PTY is not connected to any channel. */ + StdinChannel = 1, /**< Connect PTY to stdin. */ + StdoutChannel = 2, /**< Connect PTY to stdout. */ + StderrChannel = 4, /**< Connect PTY to stderr. */ + AllOutputChannels = 6, /**< Connect PTY to all output channels. */ + AllChannels = 7 /**< Connect PTY to all channels. */ + }; + + Q_DECLARE_FLAGS(PtyChannels, PtyChannelFlag) + + /** + * Constructor + */ + explicit KPtyProcess(QObject *parent = 0); + + /** + * Construct a process using an open pty master. + * + * @param ptyMasterFd an open pty master file descriptor. + * The process does not take ownership of the descriptor; + * it will not be automatically closed at any point. + */ + KPtyProcess(int ptyMasterFd, QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KPtyProcess(); + + /** + * Set to which channels the PTY should be assigned. + * + * This function must be called before starting the process. + * + * @param channels the output channel handling mode + */ + void setPtyChannels(PtyChannels channels); + + bool isRunning() const + { + bool rval; + (pid() > 0) ? rval= true : rval= false; + return rval; + + } + /** + * Query to which channels the PTY is assigned. + * + * @return the output channel handling mode + */ + PtyChannels ptyChannels() const; + + /** + * Set whether to register the process as a TTY login in utmp. + * + * Utmp is disabled by default. + * It should enabled for interactively fed processes, like terminal + * emulations. + * + * This function must be called before starting the process. + * + * @param value whether to register in utmp. + */ + void setUseUtmp(bool value); + + /** + * Get whether to register the process as a TTY login in utmp. + * + * @return whether to register in utmp + */ + bool isUseUtmp() const; + + /** + * Get the PTY device of this process. + * + * @return the PTY device + */ + KPtyDevice *pty() const; + +protected: + /** + * @reimp + */ + virtual void setupChildProcess(); + +private: + Q_PRIVATE_SLOT(d_func(), void _k_onStateChanged(QProcess::ProcessState)) +}; + + +////////////////// +// private data // +////////////////// + +struct KPtyProcessPrivate : KProcessPrivate { + KPtyProcessPrivate() : + ptyChannels(KPtyProcess::NoChannels), + addUtmp(false) + { + } + + void _k_onStateChanged(QProcess::ProcessState newState) + { + if (newState == QProcess::NotRunning && addUtmp) + pty->logout(); + } + + KPtyDevice *pty; + KPtyProcess::PtyChannels ptyChannels; + bool addUtmp : 1; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KPtyProcess::PtyChannels) + +#endif diff --git a/lib/qtermwidget.cpp b/lib/qtermwidget.cpp new file mode 100644 index 0000000..dd8b867 --- /dev/null +++ b/lib/qtermwidget.cpp @@ -0,0 +1,621 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +#include "ColorTables.h" +#include "Session.h" +#include "Screen.h" +#include "ScreenWindow.h" +#include "Emulation.h" +#include "TerminalDisplay.h" +#include "KeyboardTranslator.h" +#include "ColorScheme.h" +#include "SearchBar.h" +#include "qtermwidget.h" + + +#define STEP_ZOOM 1 + +using namespace Konsole; + +void *createTermWidget(int startnow, void *parent) +{ + return (void*) new QTermWidget(startnow, (QWidget*)parent); +} + +struct TermWidgetImpl { + TermWidgetImpl(QWidget* parent = 0); + + TerminalDisplay *m_terminalDisplay; + Session *m_session; + + Session* createSession(QWidget* parent); + TerminalDisplay* createTerminalDisplay(Session *session, QWidget* parent); +}; + +TermWidgetImpl::TermWidgetImpl(QWidget* parent) +{ + this->m_session = createSession(parent); + this->m_terminalDisplay = createTerminalDisplay(this->m_session, parent); +} + + +Session *TermWidgetImpl::createSession(QWidget* parent) +{ + Session *session = new Session(parent); + + session->setTitle(Session::NameRole, "QTermWidget"); + + /* Thats a freaking bad idea!!!! + * /bin/bash is not there on every system + * better set it to the current $SHELL + * Maybe you can also make a list available and then let the widget-owner decide what to use. + * By setting it to $SHELL right away we actually make the first filecheck obsolete. + * But as iam not sure if you want to do anything else ill just let both checks in and set this to $SHELL anyway. + */ + //session->setProgram("/bin/bash"); + + session->setProgram(getenv("SHELL")); + + + + QStringList args(""); + session->setArguments(args); + session->setAutoClose(true); + + session->setCodec(QTextCodec::codecForName("UTF-8")); + + session->setFlowControlEnabled(true); + session->setHistoryType(HistoryTypeBuffer(1000)); + + session->setDarkBackground(true); + + session->setKeyBindings(""); + return session; +} + +TerminalDisplay *TermWidgetImpl::createTerminalDisplay(Session *session, QWidget* parent) +{ +// TerminalDisplay* display = new TerminalDisplay(this); + TerminalDisplay* display = new TerminalDisplay(parent); + + display->setBellMode(TerminalDisplay::NotifyBell); + display->setTerminalSizeHint(true); + display->setTripleClickMode(TerminalDisplay::SelectWholeLine); + display->setTerminalSizeStartup(true); + + display->setRandomSeed(session->sessionId() * 31); + + return display; +} + + +QTermWidget::QTermWidget(int startnow, QWidget *parent) + : QWidget(parent) +{ + init(startnow); +} + +QTermWidget::QTermWidget(QWidget *parent) + : QWidget(parent) +{ + init(1); +} + +void QTermWidget::selectionChanged(bool textSelected) +{ + emit copyAvailable(textSelected); +} + +void QTermWidget::find() +{ + search(true, false); +} + +void QTermWidget::findNext() +{ + search(true, true); +} + +void QTermWidget::findPrevious() +{ + search(false, false); +} + +void QTermWidget::search(bool forwards, bool next) +{ + int startColumn, startLine; + + if (next) // search from just after current selection + { + m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionEnd(startColumn, startLine); + startColumn++; + } + else // search from start of current selection + { + m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionStart(startColumn, startLine); + } + + qDebug() << "current selection starts at: " << startColumn << startLine; + qDebug() << "current cursor position: " << m_impl->m_terminalDisplay->screenWindow()->cursorPosition(); + + QRegExp regExp(m_searchBar->searchText()); + regExp.setPatternSyntax(m_searchBar->useRegularExpression() ? QRegExp::RegExp : QRegExp::FixedString); + regExp.setCaseSensitivity(m_searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive); + + HistorySearch *historySearch = + new HistorySearch(m_impl->m_session->emulation(), regExp, forwards, startColumn, startLine, this); + connect(historySearch, SIGNAL(matchFound(int, int, int, int)), this, SLOT(matchFound(int, int, int, int))); + connect(historySearch, SIGNAL(noMatchFound()), this, SLOT(noMatchFound())); + connect(historySearch, SIGNAL(noMatchFound()), m_searchBar, SLOT(noMatchFound())); + historySearch->search(); +} + + +void QTermWidget::matchFound(int startColumn, int startLine, int endColumn, int endLine) +{ + ScreenWindow* sw = m_impl->m_terminalDisplay->screenWindow(); + qDebug() << "Scroll to" << startLine; + sw->scrollTo(startLine); + sw->setTrackOutput(false); + sw->notifyOutputChanged(); + sw->setSelectionStart(startColumn, startLine - sw->currentLine(), false); + sw->setSelectionEnd(endColumn, endLine - sw->currentLine()); +} + +void QTermWidget::noMatchFound() +{ + m_impl->m_terminalDisplay->screenWindow()->clearSelection(); +} + +int QTermWidget::getShellPID() +{ + return m_impl->m_session->processId(); +} + +void QTermWidget::changeDir(const QString & dir) +{ + /* + this is a very hackish way of trying to determine if the shell is in + the foreground before attempting to change the directory. It may not + be portable to anything other than Linux. + */ + QString strCmd; + strCmd.setNum(getShellPID()); + strCmd.prepend("ps -j "); + strCmd.append(" | tail -1 | awk '{ print $5 }' | grep -q \\+"); + int retval = system(strCmd.toStdString().c_str()); + + if (!retval) { + QString cmd = "cd " + dir + "\n"; + sendText(cmd); + } +} + +QSize QTermWidget::sizeHint() const +{ + QSize size = m_impl->m_terminalDisplay->sizeHint(); + size.rheight() = 150; + return size; +} + +void QTermWidget::startShellProgram() +{ + if ( m_impl->m_session->isRunning() ) { + return; + } + + m_impl->m_session->run(); +} + +void QTermWidget::init(int startnow) +{ + m_layout = new QVBoxLayout(); + m_layout->setMargin(0); + setLayout(m_layout); + + m_impl = new TermWidgetImpl(this); + m_impl->m_terminalDisplay->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_layout->addWidget(m_impl->m_terminalDisplay); + + connect(m_impl->m_session, SIGNAL(bellRequest(QString)), m_impl->m_terminalDisplay, SLOT(bell(QString))); + connect(m_impl->m_terminalDisplay, SIGNAL(notifyBell(QString)), this, SIGNAL(bell(QString))); + + connect(m_impl->m_session, SIGNAL(activity()), this, SIGNAL(activity())); + connect(m_impl->m_session, SIGNAL(silence()), this, SIGNAL(silence())); + + // That's OK, FilterChain's dtor takes care of UrlFilter. + UrlFilter *urlFilter = new UrlFilter(); + connect(urlFilter, SIGNAL(activated(QUrl)), this, SIGNAL(urlActivated(QUrl))); + m_impl->m_terminalDisplay->filterChain()->addFilter(urlFilter); + + m_searchBar = new SearchBar(this); + m_searchBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + connect(m_searchBar, SIGNAL(searchCriteriaChanged()), this, SLOT(find())); + connect(m_searchBar, SIGNAL(findNext()), this, SLOT(findNext())); + connect(m_searchBar, SIGNAL(findPrevious()), this, SLOT(findPrevious())); + m_layout->addWidget(m_searchBar); + m_searchBar->hide(); + + if (startnow && m_impl->m_session) { + m_impl->m_session->run(); + } + + this->setFocus( Qt::OtherFocusReason ); + this->setFocusPolicy( Qt::WheelFocus ); + m_impl->m_terminalDisplay->resize(this->size()); + + this->setFocusProxy(m_impl->m_terminalDisplay); + connect(m_impl->m_terminalDisplay, SIGNAL(copyAvailable(bool)), + this, SLOT(selectionChanged(bool))); + connect(m_impl->m_terminalDisplay, SIGNAL(termGetFocus()), + this, SIGNAL(termGetFocus())); + connect(m_impl->m_terminalDisplay, SIGNAL(termLostFocus()), + this, SIGNAL(termLostFocus())); + connect(m_impl->m_terminalDisplay, SIGNAL(keyPressedSignal(QKeyEvent *)), + this, SIGNAL(termKeyPressed(QKeyEvent *))); +// m_impl->m_terminalDisplay->setSize(80, 40); + + QFont font = QApplication::font(); + font.setFamily("Monospace"); + font.setPointSize(10); + font.setStyleHint(QFont::TypeWriter); + setTerminalFont(font); + m_searchBar->setFont(font); + + setScrollBarPosition(NoScrollBar); + + m_impl->m_session->addView(m_impl->m_terminalDisplay); + + connect(m_impl->m_session, SIGNAL(finished()), this, SLOT(sessionFinished())); +} + + +QTermWidget::~QTermWidget() +{ + delete m_impl; + emit destroyed(); +} + + +void QTermWidget::setTerminalFont(const QFont &font) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setVTFont(font); +} + +QFont QTermWidget::getTerminalFont() +{ + if (!m_impl->m_terminalDisplay) + return QFont(); + return m_impl->m_terminalDisplay->getVTFont(); +} + +void QTermWidget::setTerminalOpacity(qreal level) +{ + if (!m_impl->m_terminalDisplay) + return; + + m_impl->m_terminalDisplay->setOpacity(level); +} + +void QTermWidget::setShellProgram(const QString &progname) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setProgram(progname); +} + +void QTermWidget::setWorkingDirectory(const QString& dir) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setInitialWorkingDirectory(dir); +} + +QString QTermWidget::workingDirectory() +{ + if (!m_impl->m_session) + return QString(); + +#ifdef Q_OS_LINUX + // Christian Surlykke: On linux we could look at /proc//cwd which should be a link to current + // working directory (: process id of the shell). I don't know about BSD. + // Maybe we could just offer it when running linux, for a start. + QDir d(QString("/proc/%1/cwd").arg(getShellPID())); + if (!d.exists()) + { + qDebug() << "Cannot find" << d.dirName(); + goto fallback; + } + return d.canonicalPath(); +#endif + +fallback: + // fallback, initial WD + return m_impl->m_session->initialWorkingDirectory(); +} + +void QTermWidget::setArgs(const QStringList &args) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setArguments(args); +} + +void QTermWidget::setTextCodec(QTextCodec *codec) +{ + if (!m_impl->m_session) + return; + m_impl->m_session->setCodec(codec); +} + +void QTermWidget::setColorScheme(const QString& origName) +{ + const ColorScheme *cs = 0; + + const bool isFile = QFile::exists(origName); + const QString& name = isFile ? + QFileInfo(origName).baseName() : + origName; + + // avoid legacy (int) solution + if (!availableColorSchemes().contains(name)) + { + if (isFile) + { + if (ColorSchemeManager::instance()->loadCustomColorScheme(origName)) + cs = ColorSchemeManager::instance()->findColorScheme(name); + else + qWarning () << Q_FUNC_INFO + << "cannot load color scheme from" + << origName; + } + + if (!cs) + cs = ColorSchemeManager::instance()->defaultColorScheme(); + } + else + cs = ColorSchemeManager::instance()->findColorScheme(name); + + if (! cs) + { + QMessageBox::information(this, + tr("Color Scheme Error"), + tr("Cannot load color scheme: %1").arg(name)); + return; + } + ColorEntry table[TABLE_COLORS]; + cs->getColorTable(table); + m_impl->m_terminalDisplay->setColorTable(table); +} + +QStringList QTermWidget::availableColorSchemes() +{ + QStringList ret; + foreach (const ColorScheme* cs, ColorSchemeManager::instance()->allColorSchemes()) + ret.append(cs->name()); + return ret; +} + +void QTermWidget::setSize(int h, int v) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setSize(h, v); +} + +void QTermWidget::setHistorySize(int lines) +{ + if (lines < 0) + m_impl->m_session->setHistoryType(HistoryTypeFile()); + else + m_impl->m_session->setHistoryType(HistoryTypeBuffer(lines)); +} + +void QTermWidget::setScrollBarPosition(ScrollBarPosition pos) +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->setScrollBarPosition((TerminalDisplay::ScrollBarPosition)pos); +} + +void QTermWidget::scrollToEnd() +{ + if (!m_impl->m_terminalDisplay) + return; + m_impl->m_terminalDisplay->scrollToEnd(); +} + +void QTermWidget::sendText(const QString &text) +{ + m_impl->m_session->sendText(text); +} + +void QTermWidget::resizeEvent(QResizeEvent*) +{ +//qDebug("global window resizing...with %d %d", this->size().width(), this->size().height()); + m_impl->m_terminalDisplay->resize(this->size()); +} + + +void QTermWidget::sessionFinished() +{ + emit finished(); +} + + +void QTermWidget::copyClipboard() +{ + m_impl->m_terminalDisplay->copyClipboard(); +} + +void QTermWidget::pasteClipboard() +{ + m_impl->m_terminalDisplay->pasteClipboard(); +} + +void QTermWidget::pasteSelection() +{ + m_impl->m_terminalDisplay->pasteSelection(); +} + +void QTermWidget::setZoom(int step) +{ + if (!m_impl->m_terminalDisplay) + return; + + QFont font = m_impl->m_terminalDisplay->getVTFont(); + + font.setPointSize(font.pointSize() + step); + setTerminalFont(font); +} + +void QTermWidget::zoomIn() +{ + setZoom(STEP_ZOOM); +} + +void QTermWidget::zoomOut() +{ + setZoom(-STEP_ZOOM); +} + +void QTermWidget::setKeyBindings(const QString & kb) +{ + m_impl->m_session->setKeyBindings(kb); +} + +void QTermWidget::clear() +{ + m_impl->m_session->emulation()->reset(); + m_impl->m_session->refresh(); + m_impl->m_session->clearHistory(); +} + +void QTermWidget::setFlowControlEnabled(bool enabled) +{ + m_impl->m_session->setFlowControlEnabled(enabled); +} + +QStringList QTermWidget::availableKeyBindings() +{ + return KeyboardTranslatorManager::instance()->allTranslators(); +} + +QString QTermWidget::keyBindings() +{ + return m_impl->m_session->keyBindings(); +} + +void QTermWidget::toggleShowSearchBar() +{ + m_searchBar->isHidden() ? m_searchBar->show() : m_searchBar->hide(); +} + +bool QTermWidget::flowControlEnabled(void) +{ + return m_impl->m_session->flowControlEnabled(); +} + +void QTermWidget::setFlowControlWarningEnabled(bool enabled) +{ + if (flowControlEnabled()) { + // Do not show warning label if flow control is disabled + m_impl->m_terminalDisplay->setFlowControlWarningEnabled(enabled); + } +} + +void QTermWidget::setEnvironment(const QStringList& environment) +{ + m_impl->m_session->setEnvironment(environment); +} + +void QTermWidget::setMotionAfterPasting(int action) +{ + m_impl->m_terminalDisplay->setMotionAfterPasting((Konsole::MotionAfterPasting) action); +} + +int QTermWidget::historyLinesCount() +{ + return m_impl->m_terminalDisplay->screenWindow()->screen()->getHistLines(); +} + +int QTermWidget::screenColumnsCount() +{ + return m_impl->m_terminalDisplay->screenWindow()->screen()->getColumns(); +} + +void QTermWidget::setSelectionStart(int row, int column) +{ + m_impl->m_terminalDisplay->screenWindow()->screen()->setSelectionStart(column, row, true); +} + +void QTermWidget::setSelectionEnd(int row, int column) +{ + m_impl->m_terminalDisplay->screenWindow()->screen()->setSelectionEnd(column, row); +} + +void QTermWidget::getSelectionStart(int& row, int& column) +{ + m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionStart(column, row); +} + +void QTermWidget::setSelectionEnd(int& row, int& column) +{ + m_impl->m_terminalDisplay->screenWindow()->screen()->setSelectionEnd(column, row); +} + +QString QTermWidget::selectedText(bool preserveLineBreaks) +{ + return m_impl->m_terminalDisplay->screenWindow()->screen()->selectedText(preserveLineBreaks); +} + +void QTermWidget::setMonitorActivity(bool monitor) +{ + m_impl->m_session->setMonitorActivity(monitor); +} + +void QTermWidget::setMonitorSilence(bool monitor) +{ + m_impl->m_session->setMonitorSilence(monitor); +} + +void QTermWidget::setSilenceTimeout(int seconds) +{ + m_impl->m_session->setMonitorSilenceSeconds(seconds); +} + +Filter::HotSpot* QTermWidget::getHotSpotAt(const QPoint &pos) const +{ + int row = 0, column = 0; + m_impl->m_terminalDisplay->getCharacterPosition(pos, row, column); + return getHotSpotAt(row, column); +} + +Filter::HotSpot* QTermWidget::getHotSpotAt(int row, int column) const +{ + return m_impl->m_terminalDisplay->filterChain()->hotSpotAt(row, column); +} + diff --git a/lib/qtermwidget.h b/lib/qtermwidget.h new file mode 100644 index 0000000..9c550ee --- /dev/null +++ b/lib/qtermwidget.h @@ -0,0 +1,240 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef _Q_TERM_WIDGET +#define _Q_TERM_WIDGET + +#include +#include "Filter.h" + +class QVBoxLayout; +struct TermWidgetImpl; +class SearchBar; +class QUrl; + +class QTermWidget : public QWidget { + Q_OBJECT +public: + + enum ScrollBarPosition { + /** Do not show the scroll bar. */ + NoScrollBar=0, + /** Show the scroll bar on the left side of the display. */ + ScrollBarLeft=1, + /** Show the scroll bar on the right side of the display. */ + ScrollBarRight=2 + }; + + //Creation of widget + QTermWidget(int startnow, // 1 = start shell programm immediatelly + QWidget * parent = 0); + // A dummy constructor for Qt Designer. startnow is 1 by default + QTermWidget(QWidget *parent = 0); + + virtual ~QTermWidget(); + + //Initial size + QSize sizeHint() const; + + //start shell program if it was not started in constructor + void startShellProgram(); + + int getShellPID(); + + void changeDir(const QString & dir); + + //look-n-feel, if you don`t like defaults + + // Terminal font + // Default is application font with family Monospace, size 10 + // USE ONLY FIXED-PITCH FONT! + // otherwise symbols' position could be incorrect + void setTerminalFont(const QFont & font); + QFont getTerminalFont(); + void setTerminalOpacity(qreal level); + + //environment + void setEnvironment(const QStringList & environment); + + // Shell program, default is /bin/bash + void setShellProgram(const QString & progname); + + //working directory + void setWorkingDirectory(const QString & dir); + QString workingDirectory(); + + // Shell program args, default is none + void setArgs(const QStringList & args); + + //Text codec, default is UTF-8 + void setTextCodec(QTextCodec * codec); + + /** @brief Sets the color scheme, default is white on black + * + * @param[in] name The name of the color scheme, either returned from + * availableColorSchemes() or a full path to a color scheme. + */ + void setColorScheme(const QString & name); + static QStringList availableColorSchemes(); + + //set size + void setSize(int h, int v); + + // History size for scrolling + void setHistorySize(int lines); //infinite if lines < 0 + + // Presence of scrollbar + void setScrollBarPosition(ScrollBarPosition); + + // Wrapped, scroll to end. + void scrollToEnd(); + + // Send some text to terminal + void sendText(const QString & text); + + // Sets whether flow control is enabled + void setFlowControlEnabled(bool enabled); + + // Returns whether flow control is enabled + bool flowControlEnabled(void); + + /** + * Sets whether the flow control warning box should be shown + * when the flow control stop key (Ctrl+S) is pressed. + */ + void setFlowControlWarningEnabled(bool enabled); + + /*! Get all available keyboard bindings + */ + static QStringList availableKeyBindings(); + + //! Return current key bindings + QString keyBindings(); + + void setMotionAfterPasting(int); + + /** Return the number of lines in the history buffer. */ + int historyLinesCount(); + + int screenColumnsCount(); + + void setSelectionStart(int row, int column); + void setSelectionEnd(int row, int column); + void getSelectionStart(int& row, int& column); + void setSelectionEnd(int& row, int& column); + + /** + * Returns the currently selected text. + * @param preserveLineBreaks Specifies whether new line characters should + * be inserted into the returned text at the end of each terminal line. + */ + QString selectedText(bool preserveLineBreaks = true); + + void setMonitorActivity(bool); + void setMonitorSilence(bool); + void setSilenceTimeout(int seconds); + + /** Returns the available hotspot for the given point \em pos. + * + * This method may return a nullptr if no hotspot is available. + * + * @param[in] pos The point of interest in the QTermWidget coordinates. + * @return Hotspot for the given position, or nullptr if no hotspot. + */ + Filter::HotSpot* getHotSpotAt(const QPoint& pos) const; + + /** Returns the available hotspots for the given row and column. + * + * @return Hotspot for the given position, or nullptr if no hotspot. + */ + Filter::HotSpot* getHotSpotAt(int row, int column) const; + +signals: + void finished(); + void copyAvailable(bool); + + void termGetFocus(); + void termLostFocus(); + + void termKeyPressed(QKeyEvent *); + + void urlActivated(const QUrl&); + + void bell(const QString& message); + + void activity(); + void silence(); + +public slots: + // Copy selection to clipboard + void copyClipboard(); + + // Paste clipboard to terminal + void pasteClipboard(); + + // Paste selection to terminal + void pasteSelection(); + + // Set zoom + void zoomIn(); + void zoomOut(); + + /*! Set named key binding for given widget + */ + void setKeyBindings(const QString & kb); + + /*! Clear the terminal content and move to home position + */ + void clear(); + + void toggleShowSearchBar(); + +protected: + virtual void resizeEvent(QResizeEvent *); + +protected slots: + void sessionFinished(); + void selectionChanged(bool textSelected); + +private slots: + void find(); + void findNext(); + void findPrevious(); + void matchFound(int startColumn, int startLine, int endColumn, int endLine); + void noMatchFound(); + +private: + void search(bool forwards, bool next); + void setZoom(int step); + void init(int startnow); + TermWidgetImpl * m_impl; + SearchBar* m_searchBar; + QVBoxLayout *m_layout; +}; + + +//Maybe useful, maybe not + +#ifdef __cplusplus +extern "C" +#endif +void * createTermWidget(int startnow, void * parent); + +#endif + diff --git a/lib/tools.cpp b/lib/tools.cpp new file mode 100644 index 0000000..487495d --- /dev/null +++ b/lib/tools.cpp @@ -0,0 +1,85 @@ +#include "tools.h" + +#include +#include +#include + + +/*! Helper function to get possible location of layout files. +By default the KB_LAYOUT_DIR is used (linux/BSD/macports). +But in some cases (apple bundle) there can be more locations). +*/ +QString get_kb_layout_dir() +{ +#ifdef BUNDLE_KEYBOARDLAYOUTS + return QLatin1String(":/"); +#else +// qDebug() << __FILE__ << __FUNCTION__; + + QString rval = ""; + QString k(KB_LAYOUT_DIR); + QDir d(k); + + qDebug() << "default KB_LAYOUT_DIR: " << k; + + if (d.exists()) + { + rval = k.append("/"); + return rval; + } + + // subdir in the app location + d.setPath(QCoreApplication::applicationDirPath() + "/kb-layouts/"); + //qDebug() << d.path(); + if (d.exists()) + return QCoreApplication::applicationDirPath() + "/kb-layouts/"; +#ifdef Q_WS_MAC + d.setPath(QCoreApplication::applicationDirPath() + "/../Resources/kb-layouts/"); + if (d.exists()) + return QCoreApplication::applicationDirPath() + "/../Resources/kb-layouts/"; +#endif + qDebug() << "Cannot find KB_LAYOUT_DIR. Default:" << k; + return QString(); +#endif // BUNDLE_KEYBOARDLAYOUTS +} + +/*! Helper function to get possible location of layout files. +By default the COLORSCHEMES_DIR is used (linux/BSD/macports). +But in some cases (apple bundle) there can be more locations). +*/ +QString get_color_schemes_dir() +{ +#ifdef BUNDLE_COLORSCHEMES + return QLatin1String(":/"); +#else +// qDebug() << __FILE__ << __FUNCTION__; + + QString rval = ""; + QString k(COLORSCHEMES_DIR); + QDir d(k); + +// qDebug() << "default COLORSCHEMES_DIR: " << k; + + if (d.exists()) + rval = k.append("/"); + + // subdir in the app location + d.setPath(QCoreApplication::applicationDirPath() + "/color-schemes/"); + //qDebug() << d.path(); + if (d.exists()) + rval = QCoreApplication::applicationDirPath() + "/color-schemes/"; +#ifdef Q_WS_MAC + d.setPath(QCoreApplication::applicationDirPath() + "/../Resources/color-schemes/"); + if (d.exists()) + rval = QCoreApplication::applicationDirPath() + "/../Resources/color-schemes/"; +#endif +#ifdef QT_DEBUG + if(!rval.isEmpty()) { + qDebug() << "Using color-schemes: " << rval; + } else { + qDebug() << "Cannot find color-schemes in any location!"; + } +#endif + return rval; +#endif // BUNDLE_COLORSCHEMES +} diff --git a/lib/tools.h b/lib/tools.h new file mode 100644 index 0000000..b24d88f --- /dev/null +++ b/lib/tools.h @@ -0,0 +1,10 @@ +#ifndef TOOLS_H +#define TOOLS_H + +#include + +QString get_kb_layout_dir(); +QString get_color_schemes_dir(); + + +#endif diff --git a/pyqt4/README b/pyqt4/README new file mode 100644 index 0000000..b3995ce --- /dev/null +++ b/pyqt4/README @@ -0,0 +1,23 @@ +PyQt4 Bindings for QTermWidget + +By Piotr "Riklaunim" Maliński , + Alexander Slesarev + +PyQt4 QTermWidget Bindings License: GPL3 + +INSTALL: +1. Download QTermWidget from http://qtermwidget.sourceforge.net/. +2. Compile and install it: + $ cmake . + $ make + $ sudo make install +If `make install` command will not work just copy the qtermwidget.so* files to /usr/lib directory. +3. Install PyQt4 and PyQt4-devel if not yet installed. +4. Configure, compile and install bindings. Execute in terminal in the qtermwidget bindings folder: + +$ python config.py +$ make +$ sudo make install + +5. You can run ./test.py to test the installed module. + diff --git a/pyqt4/config.py b/pyqt4/config.py new file mode 100755 index 0000000..b133b5b --- /dev/null +++ b/pyqt4/config.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# PyQt4 bindings for th QTermWidget project. +# +# Copyright (C) 2009 Piotr "Riklaunim" Maliński , +# Alexander Slesarev +# +# 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 3 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, see . + +import os +import sipconfig +from PyQt4 import pyqtconfig + +# The name of the SIP build file generated by SIP and used by the build +# system. +build_file = "qtermwidget.sbf" + +# Get the PyQt configuration information. +config = pyqtconfig.Configuration() + +# Get the extra SIP flags needed by the imported qt module. Note that +# this normally only includes those flags (-x and -t) that relate to SIP's +# versioning system. +qt_sip_flags = config.pyqt_sip_flags + +# Run SIP to generate the code. Note that we tell SIP where to find the qt +# module's specification files using the -I flag. +os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "-I", + config.pyqt_sip_dir, qt_sip_flags, "qtermwidget.sip"])) + +# We are going to install the SIP specification file for this module and +# its configuration module. +installs = [] + +installs.append(["qtermwidget.sip", os.path.join(config.default_sip_dir, + "qtermwidget")]) + +installs.append(["qtermwidgetconfig.py", config.default_mod_dir]) + +# Create the Makefile. The QtModuleMakefile class provided by the +# pyqtconfig module takes care of all the extra preprocessor, compiler and +# linker flags needed by the Qt library. +makefile = pyqtconfig.QtGuiModuleMakefile( + configuration = config, + build_file = build_file, + installs = installs) + +# Add the library we are wrapping. The name doesn't include any platform +# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the +# ".dll" extension on Windows). +makefile.extra_lib_dirs.append("..") +makefile.extra_libs = ["qtermwidget"] + +# Generate the Makefile itself. +makefile.generate() + +# Now we create the configuration module. This is done by merging a Python +# dictionary (whose values are normally determined dynamically) with a +# (static) template. +content = { + # Publish where the SIP specifications for this module will be + # installed. + "qtermwidget_sip_dir": config.default_sip_dir, + + # Publish the set of SIP flags needed by this module. As these are the + # same flags needed by the qt module we could leave it out, but this + # allows us to change the flags at a later date without breaking + # scripts that import the configuration module. + "qtermwidget_sip_flags": qt_sip_flags} + +# This creates the qtermwidgetconfig.py module from the qtermwidgetconfig.py.in +# template and the dictionary. +sipconfig.create_config_module("qtermwidgetconfig.py", "config.py.in", content) diff --git a/pyqt4/config.py.in b/pyqt4/config.py.in new file mode 100644 index 0000000..e69de29 diff --git a/pyqt4/qtermwidget.sip b/pyqt4/qtermwidget.sip new file mode 100644 index 0000000..b9b0181 --- /dev/null +++ b/pyqt4/qtermwidget.sip @@ -0,0 +1,35 @@ +%Module QTermWidget 0 + +%Import QtCore/QtCoremod.sip +%Import QtGui/QtGuimod.sip + + +class QTermWidget : QWidget { + +%TypeHeaderCode +#include <../lib/qtermwidget.h> +%End + +public: + QTermWidget(int startnow = 1, QWidget *parent = 0); + ~QTermWidget(); + enum ScrollBarPosition + { + NoScrollBar=0, + ScrollBarLeft=1, + ScrollBarRight=2 + }; + void setTerminalFont(QFont &font); + void setArgs(QStringList &args); + void setTextCodec(QTextCodec *codec); + void setColorScheme(int scheme); + void setSize(int h, int v); + void setHistorySize(int lines); + void setScrollBarPosition(ScrollBarPosition); + void sendText(QString &text); +protected: + void resizeEvent(QResizeEvent *e); +private: + void *createTermWidget(int startnow, void *parent); + +}; diff --git a/pyqt4/qtermwidgetconfig.py b/pyqt4/qtermwidgetconfig.py new file mode 100644 index 0000000..e69de29 diff --git a/pyqt4/test.py b/pyqt4/test.py new file mode 100755 index 0000000..1045f8c --- /dev/null +++ b/pyqt4/test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# PyQt4 bindings for th QTermWidget project. +# +# Copyright (C) 2009 Piotr "Riklaunim" Maliński , +# Alexander Slesarev +# +# 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 3 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, see . + +import sys +from PyQt4 import Qt +import QTermWidget + +a = Qt.QApplication(sys.argv) +w = QTermWidget.QTermWidget() + +w.show() +a.exec_() diff --git a/qtermwidget.spec b/qtermwidget.spec new file mode 100644 index 0000000..a7c023c --- /dev/null +++ b/qtermwidget.spec @@ -0,0 +1,102 @@ +# norootforbuild + +%define libname libqtermwidget0 + +Name: qtermwidget +Summary: Qt4 terminal widget +Version: 0.2.0 +Release: 1 +License: GPL +Source: %{name}-%{version}.tar.bz2 +Group: Utility +URL: https://github.com/qterminal +Vendor: petr@yarpen.cz + + +%if 0%{?fedora_version} + %define breq qt4-devel + %define pref %{buildroot}/usr +%endif +%if 0%{?mandriva_version} + %define breq libqt4-devel + %define pref %{buildroot}/usr +%endif +%if 0%{?suse_version} + %define breq libqt4-devel + %define pref %{_prefix} +%endif + + +BuildRequires: gcc-c++, %{breq}, cmake +BuildRoot: %{_tmppath}/%{name}-%{version}-build +Prefix: %{_prefix} + +%description +QTermWidget is an opensource project based on KDE4 Konsole application. The main goal of this project is to provide unicode-enabled, embeddable QT4 widget for using as a built-in console (or terminal emulation widget). +Of course I`m aware about embedding abilities of original Konsole, but once I had Qt without KDE, and it was a serious problem. I decided not to rely on a chance. I could not find any interesting related project, so I had to write it. +The original Konsole`s code was rewritten entirely with QT4 only; also I have to include in the project some parts of code from kde core library. All code dealing with user interface parts and session management was removed (maybe later I bring it back somehow), and the result is quite useful, I suppose. +This library was compiled and tested on three linux systems, based on 2.4.32, 2.6.20, 2.6.23 kernels, x86 and amd64. Ther is also a sample application provided for quick start. + +%package -n %{libname} +Summary: Qt4 terminal widget - base package +Group: "Development/Libraries/C and C++" +%description -n %{libname} +QTermWidget is an opensource project based on KDE4 Konsole application. +The main goal of this project is to provide unicode-enabled, embeddable +QT4 widget for using as a built-in console (or terminal emulation widget). + +%package devel +Summary: Qt4 terminal widget - development package +Group: "Development/Libraries/C and C++" +Requires: %{libname} +%description devel +Development package for QTermWidget. Contains headers, dev-libs, +and Qt4 designer plugin. + +%prep +%setup + +%build +cmake \ + -DCMAKE_C_FLAGS="%{optflags}" \ + -DCMAKE_CXX_FLAGS="%{optflags}" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_INSTALL_PREFIX=%{pref} \ + %{_builddir}/%{name}-%{version} + +%{__make} %{?jobs:-j%jobs} + + +%install +%makeinstall + + +%clean +%{__rm} -rf %{buildroot} + +%post -n %{libname} +ldconfig + +%postun -n %{libname} +ldconfig + +%files -n %{libname} +%defattr(-,root,root,-) +%doc AUTHORS COPYING Changelog INSTALL README +%{_libdir}/lib%{name}.so.%{version} +%{_datadir}/%{name} +%{_datadir}/%{name}/* + +%files devel +%defattr(-,root,root,-) +%{_includedir}/*.h +%{_libdir}/*.so +%{_libdir}/*.so.0 +%{_libdir}/qt4/plugins/designer/lib%{name}plugin.so + +%changelog +* Mon Oct 29 2010 Petr Vanek 0.2 +- version bump, cmake builds + +* Sat Jul 26 2008 TI_Eugene 0.100 +- Initial build diff --git a/src/README b/src/README new file mode 100644 index 0000000..7f1b85d --- /dev/null +++ b/src/README @@ -0,0 +1 @@ +here is sample program which uses QTermWidet for displaying a terminal \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..509617a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,81 @@ +/* Copyright (C) 2008 e_k (e_k@users.sourceforge.net) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include +#include +#include +#include +#include + +#include "qtermwidget.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QIcon::setThemeName("oxygen"); + QMainWindow *mainWindow = new QMainWindow(); + + QTermWidget *console = new QTermWidget(); + + QMenuBar *menuBar = new QMenuBar(mainWindow); + QMenu *actionsMenu = new QMenu("Actions", menuBar); + menuBar->addMenu(actionsMenu); + actionsMenu->addAction("Find..", console, SLOT(toggleShowSearchBar()), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)); + actionsMenu->addAction("About Qt", &app, SLOT(aboutQt())); + mainWindow->setMenuBar(menuBar); + + QFont font = QApplication::font(); +#ifdef Q_WS_MAC + font.setFamily("Monaco"); +#elif defined(Q_WS_QWS) + font.setFamily("fixed"); +#else + font.setFamily("Monospace"); +#endif + font.setPointSize(12); + + console->setTerminalFont(font); + + // console->setColorScheme(COLOR_SCHEME_BLACK_ON_LIGHT_YELLOW); + console->setScrollBarPosition(QTermWidget::ScrollBarRight); + + foreach (QString arg, QApplication::arguments()) + { + if (console->availableColorSchemes().contains(arg)) + console->setColorScheme(arg); + if (console->availableKeyBindings().contains(arg)) + console->setKeyBindings(arg); + } + + mainWindow->setCentralWidget(console); + mainWindow->resize(600, 400); + + // info output + qDebug() << "* INFO *************************"; + qDebug() << " availableKeyBindings:" << console->availableKeyBindings(); + qDebug() << " keyBindings:" << console->keyBindings(); + qDebug() << " availableColorSchemes:" << console->availableColorSchemes(); + qDebug() << "* INFO END *********************"; + + // real startup + QObject::connect(console, SIGNAL(finished()), mainWindow, SLOT(close())); + + mainWindow->show(); + return app.exec(); +}