diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bb11c0a33..347dd9af6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,7 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
-exclude: '^3rdparty|COPYING|.gitmodules'
+exclude: '^3rdparty|COPYING|.gitmodules|src/libpsi/tools/zip/minizip'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
diff --git a/src/libpsi/.pre-commit-config.yaml b/src/libpsi/.pre-commit-config.yaml
new file mode 100644
index 000000000..65cc5ad1d
--- /dev/null
+++ b/src/libpsi/.pre-commit-config.yaml
@@ -0,0 +1,29 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+
+exclude: '^3rdparty|tools/zip/minizip|COPYING'
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.5.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-added-large-files
+ - id: check-merge-conflict
+- repo: https://github.com/doublify/pre-commit-clang-format
+ # for clang-tidy we can take github.com/pocc/pre-commit-hooks
+ rev: f4c4ac5948aff384af2b439bfabb2bdd65d2b3ac
+ hooks:
+ - id: clang-format
+- repo: https://github.com/Lucas-C/pre-commit-hooks
+ rev: v1.1.7
+ hooks:
+ - id: forbid-crlf
+ - id: remove-crlf
+ - id: forbid-tabs
+ - id: remove-tabs
+- repo: https://github.com/openstack-dev/bashate
+ rev: 2.0.0
+ hooks:
+ - id: bashate
+ args: ['--ignore', 'E006']
diff --git a/src/libpsi/COPYING b/src/libpsi/COPYING
new file mode 100644
index 000000000..db09c1656
--- /dev/null
+++ b/src/libpsi/COPYING
@@ -0,0 +1,295 @@
+
+ As a special exception, the copyright holder(s) give permission to link
+ this program with the Qt Library (commercial or non-commercial edition),
+ and distribute the resulting executable, without including the source
+ code for the Qt library in the source distribution.
+
+ As a special exception, the copyright holder(s) give permission to link
+ this program with any other library, and distribute the resulting
+ executable, without including the source code for the library in the
+ source distribution, provided that the library interfaces with this
+ program only via the following plugin interfaces:
+
+ 1. The Qt Plugin APIs, only as authored by Trolltech
+ 2. The QCA Plugin API, only as authored by Justin Karneges
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
diff --git a/src/libpsi/README b/src/libpsi/README
new file mode 100644
index 000000000..2504a78f1
--- /dev/null
+++ b/src/libpsi/README
@@ -0,0 +1 @@
+libpsi is internal library for Psi and Psi+ projects
diff --git a/src/libpsi/dialogs/CMakeLists.txt b/src/libpsi/dialogs/CMakeLists.txt
new file mode 100644
index 000000000..7b34c3efa
--- /dev/null
+++ b/src/libpsi/dialogs/CMakeLists.txt
@@ -0,0 +1,24 @@
+unset(HEADERS)
+unset(FORMS)
+unset(SOURCES)
+unset(UI_FORMS)
+unset(EXTRA_LDFLAGS)
+
+include_directories(libpsi/dialogs)
+
+list(APPEND HEADERS
+ grepshortcutkeydialog.h
+ )
+
+list(APPEND SOURCES
+ grepshortcutkeydialog.cpp
+ )
+
+list(APPEND FORMS
+ grepshortcutkeydialog.ui
+ )
+
+qt_wrap_ui(UI_FORMS ${FORMS})
+add_library(libpsi_dialogs STATIC ${HEADERS} ${SOURCES} ${UI_FORMS})
+target_link_libraries(libpsi_dialogs ${QT_LIBRARIES})
+target_include_directories(libpsi_dialogs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/src/libpsi/dialogs/grepshortcutkeydialog.cpp b/src/libpsi/dialogs/grepshortcutkeydialog.cpp
new file mode 100644
index 000000000..83fc1e92c
--- /dev/null
+++ b/src/libpsi/dialogs/grepshortcutkeydialog.cpp
@@ -0,0 +1,108 @@
+/*
+ * grepshortcutkeydialog.cpp - a dialog which greps a KeySequence and
+ * emits a signal with this KeySequence as Parameter
+ * Copyright (C) 2006 Cestonaro Thilo
+ *
+ * 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, see .
+ *
+ */
+
+#include "grepshortcutkeydialog.h"
+
+GrepShortcutKeyDialog::GrepShortcutKeyDialog() : QDialog(), gotKey(false)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ ui_.setupUi(this);
+ setWindowTitle(tr("Press shortcut"));
+ displayPressedKeys(QKeySequence());
+}
+
+/**
+ * Grabs the keyboard and proceeds with the default show() call.
+ */
+void GrepShortcutKeyDialog::show()
+{
+ QDialog::show();
+ grabKeyboard();
+ setFocus();
+}
+
+/**
+ * Releases the grabbed keyboard and proceeds with the default close() call.
+ */
+void GrepShortcutKeyDialog::closeEvent(QCloseEvent *event)
+{
+ releaseKeyboard();
+ event->accept();
+}
+
+void GrepShortcutKeyDialog::displayPressedKeys(const QKeySequence &keys)
+{
+ QString str = keys.toString(QKeySequence::NativeText);
+ if (str.isEmpty())
+ str = tr("Set Keys");
+ ui_.shortcutPreview->setText(str);
+}
+
+QKeySequence GrepShortcutKeyDialog::getKeySequence(QKeyEvent *event) const
+{
+ return QKeySequence((isValid(event->key()) ? event->key() : 0) | (event->modifiers() & ~Qt::KeypadModifier));
+}
+
+void GrepShortcutKeyDialog::keyPressEvent(QKeyEvent *event)
+{
+ displayPressedKeys(getKeySequence(event));
+
+ if (!isValid(event->key()) || gotKey)
+ return;
+
+ gotKey = true;
+ emit newShortcutKey(getKeySequence(event));
+ close();
+}
+
+void GrepShortcutKeyDialog::keyReleaseEvent(QKeyEvent *event) { displayPressedKeys(getKeySequence(event)); }
+
+/**
+ * Returns true if \param key could be used in a shortcut.
+ */
+bool GrepShortcutKeyDialog::isValid(int key) const
+{
+ switch (key) {
+ case 0:
+ case Qt::Key_unknown:
+ return false;
+ }
+
+ return !isModifier(key);
+}
+
+/**
+ * Returns true if \param key is modifier.
+ */
+bool GrepShortcutKeyDialog::isModifier(int key) const
+{
+ switch (key) {
+ case Qt::Key_Shift:
+ case Qt::Key_Control:
+ case Qt::Key_Meta:
+ case Qt::Key_Alt:
+ case Qt::Key_AltGr:
+ case Qt::Key_Super_L:
+ case Qt::Key_Super_R:
+ case Qt::Key_Menu:
+ return true;
+ }
+ return false;
+}
diff --git a/src/libpsi/dialogs/grepshortcutkeydialog.h b/src/libpsi/dialogs/grepshortcutkeydialog.h
new file mode 100644
index 000000000..24f2c7cb6
--- /dev/null
+++ b/src/libpsi/dialogs/grepshortcutkeydialog.h
@@ -0,0 +1,57 @@
+/*
+ * grepshortcutkeydialog.h - a dialog which greps a KeySequence and
+ * emits a signal with this KeySequence as Parameter
+ * Copyright (C) 2006 Cestonaro Thilo
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef GREPSHORTCUTKEYDIALOG_H
+#define GREPSHORTCUTKEYDIALOG_H
+
+#include "ui_grepshortcutkeydialog.h"
+
+#include
+#include
+#include
+
+class GrepShortcutKeyDialog : public QDialog {
+ Q_OBJECT
+public:
+ GrepShortcutKeyDialog();
+
+ // reimplemented
+ void show();
+
+protected:
+ // reimplemented
+ void keyPressEvent(QKeyEvent *event);
+ void keyReleaseEvent(QKeyEvent *event);
+ void closeEvent(QCloseEvent *event);
+
+signals:
+ void newShortcutKey(const QKeySequence &key);
+
+private:
+ Ui::GrepShortcutKeyDialog ui_;
+ bool gotKey;
+
+ void displayPressedKeys(const QKeySequence &keys);
+ QKeySequence getKeySequence(QKeyEvent *event) const;
+ bool isValid(int key) const;
+ bool isModifier(int key) const;
+};
+
+#endif // GREPSHORTCUTKEYDIALOG_H
diff --git a/src/libpsi/dialogs/grepshortcutkeydialog.ui b/src/libpsi/dialogs/grepshortcutkeydialog.ui
new file mode 100644
index 000000000..56091eab9
--- /dev/null
+++ b/src/libpsi/dialogs/grepshortcutkeydialog.ui
@@ -0,0 +1,77 @@
+
+ GrepShortcutKeyDialog
+
+
+ Qt::ApplicationModal
+
+
+
+ 0
+ 0
+ 255
+ 50
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+
+
+
+ Qt::NoContextMenu
+
+
+
+ 9
+
+
+ 6
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Qt::AlignHCenter
+
+
+ true
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Cancel
+
+
+
+
+
+
+
+
+ pushButton
+ clicked()
+ GrepShortcutKeyDialog
+ close()
+
+
+ 207
+ 83
+
+
+ 100
+ 23
+
+
+
+
+
diff --git a/src/libpsi/tools/CMakeLists.txt b/src/libpsi/tools/CMakeLists.txt
new file mode 100644
index 000000000..a71b842cb
--- /dev/null
+++ b/src/libpsi/tools/CMakeLists.txt
@@ -0,0 +1,280 @@
+set(CMAKE_CXX_STANDARD 17)
+
+unset(HEADERS)
+unset(FORMS)
+unset(SOURCES)
+unset(UI_FORMS)
+unset(EXTRA_LDFLAGS)
+
+if(APPLE)
+ if(USE_GROWL)
+ list(APPEND EXTRA_LDFLAGS
+ "-framework Growl -framework Cocoa"
+ )
+ list(APPEND HEADERS
+ growlnotifier/growlnotifier.h
+ )
+ list(APPEND SOURCES
+ growlnotifier/growlnotifier.mm
+ )
+ endif()
+ if(USE_MAC_DOC)
+ list(APPEND EXTRA_LDFLAGS
+ "-framework Carbon"
+ )
+ list(APPEND HEADERS
+ mac_dock/mac_dock.h
+ mac_dock/privateqt_mac.h
+ )
+ list(APPEND SOURCES
+ mac_dock/mac_dock.mm
+ mac_dock/privateqt_mac.mm
+ )
+ endif()
+endif()
+
+list(APPEND HEADERS
+ # tools
+ priorityvalidator.h
+
+ # idle
+ idle/idle.h
+
+ # systemwatch
+ systemwatch/systemwatch.h
+
+ # globalshortcut
+ globalshortcut/globalshortcuttrigger.h
+
+ # tools
+ maybe.h
+ iodeviceopener.h
+ languagemanager.h
+
+ # atomicxmlfile
+ atomicxmlfile/atomicxmlfile.h
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager.h
+
+ # simplecli
+ simplecli/simplecli.h
+
+ # spellchecker
+ spellchecker/spellchecker.h
+ spellchecker/spellhighlighter.h
+ )
+
+list(APPEND SOURCES
+ # tools
+ priorityvalidator.cpp
+ languagemanager.cpp
+
+ # spellchecker
+ spellchecker/spellchecker.cpp
+ spellchecker/spellhighlighter.cpp
+
+ # tools
+ iodeviceopener.cpp
+
+ # idle
+ idle/idle.cpp
+
+ # atomicxmlfile
+ atomicxmlfile/atomicxmlfile.cpp
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager.cpp
+
+ # systemwatch
+ systemwatch/systemwatch.cpp
+
+ # simplecli
+ simplecli/simplecli.cpp
+ )
+
+if(APPLE)
+ list(APPEND HEADERS
+ mac_dock/mac_dock.h
+ mac_dock/privateqt_mac.h
+ )
+
+ list(APPEND SOURCES
+ # globalshortcut
+ globalshortcut/globalshortcutmanager_mac.mm
+ globalshortcut/NDKeyboardLayout.m
+
+ mac_dock/mac_dock.mm
+ mac_dock/privateqt_mac.mm
+ )
+
+ list(APPEND HEADERS
+ # systemwatch
+ systemwatch/systemwatch_mac.h
+
+ # globalshortcut
+ globalshortcut/NDKeyboardLayout.h
+ )
+
+ list(APPEND SOURCES
+ #idle
+ idle/idle_mac.cpp
+
+ # systemwatch
+ systemwatch/systemwatch_mac.cpp
+ )
+elseif(WIN32)
+ list(APPEND HEADERS
+ # spellchecker
+ spellchecker/hunspellchecker.h
+ )
+
+ list(APPEND SOURCES
+ #idle
+ idle/idle_win.cpp
+
+ # systemwatch
+ systemwatch/systemwatch_win.cpp
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager_win.cpp
+
+ # spellchecker
+ spellchecker/hunspellchecker.cpp
+ )
+elseif(HAIKU)
+ list(APPEND HEADERS
+ # systemwatch
+ systemwatch/systemwatch_unix.h
+ )
+
+ list(APPEND SOURCES
+ #idle
+ idle/idle_x11.cpp
+
+ # systemwatch
+ systemwatch/systemwatch_unix.cpp
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager_haiku.cpp
+ )
+elseif(USE_X11)
+ list(APPEND HEADERS
+ # systemwatch
+ systemwatch/systemwatch_unix.h
+ )
+
+ list(APPEND SOURCES
+ #idle
+ idle/idle_x11.cpp
+
+ # systemwatch
+ systemwatch/systemwatch_unix.cpp
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager_x11.cpp
+ )
+else()
+ list(APPEND HEADERS
+ # systemwatch
+ systemwatch/systemwatch_unix.h
+ )
+
+ list(APPEND SOURCES
+ #idle
+ idle/idle_x11.cpp
+
+ # systemwatch
+ systemwatch/systemwatch_unix.cpp
+
+ # globalshortcut
+ globalshortcut/globalshortcutmanager_stub.cpp
+ )
+endif()
+
+# spellchecker
+if(USE_ENCHANT)
+ if(Enchant_VERSION)
+ if(${Enchant_VERSION} VERSION_LESS "2.0")
+ add_definitions(-DHAVE_ENCHANT)
+ else()
+ add_definitions(-DHAVE_ENCHANT2)
+ endif()
+ message(STATUS "Enchant version - ${Enchant_VERSION}")
+ else()
+ add_definitions(-DHAVE_ENCHANT)
+ endif()
+
+ include_directories(
+ ${Enchant_INCLUDE_DIR}
+ )
+
+ list(APPEND EXTRA_LDFLAGS
+ ${Enchant_LIBRARY}
+ )
+
+ list(APPEND HEADERS
+ spellchecker/enchantchecker.h
+ )
+
+ list(APPEND SOURCES
+ spellchecker/enchantchecker.cpp
+ )
+elseif(USE_HUNSPELL)
+ add_definitions(-DHAVE_HUNSPELL)
+
+ if(MSVC)
+ add_definitions(-DHUNSPELL_STATIC)
+ endif()
+
+ include_directories(
+ ${HUNSPELL_INCLUDE_DIR}
+ )
+
+ list(APPEND EXTRA_LDFLAGS
+ ${HUNSPELL_LIBRARY}
+ )
+
+ list(APPEND HEADERS
+ spellchecker/hunspellchecker.h
+ )
+
+ list(APPEND SOURCES
+ spellchecker/hunspellchecker.cpp
+ )
+elseif(APPLE)
+ list(APPEND SOURCES
+ spellchecker/macspellchecker.mm
+ )
+
+ list(APPEND HEADERS
+ spellchecker/macspellchecker.h
+ )
+elseif(USE_ASPELL)
+ add_definitions(-DHAVE_ASPELL)
+ include_directories(
+ ${ASPELL_INCLUDE_DIR}
+ )
+ list(APPEND EXTRA_LDFLAGS
+ ${ASPELL_LIBRARIES}
+ )
+ list(APPEND HEADERS
+ spellchecker/aspellchecker.h
+ )
+ list(APPEND SOURCES
+ spellchecker/aspellchecker.cpp
+ )
+endif()
+
+if(LINUX AND USE_XSS)
+ find_package(X11 COMPONENTS Xss REQUIRED)
+ include_directories(${X11_Xscreensaver_INCLUDE_PATH})
+ list(APPEND EXTRA_LDFLAGS ${X11_Xscreensaver_LIB})
+endif()
+
+qt_wrap_ui(UI_FORMS ${FORMS})
+add_library(libpsi_tools STATIC ${SOURCES} ${HEADERS} ${UI_FORMS})
+target_link_libraries(libpsi_tools iris ${QT_LIBRARIES} tools ${EXTRA_LDFLAGS})
+target_include_directories(libpsi_tools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+
+add_subdirectory(zip)
diff --git a/src/libpsi/tools/atomicxmlfile/atomicxmlfile.cpp b/src/libpsi/tools/atomicxmlfile/atomicxmlfile.cpp
new file mode 100644
index 000000000..e3e0bd869
--- /dev/null
+++ b/src/libpsi/tools/atomicxmlfile/atomicxmlfile.cpp
@@ -0,0 +1,134 @@
+/*
+ * atomicxmlfile.cpp - atomic saving of QDomDocuments in files
+ * Copyright (C) 2007 Michail Pishchagin
+ *
+ * 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, see .
+ *
+ */
+
+#include "atomicxmlfile.h"
+
+#include
+#include
+#include
+
+/**
+ * Creates new instance of AtomicXmlFile class that will be able to
+ * atomically save config file, so if application is terminated while
+ * saving config file, data is not lost.
+ */
+AtomicXmlFile::AtomicXmlFile(const QString &fileName) : fileName_(fileName) { }
+
+QStringList AtomicXmlFile::loadCandidateList() const
+{
+ QStringList fileNames;
+ fileNames << fileName_ << tempFileName() << backupFileName();
+ return fileNames;
+}
+
+/**
+ * Returns name of the file the config is first written to.
+ */
+QString AtomicXmlFile::tempFileName() const { return fileName_ + ".temp"; }
+
+/**
+ * Returns name of the back up file.
+ */
+QString AtomicXmlFile::backupFileName() const { return fileName_ + ".backup"; }
+
+bool AtomicXmlFile::saveDocument(const QDomDocument &doc, QString fileName) const
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ return false;
+ }
+
+ QTextStream text(&file);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ text.setCodec("UTF-8");
+#else
+ text.setEncoding(QStringConverter::Utf8);
+#endif
+ text << doc.toString();
+ text.flush();
+
+ bool res = (file.error() == QFile::NoError);
+ if (res)
+ res = file.flush();
+ file.close();
+
+ return res;
+}
+
+bool AtomicXmlFile::loadDocument(QDomDocument *doc, QString fileName) const
+{
+ Q_ASSERT(doc);
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+
+ return bool(doc->setContent(&file));
+}
+
+bool AtomicXmlFile::saveDocument(AtomicXmlFileWriter *writer, QString fileName) const
+{
+ Q_ASSERT(writer);
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+
+ if (!writer->write(&file)) {
+ return false;
+ }
+
+ return file.error() == QFile::NoError;
+}
+
+bool AtomicXmlFile::loadDocument(AtomicXmlFileReader *reader, QString fileName) const
+{
+ Q_ASSERT(reader);
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+
+ if (!reader->read(&file)) {
+ qWarning("Parse error in file %s at line %d, column %d:\n%s", qPrintable(fileName), (int)reader->lineNumber(),
+ (int)reader->columnNumber(), qPrintable(reader->errorString()));
+
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Check if an AtomicXmlFile exists.
+ * returns true if any of the files loadDocument tries to read exists,
+ * it *doesn't* check that there is at least one uncorupted file.
+ */
+bool AtomicXmlFile::exists(const QString &fileName)
+{
+ AtomicXmlFile tmp(fileName);
+
+ const QStringList &fileNames(tmp.loadCandidateList());
+ for (const QString &fileName : fileNames) {
+ if (QFile::exists(fileName)) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/libpsi/tools/atomicxmlfile/atomicxmlfile.h b/src/libpsi/tools/atomicxmlfile/atomicxmlfile.h
new file mode 100644
index 000000000..c21bae513
--- /dev/null
+++ b/src/libpsi/tools/atomicxmlfile/atomicxmlfile.h
@@ -0,0 +1,113 @@
+/*
+ * atomicxmlfile.h - atomic saving of QDomDocuments in files
+ * Copyright (C) 2007 Michail Pishchagin
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef ATOMICXMLFILE_H
+#define ATOMICXMLFILE_H
+
+#include
+#include
+#include
+#include
+#include
+
+class QDomDocument;
+
+class AtomicXmlFileReader : public QXmlStreamReader {
+public:
+ virtual ~AtomicXmlFileReader() { }
+ virtual bool read(QIODevice *device) = 0;
+};
+
+class AtomicXmlFileWriter : public QXmlStreamWriter {
+public:
+ virtual ~AtomicXmlFileWriter() { }
+ virtual bool write(QIODevice *device) = 0;
+};
+
+class AtomicXmlFile {
+public:
+ AtomicXmlFile(const QString &fileName);
+
+ /**
+ * Atomically save \a writer to specified name. Prior to saving, back up
+ * of old config data is created, and only then data is saved.
+ */
+ template bool saveDocument(T writer) const
+ {
+ if (!saveDocument(writer, tempFileName())) {
+ qWarning("AtomicXmlFile::saveDocument(): Unable to save '%s'. Possibly drive is full.",
+ qPrintable(tempFileName()));
+ return false;
+ }
+
+ if (QFile::exists(fileName_)) {
+ if (QFile::exists(backupFileName()))
+ QFile::remove(backupFileName());
+ if (!QFile::rename(fileName_, backupFileName())) {
+ qWarning("AtomicXmlFile::saveDocument(): Unable to rename '%s' to '%s'.", qPrintable(fileName_),
+ qPrintable(backupFileName()));
+ return false;
+ }
+ }
+
+ if (!QFile::rename(tempFileName(), fileName_)) {
+ qWarning("AtomicXmlFile::saveDocument(): Unable to rename '%s' to '%s'.", qPrintable(tempFileName()),
+ qPrintable(fileName_));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Tries to load \a reader from config file, or if that fails, from a back up.
+ */
+ template bool loadDocument(T reader) const
+ {
+ Q_ASSERT(reader);
+
+ const QStringList &fileNames(loadCandidateList());
+ for (const QString &fileName : fileNames) {
+ if (loadDocument(reader, fileName)) {
+ return true;
+ }
+ if (QFile::exists(fileName)) {
+ // The file exists, but it is incorrect. Remove it. Otherwise, enter the backup.
+ QFile::remove(fileName);
+ }
+ }
+
+ return false;
+ }
+
+ static bool exists(const QString &fileName);
+
+private:
+ QString fileName_;
+
+ QString tempFileName() const;
+ QString backupFileName() const;
+ QStringList loadCandidateList() const;
+ bool saveDocument(const QDomDocument &doc, QString fileName) const;
+ bool loadDocument(QDomDocument *doc, QString fileName) const;
+ bool saveDocument(AtomicXmlFileWriter *writer, QString fileName) const;
+ bool loadDocument(AtomicXmlFileReader *reader, QString fileName) const;
+};
+
+#endif // ATOMICXMLFILE_H
diff --git a/src/libpsi/tools/globalshortcut/NDKeyboardLayout.h b/src/libpsi/tools/globalshortcut/NDKeyboardLayout.h
new file mode 100644
index 000000000..ada0985cb
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/NDKeyboardLayout.h
@@ -0,0 +1,149 @@
+/*
+ NDKeyboardLayout.h
+
+ Created by Nathan Day on 01.18.10 under a MIT-style license.
+ Copyright (c) 2010 Nathan Day
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+/*!
+ @header NDKeyboardLayout.h
+ @abstract Header file for NDKeyboardLayout
+ @author Nathan Day
+ */
+
+#import
+#import
+#import
+
+struct ReverseMappingEntry;
+
+extern NSString *const NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification;
+extern NSString *const NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey;
+
+/*!
+ @function NDCocoaModifierFlagsForCarbonModifierFlags
+ Convert Carbon modifer flags to Cocoa modifier flags.
+ @param modifierFlags one or more of the flags shiftKey, controlKey, optionKey,
+ cmdKey
+ */
+NSUInteger NDCocoaModifierFlagsForCarbonModifierFlags(NSUInteger modifierFlags);
+/*!
+ @function NDCarbonModifierFlagsForCocoaModifierFlags
+ Convert Cocoa modifer flags to Carbon modifier flags.
+ @param modifierFlags one or more of the flags NSShiftKeyMask, NSControlKeyMask,
+ NSAlternateKeyMask, NSCommandKeyMask
+ */
+NSUInteger NDCarbonModifierFlagsForCocoaModifierFlags(NSUInteger modifierFlags);
+
+/*!
+ @class NDKeyboardLayout
+ @abstract Class for translating between key codes and key characters.
+ @discussion The key code for each key character can change between hardware and with localisation,
+ NDKeyboardLayout handles translation between key codes and key characters as well as for generating strings
+ for display purposes.
+ @helps Used by NDHotKeyEvent.
+ */
+@interface NDKeyboardLayout : NSObject {
+ CFDataRef keyboardLayoutData;
+ struct ReverseMappingEntry *mappings;
+ NSUInteger numberOfMappings;
+}
+
+@property (readonly, nonatomic) const UCKeyboardLayout *keyboardLayoutPtr;
+
+/*!
+ @method keyboardLayout
+ Get a keyboard layout for the current keyboard
+ */
++ (id)keyboardLayout;
+
+/*!
+ @method init
+ initialise a keyboard layout for the current keyboard, if that fails a keyboard layout for one of the languages
+ returned from [NSLocale preferredLanguages] is attempted and if finally if that fails a keyboard layout
+ for the most recently used ASCII-capable keyboard is created. If that fails then this method returns nil.
+ */
+- (id)init;
+/*!
+ @method initWithLanguage:
+ @abstract initialise a keyboard layout.
+ @discussion Initialises a KeyboardLayout with an TISInputSourceRef for the supplied language.
+ */
+- (id)initWithLanguage:(NSString *)langauge;
+/*!
+ @method initWithInputSource:
+ @abstract initialise a keyboard layout.
+ @discussion Initialises a KeyboardLayout with an TISInputSourceRef, this method is called with the result
+ from initWithInputSource:TISCopyCurrentKeyboardInputSource().
+ */
+- (id)initWithInputSource:(TISInputSourceRef)source;
+
+/*!
+ @method stringForCharacter:modifierFlags:
+ @abstract Get a string for display purposes.
+ @discussion stringForCharacter:modifierFlags: returns a string that can be displayed to the user, For
+ example command-z would produce ⌘Z, shift-T would produce ⇧T.
+ @param character The unmodified character on the keyboard.
+ @param modifierFlags Modifier flags NSControlKeyMask, NSAlternateKeyMask, NSShiftKeyMask,
+ NSCommandKeyMask and NSNumericPadKeyMask.
+ */
+- (NSString *)stringForCharacter:(unichar)character modifierFlags:(UInt32)modifierFlags;
+/*!
+ @method stringForKeyCode:modifierFlags:
+ @abstract Get a string for display purposes.
+ @discussion stringForKeyCode:modifierFlags: returns a string that can be displayed to the user. This method
+ is called by stringForCharacter::modifierFlags and is problem more useful most of the time.
+ @param keyCode A value specifying the virtual key code that is to be translated. For ADB keyboards, virtual key
+ codes are in the range from 0 to 127.
+ @param modifierFlags Modifier flags NSControlKeyMask, NSAlternateKeyMask, NSShiftKeyMask,
+ NSCommandKeyMask and NSNumericPadKeyMask.
+ */
+- (NSString *)stringForKeyCode:(UInt16)keyCode modifierFlags:(UInt32)modifierFlags;
+/*!
+ @method characterForKeyCode:
+ @abstract Get the key character for a given key code.
+ @discussion The character returned is the unmodified version on the keyboard.
+ @param keyCode A value specifying the virtual key code that is to be translated. For ADB keyboards, virtual key
+ codes are in the range from 0 to 127.
+ @result The character for the unmodified version of the key.
+ */
+- (unichar)characterForKeyCode:(UInt16)keyCode;
+/*!
+ @method keyCodeForCharacter:numericPad:
+ @abstract Get the key code for a given key character.
+ @discussion The character pass in must be the unshifter character for the key, for example to get the key code for
+ the '?' on keyboards where you type shift-/ to get '?' you should pass in the character '/"
+ @param character The unmodified character on the keyboard.
+ @param numericPad For the keycode of a key on the keypad where the same character is also on the main keyboard this
+ flag needs to be YES.
+ */
+- (UInt16)keyCodeForCharacter:(unichar)character numericPad:(BOOL)numericPad;
+/*!
+ @method keyCodeForCharacter:
+ @abstract Get the key code for a given key character.
+ @discussion Calls keyCodeForCharacter:numericPad: with the keypad flag set to NO
+ @param character The unmodified character on the keyboard.
+ @result A value specifying the virtual key code that is to be translated. For ADB keyboards, virtual key codes are
+ in the range from 0 to 127.
+ */
+- (UInt16)keyCodeForCharacter:(unichar)character;
+
+@end
diff --git a/src/libpsi/tools/globalshortcut/NDKeyboardLayout.m b/src/libpsi/tools/globalshortcut/NDKeyboardLayout.m
new file mode 100644
index 000000000..11e7cf25e
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/NDKeyboardLayout.m
@@ -0,0 +1,471 @@
+/*
+ NDKeyboardLayout.m
+
+ Created by Nathan Day on 01.18.10 under a MIT-style license.
+ Copyright (c) 2010 Nathan Day
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+#import "NDKeyboardLayout.h"
+
+#include
+
+NSString *const NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification
+ = @"NDKeyboardLayoutSelectedKeyboardInputSourceChanged";
+NSString *const NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey = @"NDKeyboardLayoutPreviousKeyboardLayout";
+
+struct ReverseMappingEntry {
+ UniChar character;
+ BOOL keypad;
+ UInt16 keyCode;
+};
+
+struct UnmappedEntry {
+ UniChar character;
+ UInt16 keyCode;
+ unichar description[4];
+};
+
+struct UnmappedEntry unmappedKeys[] = {
+ { NSDeleteFunctionKey, 0x33, { 0x232B, '\0', '\0', '\0' } },
+ { NSF17FunctionKey, 0x40, { 'F', '1', '7', '\0' } },
+ { NSClearDisplayFunctionKey, 0x47, { 0x2327, '\0', '\0', '\0' } },
+ { NSF18FunctionKey, 0x4F, { 'F', '1', '8', '\0' } },
+ { NSF19FunctionKey, 0x50, { 'F', '1', '9', '\0' } },
+ { NSF5FunctionKey, 0x60, { 'F', '5', '\0', '\0' } },
+ { NSF6FunctionKey, 0x61, { 'F', '6', '\0', '\0' } },
+ { NSF7FunctionKey, 0x62, { 'F', '7', '\0', '\0' } },
+ { NSF3FunctionKey, 0x63, { 'F', '3', '\0', '\0' } },
+ { NSF8FunctionKey, 0x64, { 'F', '8', '\0', '\0' } },
+ { NSF9FunctionKey, 0x65, { 'F', '9', '\0', '\0' } },
+ { NSF11FunctionKey, 0x67, { 'F', '1', '1', '\0' } },
+ { NSF14FunctionKey, 0x68, { 'F', '1', '4', '\0' } },
+ { NSF13FunctionKey, 0x69, { 'F', '1', '3', '\0' } },
+ { NSF16FunctionKey, 0x6A, { 'F', '1', '6', '\0' } },
+ { NSF10FunctionKey, 0x6D, { 'F', '1', '0', '\0' } },
+ { NSF12FunctionKey, 0x6F, { 'F', '1', '2', '\0' } },
+ { NSF15FunctionKey, 0x71, { 'F', '1', '5', '\0' } },
+ { NSHomeFunctionKey, 0x73, { 0x21F1, '\0', '\0', '\0' } },
+ { NSPageUpFunctionKey, 0x74, { 0x21DE, '\0', '\0', '\0' } },
+ { NSDeleteCharFunctionKey, 0x75, { 0x2326, '\0', '\0', '\0' } },
+ { NSF4FunctionKey, 0x76, { 'F', '4', '\0', '\0' } },
+ { NSEndFunctionKey, 0x77, { 0x21F2, '\0', '\0', '\0' } },
+ { NSF2FunctionKey, 0x78, { 'F', '2', '\0', '\0' } },
+ { NSPageDownFunctionKey, 0x79, { 0x21DF, '\0', '\0', '\0' } },
+ { NSF1FunctionKey, 0x7A, { 'F', '1', '\0', '\0' } },
+ { NSLeftArrowFunctionKey, 0x7B, { 0x2190, '\0', '\0', '\0' } },
+ { NSRightArrowFunctionKey, 0x7C, { 0x2192, '\0', '\0', '\0' } },
+ { NSDownArrowFunctionKey, 0x7D, { 0x2193, '\0', '\0', '\0' } },
+ { NSUpArrowFunctionKey, 0x7E, { 0x2191, '\0', '\0', '\0' } }
+ // {NSF20FunctionKey, 0xXXXX},
+ // {NSF21FunctionKey, 0xXXXX},
+ // {NSF22FunctionKey, 0xXXXX},
+ // {NSF23FunctionKey, 0xXXXX},
+ // {NSF24FunctionKey, 0xXXXX},
+ // {NSF25FunctionKey, 0xXXXX},
+ // {NSF26FunctionKey, 0xXXXX},
+ // {NSF27FunctionKey, 0xXXXX},
+ // {NSF28FunctionKey, 0xXXXX},
+ // {NSF29FunctionKey, 0xXXXX},
+ // {NSF30FunctionKey, 0xXXXX},
+ // {NSF31FunctionKey, 0xXXXX},
+ // {NSF32FunctionKey, 0xXXXX},
+ // {NSF33FunctionKey, 0xXXXX},
+ // {NSF34FunctionKey, 0xXXXX},
+ // {NSF35FunctionKey, 0xXXXX},
+ // {NSInsertFunctionKey, 0xXXXX},
+ // {NSBeginFunctionKey, 0xXXXX},
+ // {NSPrintScreenFunctionKey, 0xXXXX},
+ // {NSScrollLockFunctionKey, 0xXXXX},
+ // {NSPauseFunctionKey, 0xXXXX},
+ // {NSSysReqFunctionKey, 0xXXXX},
+ // {NSBreakFunctionKey, 0xXXXX},
+ // {NSResetFunctionKey, 0xXXXX},
+ // {NSStopFunctionKey, 0xXXXX},
+ // {NSMenuFunctionKey, 0xXXXX},
+ // {NSUserFunctionKey, 0xXXXX},
+ // {NSSystemFunctionKey, 0xXXXX},
+ // {NSPrintFunctionKey, 0xXXXX},
+ // {NSClearLineFunctionKey, 0xXXXX},
+ // {NSInsertLineFunctionKey, 0xXXXX},
+ // {NSDeleteLineFunctionKey, 0xXXXX},
+ // {NSInsertCharFunctionKey, 0xXXXX},
+ // {NSPrevFunctionKey, 0xXXXX},
+ // {NSNextFunctionKey, 0xXXXX},
+ // {NSSelectFunctionKey, 0xXXXX},
+ // {NSExecuteFunctionKey, 0xXXXX},
+ // {NSUndoFunctionKey, 0xXXXX},
+ // {NSRedoFunctionKey, 0xXXXX},
+ // {NSFindFunctionKey, 0xXXXX},
+ // {NSHelpFunctionKey, 0xXXXX},
+ // {NSModeSwitchFunctionKey, 0xXXXX}
+};
+
+/*@interface NDKeyboardLayout ()
+{
+@private
+ CFDataRef keyboardLayoutData;
+ struct ReverseMappingEntry * mappings;
+ NSUInteger numberOfMappings;
+}
+
+@property(readonly,nonatomic) const UCKeyboardLayout * keyboardLayoutPtr;
+
+@end*/
+
+static int _reverseMappingEntryCmpFunc(const void *a, const void *b)
+{
+ struct ReverseMappingEntry *theA = (struct ReverseMappingEntry *)a, *theB = (struct ReverseMappingEntry *)b;
+ return theA->character != theB->character ? theA->character - theB->character : theA->keypad - theB->keypad;
+}
+
+static struct ReverseMappingEntry *_searchreverseMapping(struct ReverseMappingEntry *aMapping, NSUInteger aLength,
+ struct ReverseMappingEntry *aSearchValue)
+{
+ NSInteger low = 0, high = aLength - 1, mid, result;
+
+ while (low <= high) {
+ mid = (low + high) >> 1;
+ result = _reverseMappingEntryCmpFunc(&aMapping[mid], aSearchValue);
+ if (result > 0)
+ high = mid - 1;
+ else if (result < 0)
+ low = mid + 1;
+ else
+ return &aMapping[mid];
+ }
+ return NULL;
+}
+
+static struct UnmappedEntry *_unmappedEntryForKeyCode(UInt16 aKeyCode)
+{
+ NSInteger low = 0, high = sizeof(unmappedKeys) / sizeof(*unmappedKeys) - 1, mid, result;
+
+ while (low <= high) {
+ mid = (low + high) >> 1;
+ result = unmappedKeys[mid].keyCode - aKeyCode;
+ if (result > 0)
+ high = mid - 1;
+ else if (result < 0)
+ low = mid + 1;
+ else
+ return &unmappedKeys[mid];
+ }
+ return '\0';
+}
+
+static const size_t kBufferSize = 4;
+static NSUInteger _characterForModifierFlags(unichar aBuff[kBufferSize], UInt32 aModifierFlags)
+{
+ NSUInteger thePos = 0;
+ memset(aBuff, 0, kBufferSize);
+ if (aModifierFlags & NSControlKeyMask)
+ aBuff[thePos++] = kControlUnicode;
+
+ if (aModifierFlags & NSAlternateKeyMask)
+ aBuff[thePos++] = kOptionUnicode;
+
+ if (aModifierFlags & NSShiftKeyMask)
+ aBuff[thePos++] = kShiftUnicode;
+
+ if (aModifierFlags & NSCommandKeyMask)
+ aBuff[thePos++] = kCommandUnicode;
+ return thePos;
+}
+
+/*
+ * NDCocoaModifierFlagsForCarbonModifierFlags()
+ */
+NSUInteger NDCocoaModifierFlagsForCarbonModifierFlags(NSUInteger aModifierFlags)
+{
+ NSUInteger theCocoaModifierFlags = 0;
+
+ if (aModifierFlags & shiftKey)
+ theCocoaModifierFlags |= NSShiftKeyMask;
+
+ if (aModifierFlags & controlKey)
+ theCocoaModifierFlags |= NSControlKeyMask;
+
+ if (aModifierFlags & optionKey)
+ theCocoaModifierFlags |= NSAlternateKeyMask;
+
+ if (aModifierFlags & cmdKey)
+ theCocoaModifierFlags |= NSCommandKeyMask;
+
+ return theCocoaModifierFlags;
+}
+
+/*
+ * NDCarbonModifierFlagsForCocoaModifierFlags()
+ */
+NSUInteger NDCarbonModifierFlagsForCocoaModifierFlags(NSUInteger aModifierFlags)
+{
+ NSUInteger theCarbonModifierFlags = 0;
+
+ if (aModifierFlags & NSShiftKeyMask)
+ theCarbonModifierFlags |= shiftKey;
+
+ if (aModifierFlags & NSControlKeyMask)
+ theCarbonModifierFlags |= controlKey;
+
+ if (aModifierFlags & NSAlternateKeyMask)
+ theCarbonModifierFlags |= optionKey;
+
+ if (aModifierFlags & NSCommandKeyMask)
+ theCarbonModifierFlags |= cmdKey;
+
+ return theCarbonModifierFlags;
+}
+
+@implementation NDKeyboardLayout
+
+#pragma mark Utility Methods
+
+- (void)generateMappings
+{
+ mappings = (struct ReverseMappingEntry *)calloc(128 + sizeof(unmappedKeys) / sizeof(*unmappedKeys),
+ sizeof(struct ReverseMappingEntry));
+
+ numberOfMappings = 0;
+
+ for (NSUInteger i = 0; i < 128; i++) {
+ UInt32 theDeadKeyState = 0;
+ UniCharCount theLength = 0;
+
+ if (UCKeyTranslate(self.keyboardLayoutPtr, i, kUCKeyActionDisplay, 0, LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
+ &mappings[numberOfMappings].character)
+ == noErr
+ && theLength > 0 && isprint(mappings[numberOfMappings].character)) {
+ mappings[numberOfMappings].keyCode = i;
+ numberOfMappings++;
+ }
+ }
+
+ /* add unmapped keys */
+ for (NSUInteger i = 0; i < sizeof(unmappedKeys) / sizeof(*unmappedKeys); i++) {
+ mappings[numberOfMappings].character = unmappedKeys[i].character;
+ mappings[numberOfMappings].keyCode = unmappedKeys[i].keyCode;
+ numberOfMappings++;
+ }
+
+ mappings = (struct ReverseMappingEntry *)realloc((void *)mappings,
+ numberOfMappings * sizeof(struct ReverseMappingEntry));
+
+ // sort so we can perform binary searches
+ qsort((void *)mappings, numberOfMappings, sizeof(struct ReverseMappingEntry), _reverseMappingEntryCmpFunc);
+
+ /* find keypad keys and set the keypad flag */
+ for (NSUInteger i = 1; i < numberOfMappings; i++) {
+ NSParameterAssert(mappings[i - 1].keyCode != mappings[i].keyCode);
+ if (mappings[i - 1].character == mappings[i].character) // assume large keycode is a keypad
+ {
+ if (mappings[i - 1].keyCode > mappings[i].keyCode) // make the keypad entry is second
+ {
+ UInt16 theTemp = mappings[i - 1].keyCode;
+ mappings[i - 1].keyCode = mappings[i].keyCode;
+ mappings[i].keyCode = theTemp;
+ }
+ mappings[i].keypad = YES;
+ }
+ }
+
+#ifdef DEBUGGING_CODE
+ for (NSUInteger i = 1; i < numberOfMappings; i++) {
+ fprintf(stderr, "%d -> %c[%d]%s\n", mappings[i].keyCode, (char)mappings[i].character, mappings[i].character,
+ mappings[i].keypad ? " keypad" : "");
+ NSAssert3(mappings[i - 1].character <= mappings[i].character, @"[%d] %d <= %d", i, mappings[i - 1].character,
+ mappings[i].character);
+ }
+#endif
+}
+
+#pragma mark Constructor Methods
+
+static volatile NDKeyboardLayout *kCurrentKeyboardLayout = nil;
+
+void NDKeyboardLayoutNotificationCallback(CFNotificationCenterRef aCenter, void *self, CFStringRef aName,
+ const void *anObj, CFDictionaryRef aUserInfo)
+{
+ (void)aCenter;
+ (void)aName;
+ (void)anObj;
+ (void)aUserInfo;
+ NSDictionary *theUserInfo = [NSDictionary dictionaryWithObject:kCurrentKeyboardLayout
+ forKey:NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey];
+ @synchronized(self) {
+ [kCurrentKeyboardLayout release], kCurrentKeyboardLayout = nil;
+ }
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification
+ object:self
+ userInfo:theUserInfo];
+}
+
++ (void)initialize
+{
+ CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), (const void *)self,
+ NDKeyboardLayoutNotificationCallback, kTISNotifySelectedKeyboardInputSourceChanged,
+ NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
+}
+
++ (id)keyboardLayout
+{
+ if (kCurrentKeyboardLayout == nil) {
+ @synchronized(self) { /*
+ Try different method until we succeed.
+ */
+ TISInputSourceRef (*theInputSourceFunctions[])()
+ = { TISCopyInputMethodKeyboardLayoutOverride, TISCopyCurrentKeyboardLayoutInputSource,
+ TISCopyCurrentASCIICapableKeyboardLayoutInputSource };
+
+ for (NSUInteger i = 0; i < sizeof(theInputSourceFunctions) / sizeof(*theInputSourceFunctions)
+ && kCurrentKeyboardLayout == nil;
+ i++) {
+ TISInputSourceRef theInputSource = theInputSourceFunctions[i]();
+ if (theInputSource != NULL) {
+ kCurrentKeyboardLayout = [[self alloc] initWithInputSource:theInputSource];
+ CFRelease(theInputSource);
+ }
+ }
+ }
+ }
+
+ return kCurrentKeyboardLayout;
+}
+
+- (id)init
+{
+ [self release];
+ return [[NDKeyboardLayout keyboardLayout] retain];
+}
+
+- (id)initWithLanguage:(NSString *)aLangauge
+{
+ return [self initWithInputSource:TISCopyInputSourceForLanguage((CFStringRef)aLangauge)];
+}
+
+- (id)initWithInputSource:(TISInputSourceRef)aSource
+{
+ if ((self = [super init]) != nil) {
+ if (aSource != NULL
+ && (keyboardLayoutData
+ = (CFDataRef)CFMakeCollectable(TISGetInputSourceProperty(aSource, kTISPropertyUnicodeKeyLayoutData)))
+ != nil) {
+ CFRetain(keyboardLayoutData);
+ } else
+ self = nil, [self release];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (mappings != NULL)
+ free((void *)mappings);
+ if (keyboardLayoutData != NULL)
+ CFRelease(keyboardLayoutData);
+ [super dealloc];
+}
+
+- (NSString *)stringForCharacter:(unichar)aCharacter modifierFlags:(UInt32)aModifierFlags
+{
+ return [self stringForKeyCode:[self keyCodeForCharacter:aCharacter
+ numericPad:(aModifierFlags & NSNumericPadKeyMask) != 0]
+ modifierFlags:aModifierFlags];
+}
+
+- (NSString *)stringForKeyCode:(UInt16)aKeyCode modifierFlags:(UInt32)aModifierFlags
+{
+ NSString *theResult = nil;
+ struct UnmappedEntry *theEntry = _unmappedEntryForKeyCode(aKeyCode); // is it one of the unmapped values
+
+ if (theEntry != NULL) {
+ unichar theCharacter[sizeof(theEntry->description) / sizeof(*theEntry->description) + 4 + 1];
+ memset(theCharacter, 0, sizeof(theCharacter));
+ NSUInteger thePos = _characterForModifierFlags(theCharacter, aModifierFlags);
+ memcpy(theCharacter + thePos, theEntry->description, sizeof(theEntry->description));
+ theResult =
+ [NSString stringWithCharacters:theCharacter
+ length:sizeof(theEntry->description) / sizeof(*theEntry->description) + thePos];
+ } else {
+ UInt32 theDeadKeyState = 0;
+ UniCharCount theLength = 0;
+ UniChar theCharacter[260];
+
+ NSUInteger thePos = _characterForModifierFlags(theCharacter, aModifierFlags);
+
+ if (UCKeyTranslate(self.keyboardLayoutPtr, aKeyCode, kUCKeyActionDisplay,
+ NDCarbonModifierFlagsForCocoaModifierFlags(aModifierFlags), LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState,
+ sizeof(theCharacter) / sizeof(*theCharacter) - thePos, &theLength, theCharacter + thePos)
+ == noErr) {
+
+ theResult = [[NSString stringWithCharacters:theCharacter length:theLength + thePos] uppercaseString];
+ }
+ }
+ return theResult;
+}
+
+- (unichar)characterForKeyCode:(UInt16)aKeyCode
+{
+ unichar theChar = 0;
+ struct UnmappedEntry *theEntry = _unmappedEntryForKeyCode(aKeyCode);
+
+ if (theEntry == NULL) // is it one of the unmapped values
+ {
+ UInt32 theDeadKeyState = 0;
+ UniCharCount theLength = 0;
+ UniChar theCharacter[256];
+
+ if (UCKeyTranslate(self.keyboardLayoutPtr, aKeyCode, kUCKeyActionDisplay, 0, LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, sizeof(theCharacter) / sizeof(*theCharacter),
+ &theLength, theCharacter)
+ == noErr) {
+ theChar = theCharacter[0];
+ }
+ } else
+ theChar = theEntry->character;
+ return toupper(theChar);
+}
+
+- (UInt16)keyCodeForCharacter:(unichar)aCharacter
+{
+ return [self keyCodeForCharacter:aCharacter numericPad:NO];
+}
+
+- (UInt16)keyCodeForCharacter:(unichar)aCharacter numericPad:(BOOL)aNumericPad
+{
+ struct ReverseMappingEntry theSearchValue = { tolower(aCharacter), aNumericPad, 0 };
+ struct ReverseMappingEntry *theEntry = NULL;
+ if (mappings == NULL)
+ [self generateMappings];
+ theEntry = _searchreverseMapping(mappings, numberOfMappings, &theSearchValue);
+ return theEntry ? theEntry->keyCode : '\0';
+}
+
+#pragma mark - private
+
+- (const UCKeyboardLayout *)keyboardLayoutPtr
+{
+ return (const UCKeyboardLayout *)CFDataGetBytePtr(keyboardLayoutData);
+}
+
+@end
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager.cpp b/src/libpsi/tools/globalshortcut/globalshortcutmanager.cpp
new file mode 100644
index 000000000..281bcc411
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager.cpp
@@ -0,0 +1,87 @@
+/*
+ * globalshortcutmanager.cpp - Class managing global shortcuts
+ * Copyright (C) 2006 Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#include "globalshortcutmanager.h"
+
+#include "globalshortcuttrigger.h"
+
+#include
+
+/**
+ * \brief Constructs new GlobalShortcutManager.
+ */
+GlobalShortcutManager::GlobalShortcutManager() : QObject(QCoreApplication::instance()) { }
+
+GlobalShortcutManager::~GlobalShortcutManager() { clear(); }
+
+GlobalShortcutManager *GlobalShortcutManager::instance_ = nullptr;
+
+/**
+ * \brief Returns the instance of GlobalShortcutManager.
+ */
+GlobalShortcutManager *GlobalShortcutManager::instance()
+{
+ if (!instance_)
+ instance_ = new GlobalShortcutManager();
+ return instance_;
+}
+
+/**
+ * \brief Connects a key sequence with a slot.
+ * \param key, global shortcut to be connected
+ * \param receiver, object which should receive the notification
+ * \param slot, the SLOT() of the \a receiver which should be triggerd if the \a key is activated
+ */
+void GlobalShortcutManager::connect(const QKeySequence &key, QObject *receiver, const char *slot)
+{
+ KeyTrigger *t = instance()->triggers_[key];
+ if (!t) {
+ t = new KeyTrigger(key);
+ instance()->triggers_.insert(key, t);
+ }
+
+ QObject::connect(t, SIGNAL(triggered()), receiver, slot);
+}
+
+/**
+ * \brief Disonnects a key sequence from a slot.
+ * \param key, global shortcut to be disconnected
+ * \param receiver, object which \a slot is about to be disconnected
+ * \param slot, the SLOT() of the \a receiver which should no longer be triggerd if the \a key is activated
+ */
+void GlobalShortcutManager::disconnect(const QKeySequence &key, QObject *receiver, const char *slot)
+{
+ KeyTrigger *t = instance()->triggers_[key];
+ if (!t) {
+ return;
+ }
+
+ QObject::disconnect(t, SIGNAL(triggered()), receiver, slot);
+
+ if (!t->isUsed()) {
+ delete instance()->triggers_.take(key);
+ }
+}
+
+void GlobalShortcutManager::clear()
+{
+ for (KeyTrigger *t : std::as_const(instance()->triggers_))
+ delete t;
+ instance()->triggers_.clear();
+}
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager.h b/src/libpsi/tools/globalshortcut/globalshortcutmanager.h
new file mode 100644
index 000000000..373e772d0
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager.h
@@ -0,0 +1,45 @@
+/*
+ * globalshortcutmanager.h - Class managing global shortcuts
+ * Copyright (C) 2006 Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef GLOBALSHORTCUTMANAGER_H
+#define GLOBALSHORTCUTMANAGER_H
+
+#include
+#include
+#include
+
+class KeyTrigger;
+class QObject;
+
+class GlobalShortcutManager : public QObject {
+public:
+ static GlobalShortcutManager *instance();
+ static void connect(const QKeySequence &key, QObject *receiver, const char *slot);
+ static void disconnect(const QKeySequence &key, QObject *receiver, const char *slot);
+ static void clear();
+
+private:
+ GlobalShortcutManager();
+ ~GlobalShortcutManager();
+ static GlobalShortcutManager *instance_;
+ class KeyTrigger;
+ QMap triggers_;
+};
+
+#endif // GLOBALSHORTCUTMANAGER_H
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager_haiku.cpp b/src/libpsi/tools/globalshortcut/globalshortcutmanager_haiku.cpp
new file mode 100644
index 000000000..bf93f3cd5
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager_haiku.cpp
@@ -0,0 +1,302 @@
+/*
+ * globalshortcutmanager_haiku.cpp - Haiku implementation of global shortcuts by Vitaly (Diger)
+ * Based on X11 implementation of global shortcuts
+ * Copyright (C) 2003-2006 Justin Karneges, Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#include "globalshortcutmanager.h"
+#include "globalshortcuttrigger.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class HKeyTrigger {
+public:
+ virtual ~HKeyTrigger() { }
+ virtual void activate() = 0;
+ virtual bool isAccepted(int qkey) const = 0;
+};
+
+class HKeyTriggerManager : public QObject {
+public:
+ static HKeyTriggerManager *instance()
+ {
+ if (!instance_)
+ instance_ = new HKeyTriggerManager();
+ return instance_;
+ }
+
+ void addTrigger(HKeyTrigger *trigger) { triggers_ << trigger; }
+
+ void removeTrigger(HKeyTrigger *trigger) { triggers_.removeAll(trigger); }
+
+ struct Qt_HK_Keygroup {
+ char num;
+ int sym[3];
+ };
+
+protected:
+ // reimplemented
+ bool eventFilter(QObject *o, QEvent *e)
+ {
+ if (e->type() == QEvent::KeyPress) {
+ QKeyEvent *k = static_cast(e);
+ int qkey = k->key();
+ if (k->modifiers() & Qt::ShiftModifier)
+ qkey |= Qt::SHIFT;
+ if (k->modifiers() & Qt::ControlModifier)
+ qkey |= Qt::CTRL;
+ if (k->modifiers() & Qt::AltModifier)
+ qkey |= Qt::ALT;
+ if (k->modifiers() & Qt::MetaModifier)
+ qkey |= Qt::META;
+
+ foreach (HKeyTrigger *trigger, triggers_) {
+ if (trigger->isAccepted(qkey)) {
+ trigger->activate();
+ return true;
+ }
+ }
+ }
+
+ return QObject::eventFilter(o, e);
+ }
+
+private:
+ HKeyTriggerManager() : QObject(QCoreApplication::instance())
+ {
+ QCoreApplication::instance()->installEventFilter(this);
+ }
+
+ static HKeyTriggerManager *instance_;
+ QList triggers_;
+
+private:
+ struct Qt_HK_Keymap {
+ int key;
+ Qt_HK_Keygroup hk;
+ };
+
+ static Qt_HK_Keymap qt_hk_table[];
+ static long alt_mask;
+ static long meta_mask;
+ static long super_mask;
+ static long hyper_mask;
+ static long numlock_mask;
+ static bool haveMods;
+
+ // adapted from qapplication_x11.cpp
+ static void ensureModifiers()
+ {
+ if (haveMods)
+ return;
+ }
+
+public:
+ static bool convertKeySequence(const QKeySequence &ks, unsigned int *_mod, Qt_HK_Keygroup *_kg)
+ {
+ int code = ks[0];
+ ensureModifiers();
+
+ unsigned int mod = 0;
+ /*
+ if (code & Qt::META)
+ mod |= meta_mask;
+ if (code & Qt::SHIFT)
+ mod |= ShiftMask;
+ if (code & Qt::CTRL)
+ mod |= ControlMask;
+ if (code & Qt::ALT)
+ mod |= alt_mask;
+ */
+ Qt_HK_Keygroup kg;
+ kg.num = 0;
+ kg.sym[0] = 0;
+ code &= ~Qt::KeyboardModifierMask;
+
+ bool found = false;
+ for (int n = 0; qt_hk_table[n].key != Qt::Key_unknown; ++n) {
+ if (qt_hk_table[n].key == code) {
+ kg = qt_hk_table[n].hk;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // try latin1
+ if (code >= 0x20 && code <= 0x7f) {
+ kg.num = 1;
+ kg.sym[0] = code;
+ }
+ }
+
+ if (!kg.num)
+ return false;
+
+ if (_mod)
+ *_mod = mod;
+ if (_kg)
+ *_kg = kg;
+
+ return true;
+ }
+
+ static QList ignModifiersList()
+ {
+ QList ret;
+ /*
+ if (numlock_mask) {
+ ret << 0 << LockMask << numlock_mask << (LockMask | numlock_mask);
+ }
+ else {
+ ret << 0 << LockMask;
+ }
+ */
+ return ret;
+ }
+};
+
+HKeyTriggerManager *HKeyTriggerManager::instance_ = NULL;
+class GlobalShortcutManager::KeyTrigger::Impl : public HKeyTrigger {
+private:
+ KeyTrigger *trigger_;
+ int qkey_;
+
+ struct GrabbedKey {
+ int code;
+ uint mod;
+ };
+ QList grabbedKeys_;
+
+ static bool failed;
+
+public:
+ /**
+ * Constructor registers the hotkey.
+ */
+ Impl(GlobalShortcutManager::KeyTrigger *t, const QKeySequence &ks) : trigger_(t), qkey_(ks[0])
+ {
+ HKeyTriggerManager::instance()->addTrigger(this);
+
+ HKeyTriggerManager::Qt_HK_Keygroup kg;
+ unsigned int mod;
+ }
+
+ /**
+ * Destructor unregisters the hotkey.
+ */
+ ~Impl() { HKeyTriggerManager::instance()->removeTrigger(this); }
+
+ void activate() { emit trigger_->triggered(); }
+
+ bool isAccepted(int qkey) const { return qkey_ == qkey; }
+};
+
+bool GlobalShortcutManager::KeyTrigger::Impl::failed;
+long HKeyTriggerManager::alt_mask = 0;
+long HKeyTriggerManager::meta_mask = 0;
+long HKeyTriggerManager::super_mask = 0;
+long HKeyTriggerManager::hyper_mask = 0;
+long HKeyTriggerManager::numlock_mask = 0;
+bool HKeyTriggerManager::haveMods = false;
+
+HKeyTriggerManager::Qt_HK_Keymap HKeyTriggerManager::qt_hk_table[] = {
+ { Qt::Key_Escape, { 1, { B_ESCAPE } } },
+ { Qt::Key_Tab, { 1, { B_TAB } } },
+ { Qt::Key_Backtab, { 0, { 0 } } },
+ { Qt::Key_Backspace, { 1, { B_BACKSPACE } } },
+ { Qt::Key_Return, { 1, { B_RETURN } } },
+ { Qt::Key_Enter, { 1, { B_ENTER } } },
+ { Qt::Key_Insert, { 1, { B_INSERT } } },
+ { Qt::Key_Delete, { 1, { B_DELETE } } },
+ { Qt::Key_Pause, { 1, { B_PAUSE_KEY } } },
+ { Qt::Key_Print, { 1, { B_PRINT_KEY } } },
+ { Qt::Key_SysReq, { 0, { 0 } } },
+ //{ Qt::Key_Clear, B_CLEAR_KEY},
+ { Qt::Key_Home, { 1, { B_HOME } } },
+ { Qt::Key_End, { 1, { B_END } } },
+ { Qt::Key_Left, { 1, { B_LEFT_ARROW } } },
+ { Qt::Key_Up, { 1, { B_UP_ARROW } } },
+ { Qt::Key_Right, { 1, { B_RIGHT_ARROW } } },
+ { Qt::Key_Down, { 1, { B_DOWN_ARROW } } },
+ { Qt::Key_PageUp, { 1, { B_PAGE_UP } } },
+ { Qt::Key_PageDown, { 1, { B_PAGE_DOWN } } },
+ { Qt::Key_Shift, { 1, { B_SHIFT_KEY } } },
+ { Qt::Key_Control, { 1, { B_CONTROL_KEY } } },
+ { Qt::Key_Meta, { 1, { B_LEFT_OPTION_KEY } } },
+ { Qt::Key_Alt, { 1, { B_MENU_KEY } } },
+ { Qt::Key_CapsLock, { 1, { B_CAPS_LOCK } } },
+ { Qt::Key_NumLock, { 1, { B_NUM_LOCK } } },
+ { Qt::Key_ScrollLock, { 1, { B_SCROLL_KEY } } },
+ { Qt::Key_F1, { 1, { B_F1_KEY } } },
+ { Qt::Key_F2, { 1, { B_F2_KEY } } },
+ { Qt::Key_F3, { 1, { B_F3_KEY } } },
+ { Qt::Key_F4, { 1, { B_F4_KEY } } },
+ { Qt::Key_F5, { 1, { B_F5_KEY } } },
+ { Qt::Key_F6, { 1, { B_F6_KEY } } },
+ { Qt::Key_F7, { 1, { B_F7_KEY } } },
+ { Qt::Key_F8, { 1, { B_F8_KEY } } },
+ { Qt::Key_F9, { 1, { B_F9_KEY } } },
+ { Qt::Key_F10, { 1, { B_F10_KEY } } },
+ { Qt::Key_F11, { 1, { B_F11_KEY } } },
+ { Qt::Key_F12, { 1, { B_F12_KEY } } },
+ { Qt::Key_F13, { 0, { 0 } } },
+ { Qt::Key_F14, { 0, { 0 } } },
+ { Qt::Key_F15, { 0, { 0 } } },
+ { Qt::Key_F16, { 0, { 0 } } },
+ { Qt::Key_F17, { 0, { 0 } } },
+ { Qt::Key_F18, { 0, { 0 } } },
+ { Qt::Key_F19, { 0, { 0 } } },
+ { Qt::Key_F20, { 0, { 0 } } },
+ { Qt::Key_F21, { 0, { 0 } } },
+ { Qt::Key_F22, { 0, { 0 } } },
+ { Qt::Key_F23, { 0, { 0 } } },
+ { Qt::Key_F24, { 0, { 0 } } },
+ { Qt::Key_F25, { 0, { 0 } } },
+ { Qt::Key_F26, { 0, { 0 } } },
+ { Qt::Key_F27, { 0, { 0 } } },
+ { Qt::Key_F28, { 0, { 0 } } },
+ { Qt::Key_F29, { 0, { 0 } } },
+ { Qt::Key_F30, { 0, { 0 } } },
+ { Qt::Key_F31, { 0, { 0 } } },
+ { Qt::Key_F32, { 0, { 0 } } },
+ { Qt::Key_F33, { 0, { 0 } } },
+ { Qt::Key_F34, { 0, { 0 } } },
+ { Qt::Key_F35, { 0, { 0 } } },
+ { Qt::Key_Super_L, { 0, { 0 } } },
+ { Qt::Key_Super_R, { 0, { 0 } } },
+ { Qt::Key_Menu, { 0, { 0 } } },
+ { Qt::Key_Hyper_L, { 0, { 0 } } },
+ { Qt::Key_Hyper_R, { 0, { 0 } } },
+ { Qt::Key_Help, { 0, { 0 } } },
+ { Qt::Key_Direction_L, { 0, { 0 } } },
+ { Qt::Key_Direction_R, { 0, { 0 } } },
+
+ { Qt::Key_unknown, { 0, { 0 } } },
+};
+GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &key) { d = new Impl(this, key); }
+
+GlobalShortcutManager::KeyTrigger::~KeyTrigger()
+{
+ delete d;
+ d = 0;
+}
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager_mac.mm b/src/libpsi/tools/globalshortcut/globalshortcutmanager_mac.mm
new file mode 100644
index 000000000..3bad28dbd
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager_mac.mm
@@ -0,0 +1,228 @@
+/*
+ * globalshortcutmanager_mac.cpp - Mac OS X implementation of global shortcuts
+ * Copyright (C) 2003-2007 Eric Smith, Michail Pishchagin
+ *
+ * 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, see .
+ *
+ */
+
+#import "NDKeyboardLayout.h"
+
+#include "globalshortcutmanager.h"
+#include "globalshortcuttrigger.h"
+
+#include
+#include
+
+// TODO:
+// - don't invoke hotkey if there is a modal dialog?
+// - do multi-mapping, like the x11 version
+
+class MacKeyTrigger {
+public:
+ virtual ~MacKeyTrigger() { }
+ virtual void activate() = 0;
+ virtual bool isAccepted(int id) const = 0;
+};
+
+class MacKeyTriggerManager : public QObject {
+public:
+ static MacKeyTriggerManager *instance()
+ {
+ if (!instance_)
+ instance_ = new MacKeyTriggerManager();
+ return instance_;
+ }
+
+ void addTrigger(MacKeyTrigger *trigger) { triggers_ << trigger; }
+
+ void removeTrigger(MacKeyTrigger *trigger) { triggers_.removeAll(trigger); }
+
+private:
+ MacKeyTriggerManager() : QObject(QCoreApplication::instance())
+ {
+ hot_key_function_ = NewEventHandlerUPP(hotKeyHandler);
+ EventTypeSpec type;
+ type.eventClass = kEventClassKeyboard;
+ type.eventKind = kEventHotKeyPressed;
+ InstallApplicationEventHandler(hot_key_function_, 1, &type, this, NULL);
+ }
+
+ /**
+ * Callback function invoked when the user hits a hot-key.
+ */
+ static pascal OSStatus hotKeyHandler(EventHandlerCallRef /*nextHandler*/, EventRef theEvent, void *userData)
+ {
+ EventHotKeyID hkID;
+ GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL,
+ &hkID);
+ static_cast(userData)->activated(hkID.id);
+ return noErr;
+ }
+
+ void activated(int id)
+ {
+ foreach (MacKeyTrigger *trigger, triggers_) {
+ if (trigger->isAccepted(id)) {
+ trigger->activate();
+ break;
+ }
+ }
+ }
+
+ static MacKeyTriggerManager *instance_;
+ QList triggers_;
+
+ static EventHandlerUPP hot_key_function_;
+
+private:
+ struct Qt_Mac_Keymap {
+ int qt_key;
+ int mac_key;
+ };
+
+ static Qt_Mac_Keymap qt_keymap[];
+
+public:
+ static bool convertKeySequence(const QKeySequence &ks, quint32 *_key, quint32 *_mod)
+ {
+ int code = ks[0];
+
+ quint32 mod = 0;
+ if (code & Qt::META)
+ mod |= controlKey;
+ if (code & Qt::SHIFT)
+ mod |= shiftKey;
+ if (code & Qt::CTRL)
+ mod |= cmdKey;
+ if (code & Qt::ALT)
+ mod |= optionKey;
+
+ code &= ~Qt::KeyboardModifierMask;
+ quint32 key = 0;
+ for (int n = 0; qt_keymap[n].qt_key != Qt::Key_unknown; ++n) {
+ if (qt_keymap[n].qt_key == code) {
+ key = qt_keymap[n].mac_key;
+ break;
+ }
+ }
+ if (key == 0) {
+ key = [[NDKeyboardLayout keyboardLayout] keyCodeForCharacter:(code & 0xffff)];
+ }
+
+ if (_mod)
+ *_mod = mod;
+ if (_key)
+ *_key = key;
+
+ return true;
+ }
+};
+
+MacKeyTriggerManager *MacKeyTriggerManager::instance_ = NULL;
+EventHandlerUPP MacKeyTriggerManager::hot_key_function_ = NULL;
+
+class GlobalShortcutManager::KeyTrigger::Impl : public MacKeyTrigger {
+private:
+ KeyTrigger *trigger_;
+ EventHotKeyRef hotKey_;
+ int id_;
+ static int nextId;
+
+public:
+ /**
+ * Constructor registers the hotkey.
+ */
+ Impl(GlobalShortcutManager::KeyTrigger *t, const QKeySequence &ks) : trigger_(t), id_(0)
+ {
+ MacKeyTriggerManager::instance()->addTrigger(this);
+
+ quint32 key, mod;
+ if (MacKeyTriggerManager::convertKeySequence(ks, &key, &mod)) {
+ EventHotKeyID hotKeyID;
+ hotKeyID.signature = 'QtHK';
+ hotKeyID.id = nextId;
+
+ OSStatus ret = RegisterEventHotKey(key, mod, hotKeyID, GetApplicationEventTarget(), 0, &hotKey_);
+ if (ret != 0) {
+ qWarning("RegisterEventHotKey(%d, %d): %d", key, mod, (int)ret);
+ return;
+ }
+
+ id_ = nextId++;
+ }
+ }
+
+ /**
+ * Destructor unregisters the hotkey.
+ */
+ ~Impl()
+ {
+ MacKeyTriggerManager::instance()->removeTrigger(this);
+
+ if (id_)
+ UnregisterEventHotKey(hotKey_);
+ }
+
+ void activate() { emit trigger_->triggered(); }
+
+ bool isAccepted(int id) const { return id_ == id; }
+};
+
+/*
+ * The following table is from Apple sample-code.
+ * Apple's headers don't appear to define any constants for the virtual key
+ * codes of special keys, but these constants are somewhat documented in the chart at
+ *
+ *
+ * The constants on the chartappear to be the same values as are used in Apple's iGetKeys
+ * sample.
+ * .
+ *
+ * See also .
+ */
+MacKeyTriggerManager::Qt_Mac_Keymap MacKeyTriggerManager::qt_keymap[]
+ = { { Qt::Key_Escape, 0x35 }, { Qt::Key_Tab, 0x30 }, { Qt::Key_Backtab, 0 }, { Qt::Key_Backspace, 0x33 },
+ { Qt::Key_Return, 0x24 }, { Qt::Key_Enter, 0x4c }, // Return & Enter are different on the Mac
+ { Qt::Key_Insert, 0 }, { Qt::Key_Delete, 0x75 }, { Qt::Key_Pause, 0 }, { Qt::Key_Print, 0 },
+ { Qt::Key_SysReq, 0 }, { Qt::Key_Clear, 0x47 }, { Qt::Key_Home, 0x73 }, { Qt::Key_End, 0x77 },
+ { Qt::Key_Left, 0x7b }, { Qt::Key_Up, 0x7e }, { Qt::Key_Right, 0x7c }, { Qt::Key_Down, 0x7d },
+ { Qt::Key_PageUp, 0x74 }, // Page Up
+ { Qt::Key_PageDown, 0x79 }, // Page Down
+ { Qt::Key_Shift, 0x38 }, { Qt::Key_Control, 0x3b }, { Qt::Key_Meta, 0x37 }, // Command
+ { Qt::Key_Alt, 0x3a }, // Option
+ { Qt::Key_CapsLock, 57 }, { Qt::Key_NumLock, 0 }, { Qt::Key_ScrollLock, 0 }, { Qt::Key_F1, 0x7a },
+ { Qt::Key_F2, 0x78 }, { Qt::Key_F3, 0x63 }, { Qt::Key_F4, 0x76 }, { Qt::Key_F5, 0x60 },
+ { Qt::Key_F6, 0x61 }, { Qt::Key_F7, 0x62 }, { Qt::Key_F8, 0x64 }, { Qt::Key_F9, 0x65 },
+ { Qt::Key_F10, 0x6d }, { Qt::Key_F11, 0x67 }, { Qt::Key_F12, 0x6f }, { Qt::Key_F13, 0x69 },
+ { Qt::Key_F14, 0x6b }, { Qt::Key_F15, 0x71 }, { Qt::Key_F16, 0 }, { Qt::Key_F17, 0 },
+ { Qt::Key_F18, 0 }, { Qt::Key_F19, 0 }, { Qt::Key_F20, 0 }, { Qt::Key_F21, 0 },
+ { Qt::Key_F22, 0 }, { Qt::Key_F23, 0 }, { Qt::Key_F24, 0 }, { Qt::Key_F25, 0 },
+ { Qt::Key_F26, 0 }, { Qt::Key_F27, 0 }, { Qt::Key_F28, 0 }, { Qt::Key_F29, 0 },
+ { Qt::Key_F30, 0 }, { Qt::Key_F31, 0 }, { Qt::Key_F32, 0 }, { Qt::Key_F33, 0 },
+ { Qt::Key_F34, 0 }, { Qt::Key_F35, 0 }, { Qt::Key_Super_L, 0 }, { Qt::Key_Super_R, 0 },
+ { Qt::Key_Menu, 0 }, { Qt::Key_Hyper_L, 0 }, { Qt::Key_Hyper_R, 0 }, { Qt::Key_Help, 0x72 },
+ { Qt::Key_Direction_L, 0 }, { Qt::Key_Direction_R, 0 },
+
+ { Qt::Key_unknown, 0 } };
+
+int GlobalShortcutManager::KeyTrigger::Impl::nextId = 1;
+
+GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &key) { d = new Impl(this, key); }
+
+GlobalShortcutManager::KeyTrigger::~KeyTrigger()
+{
+ delete d;
+ d = 0;
+}
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager_stub.cpp b/src/libpsi/tools/globalshortcut/globalshortcutmanager_stub.cpp
new file mode 100644
index 000000000..febef86f9
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager_stub.cpp
@@ -0,0 +1,28 @@
+/*
+ * globalshortcutmanager_x11.cpp - X11 implementation of global shortcuts
+ * Copyright (C) 2003-2007 Justin Karneges, Michail Pishchagin
+ *
+ * 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, see .
+ *
+ */
+
+#include "globalshortcutmanager.h"
+#include "globalshortcuttrigger.h"
+
+#include
+#include
+
+GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &) { }
+
+GlobalShortcutManager::KeyTrigger::~KeyTrigger() { }
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager_win.cpp b/src/libpsi/tools/globalshortcut/globalshortcutmanager_win.cpp
new file mode 100644
index 000000000..0f696e659
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager_win.cpp
@@ -0,0 +1,218 @@
+/*
+ * globalshortcutmanager_win.cpp - Windows implementation of global shortcuts
+ * Copyright (C) 2003-2006 Justin Karneges, Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#include "globalshortcutmanager.h"
+#include "globalshortcuttrigger.h"
+
+#include
+#include
+#include
+#include
+
+class GlobalShortcutManager::KeyTrigger::Impl : public QWidget {
+ class WinEventFilter : public QAbstractNativeEventFilter {
+ GlobalShortcutManager::KeyTrigger::Impl *impl;
+
+ public:
+ WinEventFilter(GlobalShortcutManager::KeyTrigger::Impl *parent) : impl(parent)
+ {
+ qApp->installNativeEventFilter(this);
+ }
+
+ virtual bool nativeEventFilter(const QByteArray &eventType, void *m, long *result) Q_DECL_OVERRIDE
+ {
+ if (eventType == "windows_generic_MSG") {
+ return impl->nativeEvent(eventType, static_cast(m), result);
+ }
+ return false;
+ }
+ };
+
+public:
+ /**
+ * Constructor registers the hotkey.
+ */
+ Impl(GlobalShortcutManager::KeyTrigger *t, const QKeySequence &ks) :
+ filter(new WinEventFilter(this)), trigger_(t), id_(0)
+ {
+ UINT mod = 0, key = 0;
+ if (convertKeySequence(ks, &mod, &key))
+ if (RegisterHotKey(HWND(winId()), nextId, mod, key))
+ id_ = nextId++;
+ }
+
+ /**
+ * Destructor unregisters the hotkey.
+ */
+ ~Impl()
+ {
+ delete filter;
+ if (id_)
+ UnregisterHotKey(HWND(winId()), id_);
+ }
+
+ /**
+ * Triggers triggered() signal when the hotkey is activated.
+ */
+ bool nativeEvent(const QByteArray &eventType, MSG *m, long *result)
+ {
+ Q_UNUSED(eventType);
+ if (m->message == WM_HOTKEY && m->wParam == id_) {
+ emit trigger_->triggered();
+ return true;
+ }
+ Q_UNUSED(result);
+ return false;
+ }
+
+private:
+ WinEventFilter *filter;
+ KeyTrigger *trigger_;
+ WPARAM id_;
+ static WPARAM nextId;
+
+private:
+ struct Qt_VK_Keymap {
+ int key;
+ UINT vk;
+ };
+ static Qt_VK_Keymap qt_vk_table[];
+
+ static bool convertKeySequence(const QKeySequence &ks, UINT *mod_, UINT *key_)
+ {
+ int code = ks[0];
+
+ UINT mod = 0;
+ if (code & Qt::META)
+ mod |= MOD_WIN;
+ if (code & Qt::SHIFT)
+ mod |= MOD_SHIFT;
+ if (code & Qt::CTRL)
+ mod |= MOD_CONTROL;
+ if (code & Qt::ALT)
+ mod |= MOD_ALT;
+
+ UINT key = 0;
+ code &= ~Qt::KeyboardModifierMask;
+ if (code >= 0x20 && code <= 0x7f)
+ key = code;
+ else {
+ for (int n = 0; qt_vk_table[n].key != Qt::Key_unknown; ++n) {
+ if (qt_vk_table[n].key == code) {
+ key = qt_vk_table[n].vk;
+ break;
+ }
+ }
+ if (!key)
+ return false;
+ }
+
+ if (mod)
+ *mod_ = mod;
+ if (key)
+ *key_ = key;
+
+ return true;
+ }
+};
+
+GlobalShortcutManager::KeyTrigger::Impl::Qt_VK_Keymap GlobalShortcutManager::KeyTrigger::Impl::qt_vk_table[] = {
+ { Qt::Key_Escape, VK_ESCAPE },
+ { Qt::Key_Tab, VK_TAB },
+ { Qt::Key_Backtab, 0 },
+ { Qt::Key_Backspace, VK_BACK },
+ { Qt::Key_Return, VK_RETURN },
+ { Qt::Key_Enter, VK_RETURN },
+ { Qt::Key_Insert, VK_INSERT },
+ { Qt::Key_Delete, VK_DELETE },
+ { Qt::Key_Pause, VK_PAUSE },
+ { Qt::Key_Print, VK_PRINT },
+ { Qt::Key_SysReq, 0 },
+ { Qt::Key_Clear, VK_CLEAR },
+ { Qt::Key_Home, VK_HOME },
+ { Qt::Key_End, VK_END },
+ { Qt::Key_Left, VK_LEFT },
+ { Qt::Key_Up, VK_UP },
+ { Qt::Key_Right, VK_RIGHT },
+ { Qt::Key_Down, VK_DOWN },
+ { Qt::Key_PageUp, VK_PRIOR },
+ { Qt::Key_PageDown, VK_NEXT },
+ { Qt::Key_Shift, VK_SHIFT },
+ { Qt::Key_Control, VK_CONTROL },
+ { Qt::Key_Meta, VK_LWIN },
+ { Qt::Key_Alt, VK_MENU },
+ { Qt::Key_CapsLock, VK_CAPITAL },
+ { Qt::Key_NumLock, VK_NUMLOCK },
+ { Qt::Key_ScrollLock, VK_SCROLL },
+ { Qt::Key_F1, VK_F1 },
+ { Qt::Key_F2, VK_F2 },
+ { Qt::Key_F3, VK_F3 },
+ { Qt::Key_F4, VK_F4 },
+ { Qt::Key_F5, VK_F5 },
+ { Qt::Key_F6, VK_F6 },
+ { Qt::Key_F7, VK_F7 },
+ { Qt::Key_F8, VK_F8 },
+ { Qt::Key_F9, VK_F9 },
+ { Qt::Key_F10, VK_F10 },
+ { Qt::Key_F11, VK_F11 },
+ { Qt::Key_F12, VK_F12 },
+ { Qt::Key_F13, VK_F13 },
+ { Qt::Key_F14, VK_F14 },
+ { Qt::Key_F15, VK_F15 },
+ { Qt::Key_F16, VK_F16 },
+ { Qt::Key_F17, VK_F17 },
+ { Qt::Key_F18, VK_F18 },
+ { Qt::Key_F19, VK_F19 },
+ { Qt::Key_F20, VK_F20 },
+ { Qt::Key_F21, VK_F21 },
+ { Qt::Key_F22, VK_F22 },
+ { Qt::Key_F23, VK_F23 },
+ { Qt::Key_F24, VK_F24 },
+ { Qt::Key_F25, 0 },
+ { Qt::Key_F26, 0 },
+ { Qt::Key_F27, 0 },
+ { Qt::Key_F28, 0 },
+ { Qt::Key_F29, 0 },
+ { Qt::Key_F30, 0 },
+ { Qt::Key_F31, 0 },
+ { Qt::Key_F32, 0 },
+ { Qt::Key_F33, 0 },
+ { Qt::Key_F34, 0 },
+ { Qt::Key_F35, 0 },
+ { Qt::Key_Super_L, 0 },
+ { Qt::Key_Super_R, 0 },
+ { Qt::Key_Menu, 0 },
+ { Qt::Key_Hyper_L, 0 },
+ { Qt::Key_Hyper_R, 0 },
+ { Qt::Key_Help, 0 },
+ { Qt::Key_Direction_L, 0 },
+ { Qt::Key_Direction_R, 0 },
+
+ { Qt::Key_unknown, 0 },
+};
+
+WPARAM GlobalShortcutManager::KeyTrigger::Impl::nextId = 1;
+
+GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &key) { d = new Impl(this, key); }
+
+GlobalShortcutManager::KeyTrigger::~KeyTrigger()
+{
+ delete d;
+ d = nullptr;
+}
diff --git a/src/libpsi/tools/globalshortcut/globalshortcutmanager_x11.cpp b/src/libpsi/tools/globalshortcut/globalshortcutmanager_x11.cpp
new file mode 100644
index 000000000..5534a0d13
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcutmanager_x11.cpp
@@ -0,0 +1,486 @@
+/*
+ * globalshortcutmanager_x11.cpp - X11 implementation of global shortcuts
+ * Copyright (C) 2003-2007 Justin Karneges, Michail Pishchagin
+ *
+ * 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, see .
+ *
+ */
+
+#include "globalshortcutmanager.h"
+#include "globalshortcuttrigger.h"
+
+#include
+#include
+#include
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include
+#else
+#include
+#endif
+#include
+#include
+#include
+
+#ifdef KeyPress
+// defined by X11 headers
+const int XKeyPress = KeyPress;
+const int XKeyRelease = KeyRelease;
+#undef KeyPress
+#endif
+
+namespace {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+unsigned long getRootWindow()
+{
+ auto x11app = qApp->nativeInterface();
+ if (!x11app) {
+ return -1; // wayland?
+ }
+ return DefaultRootWindow(x11app->display());
+ // auto screen = xcb_setup_roots_iterator ( xcb_get_setup (x11app->connection()) ).data;
+ // auto gc = xcb_generate_id ( x11app->connection() );
+
+ // /* root window */
+ // auto draw = screen->root;
+}
+#endif
+}
+
+class X11KeyTrigger {
+public:
+ virtual ~X11KeyTrigger() { }
+ virtual void activate() = 0;
+ virtual bool isAccepted(const QKeySequence &qkey) const = 0;
+};
+
+class X11KeyTriggerManager : public QObject {
+public:
+ static X11KeyTriggerManager *instance()
+ {
+ if (!instance_)
+ instance_ = new X11KeyTriggerManager();
+ return instance_;
+ }
+
+ void addTrigger(X11KeyTrigger *trigger) { triggers_ << trigger; }
+
+ void removeTrigger(X11KeyTrigger *trigger) { triggers_.removeAll(trigger); }
+
+ struct Qt_XK_Keygroup {
+ char num;
+ int sym[3];
+ };
+
+protected:
+ // reimplemented
+ bool eventFilter(QObject *o, QEvent *e)
+ {
+ if (e->type() == QEvent::KeyPress) {
+ QKeyEvent *k = static_cast(e);
+ int qkey = k->key();
+ if (k->modifiers() & Qt::ShiftModifier)
+ qkey |= Qt::SHIFT;
+ if (k->modifiers() & Qt::ControlModifier)
+ qkey |= Qt::CTRL;
+ if (k->modifiers() & Qt::AltModifier)
+ qkey |= Qt::ALT;
+ if (k->modifiers() & Qt::MetaModifier)
+ qkey |= Qt::META;
+
+ for (X11KeyTrigger *trigger : std::as_const(triggers_)) {
+ if (trigger->isAccepted(QKeySequence(qkey))) {
+ trigger->activate();
+ return true;
+ }
+ }
+ }
+
+ return QObject::eventFilter(o, e);
+ }
+
+private:
+ X11KeyTriggerManager() : QObject(QCoreApplication::instance())
+ {
+ QCoreApplication::instance()->installEventFilter(this);
+ }
+
+ static X11KeyTriggerManager *instance_;
+ QList triggers_;
+
+private:
+ struct Qt_XK_Keymap {
+ int key;
+ Qt_XK_Keygroup xk;
+ };
+
+ static Qt_XK_Keymap qt_xk_table[];
+ static long alt_mask;
+ static long meta_mask;
+ static long super_mask;
+ static long hyper_mask;
+ static long numlock_mask;
+ static bool haveMods;
+
+ // adapted from qapplication_x11.cpp
+ static void ensureModifiers()
+ {
+ if (haveMods)
+ return;
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ if (!QX11Info::isPlatformX11()) {
+ return; // wayland?
+ }
+
+ Display *appDpy = QX11Info::display();
+#else
+ auto x11app = qApp->nativeInterface();
+ if (!x11app) {
+ return; // wayland?
+ }
+ Display *appDpy = x11app->display();
+#endif
+#if !defined(LIMIT_X11_USAGE)
+ XModifierKeymap *map = XGetModifierMapping(appDpy);
+#endif
+ if (map) {
+ // XKeycodeToKeysym helper code adapeted from xmodmap
+ int min_keycode, max_keycode, keysyms_per_keycode_return, keysyms_per_keycode = 1;
+ XDisplayKeycodes(appDpy, &min_keycode, &max_keycode);
+ XFree(XGetKeyboardMapping(appDpy, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode));
+
+ int i, maskIndex = 0, mapIndex = 0;
+ for (maskIndex = 0; maskIndex < 8; maskIndex++) {
+ for (i = 0; i < map->max_keypermod; i++) {
+ if (map->modifiermap[mapIndex]) {
+ KeySym *sym = nullptr;
+ int symIndex = 0;
+ do {
+ if (sym)
+ XFree(sym);
+ sym = XGetKeyboardMapping(appDpy, map->modifiermap[mapIndex], 1,
+ &keysyms_per_keycode_return);
+ symIndex++;
+ } while (!sym[0] && symIndex < keysyms_per_keycode);
+ if (alt_mask == 0 && (sym[0] == XK_Alt_L || sym[0] == XK_Alt_R)) {
+ alt_mask = 1 << maskIndex;
+ }
+ if (meta_mask == 0 && (sym[0] == XK_Meta_L || sym[0] == XK_Meta_R)) {
+ meta_mask = 1 << maskIndex;
+ }
+ if (super_mask == 0 && (sym[0] == XK_Super_L || sym[0] == XK_Super_R)) {
+ super_mask = 1 << maskIndex;
+ }
+ if (hyper_mask == 0 && (sym[0] == XK_Hyper_L || sym[0] == XK_Hyper_R)) {
+ hyper_mask = 1 << maskIndex;
+ }
+ if (numlock_mask == 0 && (sym[0] == XK_Num_Lock)) {
+ numlock_mask = 1 << maskIndex;
+ }
+ XFree(sym);
+ }
+ mapIndex++;
+ }
+ }
+
+ XFreeModifiermap(map);
+
+ // logic from qt source see gui/kernel/qkeymapper_x11.cpp
+ if (meta_mask == 0 || meta_mask == alt_mask) {
+ // no meta keys... s,meta,super,
+ meta_mask = super_mask;
+ if (meta_mask == 0 || meta_mask == alt_mask) {
+ // no super keys either? guess we'll use hyper then
+ meta_mask = hyper_mask;
+ }
+ }
+ } else {
+ // assume defaults
+ alt_mask = Mod1Mask;
+ meta_mask = Mod4Mask;
+ }
+
+ haveMods = true;
+ }
+
+public:
+ static bool convertKeySequence(const QKeySequence &ks, unsigned int *_mod, Qt_XK_Keygroup *_kg)
+ {
+ int code = ks[0];
+ Qt_XK_Keygroup kg;
+ kg.num = 0;
+ kg.sym[0] = 0;
+
+ ensureModifiers();
+
+ unsigned int mod = 0;
+ if (code & Qt::META)
+ mod |= meta_mask;
+ if (code & Qt::SHIFT)
+ mod |= ShiftMask;
+ if (code & Qt::CTRL)
+ mod |= ControlMask;
+ if (code & Qt::ALT)
+ mod |= alt_mask;
+
+ code &= ~Qt::KeyboardModifierMask;
+
+ bool found = false;
+ for (int n = 0; qt_xk_table[n].key != Qt::Key_unknown; ++n) {
+ if (qt_xk_table[n].key == code) {
+ kg = qt_xk_table[n].xk;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // try latin1
+ if (code >= 0x20 && code <= 0x7f) {
+ kg.num = 1;
+ kg.sym[0] = code;
+ }
+ }
+
+ if (!kg.num)
+ return false;
+
+ if (_mod)
+ *_mod = mod;
+ if (_kg)
+ *_kg = kg;
+
+ return true;
+ }
+
+ static QList ignModifiersList()
+ {
+ QList ret;
+ if (numlock_mask) {
+ ret << 0 << LockMask << numlock_mask << (LockMask | numlock_mask);
+ } else {
+ ret << 0 << LockMask;
+ }
+ return ret;
+ }
+};
+
+X11KeyTriggerManager *X11KeyTriggerManager::instance_ = nullptr;
+
+class GlobalShortcutManager::KeyTrigger::Impl : public X11KeyTrigger {
+private:
+ KeyTrigger *trigger_;
+ QKeySequence qkey_;
+
+ struct GrabbedKey {
+ int code;
+ uint mod;
+ };
+ QList grabbedKeys_;
+
+ static bool failed;
+ static int XGrabErrorHandler(Display *, XErrorEvent *e)
+ {
+ char answ[256];
+ XGetErrorText(e->display, e->error_code, answ, sizeof(answ));
+ qDebug("failed to grab key, Code: %d, %s", e->error_code, answ);
+ failed = true;
+ return 0;
+ }
+
+ void bind(int keysym, unsigned int mod)
+ {
+#if defined(LIMIT_X11_USAGE)
+ return;
+#endif
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ if (!QX11Info::isPlatformX11()) {
+ return; // wayland?
+ }
+
+ Display *appDpy = QX11Info::display();
+ auto rootWindow = QX11Info::appRootWindow();
+#else
+ auto x11app = qApp->nativeInterface();
+ if (!x11app) {
+ return; // wayland?
+ }
+ Display *appDpy = x11app->display();
+ auto rootWindow = getRootWindow();
+#endif
+
+ int code = XKeysymToKeycode(appDpy, keysym);
+
+ // don't grab keys with empty code (because it means just the modifier key)
+ if (keysym && !code)
+ return;
+
+ failed = false;
+ XErrorHandler savedErrorHandler = XSetErrorHandler(XGrabErrorHandler);
+ const auto &modifiers = X11KeyTriggerManager::ignModifiersList();
+ for (long mask_mod : modifiers) {
+ XGrabKey(appDpy, code, mod | mask_mod, rootWindow, False, GrabModeAsync, GrabModeAsync);
+ GrabbedKey grabbedKey;
+ grabbedKey.code = code;
+ grabbedKey.mod = mod | mask_mod;
+ grabbedKeys_ << grabbedKey;
+ }
+ XSync(appDpy, False);
+ XSetErrorHandler(savedErrorHandler);
+ }
+
+public:
+ /**
+ * Constructor registers the hotkey.
+ */
+ Impl(GlobalShortcutManager::KeyTrigger *t, const QKeySequence &ks) : trigger_(t), qkey_(ks)
+ {
+ X11KeyTriggerManager::instance()->addTrigger(this);
+
+ X11KeyTriggerManager::Qt_XK_Keygroup kg;
+ unsigned int mod;
+ if (X11KeyTriggerManager::convertKeySequence(ks, &mod, &kg))
+ for (int n = 0; n < kg.num; ++n)
+ bind(kg.sym[n], mod);
+ }
+
+ /**
+ * Destructor unregisters the hotkey.
+ */
+ ~Impl()
+ {
+ X11KeyTriggerManager::instance()->removeTrigger(this);
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ if (!QX11Info::isPlatformX11()) {
+ return; // wayland?
+ }
+
+ Display *appDpy = QX11Info::display();
+ auto rootWindow = QX11Info::appRootWindow();
+#else
+ auto x11app = qApp->nativeInterface();
+ if (!x11app) {
+ return; // wayland?
+ }
+ Display *appDpy = x11app->display();
+ auto rootWindow = getRootWindow();
+#endif
+
+ for (GrabbedKey key : std::as_const(grabbedKeys_))
+ XUngrabKey(appDpy, key.code, key.mod, rootWindow);
+ }
+
+ void activate() { emit trigger_->triggered(); }
+
+ bool isAccepted(const QKeySequence &qkey) const { return qkey_ == qkey; }
+};
+
+bool GlobalShortcutManager::KeyTrigger::Impl::failed;
+long X11KeyTriggerManager::alt_mask = 0;
+long X11KeyTriggerManager::meta_mask = 0;
+long X11KeyTriggerManager::super_mask = 0;
+long X11KeyTriggerManager::hyper_mask = 0;
+long X11KeyTriggerManager::numlock_mask = 0;
+bool X11KeyTriggerManager::haveMods = false;
+
+X11KeyTriggerManager::Qt_XK_Keymap X11KeyTriggerManager::qt_xk_table[] = {
+ { Qt::Key_Escape, { 1, { XK_Escape } } },
+ { Qt::Key_Tab, { 2, { XK_Tab, XK_KP_Tab } } },
+ { Qt::Key_Backtab, { 1, { XK_ISO_Left_Tab } } },
+ { Qt::Key_Backspace, { 1, { XK_BackSpace } } },
+ { Qt::Key_Return, { 1, { XK_Return } } },
+ { Qt::Key_Enter, { 1, { XK_KP_Enter } } },
+ { Qt::Key_Insert, { 2, { XK_Insert, XK_KP_Insert } } },
+ { Qt::Key_Delete, { 3, { XK_Delete, XK_KP_Delete, XK_Clear } } },
+ { Qt::Key_Pause, { 1, { XK_Pause } } },
+ { Qt::Key_Print, { 1, { XK_Print } } },
+ { Qt::Key_SysReq, { 1, { XK_Sys_Req } } },
+ { Qt::Key_Clear, { 1, { XK_KP_Begin } } },
+ { Qt::Key_Home, { 2, { XK_Home, XK_KP_Home } } },
+ { Qt::Key_End, { 2, { XK_End, XK_KP_End } } },
+ { Qt::Key_Left, { 2, { XK_Left, XK_KP_Left } } },
+ { Qt::Key_Up, { 2, { XK_Up, XK_KP_Up } } },
+ { Qt::Key_Right, { 2, { XK_Right, XK_KP_Right } } },
+ { Qt::Key_Down, { 2, { XK_Down, XK_KP_Down } } },
+ { Qt::Key_PageUp, { 2, { XK_Prior, XK_KP_Prior } } },
+ { Qt::Key_PageDown, { 2, { XK_Next, XK_KP_Next } } },
+ { Qt::Key_Shift, { 3, { XK_Shift_L, XK_Shift_R, XK_Shift_Lock } } },
+ { Qt::Key_Control, { 2, { XK_Control_L, XK_Control_R } } },
+ { Qt::Key_Meta, { 2, { XK_Meta_L, XK_Meta_R } } },
+ { Qt::Key_Alt, { 2, { XK_Alt_L, XK_Alt_R } } },
+ { Qt::Key_CapsLock, { 1, { XK_Caps_Lock } } },
+ { Qt::Key_NumLock, { 1, { XK_Num_Lock } } },
+ { Qt::Key_ScrollLock, { 1, { XK_Scroll_Lock } } },
+ { Qt::Key_Space, { 2, { XK_space, XK_KP_Space } } },
+ { Qt::Key_Equal, { 2, { XK_equal, XK_KP_Equal } } },
+ { Qt::Key_Asterisk, { 2, { XK_asterisk, XK_KP_Multiply } } },
+ { Qt::Key_Plus, { 2, { XK_plus, XK_KP_Add } } },
+ { Qt::Key_Comma, { 2, { XK_comma, XK_KP_Separator } } },
+ { Qt::Key_Minus, { 2, { XK_minus, XK_KP_Subtract } } },
+ { Qt::Key_Period, { 2, { XK_period, XK_KP_Decimal } } },
+ { Qt::Key_Slash, { 2, { XK_slash, XK_KP_Divide } } },
+ { Qt::Key_F1, { 1, { XK_F1 } } },
+ { Qt::Key_F2, { 1, { XK_F2 } } },
+ { Qt::Key_F3, { 1, { XK_F3 } } },
+ { Qt::Key_F4, { 1, { XK_F4 } } },
+ { Qt::Key_F5, { 1, { XK_F5 } } },
+ { Qt::Key_F6, { 1, { XK_F6 } } },
+ { Qt::Key_F7, { 1, { XK_F7 } } },
+ { Qt::Key_F8, { 1, { XK_F8 } } },
+ { Qt::Key_F9, { 1, { XK_F9 } } },
+ { Qt::Key_F10, { 1, { XK_F10 } } },
+ { Qt::Key_F11, { 1, { XK_F11 } } },
+ { Qt::Key_F12, { 1, { XK_F12 } } },
+ { Qt::Key_F13, { 1, { XK_F13 } } },
+ { Qt::Key_F14, { 1, { XK_F14 } } },
+ { Qt::Key_F15, { 1, { XK_F15 } } },
+ { Qt::Key_F16, { 1, { XK_F16 } } },
+ { Qt::Key_F17, { 1, { XK_F17 } } },
+ { Qt::Key_F18, { 1, { XK_F18 } } },
+ { Qt::Key_F19, { 1, { XK_F19 } } },
+ { Qt::Key_F20, { 1, { XK_F20 } } },
+ { Qt::Key_F21, { 1, { XK_F21 } } },
+ { Qt::Key_F22, { 1, { XK_F22 } } },
+ { Qt::Key_F23, { 1, { XK_F23 } } },
+ { Qt::Key_F24, { 1, { XK_F24 } } },
+ { Qt::Key_F25, { 1, { XK_F25 } } },
+ { Qt::Key_F26, { 1, { XK_F26 } } },
+ { Qt::Key_F27, { 1, { XK_F27 } } },
+ { Qt::Key_F28, { 1, { XK_F28 } } },
+ { Qt::Key_F29, { 1, { XK_F29 } } },
+ { Qt::Key_F30, { 1, { XK_F30 } } },
+ { Qt::Key_F31, { 1, { XK_F31 } } },
+ { Qt::Key_F32, { 1, { XK_F32 } } },
+ { Qt::Key_F33, { 1, { XK_F33 } } },
+ { Qt::Key_F34, { 1, { XK_F34 } } },
+ { Qt::Key_F35, { 1, { XK_F35 } } },
+ { Qt::Key_Super_L, { 1, { XK_Super_L } } },
+ { Qt::Key_Super_R, { 1, { XK_Super_R } } },
+ { Qt::Key_Menu, { 1, { XK_Menu } } },
+ { Qt::Key_Hyper_L, { 1, { XK_Hyper_L } } },
+ { Qt::Key_Hyper_R, { 1, { XK_Hyper_R } } },
+ { Qt::Key_Help, { 1, { XK_Help } } },
+ { Qt::Key_Direction_L, { 0, { 0 } } },
+ { Qt::Key_Direction_R, { 0, { 0 } } },
+
+ { Qt::Key_unknown, { 0, { 0 } } },
+};
+
+GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &key) { d = new Impl(this, key); }
+
+GlobalShortcutManager::KeyTrigger::~KeyTrigger()
+{
+ delete d;
+ d = nullptr;
+}
diff --git a/src/libpsi/tools/globalshortcut/globalshortcuttrigger.h b/src/libpsi/tools/globalshortcut/globalshortcuttrigger.h
new file mode 100644
index 000000000..d6a20e200
--- /dev/null
+++ b/src/libpsi/tools/globalshortcut/globalshortcuttrigger.h
@@ -0,0 +1,57 @@
+/*
+ * globalshortcuttrigger.h - Helper class activating global shortcut
+ * Copyright (C) 2006 Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef GLOBALSHORTCUTTRIGGER_H
+#define GLOBALSHORTCUTTRIGGER_H
+
+#include "globalshortcutmanager.h"
+
+#include
+
+class GlobalShortcutManager::KeyTrigger : public QObject {
+ Q_OBJECT
+public:
+ /**
+ * Is there any slot connected to this hotkey?
+ */
+ bool isUsed() const { return QObject::receivers(SIGNAL(triggered())) > 0; }
+
+signals:
+ void triggered();
+
+private:
+ /**
+ * Registers the \a key.
+ */
+ KeyTrigger(const QKeySequence &key);
+ /**
+ * Unregisters the key.
+ */
+ ~KeyTrigger();
+
+ friend class GlobalShortcutManager;
+
+ /**
+ * Platform-specific helper
+ */
+ class Impl;
+ Impl *d;
+};
+
+#endif // GLOBALSHORTCUTTRIGGER_H
diff --git a/src/libpsi/tools/growlnotifier/ChangeLog b/src/libpsi/tools/growlnotifier/ChangeLog
new file mode 100644
index 000000000..c49e07e9e
--- /dev/null
+++ b/src/libpsi/tools/growlnotifier/ChangeLog
@@ -0,0 +1,9 @@
+2005-05-05, Remko Troncon
+ * Created a more extensive example
+ * Added timeout signal (new in Growl 0.7)
+
+2005-04-15, Remko Troncon
+ * Implemented the click notification
+
+2005-04-11, Remko Troncon
+ * Start
diff --git a/src/libpsi/tools/growlnotifier/growlnotifier.cpp b/src/libpsi/tools/growlnotifier/growlnotifier.cpp
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/libpsi/tools/growlnotifier/growlnotifier.h b/src/libpsi/tools/growlnotifier/growlnotifier.h
new file mode 100644
index 000000000..72e1c00a3
--- /dev/null
+++ b/src/libpsi/tools/growlnotifier/growlnotifier.h
@@ -0,0 +1,56 @@
+/*
+ * growlnotifier.h - A simple Qt interface to Growl
+ *
+ * Copyright (C) 2005 Remko Troncon
+ * Copyright (C) 2012 Evgeny Khryukin
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#ifndef GROWLNOTIFIER_H
+#define GROWLNOTIFIER_H
+
+#include
+#include
+
+class GrowlNotifierSignaler;
+
+/**
+ * \brief A simple interface to Growl.
+ */
+class GrowlNotifier {
+public:
+ GrowlNotifier(const QStringList ¬ifications, const QStringList &default_notifications,
+ const QString &app_name = "");
+
+ virtual ~GrowlNotifier();
+
+ void notify(const QString &name, const QString &title, const QString &description, const QPixmap &icon = QPixmap(),
+ bool sticky = false, const QObject *receiver = 0, const char *clicked_slot = 0,
+ const char *timeout_slot = 0, void *context = 0);
+
+ static bool isRunning();
+
+private:
+ class Private;
+ Private *d;
+};
+
+#endif // GROWLNOTIFIER_H
diff --git a/src/libpsi/tools/growlnotifier/growlnotifier.mm b/src/libpsi/tools/growlnotifier/growlnotifier.mm
new file mode 100644
index 000000000..4def1e6ed
--- /dev/null
+++ b/src/libpsi/tools/growlnotifier/growlnotifier.mm
@@ -0,0 +1,250 @@
+/*
+ * growlnotifier.mm - A simple Qt interface to Growl
+ *
+ * Copyright (C) 2005 Remko Troncon
+ * Copyright (C) 2012 Evgeny Khryukin
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "growlnotifier.h"
+
+#include
+#include
+#include
+#include
+
+//------------------------------------------------------------------------------
+
+/**
+ * \brief A class for emitting a clicked signal to the interested party.
+ */
+class GrowlNotifierSignaler : public QObject {
+ Q_OBJECT
+public:
+ GrowlNotifierSignaler() : QObject() { }
+ ~GrowlNotifierSignaler() { }
+
+ void emitNotification(void *context) { emit notification(context); }
+
+signals:
+ void notification(void *);
+};
+
+NSString *qString2NSString(const QString &str) { return [NSString stringWithUTF8String:str.toUtf8().constData()]; }
+
+NSArray *qStringList2NSArray(const QStringList &list)
+{
+ int cnt = list.count();
+ if (!cnt)
+ return nil;
+
+ NSString *strings[cnt];
+ for (int i = 0; i < cnt; i++) {
+ strings[i] = qString2NSString(list.at(i));
+ }
+ return [NSArray arrayWithObjects:strings count:cnt];
+}
+
+NSData *qPixmap2NSData(const QPixmap &p)
+{
+ if (p.isNull()) {
+ return nil;
+ }
+
+ QByteArray img_data;
+ QBuffer buffer(&img_data);
+ buffer.open(QIODevice::WriteOnly);
+ p.save(&buffer, "PNG");
+ return [NSData dataWithBytes:img_data.constData() length:img_data.size()];
+}
+
+@interface GrowlController : NSObject {
+@private
+ NSArray *allNotifications;
+ NSArray *defaultNotifications;
+ NSString *appName;
+}
+- (void)setAllNotifications:(NSArray *)all setDefaultNotifications:(NSArray *)def setName:(NSString *)name;
+@end
+
+@implementation GrowlController
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ allNotifications = nil;
+ defaultNotifications = nil;
+ appName = nil;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (allNotifications)
+ [allNotifications release];
+ if (defaultNotifications)
+ [defaultNotifications release];
+ if (appName)
+ [appName release];
+ [super dealloc];
+}
+
+- (void)setAllNotifications:(NSArray *)all setDefaultNotifications:(NSArray *)def setName:(NSString *)name
+{
+ allNotifications = all;
+ defaultNotifications = def;
+ appName = name;
+ if (allNotifications)
+ [allNotifications retain];
+ if (defaultNotifications)
+ [defaultNotifications retain];
+ if (appName)
+ [appName retain];
+}
+
+- (NSDictionary *)registrationDictionaryForGrowl
+{
+
+ NSDictionary *dict =
+ [NSDictionary dictionaryWithObjectsAndKeys:allNotifications, GROWL_NOTIFICATIONS_ALL, defaultNotifications,
+ GROWL_NOTIFICATIONS_DEFAULT, nil];
+ return dict;
+}
+
+- (NSString *)applicatioNameForGrowl
+{
+ return appName;
+}
+
+- (NSData *)applicationIconDataForGrowl
+{
+ return nil;
+}
+
+- (void)growlNotificationWasClicked:(id)clickContext
+{
+ if (!clickContext)
+ return;
+
+ GrowlNotifierSignaler *signaler = *(GrowlNotifierSignaler **)([[clickContext objectAtIndex:0] bytes]);
+ const QObject *receiver = *(const QObject **)([[clickContext objectAtIndex:1] bytes]);
+ const char *slot = *(const char **)([[clickContext objectAtIndex:3] bytes]);
+ void *qcontext = *(void **)([[clickContext objectAtIndex:2] bytes]);
+
+ QObject::connect(signaler, SIGNAL(notification(void *)), receiver, slot);
+ signaler->emitNotification(qcontext);
+ QObject::disconnect(signaler, SIGNAL(notification(void *)), receiver, slot);
+}
+
+- (void)growlNotificationTimedOut:(id)clickContext
+{
+ if (!clickContext)
+ return;
+
+ GrowlNotifierSignaler *signaler = *(GrowlNotifierSignaler **)([[clickContext objectAtIndex:0] bytes]);
+ const QObject *receiver = *(const QObject **)([[clickContext objectAtIndex:1] bytes]);
+ const char *slot = *(const char **)([[clickContext objectAtIndex:4] bytes]);
+ void *qcontext = *(void **)([[clickContext objectAtIndex:2] bytes]);
+
+ QObject::connect(signaler, SIGNAL(notification(void *)), receiver, slot);
+ signaler->emitNotification(qcontext);
+ QObject::disconnect(signaler, SIGNAL(notification(void *)), receiver, slot);
+}
+
+@end
+
+//------------------------------------------------------------------------------
+class GrowlNotifier::Private : public GrowlNotifierSignaler {
+public:
+ Private() : GrowlNotifierSignaler() { }
+ ~Private() { }
+
+ GrowlController *controller_;
+};
+
+/**
+ * Constructs a GrowlNotifier.
+ *
+ * \param notifications the list names of all notifications that can be sent
+ * by this notifier.
+ * \param default_notifications the list of names of the notifications that
+ * should be enabled by default.
+ * \param app the name of the application under which the notifier should
+ * register with growl.
+ */
+GrowlNotifier::GrowlNotifier(const QStringList ¬ifications, const QStringList &default_notifications,
+ const QString &app)
+{
+ d = new Private;
+ d->controller_ = [[GrowlController alloc] init];
+ [d->controller_ setAllNotifications:qStringList2NSArray(notifications)
+ setDefaultNotifications:qStringList2NSArray(default_notifications)
+ setName:qString2NSString(app)];
+
+ [GrowlApplicationBridge setGrowlDelegate:d->controller_];
+}
+
+GrowlNotifier::~GrowlNotifier()
+{
+ [d->controller_ release];
+ delete d;
+}
+
+/**
+ * \brief Sends a notification to Growl.
+ *
+ * \param name the registered name of the notification.
+ * \param title the title for the notification.
+ * \param description the description of the notification.
+ * \param icon the icon of the notification.
+ * \param sticky whether the notification should be sticky (i.e. require a
+ * click to discard.
+ * \param receiver the receiving object which will be signaled when the
+ * notification is clicked. May be NULL.
+ * \param slot the slot to be signaled when the notification is clicked.
+ * \param context the context which will be passed back to the slot
+ * May be NULL.
+ */
+void GrowlNotifier::notify(const QString &name, const QString &title, const QString &description, const QPixmap &p,
+ bool sticky, const QObject *receiver, const char *clicked_slot, const char *timeout_slot,
+ void *qcontext)
+{
+ NSArray *con = [NSArray arrayWithObjects:[NSData dataWithBytes:&d length:sizeof(GrowlNotifierSignaler *)],
+ [NSData dataWithBytes:&receiver length:sizeof(const QObject *)],
+ [NSData dataWithBytes:&qcontext length:sizeof(void *)],
+ [NSData dataWithBytes:&clicked_slot length:sizeof(const char *)],
+ [NSData dataWithBytes:&timeout_slot length:sizeof(const char *)], nil];
+ [GrowlApplicationBridge notifyWithTitle:qString2NSString(title)
+ description:qString2NSString(description)
+ notificationName:qString2NSString(name)
+ iconData:qPixmap2NSData(p)
+ priority:0
+ isSticky:sticky ? YES : NO
+ clickContext:con];
+}
+
+bool GrowlNotifier::isRunning() { return [GrowlApplicationBridge isGrowlRunning]; }
+
+//-----------------------------------------------------------------------------
+
+#include "growlnotifier.moc"
diff --git a/src/libpsi/tools/growlnotifier/growltest.cpp b/src/libpsi/tools/growlnotifier/growltest.cpp
new file mode 100644
index 000000000..f715c1d55
--- /dev/null
+++ b/src/libpsi/tools/growlnotifier/growltest.cpp
@@ -0,0 +1,130 @@
+/*
+ * growltest.cpp: A test program for the GrowlNotifier class
+ * Copyright (C) 2005 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "growlnotifier.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class GrowlTestWidget : public QWidget {
+ Q_OBJECT
+
+public:
+ GrowlTestWidget(QWidget *parent = 0);
+
+public slots:
+ void do_notification1();
+ void do_notification2();
+ void notification_clicked();
+
+private:
+ QLineEdit *text, *title;
+ QCheckBox *sticky;
+ GrowlNotifier *growlNotifier;
+};
+
+GrowlTestWidget::GrowlTestWidget(QWidget *parent) : QWidget(parent)
+{
+ // Initialize widgets
+ QGridLayout *layout = new QGridLayout(this);
+
+ layout->addWidget(new QLabel("Title", this), 0, 0);
+ title = new QLineEdit(this);
+ title->setText("My Text");
+ layout->addWidget(title, 0, 1);
+
+ layout->addWidget(new QLabel("Text", this), 1, 0);
+ text = new QLineEdit(this);
+ text->setText("My Description");
+ layout->addWidget(text, 1, 1);
+
+ // layout->addWidget(new QLabel("Sticky",this),2,0);
+ // sticky = new QCheckBox(this);
+ // sticky->setTristate();
+ // layout->addWidget(sticky,2,1);
+
+ QPushButton *notification1 = new QPushButton("Notification 1", this);
+ connect(notification1, SIGNAL(clicked()), SLOT(do_notification1()));
+ layout->addWidget(notification1, 3, 0);
+
+ QPushButton *notification2 = new QPushButton("Notification 2", this);
+ connect(notification2, SIGNAL(clicked()), SLOT(do_notification2()));
+ layout->addWidget(notification2, 3, 1);
+
+ // Initialize GrowlNotifier
+ QStringList nots, defaults;
+ nots << "Notification 1" << "Notification 2";
+ defaults << "Notification 1";
+ growlNotifier = new GrowlNotifier(nots, defaults, "GrowlNotifierTest");
+}
+
+int main(int argc, char **argv)
+{
+ QApplication a(argc, argv);
+ GrowlTestWidget w;
+ w.show();
+ return a.exec();
+}
+
+void GrowlTestWidget::do_notification1()
+{
+ // if (sticky->state() != QButton::NoChange) {
+ // growlNotifier->notify("Notification 1", title->text(), text->text(), QPixmap(), sticky->isChecked(), this,
+ // SLOT(notification_clicked()));
+ //}
+ // else {
+ // growlNotifier->notify("Notification 1", title->text(), text->text(), QPixmap());
+ //}
+ growlNotifier->notify("Notification 1", title->text(), text->text(), QPixmap(), false, this,
+ SLOT(notification_clicked()));
+}
+
+void GrowlTestWidget::do_notification2()
+{
+ // if (sticky->state() != QButton::NoChange) {
+ // growlNotifier->notify("Notification 2", title->text(), text->text(), QPixmap(), sticky->isChecked(), this,
+ // SLOT(notification_clicked()));
+ //}
+ // else {
+ // growlNotifier->notify("Notification 2", title->text(), text->text(), QPixmap());
+ //}
+ growlNotifier->notify("Notification 2", title->text(), text->text(), QPixmap(), false, this,
+ SLOT(notification_clicked()));
+}
+
+void GrowlTestWidget::notification_clicked()
+{
+ QMessageBox::information(0, "Information", "Notification was clicked\n");
+}
+
+#include "growltest.moc"
diff --git a/src/libpsi/tools/idle/idle.cpp b/src/libpsi/tools/idle/idle.cpp
new file mode 100644
index 000000000..dbe37dc02
--- /dev/null
+++ b/src/libpsi/tools/idle/idle.cpp
@@ -0,0 +1,127 @@
+/*
+ * idle.cpp - detect desktop idle time
+ * Copyright (C) 2003 Justin Karneges
+ *
+ * This library 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.1 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
+ * Lesser 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, see .
+ *
+ */
+
+#include "idle.h"
+
+#include
+#include
+#include
+
+static IdlePlatform *platform = nullptr;
+static int platform_ref = 0;
+
+class Idle::Private {
+public:
+ Private() = default;
+
+ QPoint lastMousePos;
+ QDateTime idleSince;
+
+ bool active = false;
+ int idleTime = 0;
+ QDateTime startTime;
+ QTimer checkTimer;
+};
+
+Idle::Idle()
+{
+ d = new Private;
+
+ // try to use platform idle
+ if (!platform) {
+ IdlePlatform *p = new IdlePlatform;
+ if (p->init())
+ platform = p;
+ else
+ delete p;
+ }
+ if (platform)
+ ++platform_ref;
+
+ connect(&d->checkTimer, SIGNAL(timeout()), SLOT(doCheck()));
+}
+
+Idle::~Idle()
+{
+ if (platform) {
+ --platform_ref;
+ if (platform_ref == 0) {
+ delete platform;
+ platform = nullptr;
+ }
+ }
+ delete d;
+}
+
+bool Idle::isActive() const { return d->active; }
+
+bool Idle::usingPlatform() const { return (platform ? true : false); }
+
+void Idle::start()
+{
+ d->startTime = QDateTime::currentDateTime();
+
+ if (!platform) {
+ // generic idle
+ d->lastMousePos = QCursor::pos();
+ d->idleSince = QDateTime::currentDateTime();
+ }
+
+ // poll every 5 seconds (use a lower value if you need more accuracy)
+ d->checkTimer.start(5000);
+}
+
+void Idle::stop() { d->checkTimer.stop(); }
+
+void Idle::doCheck()
+{
+ int i;
+ if (platform)
+ i = platform->secondsIdle();
+ else {
+ QPoint curMousePos = QCursor::pos();
+ QDateTime curDateTime = QDateTime::currentDateTime();
+ if (d->lastMousePos != curMousePos) {
+ d->lastMousePos = curMousePos;
+ d->idleSince = curDateTime;
+ }
+ i = d->idleSince.secsTo(curDateTime);
+ }
+
+ // set 'beginIdle' to the beginning of the idle time (by backtracking 'i' seconds from now)
+ QDateTime beginIdle = QDateTime::currentDateTime().addSecs(-i);
+
+ // set 't' to hold the number of seconds between 'beginIdle' and 'startTime'
+ int t = beginIdle.secsTo(d->startTime);
+
+ // beginIdle later than (or equal to) startTime?
+ if (t <= 0) {
+ // scoot ourselves up to the new idle start
+ d->startTime = beginIdle;
+ }
+ // beginIdle earlier than startTime?
+ else if (t > 0) {
+ // do nothing
+ }
+
+ // how long have we been idle?
+ int idleTime = d->startTime.secsTo(QDateTime::currentDateTime());
+
+ emit secondsIdle(idleTime);
+}
diff --git a/src/libpsi/tools/idle/idle.h b/src/libpsi/tools/idle/idle.h
new file mode 100644
index 000000000..0cbe4f943
--- /dev/null
+++ b/src/libpsi/tools/idle/idle.h
@@ -0,0 +1,62 @@
+/*
+ * idle.h - detect desktop idle time
+ * Copyright (C) 2003 Justin Karneges
+ *
+ * This library 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.1 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
+ * Lesser 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, see .
+ *
+ */
+
+#ifndef IDLE_H
+#define IDLE_H
+
+#include
+
+class IdlePlatform;
+
+class Idle : public QObject {
+ Q_OBJECT
+public:
+ Idle();
+ ~Idle();
+
+ bool isActive() const;
+ bool usingPlatform() const;
+ void start();
+ void stop();
+
+signals:
+ void secondsIdle(int);
+
+private slots:
+ void doCheck();
+
+private:
+ class Private;
+ Private *d;
+};
+
+class IdlePlatform {
+public:
+ IdlePlatform();
+ ~IdlePlatform();
+
+ bool init();
+ int secondsIdle();
+
+private:
+ class Private;
+ Private *d;
+};
+
+#endif // IDLE_H
diff --git a/src/libpsi/tools/idle/idle_mac.cpp b/src/libpsi/tools/idle/idle_mac.cpp
new file mode 100644
index 000000000..6e4ee483b
--- /dev/null
+++ b/src/libpsi/tools/idle/idle_mac.cpp
@@ -0,0 +1,154 @@
+/*
+ * idle_mac.cpp - detect desktop idle time
+ * Copyright (C) 2003 Tarkvara Design Inc.
+ *
+ * This library 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.1 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
+ * Lesser 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, see .
+ *
+ */
+
+#include "idle.h"
+
+#include
+
+// Why does Apple have to make this so complicated?
+static OSStatus LoadFrameworkBundle(CFStringRef framework, CFBundleRef *bundlePtr)
+{
+ OSStatus err;
+ FSRef frameworksFolderRef;
+ CFURLRef baseURL;
+ CFURLRef bundleURL;
+
+ if (bundlePtr == nil)
+ return (-1);
+
+ *bundlePtr = nil;
+
+ baseURL = nil;
+ bundleURL = nil;
+
+ err = FSFindFolder(kOnAppropriateDisk, kFrameworksFolderType, true, &frameworksFolderRef);
+ if (err == noErr) {
+ baseURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &frameworksFolderRef);
+ if (baseURL == nil) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == noErr) {
+ bundleURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, baseURL, framework, false);
+ if (bundleURL == nil) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == noErr) {
+ *bundlePtr = CFBundleCreate(kCFAllocatorSystemDefault, bundleURL);
+ if (*bundlePtr == nil) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+ if (err == noErr) {
+ if (!CFBundleLoadExecutable(*bundlePtr)) {
+ err = coreFoundationUnknownErr;
+ }
+ }
+
+ // Clean up.
+ if (err != noErr && *bundlePtr != nil) {
+ CFRelease(*bundlePtr);
+ *bundlePtr = nil;
+ }
+ if (bundleURL != nil) {
+ CFRelease(bundleURL);
+ }
+ if (baseURL != nil) {
+ CFRelease(baseURL);
+ }
+
+ return err;
+}
+
+class IdlePlatform::Private {
+public:
+ EventLoopTimerRef mTimerRef;
+ int mSecondsIdle;
+
+ Private() : mTimerRef(0), mSecondsIdle(0) { }
+
+ static pascal void IdleTimerAction(EventLoopTimerRef, EventLoopIdleTimerMessage inState, void *inUserData);
+};
+
+pascal void IdlePlatform::Private::IdleTimerAction(EventLoopTimerRef, EventLoopIdleTimerMessage inState,
+ void *inUserData)
+{
+ switch (inState) {
+ case kEventLoopIdleTimerStarted:
+ case kEventLoopIdleTimerStopped:
+ // Get invoked with this constant at the start of the idle period,
+ // or whenever user activity cancels the idle.
+ ((IdlePlatform::Private *)inUserData)->mSecondsIdle = 0;
+ break;
+ case kEventLoopIdleTimerIdling:
+ // Called every time the timer fires (i.e. every second).
+ ((IdlePlatform::Private *)inUserData)->mSecondsIdle++;
+ break;
+ }
+}
+
+IdlePlatform::IdlePlatform() { d = new Private(); }
+
+IdlePlatform::~IdlePlatform()
+{
+ RemoveEventLoopTimer(d->mTimerRef);
+ delete d;
+}
+
+// Typedef for the function we're getting back from CFBundleGetFunctionPointerForName.
+typedef OSStatus (*InstallEventLoopIdleTimerPtr)(EventLoopRef inEventLoop, EventTimerInterval inFireDelay,
+ EventTimerInterval inInterval, EventLoopIdleTimerUPP inTimerProc,
+ void *inTimerData, EventLoopTimerRef *outTimer);
+
+bool IdlePlatform::init()
+{
+ // May already be init'ed.
+ if (d->mTimerRef) {
+ return true;
+ }
+
+ // According to the docs, InstallEventLoopIdleTimer is new in 10.2.
+ // According to the headers, it has been around since 10.0.
+ // One of them is lying. We'll play it safe and weak-link the function.
+
+ // Load the "Carbon.framework" bundle.
+ CFBundleRef carbonBundle;
+ if (LoadFrameworkBundle(CFSTR("Carbon.framework"), &carbonBundle) != noErr) {
+ return false;
+ }
+
+ // Load the Mach-O function pointers for the routine we will be using.
+ InstallEventLoopIdleTimerPtr myInstallEventLoopIdleTimer
+ = (InstallEventLoopIdleTimerPtr)CFBundleGetFunctionPointerForName(carbonBundle,
+ CFSTR("InstallEventLoopIdleTimer"));
+ if (myInstallEventLoopIdleTimer == 0) {
+ return false;
+ }
+
+ EventLoopIdleTimerUPP timerUPP = NewEventLoopIdleTimerUPP(Private::IdleTimerAction);
+ if ((*myInstallEventLoopIdleTimer)(GetMainEventLoop(), kEventDurationSecond, kEventDurationSecond, timerUPP, 0,
+ &d->mTimerRef)) {
+ return true;
+ }
+
+ return false;
+}
+
+int IdlePlatform::secondsIdle() { return d->mSecondsIdle; }
diff --git a/src/libpsi/tools/idle/idle_win.cpp b/src/libpsi/tools/idle/idle_win.cpp
new file mode 100644
index 000000000..3e9707162
--- /dev/null
+++ b/src/libpsi/tools/idle/idle_win.cpp
@@ -0,0 +1,100 @@
+/*
+ * idle_win.cpp - detect desktop idle time
+ * Copyright (C) 2003 Justin Karneges
+ *
+ * This library 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.1 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
+ * Lesser 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, see .
+ *
+ */
+
+#include "idle.h"
+
+#include
+#include
+
+#if defined(Q_OS_WIN32) && !defined(Q_CC_GNU) && (_WIN32_WINNT < 0x0500)
+typedef struct tagLASTINPUTINFO {
+ UINT cbSize;
+ DWORD dwTime;
+} LASTINPUTINFO, *PLASTINPUTINFO;
+#endif
+
+class IdlePlatform::Private {
+public:
+ Private()
+ {
+ GetLastInputInfo = 0;
+ IdleUIGetLastInputTime = 0;
+ lib = 0;
+ }
+
+ typedef BOOL(__stdcall *GetLastInputInfoFunc)(PLASTINPUTINFO);
+ typedef DWORD(__stdcall *IdleUIGetLastInputTimeFunc)(void);
+ GetLastInputInfoFunc GetLastInputInfo;
+ IdleUIGetLastInputTimeFunc IdleUIGetLastInputTime;
+ QLibrary *lib;
+};
+
+IdlePlatform::IdlePlatform() { d = new Private; }
+
+IdlePlatform::~IdlePlatform()
+{
+ delete d->lib;
+ delete d;
+}
+
+bool IdlePlatform::init()
+{
+ if (d->lib)
+ return true;
+
+ // try to find the built-in Windows 2000 function
+ d->lib = new QLibrary("user32");
+ if (d->lib->load() && (d->GetLastInputInfo = (Private::GetLastInputInfoFunc)d->lib->resolve("GetLastInputInfo"))) {
+ return true;
+ } else {
+ delete d->lib;
+ d->lib = 0;
+ }
+
+ // fall back on idleui
+ d->lib = new QLibrary("idleui");
+ if (d->lib->load()
+ && (d->IdleUIGetLastInputTime
+ = (Private::IdleUIGetLastInputTimeFunc)d->lib->resolve("IdleUIGetLastInputTime"))) {
+ return true;
+ } else {
+ delete d->lib;
+ d->lib = 0;
+ }
+
+ return false;
+}
+
+int IdlePlatform::secondsIdle()
+{
+ int i;
+ if (d->GetLastInputInfo) {
+ LASTINPUTINFO li;
+ li.cbSize = sizeof(LASTINPUTINFO);
+ bool ok = d->GetLastInputInfo(&li);
+ if (!ok)
+ return 0;
+ i = li.dwTime;
+ } else if (d->IdleUIGetLastInputTime) {
+ i = d->IdleUIGetLastInputTime();
+ } else
+ return 0;
+
+ return (GetTickCount() - i) / 1000;
+}
diff --git a/src/libpsi/tools/idle/idle_x11.cpp b/src/libpsi/tools/idle/idle_x11.cpp
new file mode 100644
index 000000000..d50cc3d45
--- /dev/null
+++ b/src/libpsi/tools/idle/idle_x11.cpp
@@ -0,0 +1,154 @@
+/*
+ * idle_x11.cpp - detect desktop idle time
+ * Copyright (C) 2003 Justin Karneges
+ *
+ * This library 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.1 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
+ * Lesser 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, see .
+ *
+ */
+
+#include "idle.h"
+
+#ifdef HAVE_XSS
+#include
+#include
+#include
+#include
+#include
+#include
+
+static XErrorHandler old_handler = 0;
+extern "C" int xerrhandler(Display *dpy, XErrorEvent *err)
+{
+ if (err->error_code == BadDrawable)
+ return 0;
+
+ return (*old_handler)(dpy, err);
+}
+#endif // HAVE_XSS
+
+#ifdef USE_DBUS
+
+#include
+#include
+#include
+#include
+#include
+
+// Screen Saver dbus services
+static const QLatin1String COMMON_SS_SERV("org.freedesktop.ScreenSaver");
+static const QLatin1String COMMON_SS_PATH("/ScreenSaver");
+static const QLatin1String KDE_SS_SERV("org.kde.screensaver");
+static const QLatin1String GNOME_SS_SERV("org.gnome.Mutter.IdleMonitor");
+static const QLatin1String GNOME_SS_PATH("/org/gnome/Mutter/IdleMonitor/Core");
+// Screen saver functions
+static const QLatin1String GNOME_SS_F("GetIdletime");
+static const QLatin1String COMMON_SS_F("GetSessionIdleTime");
+
+#endif // USE_DBUS
+
+class IdlePlatform::Private {
+public:
+ Private() { }
+
+#ifdef USE_DBUS
+ QString dbusService = QString();
+#endif // USE_DBUS
+#ifdef HAVE_XSS
+ XScreenSaverInfo *ss_info = nullptr;
+#endif // HAVE_XSS
+};
+
+IdlePlatform::IdlePlatform()
+{
+#if defined(HAVE_XSS) || defined(USE_DBUS)
+ d = new Private;
+#else
+ d = nullptr;
+#endif
+}
+
+IdlePlatform::~IdlePlatform()
+{
+#ifdef HAVE_XSS
+ if (d->ss_info)
+ XFree(d->ss_info);
+ if (old_handler) {
+ XSetErrorHandler(old_handler);
+ old_handler = 0;
+ }
+#endif // HAVE_XSS
+ if (d)
+ delete d;
+}
+
+bool IdlePlatform::init()
+{
+#ifdef USE_DBUS
+ // if DBUS idle is available using it else try to use XSS functions
+ const auto services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
+ const QStringList idleServices = { COMMON_SS_SERV, KDE_SS_SERV, GNOME_SS_SERV };
+ // find first available dbus-service
+ for (const auto &service : idleServices) {
+ if (services.contains(service)) {
+ d->dbusService = service;
+ return true;
+ }
+ }
+#endif // USE_DBUS
+#ifdef HAVE_XSS
+ if (!QX11Info::isPlatformX11())
+ return false;
+
+ if (d->ss_info)
+ return true;
+
+ old_handler = XSetErrorHandler(xerrhandler);
+
+ int event_base, error_base;
+ if (XScreenSaverQueryExtension(QX11Info::display(), &event_base, &error_base)) {
+ d->ss_info = XScreenSaverAllocInfo();
+ return true;
+ }
+#endif // HAVE_XSS
+ return false;
+}
+
+int IdlePlatform::secondsIdle()
+{
+#ifdef USE_DBUS
+ if (!d->dbusService.isEmpty()) {
+ // KDE and freedesktop uses the same path interface and method but gnome uses other
+ bool isNotGnome = d->dbusService == COMMON_SS_SERV || d->dbusService == KDE_SS_SERV;
+ const QLatin1String iface = isNotGnome ? COMMON_SS_SERV : GNOME_SS_SERV;
+ const QLatin1String path = isNotGnome ? COMMON_SS_PATH : GNOME_SS_PATH;
+ const QLatin1String method = isNotGnome ? COMMON_SS_F : GNOME_SS_F;
+ auto interface = QDBusInterface(d->dbusService, path, iface);
+ if (interface.isValid()) {
+ QDBusReply reply = interface.call(method);
+ // probably reply value for freedesktop and kde need to be converted to seconds
+ if (reply.isValid())
+ return isNotGnome ? reply.value() / 1000 : reply.value();
+ }
+ }
+#endif // USE_DBUS
+#ifdef HAVE_XSS
+ if (!d->ss_info)
+ return 0;
+
+ if (!XScreenSaverQueryInfo(QX11Info::display(), QX11Info::appRootWindow(), d->ss_info))
+ return 0;
+ return d->ss_info->idle / 1000;
+#endif // HAVE_XSS
+ return 0;
+}
diff --git a/src/libpsi/tools/iodeviceopener.cpp b/src/libpsi/tools/iodeviceopener.cpp
new file mode 100644
index 000000000..d505532de
--- /dev/null
+++ b/src/libpsi/tools/iodeviceopener.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Remko Troncon
+ *
+ * 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, see .
+ *
+ */
+
+#include "iodeviceopener.h"
+
+IODeviceOpener::IODeviceOpener(QIODevice *device, QIODevice::OpenModeFlag mode) : device_(device), close_(false)
+{
+ if (!device_->isOpen()) {
+ if (!device_->open(mode)) {
+ isOpen_ = false;
+ return;
+ }
+ close_ = true;
+ } else if (!(device_->openMode() & mode)) {
+ isOpen_ = false;
+ return;
+ }
+ isOpen_ = true;
+}
+
+IODeviceOpener::~IODeviceOpener()
+{
+ if (close_) {
+ device_->close();
+ }
+}
diff --git a/src/libpsi/tools/iodeviceopener.h b/src/libpsi/tools/iodeviceopener.h
new file mode 100644
index 000000000..163245537
--- /dev/null
+++ b/src/libpsi/tools/iodeviceopener.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 Remko Troncon
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef IODEVICEOPENER_H
+#define IODEVICEOPENER_H
+
+#include
+#include
+
+/**
+ * An IODeviceOpener is used to ensure that an IODevice is opened.
+ * If the QIODevice was not open when the opener was created, it will be closed
+ * again when the opener is destroyed.
+ *
+ * Example:
+ * void foo(QIODevice* device) {
+ * IODeviceOpener opener(device, QIODevice::ReadOnly);
+ * if (!opener.isOpen()) {
+ * qDebug() << "Error opening QIODevice";
+ * return;
+ * }
+ * ...
+ * device->readAll()
+ * ...
+ * }
+ */
+class IODeviceOpener {
+public:
+ /**
+ * Opens an QIODevice in a specific mode if the device was not opened
+ * yet.
+ * If the device was already open but in a different, non-compatible
+ * mode than the one requested, isOpen() will return false.
+ */
+ IODeviceOpener(QIODevice *device, QIODevice::OpenModeFlag mode);
+
+ /**
+ * Closes the QIODevice passed to the constructor if the IODevice was not
+ * opened at that time.
+ */
+ ~IODeviceOpener();
+
+ /**
+ * Checks whether the io device was opened succesfully in the mode
+ * requested.
+ */
+ bool isOpen() const { return isOpen_; }
+
+private:
+ QPointer device_;
+ bool close_;
+ bool isOpen_;
+};
+
+#endif // IODEVICEOPENER_H
diff --git a/src/libpsi/tools/languagemanager.cpp b/src/libpsi/tools/languagemanager.cpp
new file mode 100644
index 000000000..bbad81a2f
--- /dev/null
+++ b/src/libpsi/tools/languagemanager.cpp
@@ -0,0 +1,254 @@
+#include "languagemanager.h"
+
+#include
+#include
+
+LanguageManager::LangId LanguageManager::fromString(const QString &langDesc)
+{
+ QLocale loc(langDesc);
+ LangId id;
+ if (loc == QLocale::c()) {
+ return id; // It's default initialized to any lang, any country, any script. consider as a error
+ }
+ int cnt = langDesc.count(QRegularExpression("[_-]"));
+ id.language = loc.language();
+ if (cnt) {
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ id.country = loc.country(); // supposing if there are two components then it's lways country and not script
+#else
+ id.country = loc.territory();
+#endif
+ if (cnt > 1) { // lang_script_country
+ id.script = loc.script();
+ }
+ }
+ return id;
+}
+
+// returns [lang][-script][-country]
+QString LanguageManager::toString(const LanguageManager::LangId &id)
+{
+ QLocale loc((QLocale::Language)id.language, (QLocale::Script)id.script, (QLocale::Country)id.country);
+ QStringList ret;
+ QStringList langCountry = loc.name().split('_');
+ if (id.language) {
+ ret.append(langCountry[0]); // language
+ }
+ if (id.script) {
+ QStringList items = loc.bcp47Name().split(QRegularExpression("[.@]"))[0].split('-');
+ if (items.count() == 3) { // we have script
+ ret.append(items[1]);
+ }
+ }
+ if (id.country) {
+ ret.append(langCountry[1]);
+ }
+ return ret.join('-');
+}
+
+/**
+ * @brief LanguageManager::bestUiMatch
+ *
+ * Lookups the best match from available locales against each next
+ * locale from QLocale::uiLanguages.
+ * For example available is comprised of en_ANY, en_US, ru_ANY (depends on LangId fields),
+ * and uiLanguages has ru_RU then "ru_ANY" will be selected and returned.
+ * If uiLanguages locale is en_US for the example above, then en_US
+ * will be selected with language and country in LangId.
+ *
+ * Another case is when available have something like en_US, ru_RU, ru_UA but
+ * uiLanguages has just "ru" then system locale will be checked for country.
+ * In case of Russia, ru_RU will be selected for Belarus nothing will selected.
+ *
+ * Exmaples:
+ * available | ui | selected |
+ * -------------------------------------------------
+ * en_ANY en_US | en_US | en_US
+ * en_ANY | en_US | en_ANY
+ * en_US | en | en_US if system is US. nothing otherwise
+ *
+ *
+ * @param avail available languages to select from.
+ * @param justOne just one langId in result is enough
+ * @return priority sorted languages list. best match comes first
+ */
+QList LanguageManager::bestUiMatch(const QSet &avail, bool justOne)
+{
+ QLocale def; // default locale (or system locale if default is not set). FIXME get from settings
+ static QSet uiLangs;
+ if (uiLangs.isEmpty()) {
+ const auto languages = QLocale::system().uiLanguages();
+ for (auto const &l : languages) {
+ auto id = fromString(l);
+ if (id.language) {
+ uiLangs.insert(id);
+ }
+ }
+ }
+ QList ret;
+ QList toCheck;
+ toCheck.reserve(4);
+ for (auto uiId : uiLangs) {
+ toCheck.clear();
+ // check if ui locale looks like system locale and set missed parts
+ if (uiId.language == def.language()) { // matches with system. consider country and script from system to be
+ // preferred if not set in ui
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ auto country = def.country();
+#else
+ auto country = def.territory();
+#endif
+ if (!uiId.country && (!uiId.script || uiId.script == def.script())) {
+ uiId.country = country;
+ }
+ if (!uiId.script && (!uiId.country || uiId.country == country)) {
+ uiId.script = def.script();
+ }
+ }
+ // now when everything is filled we can fallback from example 3 to 2 if it was system.
+ // of course it's still possible to have just language in ui.
+
+ // next things are simple.
+ // first try uiId match as is and add to result if it's within available.
+ // then check if we can remove script or country and try again.
+ // repeat till we have just language
+ toCheck.append(uiId);
+
+ auto copyId = uiId;
+ // try to check with any script
+ if (uiId.script != QLocale::AnyScript) {
+ uiId.script = QLocale::AnyScript;
+ toCheck.append(uiId);
+ uiId = copyId;
+ }
+ // try to check with any country
+ if (uiId.country != QLocale::AnyCountry) {
+ uiId.country = QLocale::AnyCountry;
+ toCheck.append(uiId);
+ uiId = copyId;
+ }
+ // try to check any script and any country
+ if (uiId.script != QLocale::AnyScript && uiId.country != QLocale::AnyCountry) {
+ uiId.script = QLocale::AnyScript;
+ uiId.country = QLocale::AnyCountry;
+ toCheck.append(uiId);
+ }
+
+ for (auto const &id : toCheck) {
+ if (avail.contains(id)) {
+ ret.append(id);
+ if (justOne) {
+ return ret;
+ }
+ }
+ }
+ }
+ LangId defLangId;
+ if (avail.contains(defLangId)) {
+ ret.append(defLangId);
+ }
+ return ret;
+}
+
+QString LanguageManager::bestUiMatch(QHash langToText)
+{
+ QHash langs;
+ for (auto l = langToText.constBegin(); l != langToText.constEnd(); ++l) {
+ langs.insert(LanguageManager::fromString(l.key()),
+ l.value()); // FIXME: all unknown languages will be converted to C/default locale
+ }
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ auto preferred = LanguageManager::bestUiMatch(langs.keys().toSet(), true);
+#else
+ auto preferred = LanguageManager::bestUiMatch(QSet(langs.keyBegin(), langs.keyEnd()), true);
+#endif
+ if (preferred.count()) {
+ return langs.value(preferred.first());
+ }
+ return QString();
+}
+
+QString LanguageManager::languageName(const LanguageManager::LangId &id)
+{
+ bool needCountry = true;
+ if (id.language == QLocale::AnyLanguage) {
+ return QObject::tr("Any Language");
+ }
+ QLocale loc((QLocale::Language)id.language, (QLocale::Script)id.script, (QLocale::Country)id.country);
+
+ QString name;
+ if (loc.language() == QLocale::English || loc.language() == QLocale::Spanish) {
+ // english and espanol use country in language name
+ if (id.country) {
+ needCountry = false;
+ } else {
+ name = loc.language() == QLocale::English ? QStringLiteral("English") : QStringLiteral("Español");
+ }
+ }
+
+ if (name.isEmpty()) {
+ name = loc.nativeLanguageName();
+ }
+ if (name.isEmpty()) {
+ name = QLocale::languageToString(loc.language());
+ } else if (loc.script() != QLocale::LatinScript
+ && loc.script()
+ != QLocale().script()) { // if not latin and not deafuls, then probaby it's somethingunreadable
+ name += (" [" + QLocale::languageToString(loc.language()) + "]");
+ }
+ if (id.script) {
+ name += " - " + QLocale::scriptToString(loc.script());
+ }
+ if (needCountry && id.country) {
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ name += " - " + loc.nativeCountryName();
+#else
+ name += " - " + loc.nativeTerritoryName();
+#endif
+ }
+ return name;
+}
+
+QString LanguageManager::countryName(const LanguageManager::LangId &id)
+{
+ QLocale loc((QLocale::Language)id.language, (QLocale::Script)id.script, (QLocale::Country)id.country);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QString ret = loc.nativeCountryName();
+#else
+ QString ret = loc.nativeTerritoryName();
+#endif
+ if (loc.language() != QLocale().language() && loc.script() != QLocale::LatinScript) {
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ ret += " (" + loc.countryToString(loc.country()) + ")";
+#else
+ ret += " (" + loc.territoryToString(loc.territory()) + ")";
+#endif
+ }
+ return ret;
+}
+
+QSet LanguageManager::deserializeLanguageSet(const QString &str)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ QStringList langs = str.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
+#else
+ QStringList langs = str.split(QRegularExpression("\\s+"), QString::SkipEmptyParts);
+#endif
+ QSet ret;
+ for (auto const &l : langs) {
+ auto id = fromString(l);
+ if (id.language) {
+ ret.insert(id);
+ }
+ }
+ return ret;
+}
+
+QString LanguageManager::serializeLanguageSet(const QSet &langs)
+{
+ QStringList ret;
+ for (auto const &l : langs) {
+ ret.append(toString(l));
+ }
+ return ret.join(' ');
+}
diff --git a/src/libpsi/tools/languagemanager.h b/src/libpsi/tools/languagemanager.h
new file mode 100644
index 000000000..2a55cf73a
--- /dev/null
+++ b/src/libpsi/tools/languagemanager.h
@@ -0,0 +1,43 @@
+#ifndef LANGUAGEMANAGER_H
+#define LANGUAGEMANAGER_H
+
+#include
+#include
+
+class LanguageManager {
+public:
+ struct LangId {
+ quint16 language = QLocale::AnyLanguage;
+ quint16 country = QLocale::AnyCountry;
+ quint8 script = QLocale::AnyScript; // in qt-5.9.2 it's less than 256
+ };
+
+ static LangId fromString(const QString &langDesc);
+ static QString toString(const LangId &id);
+ static QList bestUiMatch(const QSet &avail, bool justOne = false);
+ static QString bestUiMatch(QHash langToText);
+ static QString languageName(const LangId &id);
+ static QString countryName(const LangId &id);
+ static QSet deserializeLanguageSet(const QString &);
+ static QString serializeLanguageSet(const QSet &langs);
+};
+
+inline uint qHash(const LanguageManager::LangId &t) { return qHash(t.language) ^ qHash(t.country) ^ qHash(t.script); }
+
+// weird sorting operator
+inline bool operator<(const LanguageManager::LangId &a, const LanguageManager::LangId &b)
+{
+ if (a.script == b.script) {
+ return ((a.language << 16) + a.country) < ((b.language << 16) + b.country);
+ }
+ return a.script < b.script;
+}
+
+inline bool operator==(const LanguageManager::LangId &a, const LanguageManager::LangId &b)
+{
+ return a.language == b.language && a.country == b.country && a.script == b.script;
+}
+
+Q_DECLARE_METATYPE(LanguageManager::LangId)
+
+#endif // LANGUAGEMANAGER_H
diff --git a/src/libpsi/tools/mac_dock/docktest.cpp b/src/libpsi/tools/mac_dock/docktest.cpp
new file mode 100644
index 000000000..afb7dc70c
--- /dev/null
+++ b/src/libpsi/tools/mac_dock/docktest.cpp
@@ -0,0 +1,74 @@
+/*
+ * docktest.cpp: A test program for the dock class
+ * Copyright (C) 2005 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "mac_dock.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class DockTestWidget : public QWidget {
+ Q_OBJECT
+
+public:
+ DockTestWidget(QWidget *parent = 0);
+
+public slots:
+ void do_overlay();
+
+private:
+ QLineEdit *text;
+};
+
+DockTestWidget::DockTestWidget(QWidget *parent) : QWidget(parent)
+{
+ // Initialize widgets
+ QGridLayout *layout = new QGridLayout(this);
+
+ layout->addWidget(new QLabel("Text", this), 0, 0);
+ text = new QLineEdit(this);
+ text->setText("1");
+ layout->addWidget(text, 0, 1);
+
+ QPushButton *overlay = new QPushButton("Overlay", this);
+ connect(overlay, SIGNAL(clicked()), SLOT(do_overlay()));
+ layout->addWidget(overlay, 1, 0);
+}
+
+int main(int argc, char **argv)
+{
+ QApplication a(argc, argv);
+ DockTestWidget w;
+ w.show();
+ return a.exec();
+}
+
+void DockTestWidget::do_overlay() { MacDock::overlay(text->text()); }
+
+#include "docktest.moc"
diff --git a/src/libpsi/tools/mac_dock/mac_dock.h b/src/libpsi/tools/mac_dock/mac_dock.h
new file mode 100644
index 000000000..b8906cd2e
--- /dev/null
+++ b/src/libpsi/tools/mac_dock/mac_dock.h
@@ -0,0 +1,17 @@
+#ifndef MAC_DOCK_H
+#define MAC_DOCK_H
+
+#include
+
+class MacDock {
+public:
+ static void startBounce();
+ static void stopBounce();
+ static void overlay(const QString &text = QString());
+
+private:
+ static bool isBouncing;
+ static bool overlayed;
+};
+
+#endif // MAC_DOCK_H
diff --git a/src/libpsi/tools/mac_dock/mac_dock.mm b/src/libpsi/tools/mac_dock/mac_dock.mm
new file mode 100644
index 000000000..8a08ecb8d
--- /dev/null
+++ b/src/libpsi/tools/mac_dock/mac_dock.mm
@@ -0,0 +1,22 @@
+#include "mac_dock.h"
+
+#include "privateqt_mac.h"
+
+#include
+
+static NSInteger requestType = 0;
+
+void MacDock::startBounce() { requestType = [NSApp requestUserAttention:NSCriticalRequest]; }
+
+void MacDock::stopBounce()
+{
+ if (requestType) {
+ [NSApp cancelUserAttentionRequest:requestType];
+ requestType = 0;
+ }
+}
+
+void MacDock::overlay(const QString &text)
+{
+ [[NSApp dockTile] setBadgeLabel:(NSString *)QtCFString::toCFStringRef(text)];
+}
diff --git a/src/libpsi/tools/mac_dock/privateqt_mac.h b/src/libpsi/tools/mac_dock/privateqt_mac.h
new file mode 100644
index 000000000..ccadcf58d
--- /dev/null
+++ b/src/libpsi/tools/mac_dock/privateqt_mac.h
@@ -0,0 +1,91 @@
+/*
+ * privateqt_mac.h
+ * Copyright (C) 2009 Yandex LLC (Michail Pishchagin)
+ * based on http://doc.trolltech.com/solutions/4/qtspellcheckingtextedit/
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef PRIVATEQT_MAC_H
+#define PRIVATEQT_MAC_H
+
+#include
+
+#include
+
+template class QtCFType {
+public:
+ inline QtCFType(const T &t = 0) : type(t) { }
+ inline QtCFType(const QtCFType &helper) : type(helper.type)
+ {
+ if (type)
+ CFRetain(type);
+ }
+ inline ~QtCFType()
+ {
+ if (type)
+ CFRelease(type);
+ }
+ inline operator T() { return type; }
+ inline QtCFType operator=(const QtCFType &helper)
+ {
+ if (helper.type)
+ CFRetain(helper.type);
+ CFTypeRef type2 = type;
+ type = helper.type;
+ if (type2)
+ CFRelease(type2);
+ return *this;
+ }
+ inline T *operator&() { return &type; }
+ static QtCFType constructFromGet(const T &t)
+ {
+ CFRetain(t);
+ return QtCFType(t);
+ }
+
+protected:
+ T type;
+};
+
+class QtCFString : public QtCFType {
+public:
+ inline QtCFString(const QString &str) : QtCFType(0), string(str) { }
+ inline QtCFString(const CFStringRef cfstr = 0) : QtCFType(cfstr) { }
+ inline QtCFString(const QtCFType &other) : QtCFType(other) { }
+ operator QString() const;
+ operator CFStringRef() const;
+ static QString toQString(CFStringRef cfstr);
+ static CFStringRef toCFStringRef(const QString &str);
+
+private:
+ QString string;
+};
+
+class QtMacCocoaAutoReleasePool {
+private:
+ void *pool;
+
+public:
+ QtMacCocoaAutoReleasePool();
+ ~QtMacCocoaAutoReleasePool();
+
+ inline void *handle() const { return pool; }
+};
+
+struct _NSRange;
+typedef struct _NSRange NSRange;
+
+#endif // PRIVATEQT_MAC_H
diff --git a/src/libpsi/tools/mac_dock/privateqt_mac.mm b/src/libpsi/tools/mac_dock/privateqt_mac.mm
new file mode 100644
index 000000000..b2dd02420
--- /dev/null
+++ b/src/libpsi/tools/mac_dock/privateqt_mac.mm
@@ -0,0 +1,66 @@
+/*
+ * privateqt_mac.mm
+ * Copyright (C) 2009 Yandex LLC (Michail Pishchagin)
+ * based on http://doc.trolltech.com/solutions/4/qtspellcheckingtextedit/
+ *
+ * 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, see .
+ *
+ */
+
+#include "privateqt_mac.h"
+
+#import
+#include
+#include
+
+QString QtCFString::toQString(CFStringRef str)
+{
+ if (!str)
+ return QString();
+ CFIndex length = CFStringGetLength(str);
+ const UniChar *chars = CFStringGetCharactersPtr(str);
+ if (chars)
+ return QString(reinterpret_cast(chars), length);
+
+ QVarLengthArray buffer(length);
+ CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data());
+ return QString(reinterpret_cast(buffer.constData()), length);
+}
+
+QtCFString::operator QString() const
+{
+ if (string.isEmpty() && type)
+ const_cast(this)->string = toQString(type);
+ return string;
+}
+
+CFStringRef QtCFString::toCFStringRef(const QString &string)
+{
+ return CFStringCreateWithCharacters(0, reinterpret_cast(string.unicode()), string.length());
+}
+
+QtCFString::operator CFStringRef() const
+{
+ if (!type)
+ const_cast(this)->type = toCFStringRef(string);
+ return type;
+}
+
+QtMacCocoaAutoReleasePool::QtMacCocoaAutoReleasePool()
+{
+ NSApplicationLoad();
+ pool = (void *)[[NSAutoreleasePool alloc] init];
+}
+
+QtMacCocoaAutoReleasePool::~QtMacCocoaAutoReleasePool() { [(NSAutoreleasePool *)pool release]; }
diff --git a/src/libpsi/tools/maybe.h b/src/libpsi/tools/maybe.h
new file mode 100644
index 000000000..e231ebd29
--- /dev/null
+++ b/src/libpsi/tools/maybe.h
@@ -0,0 +1,65 @@
+/*
+ * maybe.h
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef MAYBE_H
+#define MAYBE_H
+
+/**
+ * \brief A template class either containing no value or a specific value.
+ * This container is especially handy for types that do not have an isNull()
+ * or isEmpty() (such as primitive types).
+ *
+ * Example: Returns the division of numer and denom if it is an integer
+ * \code
+ * Maybe integer_divide(int numer, int denom) {
+ * if (numer % denom == 0)
+ * return Maybe(numer / denom);
+ * else
+ * return Maybe();
+ * }
+ * \endcode
+ */
+template class Maybe {
+public:
+ /**
+ * \brief Constructs a Maybe container with no value.
+ */
+ Maybe() : hasValue_(false) { }
+
+ /**
+ * \brief Constructs a Maybe container with a value.
+ */
+ Maybe(const T &value) : value_(value), hasValue_(true) { }
+
+ /**
+ * \brief Checks whether this container has a value.
+ */
+ bool hasValue() const { return hasValue_; }
+
+ /**
+ * \brief Returns the value of the container.
+ */
+ const T &value() const { return value_; }
+
+private:
+ T value_;
+ bool hasValue_;
+};
+
+#endif // MAYBE_H
diff --git a/src/libpsi/tools/priorityvalidator.cpp b/src/libpsi/tools/priorityvalidator.cpp
new file mode 100644
index 000000000..1d1bf2be9
--- /dev/null
+++ b/src/libpsi/tools/priorityvalidator.cpp
@@ -0,0 +1,37 @@
+/*
+ * priorityvalidator.cpp - XMPP priority validator
+ * Copyright (C) 2010 Dmitriy.trt
+ *
+ * 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, see .
+ *
+ */
+
+#include "priorityvalidator.h"
+
+QValidator::State PriorityValidator::validate(QString &input, int & /*pos*/) const
+{
+ if (input.isEmpty()) {
+ return QValidator::Acceptable;
+ } else if (input == "-") {
+ return QValidator::Intermediate;
+ } else {
+ bool ok = false;
+ int val = input.toInt(&ok);
+ if (ok && val >= -128 && val <= 127) {
+ return QValidator::Acceptable;
+ } else {
+ return QValidator::Invalid;
+ }
+ }
+}
diff --git a/src/libpsi/tools/priorityvalidator.h b/src/libpsi/tools/priorityvalidator.h
new file mode 100644
index 000000000..2d0cc9f44
--- /dev/null
+++ b/src/libpsi/tools/priorityvalidator.h
@@ -0,0 +1,32 @@
+/*
+ * priorityvalidator.h - XMPP priority validator
+ * Copyright (C) 2010 Dmitriy.trt
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef PRIORITYVALIDATOR_H
+#define PRIORITYVALIDATOR_H
+
+#include
+
+class PriorityValidator : public QValidator {
+ Q_OBJECT
+public:
+ PriorityValidator(QObject *parent = nullptr) : QValidator(parent) {};
+ virtual State validate(QString &input, int &pos) const;
+};
+
+#endif // PRIORITYVALIDATOR_H
diff --git a/src/libpsi/tools/simplecli/simplecli.cpp b/src/libpsi/tools/simplecli/simplecli.cpp
new file mode 100644
index 000000000..ea0809634
--- /dev/null
+++ b/src/libpsi/tools/simplecli/simplecli.cpp
@@ -0,0 +1,258 @@
+/*
+ * simplecli.cpp - Simple CommandLine Interface parser / manager
+ * Copyright (C) 2009 Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#include "simplecli.h"
+
+#include
+
+/**
+ * \class SimpleCli
+ * \brief Simple Commandline Interface parser.
+ *
+ * This class allows you to define Commandline Interface for your application
+ * and then use this information to convert argv to a map of options and their values.
+ *
+ * Please note that support for short options (-x) is very limited
+ * and provided only to support options like -h and -v.
+ */
+
+/**
+ * \brief Define a switch (option that does not have a value)
+ */
+void SimpleCli::defineSwitch(const QByteArray &name, const QString &help)
+{
+ argdefs[name] = Arg(name, "", help, false);
+ aliases[name] = name;
+}
+
+/**
+ * \brief Define a parameter (option that requires a value)
+ */
+void SimpleCli::defineParam(const QByteArray &name, const QString &valueHelp, const QString &help)
+{
+ argdefs[name] = Arg(name, valueHelp, help, true);
+ aliases[name] = name;
+}
+
+/**
+ * \brief Add alias for already existing option.
+ * \a alias will be mapped to \a originalName in parse() result.
+ */
+void SimpleCli::defineAlias(const QByteArray &alias, const QByteArray &originalName)
+{
+ if (!argdefs.contains(originalName)) {
+ qDebug("CLI: cannot add alias '%s' because name '%s' does not exist", alias.constData(),
+ originalName.constData());
+ return;
+ }
+ argdefs[originalName].aliases.append(alias);
+ aliases[alias] = originalName;
+ if (alias.length() == 1 && argdefs[originalName].shortName.isNull()) {
+ argdefs[originalName].shortName = alias.at(0);
+ }
+}
+
+/**
+ * \brief Parse \a argv into a name,value map.
+ * \param terminalArgs stop parsing when one of these options is found (it will be included in result)
+ * \param safeArgs if not NULL, will be used to pass number of arguments before terminal argument (or argc if there was
+ * no terminal argument)
+ *
+ * Supported options syntax: --switch; --param=value; --param value; -switch; -param=value; -param value.
+ * Additionally on Windows: /switch; /param:value; /param value.
+ *
+ * When creating the map, alias names are converted to original option names.
+ *
+ * Use \a terminalArgs if you want need to stop parsing after certain options for security reasons, etc.
+ */
+QHash SimpleCli::parse(int argc, char *argv[], const QList &terminalArgs,
+ int *safeArgc)
+{
+#ifdef Q_OS_WIN
+ const bool winmode = true;
+#else
+ const bool winmode = false;
+#endif
+
+ QHash map;
+ int safe = 1;
+ int n = 1;
+ for (; n < argc; ++n) {
+ QByteArray str = QByteArray(argv[n]);
+ QByteArray left, right;
+ int sep = str.indexOf('=');
+ bool explicitValue = false;
+ if (sep == -1) {
+ left = str;
+ } else {
+ left = str.mid(0, sep);
+ right = str.mid(sep + 1);
+ explicitValue = true;
+ }
+
+ bool unnamedArgument = true;
+ if (left.startsWith("--")) {
+ left = left.mid(2);
+ unnamedArgument = false;
+ } else if (left.startsWith('-') || (left.startsWith('/') && winmode)) {
+ left = left.mid(1);
+ unnamedArgument = false;
+ } else if (n == 1 && left.startsWith("xmpp:")) {
+ unnamedArgument = false;
+ left = "uri";
+ right = str;
+ }
+
+ QByteArray name, value;
+ if (unnamedArgument) {
+ value = left;
+ } else {
+ name = left;
+ value = right;
+ if (aliases.contains(name)) {
+ name = argdefs[aliases[name]].name;
+ bool needsValue = argdefs[name].needsValue;
+ if (!needsValue && explicitValue) {
+ continue; // ignore strange switch with value
+ }
+ if (needsValue && value.isNull() && n + 1 < argc) {
+ value = QByteArray(argv[++n]);
+ }
+ }
+ }
+
+ if (map.contains(name)) {
+ qDebug("CLI: Ignoring next value ('%s') for '%s' arg.", value.constData(), name.constData());
+ } else {
+ map[name] = value;
+ }
+
+ if (terminalArgs.contains(name)) {
+ break;
+ } else {
+ safe = n + 1;
+ }
+ }
+
+ if (safeArgc) {
+ *safeArgc = safe;
+ }
+ return map;
+}
+
+/**
+ * \brief Produce options description, for use in --help.
+ * \param textWidth wrap text when wider than \a textWidth
+ */
+QString SimpleCli::optionsHelp(int textWidth)
+{
+ QString ret;
+
+ int margin = 2;
+
+ int longest = -1;
+ bool foundShort = false;
+
+ for (const Arg &arg : std::as_const(argdefs)) {
+ if (arg.needsValue) {
+ longest = qMax(arg.name.length() + arg.valueHelp.length() + 1, longest);
+ } else {
+ longest = qMax(arg.name.length(), longest);
+ }
+
+ foundShort = foundShort || !arg.shortName.isNull();
+ }
+ longest += 2; // 2 = length("--")
+ int helpPadding = longest + 6; // 6 = 2 (left margin) + 2 (space before help) + 2 (next line indent)
+ if (foundShort) {
+ helpPadding += 4; // 4 = length("-x, ")
+ }
+
+ for (const Arg &arg : std::as_const(argdefs)) {
+ QString line;
+ line.fill(' ', margin);
+ if (foundShort) {
+ if (arg.shortName.isNull()) {
+ line += " ";
+ } else {
+ line += '-' + arg.shortName + ", ";
+ }
+ }
+ QString longarg = "--" + arg.name;
+ if (arg.needsValue) {
+ longarg += '=' + arg.valueHelp;
+ }
+ line += longarg;
+ line += QString().fill(' ', longest - longarg.length() + 2); // 2 (space before help)
+ line += arg.help;
+
+ ret += wrap(line, textWidth, helpPadding, 0);
+ }
+
+ return ret;
+}
+
+/**
+ * \brief Wrap text for printing on console.
+ * \param text text to be wrapped
+ * \param width width of the text
+ * \param margin left margin (filled with spaces)
+ * \param firstMargin margin in first line
+ * Note: This function is designed for text that do not contain tabs or line breaks.
+ * Results may be not pretty if such \a text is passed.
+ */
+QString SimpleCli::wrap(QString text, int width, int margin, int firstMargin)
+{
+ if (firstMargin < 0) {
+ firstMargin = margin;
+ }
+
+ QString output;
+
+ int prevBreak = -1;
+ int currentMargin = firstMargin;
+ int nextBreak;
+
+ do {
+ nextBreak = prevBreak + width + 1 - currentMargin;
+ if (nextBreak < text.length()) {
+ int lastSpace = text.lastIndexOf(' ', nextBreak);
+ if (lastSpace > prevBreak) {
+ nextBreak = lastSpace;
+ } else {
+ // will be a bit longer...
+ nextBreak = text.indexOf(' ', nextBreak);
+ if (nextBreak == -1) {
+ nextBreak = text.length();
+ }
+ }
+ } else {
+ nextBreak = text.length();
+ }
+
+ output += QString().fill(' ', currentMargin);
+ output += QStringView { text }.mid(prevBreak + 1, nextBreak - prevBreak - 1);
+ output += '\n';
+
+ prevBreak = nextBreak;
+ currentMargin = margin;
+ } while (nextBreak < text.length());
+
+ return output;
+}
diff --git a/src/libpsi/tools/simplecli/simplecli.h b/src/libpsi/tools/simplecli/simplecli.h
new file mode 100644
index 000000000..af8f1220b
--- /dev/null
+++ b/src/libpsi/tools/simplecli/simplecli.h
@@ -0,0 +1,64 @@
+/*
+ * simplecli.h - Simple CommandLine Interface parser / manager
+ * Copyright (C) 2009 Maciej Niedzielski
+ *
+ * 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, see .
+ *
+ */
+
+#ifndef SIMPLECLI_H
+#define SIMPLECLI_H
+
+#include
+#include
+#include
+
+class SimpleCli : public QObject {
+public:
+ void defineSwitch(const QByteArray &name, const QString &help = QString());
+ void defineParam(const QByteArray &name, const QString &valueHelp = QString("ARG"),
+ const QString &help = QString());
+
+ void defineAlias(const QByteArray &alias, const QByteArray &originalName);
+
+ QHash
+ parse(int argc, char *argv[], const QList &terminalArgs = QList(), int *safeArgc = nullptr);
+
+ QString optionsHelp(int textWidth);
+ static QString wrap(QString text, int width, int margin = 0, int firstMargin = -1);
+
+private:
+ struct Arg {
+ QByteArray name;
+ QList aliases;
+ QChar shortName;
+
+ bool needsValue;
+
+ QString help;
+ QString valueHelp;
+
+ Arg(const QByteArray &argName, const QString &argValueHelp, const QString &argHelp, bool argNeedsValue) :
+ name(argName), needsValue(argNeedsValue), help(argHelp), valueHelp(argValueHelp)
+ {
+ }
+
+ Arg() : needsValue(false) { } // needed only by QMap
+ };
+
+ QMap argdefs;
+ QMap aliases;
+};
+
+#endif // SIMPLECLI_H
diff --git a/src/libpsi/tools/spellchecker/aspellchecker.cpp b/src/libpsi/tools/spellchecker/aspellchecker.cpp
new file mode 100644
index 000000000..182534627
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/aspellchecker.cpp
@@ -0,0 +1,154 @@
+/*
+ * aspellchecker.cpp
+ *
+ * Copyright (C) 2006 Remko Troncon
+ * Thanks to Ephraim.
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "aspellchecker.h"
+
+#include "aspell.h"
+
+#include
+#include
+#include
+
+ASpellChecker::ASpellChecker() : config_(new_aspell_config())
+{
+ aspell_config_replace(config_, "encoding", "utf-8");
+#ifdef Q_OS_WIN
+ aspell_config_replace(config_, "conf-dir", QDir::homePath().toLocal8Bit().data());
+ aspell_config_replace(config_, "data-dir",
+ QString("%1/aspell").arg(QCoreApplication::applicationDirPath()).toLocal8Bit().data());
+ aspell_config_replace(config_, "dict-dir",
+ QString("%1/aspell").arg(QCoreApplication::applicationDirPath()).toLocal8Bit().data());
+#endif
+ setActiveLanguages(getAllLanguages());
+}
+
+ASpellChecker::~ASpellChecker()
+{
+ if (config_) {
+ delete_aspell_config(config_);
+ config_ = NULL;
+ }
+
+ clearSpellers();
+}
+
+bool ASpellChecker::isCorrect(const QString &word)
+{
+ if (spellers_.isEmpty())
+ return true;
+
+ for (AspellSpeller *speller : spellers_) {
+ if (aspell_speller_check(speller, word.toUtf8().constData(), -1) != 0)
+ return true;
+ }
+ return false;
+}
+
+QList ASpellChecker::suggestions(const QString &word)
+{
+ QList words;
+
+ for (AspellSpeller *speller : spellers_) {
+ const AspellWordList *list = aspell_speller_suggest(speller, word.toUtf8(), -1);
+ AspellStringEnumeration *elements = aspell_word_list_elements(list);
+ const char *c_word;
+ while ((c_word = aspell_string_enumeration_next(elements)) != NULL) {
+ QString suggestion = QString::fromUtf8(c_word);
+ if (suggestion.size() > 2)
+ words.append(suggestion);
+ }
+ delete_aspell_string_enumeration(elements);
+ }
+ return words;
+}
+
+bool ASpellChecker::add(const QString &word)
+{
+ bool result = false;
+ if (config_ && !spellers_.empty()) {
+ QString trimmed_word = word.trimmed();
+ if (!word.isEmpty()) {
+ aspell_speller_add_to_personal(spellers_.first(), trimmed_word.toUtf8(), trimmed_word.toUtf8().length());
+ aspell_speller_save_all_word_lists(spellers_.first());
+ result = true;
+ }
+ }
+ return result;
+}
+
+bool ASpellChecker::available() const { return !spellers_.isEmpty(); }
+
+bool ASpellChecker::writable() const { return false; }
+
+QSet ASpellChecker::getAllLanguages() const
+{
+ QSet langs;
+
+ AspellDictInfoList *dict_info_list = get_aspell_dict_info_list(config_);
+
+ if (!aspell_dict_info_list_empty(dict_info_list)) {
+ AspellDictInfoEnumeration *dict_info_enum = aspell_dict_info_list_elements(dict_info_list);
+
+ while (!aspell_dict_info_enumeration_at_end(dict_info_enum)) {
+ const AspellDictInfo *dict_info = aspell_dict_info_enumeration_next(dict_info_enum);
+ auto id = LanguageManager::fromString(QString::fromLatin1(dict_info->code));
+ if (id.language) {
+ langs.insert(id);
+ }
+ }
+
+ delete_aspell_dict_info_enumeration(dict_info_enum);
+ }
+
+ return langs;
+}
+
+void ASpellChecker::setActiveLanguages(const QSet &langs)
+{
+ clearSpellers();
+
+ for (auto const &lang : langs) {
+ AspellConfig *conf = aspell_config_clone(config_);
+ aspell_config_replace(
+ conf, "lang",
+ LanguageManager::toString(lang).replace(QLatin1Char('-'), QLatin1Char('_')).toUtf8().constData());
+ AspellCanHaveError *ret = new_aspell_speller(conf);
+ if (aspell_error_number(ret) == 0) {
+ spellers_.append(to_aspell_speller(ret));
+ } else {
+ qDebug() << QString("Aspell error: %1").arg(aspell_error_message(ret));
+ }
+ delete_aspell_config(conf);
+ }
+}
+
+void ASpellChecker::clearSpellers()
+{
+ for (AspellSpeller *speller : spellers_)
+ delete_aspell_speller(speller);
+
+ spellers_.clear();
+}
diff --git a/src/libpsi/tools/spellchecker/aspellchecker.h b/src/libpsi/tools/spellchecker/aspellchecker.h
new file mode 100644
index 000000000..a57332889
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/aspellchecker.h
@@ -0,0 +1,60 @@
+/*
+ * aspellchecker.h
+ *
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#ifndef ASPELLCHECKER_H
+#define ASPELLCHECKER_H
+
+#include "spellchecker.h"
+
+#include
+#include
+
+struct AspellConfig;
+struct AspellSpeller;
+
+class ASpellChecker : public SpellChecker {
+public:
+ ASpellChecker();
+ ~ASpellChecker();
+ virtual QList suggestions(const QString &);
+ virtual bool isCorrect(const QString &);
+ virtual bool add(const QString &);
+ virtual bool available() const;
+ virtual bool writable() const;
+
+ virtual void setActiveLanguages(const QSet &langs);
+ virtual QSet getAllLanguages() const;
+
+private:
+ void clearSpellers();
+
+private:
+ AspellConfig *config_;
+
+ typedef QList ASpellers;
+ ASpellers spellers_;
+};
+
+#endif // ASPELLCHECKER_H
diff --git a/src/libpsi/tools/spellchecker/enchantchecker.cpp b/src/libpsi/tools/spellchecker/enchantchecker.cpp
new file mode 100644
index 000000000..991265752
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/enchantchecker.cpp
@@ -0,0 +1,157 @@
+/*
+ * enchantchecker.cpp
+ *
+ * Copyright (C) 2009 Caolán McNamara
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "enchantchecker.h"
+
+#include "enchant++.h"
+
+#include
+#include
+#include
+#include
+
+static enchant::Broker *broker;
+
+EnchantChecker::EnchantChecker()
+{
+#ifdef HAVE_ENCHANT2
+ broker = new enchant::Broker();
+#else
+ broker = enchant::Broker::instance();
+#endif
+ if (broker) {
+ broker->list_dicts(enchantDictDescribeFn, static_cast(this));
+ QTimer::singleShot(1000, [=]() { this->setActiveLanguages(getAllLanguages()); });
+ }
+}
+
+EnchantChecker::~EnchantChecker()
+{
+ clearSpellers();
+#ifdef HAVE_ENCHANT2
+ delete broker;
+#endif
+}
+
+bool EnchantChecker::isCorrect(const QString &word)
+{
+ if (spellers_.isEmpty())
+ return true;
+
+ for (enchant::Dict *speller : spellers_) {
+ if (speller->check(word.toUtf8().constData()))
+ return true;
+ }
+ return false;
+}
+
+QList EnchantChecker::suggestions(const QString &word)
+{
+ QList words;
+
+ for (enchant::Dict *speller : spellers_) {
+ std::vector out_suggestions;
+ speller->suggest(word.toUtf8().constData(), out_suggestions);
+ std::vector::iterator aE = out_suggestions.end();
+ for (std::vector::iterator aI = out_suggestions.begin(); aI != aE; ++aI) {
+ words += QString::fromUtf8(aI->c_str());
+ }
+ }
+ return words;
+}
+
+bool EnchantChecker::add(const QString &word)
+{
+ bool result = false;
+ if (!spellers_.isEmpty()) {
+ QString trimmed_word = word.trimmed();
+ if (!word.isEmpty()) {
+#ifdef HAVE_ENCHANT2
+ spellers_.first()->add(word.toUtf8().constData());
+#else
+ spellers_.first()->add_to_pwl(word.toUtf8().constData());
+#endif
+ result = true;
+ }
+ }
+ return result;
+}
+
+bool EnchantChecker::available() const { return (spellers_.isEmpty() != true); }
+
+bool EnchantChecker::writable() const { return false; }
+
+QSet EnchantChecker::getAllLanguages() const
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ return allLanguages_.keys().toSet();
+#else
+ return QSet(allLanguages_.keyBegin(), allLanguages_.keyEnd());
+#endif
+}
+
+void EnchantChecker::setActiveLanguages(const QSet &langs)
+{
+ clearSpellers();
+
+ for (auto const &lang : langs) {
+ auto it = allLanguages_.constFind(lang);
+ if (it == allLanguages_.constEnd())
+ continue;
+
+ try {
+ spellers_ << broker->request_dict(it.value().toStdString());
+ } catch (enchant::Exception &e) {
+ qWarning() << QString("Enchant error: %1").arg(e.what());
+ }
+ }
+}
+
+void EnchantChecker::clearSpellers()
+{
+ qDeleteAll(spellers_);
+ spellers_.clear();
+}
+
+void EnchantChecker::enchantDictDescribeFn(const char *const lang_tag, const char *const provider_name,
+ const char *const provider_desc, const char *const provider_file,
+ void *user_data)
+{
+ Q_UNUSED(provider_name);
+ Q_UNUSED(provider_desc);
+ Q_UNUSED(provider_file);
+ EnchantChecker *enchantChecker = static_cast(user_data);
+
+ QString lang(QString::fromLatin1(lang_tag));
+ auto id = LanguageManager::fromString(lang);
+ if (!id.language) {
+ id = LanguageManager::fromString(lang.section(QLatin1Char('_'), 0, 0));
+ }
+ if (id.language) {
+ if (!enchantChecker->allLanguages_.contains(id)) {
+ enchantChecker->allLanguages_.insert(id, lang);
+ }
+ }
+}
diff --git a/src/libpsi/tools/spellchecker/enchantchecker.h b/src/libpsi/tools/spellchecker/enchantchecker.h
new file mode 100644
index 000000000..a421823bc
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/enchantchecker.h
@@ -0,0 +1,61 @@
+/*
+ * enchantchecker.h
+ *
+ * Copyright (C) 2009 Caolán McNamara
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#ifndef ENCHANTCHECKER_H
+#define ENCHANTCHECKER_H
+
+#include "spellchecker.h"
+
+#include
+
+namespace enchant {
+class Dict;
+}
+
+class EnchantChecker : public SpellChecker {
+public:
+ EnchantChecker();
+ ~EnchantChecker();
+ virtual QList suggestions(const QString &);
+ virtual bool isCorrect(const QString &);
+ virtual bool add(const QString &);
+ virtual bool available() const;
+ virtual bool writable() const;
+
+ virtual void setActiveLanguages(const QSet &langs);
+ virtual QSet getAllLanguages() const;
+
+private:
+ static void enchantDictDescribeFn(const char *const lang_tag, const char *const provider_name,
+ const char *const provider_desc, const char *const provider_file,
+ void *user_data);
+ void clearSpellers();
+
+ typedef QList EnchantDictList;
+ EnchantDictList spellers_;
+ QHash allLanguages_;
+};
+
+#endif // ENCHANTCHECKER_H
diff --git a/src/libpsi/tools/spellchecker/hunspellchecker.cpp b/src/libpsi/tools/spellchecker/hunspellchecker.cpp
new file mode 100644
index 000000000..bd28db2b5
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/hunspellchecker.cpp
@@ -0,0 +1,242 @@
+/*
+ * hunspellchecker.cpp
+ *
+ * Copyright (C) 2015 Sergey Ilinykh, Vitaly Tonkacheyev
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "hunspellchecker.h"
+
+#ifdef Q_OS_WIN
+#include "applicationinfo.h"
+#endif
+#include "config.h"
+#include "languagemanager.h"
+
+#include
+// #include
+#include
+#include
+#include
+#include
+#include
+#include
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include
+#endif
+#include
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#define HS_STRING(text) li.codec->fromUnicode(text).toStdString()
+#define QT_STRING(text) QString(li.codec->toUnicode(item.c_str()))
+#else
+#define HS_STRING(text) QByteArray(li.encoder(text)).toStdString()
+#define QT_STRING(text) li.decoder(QByteArray::fromStdString(item))
+#endif
+
+HunspellChecker::HunspellChecker()
+{
+ getDictPaths();
+ getSupportedLanguages();
+}
+
+HunspellChecker::~HunspellChecker() { }
+
+void HunspellChecker::getDictPaths()
+{
+ if (dictPaths_.isEmpty()) {
+ QSet dictPathSet;
+ QString pathFromEnv = QString::fromLocal8Bit(qgetenv("MYSPELL_DICT_DIR"));
+ if (!pathFromEnv.isEmpty())
+ dictPathSet << pathFromEnv;
+#if defined(Q_OS_WIN)
+ dictPathSet << QCoreApplication::applicationDirPath() + QLatin1String("/myspell/dicts")
+ << ApplicationInfo::homeDir(ApplicationInfo::DataLocation) + QLatin1String("/myspell/dicts");
+
+#elif defined(Q_OS_MAC)
+ dictPathSet << QCoreApplication::applicationDirPath()
+ + QLatin1String("/../Resources/myspell/dicts") // relative path to dictionaries inside app bundle
+ << QLatin1String("/opt/local/share/myspell"); // MacPorts standard paths
+#elif defined(Q_OS_HAIKU)
+ dictPathSet << QLatin1String("/system/data/hunspell");
+#else
+ dictPathSet << QLatin1String("/usr/share/myspell") << QLatin1String("/usr/share/hunspell")
+ << QLatin1String("/usr/local/share/myspell") << QLatin1String("/usr/local/share/hunspell")
+ << QString("%1/.local/share/myspell").arg(QDir::home().absolutePath())
+ << QString("%1/.local/share/hunspell").arg(QDir::home().absolutePath());
+#endif
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ dictPaths_ = dictPathSet.values();
+#else
+ dictPaths_ = dictPathSet.toList();
+#endif
+#if defined(Q_OS_LINUX) && defined(SHARE_SUFF)
+ // Special hack for correct work of AppImage, snap and flatpak builds
+ static const QString &&additionalPath
+ = QDir().absoluteFilePath(qApp->applicationDirPath() + "/../share/" SHARE_SUFF);
+ dictPaths_.prepend(additionalPath + "/hunspell");
+ dictPaths_.prepend(additionalPath + "/myspell");
+#endif
+ }
+}
+
+bool HunspellChecker::scanDictPaths(const QString &language, QFileInfo &aff, QFileInfo &dic)
+{
+ for (const QString &dictPath : std::as_const(dictPaths_)) {
+ QDir dir(dictPath);
+ if (dir.exists()) {
+ QFileInfo affInfo(dir.filePath(language + QLatin1String(".aff")));
+ QFileInfo dicInfo(dir.filePath(language + QLatin1String(".dic")));
+ if (affInfo.isReadable() && dicInfo.isReadable()) {
+ aff = affInfo;
+ dic = dicInfo;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void HunspellChecker::getSupportedLanguages()
+{
+ QSet retHash;
+ for (const QString &dictPath : std::as_const(dictPaths_)) {
+ QDir dir(dictPath);
+ if (!dir.exists()) {
+ continue;
+ }
+ const auto fileInfoList = dir.entryInfoList(QStringList() << "*.dic", QDir::Files);
+ for (const QFileInfo &fi : fileInfoList) {
+ auto id = LanguageManager::fromString(fi.baseName());
+ if (id.language) {
+ retHash.insert(id);
+ }
+ }
+ }
+ supportedLangs_ = retHash;
+}
+
+void HunspellChecker::addLanguage(const LanguageManager::LangId &langId)
+{
+ QString language = LanguageManager::toString(langId).replace('-', '_');
+ QFileInfo aff, dic;
+ if (scanDictPaths(language, aff, dic)) {
+ LangItem li;
+ // TODO on windows it makes sense to use "\\\\?\\" prefix to paths
+ li.hunspell_ = HunspellPtr(new Hunspell(aff.absoluteFilePath().toUtf8(), dic.absoluteFilePath().toLocal8Bit()));
+ QByteArray codecName(li.hunspell_->get_dic_encoding());
+ if (codecName.startsWith("microsoft-cp125")) {
+ codecName.replace(0, sizeof("microsoft-cp") - 1, "Windows-");
+ } else if (codecName.startsWith("TIS620-2533")) {
+ codecName.resize(sizeof("TIS620") - 1);
+ }
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ li.codec = QTextCodec::codecForName(codecName);
+ if (li.codec) {
+#else
+ li.encoder = QStringEncoder(codecName.data());
+ li.decoder = QStringDecoder(codecName.data());
+ if (li.encoder.isValid() && li.decoder.isValid()) {
+#endif
+ li.info.langId = langId;
+ li.info.filename = dic.filePath();
+ languages_.push_back(std::move(li));
+ } else {
+ qDebug("Unsupported myspell dict encoding: \"%s\" for %s", codecName.data(), qPrintable(dic.fileName()));
+ }
+ }
+}
+
+QList HunspellChecker::suggestions(const QString &word)
+{
+ QStringList qtResult;
+ for (LangItem &li : languages_) {
+ std::vector result = li.hunspell_->suggest(HS_STRING(word));
+ if (!result.empty()) {
+ for (const std::string &item : result) {
+ qtResult << QT_STRING(item); // QString(li.codec->toUnicode(item.c_str()));
+ }
+ }
+ }
+ return std::move(qtResult);
+}
+
+bool HunspellChecker::isCorrect(const QString &word)
+{
+ for (LangItem &li : languages_) {
+ if (li.hunspell_->spell(HS_STRING(word)) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+bool HunspellChecker::add(const QString &word)
+{
+ if (!word.isEmpty()) {
+ QString trimmed_word = word.trimmed();
+ for (LangItem &li : languages_) {
+ if (li.hunspell_->add(HS_STRING(trimmed_word)) != 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+bool HunspellChecker::available() const
+{
+ for (const LangItem &li : languages_) {
+ if (li.hunspell_) {
+ return true;
+ }
+ }
+ return false;
+}
+bool HunspellChecker::writable() const { return false; }
+
+void HunspellChecker::unloadLanguage(const LanguageManager::LangId &langId)
+{
+ for (auto it = languages_.begin(); it != languages_.end();) {
+ if ((*it).info.langId == langId)
+ it = languages_.erase(it);
+ else
+ ++it;
+ }
+}
+
+QSet HunspellChecker::getAllLanguages() const { return supportedLangs_; }
+
+void HunspellChecker::setActiveLanguages(const QSet &newLangs)
+{
+ QSet loadedLangs;
+ for (const LangItem &item : std::as_const(languages_)) {
+ loadedLangs << item.info.langId;
+ }
+ QSet langsToUnload = loadedLangs - newLangs;
+ QSet langsToLoad = newLangs - loadedLangs;
+ QSetIterator it(langsToUnload);
+ while (it.hasNext()) {
+ unloadLanguage(it.next());
+ }
+ it = langsToLoad;
+ while (it.hasNext()) {
+ addLanguage(it.next());
+ }
+}
diff --git a/src/libpsi/tools/spellchecker/hunspellchecker.h b/src/libpsi/tools/spellchecker/hunspellchecker.h
new file mode 100644
index 000000000..24a986208
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/hunspellchecker.h
@@ -0,0 +1,86 @@
+/*
+ * hunspellchecker.h
+ *
+ * Copyright (C) 2015 Sergey Ilinykh, Vitaly Tonkacheyev
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+#ifndef HUNSPELLCHECKER_H
+#define HUNSPELLCHECKER_H
+
+#include "languagemanager.h"
+#include "spellchecker.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include
+#include
+#endif
+
+class Hunspell;
+class QTextCodec;
+
+typedef QSharedPointer HunspellPtr;
+
+class HunspellChecker : public SpellChecker {
+public:
+ HunspellChecker();
+ ~HunspellChecker();
+ virtual QList suggestions(const QString &);
+ virtual bool isCorrect(const QString &word);
+ virtual bool add(const QString &word);
+ virtual bool available() const;
+ virtual bool writable() const;
+ virtual void setActiveLanguages(const QSet &langs);
+ virtual QSet getAllLanguages() const;
+
+private:
+ struct DictInfo {
+ LanguageManager::LangId langId;
+ QString filename;
+ };
+ struct LangItem {
+ HunspellPtr hunspell_;
+ DictInfo info;
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QTextCodec *codec;
+#else
+ QStringEncoder encoder;
+ QStringDecoder decoder;
+#endif
+ };
+ void getSupportedLanguages();
+ void addLanguage(const LanguageManager::LangId &langId);
+ void getDictPaths();
+ bool scanDictPaths(const QString &language, QFileInfo &aff, QFileInfo &dic);
+ void unloadLanguage(const LanguageManager::LangId &langId);
+
+private:
+ std::list languages_;
+ QStringList dictPaths_;
+ QSet supportedLangs_;
+};
+
+#endif // HUNSPELLCHECKER_H
diff --git a/src/libpsi/tools/spellchecker/macspellchecker.h b/src/libpsi/tools/spellchecker/macspellchecker.h
new file mode 100644
index 000000000..999f9fe5b
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/macspellchecker.h
@@ -0,0 +1,45 @@
+/*
+ * macspellchecker.h
+ *
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#ifndef MACSPELLCHECKER_H
+#define MACSPELLCHECKER_H
+
+#include "spellchecker.h"
+
+#include
+#include
+
+class MacSpellChecker : public SpellChecker {
+public:
+ MacSpellChecker();
+ ~MacSpellChecker();
+ virtual QList suggestions(const QString &);
+ virtual bool isCorrect(const QString &);
+ virtual bool add(const QString &);
+ virtual bool available() const;
+ virtual bool writable() const;
+};
+
+#endif // MACSPELLCHECKER_H
diff --git a/src/libpsi/tools/spellchecker/macspellchecker.mm b/src/libpsi/tools/spellchecker/macspellchecker.mm
new file mode 100644
index 000000000..d02627aeb
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/macspellchecker.mm
@@ -0,0 +1,59 @@
+/*
+ * macspellchecker.cpp
+ *
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "macspellchecker.h"
+
+#include
+
+MacSpellChecker::MacSpellChecker() { }
+
+MacSpellChecker::~MacSpellChecker() { }
+
+bool MacSpellChecker::isCorrect(const QString &word)
+{
+ NSString *ns_word = [NSString stringWithUTF8String:word.toUtf8().data()];
+ NSRange range = { 0, 0 };
+ range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:ns_word startingAt:0];
+ return (range.length == 0);
+}
+
+QList MacSpellChecker::suggestions(const QString &word)
+{
+ QList s;
+
+ NSString *ns_word = [NSString stringWithUTF8String:word.toUtf8().data()];
+ NSArray *ns_suggestions = [[NSSpellChecker sharedSpellChecker] guessesForWord:ns_word];
+ for (unsigned int i = 0; i < [ns_suggestions count]; i++) {
+ s += QString::fromUtf8([[ns_suggestions objectAtIndex:i] UTF8String]);
+ }
+
+ return s;
+}
+
+bool MacSpellChecker::add(const QString & /*word*/) { return false; }
+
+bool MacSpellChecker::available() const { return true; }
+
+bool MacSpellChecker::writable() const { return false; }
diff --git a/src/libpsi/tools/spellchecker/spellchecker.cpp b/src/libpsi/tools/spellchecker/spellchecker.cpp
new file mode 100644
index 000000000..4689e5e85
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/spellchecker.cpp
@@ -0,0 +1,72 @@
+/*
+ * spellchecker.cpp
+ *
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#include "spellchecker.h"
+
+#if defined(Q_OS_MAC) && !defined(HAVE_HUNSPELL)
+#include "macspellchecker.h"
+#elif defined(HAVE_ENCHANT)
+#include "enchantchecker.h"
+#elif defined(HAVE_ASPELL)
+#include "aspellchecker.h"
+#elif defined(HAVE_HUNSPELL)
+#include "hunspellchecker.h"
+#endif
+
+#include
+
+SpellChecker *SpellChecker::instance()
+{
+ if (!instance_) {
+#if defined(Q_OS_MAC) && !defined(HAVE_HUNSPELL)
+ instance_ = new MacSpellChecker();
+#elif defined(HAVE_ENCHANT)
+ instance_ = new EnchantChecker();
+#elif defined(HAVE_ASPELL)
+ instance_ = new ASpellChecker();
+#elif defined(HAVE_HUNSPELL)
+ instance_ = new HunspellChecker();
+#else
+ instance_ = new SpellChecker();
+#endif
+ }
+ return instance_;
+}
+
+SpellChecker::SpellChecker() : QObject(QCoreApplication::instance()) { }
+
+SpellChecker::~SpellChecker() { }
+
+bool SpellChecker::available() const { return false; }
+
+bool SpellChecker::writable() const { return true; }
+
+bool SpellChecker::isCorrect(const QString &) { return true; }
+
+QList SpellChecker::suggestions(const QString &) { return QList(); }
+
+bool SpellChecker::add(const QString &) { return false; }
+
+SpellChecker *SpellChecker::instance_ = nullptr;
diff --git a/src/libpsi/tools/spellchecker/spellchecker.h b/src/libpsi/tools/spellchecker/spellchecker.h
new file mode 100644
index 000000000..7c27fb303
--- /dev/null
+++ b/src/libpsi/tools/spellchecker/spellchecker.h
@@ -0,0 +1,56 @@
+/*
+ * spellchecker.h
+ *
+ * Copyright (C) 2006 Remko Troncon
+ *
+ * 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.
+ *
+ * You can also redistribute and/or modify this program under the
+ * terms of the Psi License, specified in the accompanied COPYING
+ * file, as published by the Psi Project; either dated January 1st,
+ * 2005, 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 .
+ *
+ */
+
+#ifndef SPELLCHECKER_H
+#define SPELLCHECKER_H
+
+#include "languagemanager.h"
+
+#include
+#include
+#include
+#include
+
+class SpellChecker : public QObject {
+public:
+ static SpellChecker *instance();
+ virtual bool available() const;
+ virtual bool writable() const;
+ virtual QList suggestions(const QString &);
+ virtual bool isCorrect(const QString &);
+ virtual bool add(const QString &);
+
+ virtual void setActiveLanguages(const QSet &) { }
+ virtual QSet getAllLanguages() const { return QSet