From b3c7c07f94a809149b10a38f86a38fc08106de72 Mon Sep 17 00:00:00 2001 From: Simon Quigley Date: Sat, 10 Feb 2018 19:34:21 -0600 Subject: [PATCH] New upstream version 0.3.0 --- .gitignore | 1 + CMakeLists.txt | 83 ++ COPYING | 339 +++++ README.md | 26 + dbus/org.freedesktop.Notifications.xml | 38 + resources/nm-tray-autostart.desktop | 7 + resources/nm-tray.desktop | 8 + src/connectioninfo.cpp | 102 ++ src/connectioninfo.h | 56 + src/connectioninfo.ui | 34 + src/icons.cpp | 116 ++ src/icons.h | 53 + src/log.cpp | 31 + src/log.h | 31 + src/main.cpp | 40 + src/menuview.cpp | 159 +++ src/menuview.h | 62 + src/nmlist.cpp | 93 ++ src/nmlist.h | 46 + src/nmlist.ui | 55 + src/nmmodel.cpp | 1570 ++++++++++++++++++++++++ src/nmmodel.h | 98 ++ src/nmmodel_p.h | 129 ++ src/nmproxy.cpp | 259 ++++ src/nmproxy.h | 79 ++ src/translate.cpp | 49 + src/tray.cpp | 397 ++++++ src/tray.h | 58 + src/windowmenu.cpp | 182 +++ src/windowmenu.h | 46 + translations/nm-tray.ts | 253 ++++ translations/nm-tray_de.ts | 260 ++++ translations/nm-tray_pl.ts | 253 ++++ translations/nm-tray_sk.ts | 254 ++++ 34 files changed, 5267 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 README.md create mode 100644 dbus/org.freedesktop.Notifications.xml create mode 100644 resources/nm-tray-autostart.desktop create mode 100644 resources/nm-tray.desktop create mode 100644 src/connectioninfo.cpp create mode 100644 src/connectioninfo.h create mode 100644 src/connectioninfo.ui create mode 100644 src/icons.cpp create mode 100644 src/icons.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/main.cpp create mode 100644 src/menuview.cpp create mode 100644 src/menuview.h create mode 100644 src/nmlist.cpp create mode 100644 src/nmlist.h create mode 100644 src/nmlist.ui create mode 100644 src/nmmodel.cpp create mode 100644 src/nmmodel.h create mode 100644 src/nmmodel_p.h create mode 100644 src/nmproxy.cpp create mode 100644 src/nmproxy.h create mode 100644 src/translate.cpp create mode 100644 src/tray.cpp create mode 100644 src/tray.h create mode 100644 src/windowmenu.cpp create mode 100644 src/windowmenu.h create mode 100644 translations/nm-tray.ts create mode 100644 translations/nm-tray_de.ts create mode 100644 translations/nm-tray_pl.ts create mode 100644 translations/nm-tray_sk.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d76c52e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,83 @@ +project(nm-tray) +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + +set(NM_TRAY_VERSION "0.3.0") + +set(QT_MIN_VERSION "5.4.0") + +set(CMAKE_AUTOMOC on) +set(CMAKE_AUTOUIC on) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS + Widgets + Gui + Network + DBus + + LinguistTools +) +find_package(KF5NetworkManagerQt "5.24.0" REQUIRED) +include(GNUInstallDirs) + +set(TRANSLATION_DIR "${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}") +add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS "-DTRANSLATION_DIR=\"${TRANSLATION_DIR}\"" "-DNM_TRAY_VERSION=\"${NM_TRAY_VERSION}\"") +if (NOT NM_TRAY_XDG_AUTOSTART_DIR) + #Note: we need the default to be /etc... (not the ${CMAKE_INSTALL_PREFIX}/etc) + set(NM_TRAY_XDG_AUTOSTART_DIR "/${CMAKE_INSTALL_SYSCONFDIR}/xdg/autostart") +endif () + +message(STATUS "Translations destination dir: ${TRANSLATION_DIR}") +message(STATUS "Autostart .desktop entry destination dir: ${NM_TRAY_XDG_AUTOSTART_DIR}\n (can be overriden by setting the NM_TRAY_XDG_AUTOSTART_DIR cmake variable)") + +set(SRCS + src/log.cpp + src/icons.cpp + src/nmmodel.cpp + src/nmproxy.cpp + src/connectioninfo.cpp + src/nmlist.cpp + src/menuview.cpp + src/windowmenu.cpp + src/tray.cpp + src/translate.cpp + src/main.cpp +) + + +file(GLOB TSS "translations/${PROJECT_NAME}_*.ts") +if (UPDATE_TRANSLATIONS) + message(WARNING "!! Disable updating translation after make (-DUPDATE_TRANSLATIONS=no) to avoid 'make clean' delete them !!") + qt5_create_translation(QMS "translations/${PROJECT_NAME}.ts" ${TSS} "src") +else () + qt5_add_translation(QMS ${TSS}) +endif() + +qt5_add_dbus_interface(SRCS + dbus/org.freedesktop.Notifications.xml + dbus/org.freedesktop.Notifications +) + +add_executable(nm-tray + ${SRCS} + ${QMS} +) + +set_property(TARGET nm-tray PROPERTY CXX_STANDARD 11) +set_property(TARGET nm-tray PROPERTY CXX_STANDARD_REQUIRED on) + +target_link_libraries(nm-tray + Qt5::Widgets + Qt5::Gui + KF5::NetworkManagerQt +) + +if (WITH_MODEMMANAGER_SUPPORT) + target_link_libraries(nm-tray KF5::ModemManagerQt) +endif() + +install(TARGETS nm-tray RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" COMPONENT runtime) +install(FILES ${QMS} DESTINATION "${TRANSLATION_DIR}" COMPONENT translations) +install(FILES "resources/nm-tray.desktop" DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/applications" COMPONENT data) +install(FILES "resources/nm-tray-autostart.desktop" DESTINATION "${NM_TRAY_XDG_AUTOSTART_DIR}" COMPONENT data) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5ebf6c --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# nm-tray + +**nm-tray** is a simple [NetworkManager](https://wiki.gnome.org/Projects/NetworkManager) front end with information icon residing in system tray (like e.g. nm-applet). +It's a pure Qt application. For interaction with *NetworkManager* it uses API provided by [**KF5::NetworkManagerQt**](https://projects.kde.org/projects/frameworks/networkmanager-qt) -> plain DBus communication. + +**! This is work in progress and there are still many TODOs and debugs all around in the code !** + +## License + +This software is licensed under [GNU GPLv2 or later](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) + +## Build example + + git clone https://github.com/palinek/nm-tray.git + cd nm-tray + mkdir build + cd build + cmake .. + make + ./nm-tray + +## Packages + +For [arch users](https://www.archlinux.org/) there is an AUR packge [nm-tray-git](https://aur.archlinux.org/packages/nm-tray-git/) (thanks to [pmattern](https://github.com/pmattern)). + +For [openSUSE users](https://www.opensuse.org/) there is a [package](https://build.opensuse.org/package/show/X11:LXQt:git/nm-tray) in the [X11:LXQt:git](https://build.opensuse.org/project/show/X11:LXQt:git) devel project of OBS. diff --git a/dbus/org.freedesktop.Notifications.xml b/dbus/org.freedesktop.Notifications.xml new file mode 100644 index 0000000..33648b4 --- /dev/null +++ b/dbus/org.freedesktop.Notifications.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/nm-tray-autostart.desktop b/resources/nm-tray-autostart.desktop new file mode 100644 index 0000000..94aa8bc --- /dev/null +++ b/resources/nm-tray-autostart.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=nm-tray +TryExec=nm-tray +Exec=nm-tray +NotShowIn=KDE;GNOME; +X-LXQt-Need-Tray=true diff --git a/resources/nm-tray.desktop b/resources/nm-tray.desktop new file mode 100644 index 0000000..1ef7353 --- /dev/null +++ b/resources/nm-tray.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=nm-tray +Comment=NetworkManager frontend (tray icon) +Icon=network-transmit +TryExec=nm-tray +Exec=nm-tray +Categories=Network;System;Monitor;Qt;TrayIcon diff --git a/src/connectioninfo.cpp b/src/connectioninfo.cpp new file mode 100644 index 0000000..a64fd6b --- /dev/null +++ b/src/connectioninfo.cpp @@ -0,0 +1,102 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "connectioninfo.h" +#include "ui_connectioninfo.h" +#include "nmproxy.h" +#include +#include +#include +#include + +ConnectionInfo::ConnectionInfo(NmModel * model, QWidget *parent) + : QDialog{parent} + , ui{new Ui::ConnectionInfo} + , mModel{model} + , mActive{new NmProxy} + , mSorted{new QSortFilterProxyModel} + +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + mActive->setNmModel(mModel, NmModel::ActiveConnectionType); + mSorted->setSortCaseSensitivity(Qt::CaseInsensitive); + mSorted->sort(0); + mSorted->setSourceModel(mActive.data()); + for (int i = 0, row_cnt = mSorted->rowCount(); i < row_cnt; ++i) + { + addTab(mSorted->index(i, 0)); + } + + connect(mSorted.data(), &QAbstractItemModel::rowsInserted, [this] (QModelIndex const & parent, int first, int last) { + ui->tabWidget->setUpdatesEnabled(false); + for (int i = first; i <= last; ++i) + addTab(mSorted->index(i, 0, parent)); + ui->tabWidget->setUpdatesEnabled(true); + }); + connect(mSorted.data(), &QAbstractItemModel::rowsAboutToBeRemoved, [this] (QModelIndex const & parent, int first, int last) { + ui->tabWidget->setUpdatesEnabled(false); + for (int i = first; i <= last; ++i) + removeTab(mSorted->index(i, 0, parent)); + ui->tabWidget->setUpdatesEnabled(true); + }); + connect(mSorted.data(), &QAbstractItemModel::dataChanged, [this] (const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & /*roles*/) { + ui->tabWidget->setUpdatesEnabled(false); + for (auto const & i : QItemSelection{topLeft, bottomRight}.indexes()) + changeTab(i); + ui->tabWidget->setUpdatesEnabled(true); + }); +} + +ConnectionInfo::~ConnectionInfo() +{ +} + +void ConnectionInfo::addTab(QModelIndex const & index) +{ + QScrollArea * content = new QScrollArea; + QLabel * l = new QLabel{mSorted->data(index, NmModel::ActiveConnectionInfoRole).toString()}; + content->setWidget(l); + //QTabWidget takes ownership of the page (if we will not take it back) + ui->tabWidget->insertTab(index.row(), content, mSorted->data(index, NmModel::IconRole).value(), mSorted->data(index, NmModel::NameRole).toString()); +} + +void ConnectionInfo::removeTab(QModelIndex const & index) +{ + const int i = index.row(); + QWidget * w = ui->tabWidget->widget(i); + ui->tabWidget->removeTab(i); + w->deleteLater(); +} + +void ConnectionInfo::changeTab(QModelIndex const & index) +{ + const int i = index.row(); + QScrollArea * content = qobject_cast(ui->tabWidget->widget(i)); + Q_ASSERT(nullptr != content); + // Note: the text is HTML formatted and the QLabel probably creates some DOM internal represntation. + // Should the DOM structure change, the QLabel will not update correctly upon the plain setText() + content->setWidget(new QLabel{mSorted->data(index, NmModel::ActiveConnectionInfoRole).toString()}); + ui->tabWidget->tabBar()->setTabText(i, mSorted->data(index, NmModel::NameRole).toString()); + ui->tabWidget->tabBar()->setTabIcon(i, mSorted->data(index, NmModel::IconRole).value()); +} diff --git a/src/connectioninfo.h b/src/connectioninfo.h new file mode 100644 index 0000000..c4ede49 --- /dev/null +++ b/src/connectioninfo.h @@ -0,0 +1,56 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#ifndef CONNECTIONINFO_H +#define CONNECTIONINFO_H + +#include + +namespace Ui { +class ConnectionInfo; +} + +class NmModel; +class NmProxy; +class QSortFilterProxyModel; + +class ConnectionInfo : public QDialog +{ + Q_OBJECT + +public: + explicit ConnectionInfo(NmModel * model, QWidget *parent = nullptr); + ~ConnectionInfo(); + +private: + void addTab(QModelIndex const & index); + void removeTab(QModelIndex const & index); + void changeTab(QModelIndex const & index); + +private: + QScopedPointer ui; + NmModel * mModel; + QScopedPointer mActive; + QScopedPointer mSorted; +}; + +#endif // CONNECTIONINFO_H diff --git a/src/connectioninfo.ui b/src/connectioninfo.ui new file mode 100644 index 0000000..8793438 --- /dev/null +++ b/src/connectioninfo.ui @@ -0,0 +1,34 @@ + + + ConnectionInfo + + + + 0 + 0 + 570 + 614 + + + + Connection information + + + + + + -1 + + + + 32 + 32 + + + + + + + + + diff --git a/src/icons.cpp b/src/icons.cpp new file mode 100644 index 0000000..c2af9f8 --- /dev/null +++ b/src/icons.cpp @@ -0,0 +1,116 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "icons.h" + +#include +#include + +namespace icons +{ + QIcon getIcon(Icon ico) + { + static const QStringList i_empty; + QStringList const * icon_names = &i_empty; + switch (ico) + { + case NETWORK_OFFLINE: + static const QStringList i_network_offline = { QStringLiteral("network-offline-symbolic") }; + icon_names = &i_network_offline; + break; + case NETWORK_WIRED: + static const QStringList i_network_wired = { QStringLiteral("network-wired-symbolic") }; + icon_names = &i_network_wired; + break; + case NETWORK_WIRED_DISCONNECTED: + static const QStringList i_network_wired_disconnected = { QStringLiteral("network-wired-disconnected-symbolic") }; + icon_names = &i_network_wired_disconnected; + break; + case NETWORK_WIFI_DISCONNECTED: + static const QStringList i_wifi_disconnected = { QStringLiteral("network-wireless-disconnected-symbolic") }; + icon_names = &i_wifi_disconnected; + break; + case NETWORK_WIFI_ACQUIRING: + static const QStringList i_wifi_acquiring = { QStringLiteral("network-wireless-acquiring-symbolic") }; + icon_names = &i_wifi_acquiring; + break; + case NETWORK_WIFI_NONE: + static const QStringList i_wifi_none = { QStringLiteral("network-wireless-signal-none-symbolic"), QStringLiteral("network-wireless-connected-00-symbolic") }; + icon_names = &i_wifi_none; + break; + case NETWORK_WIFI_WEAK: + static const QStringList i_wifi_weak = { QStringLiteral("network-wireless-signal-weak-symbolic"), QStringLiteral("network-wireless-connected-25-symbolic") }; + icon_names = &i_wifi_weak; + break; + case NETWORK_WIFI_OK: + static const QStringList i_wifi_ok = { QStringLiteral("network-wireless-signal-ok-symbolic"), QStringLiteral("network-wireless-connected-50-symbolic") }; + icon_names = &i_wifi_ok; + break; + case NETWORK_WIFI_GOOD: + static const QStringList i_wifi_good = { QStringLiteral("network-wireless-signal-good-symbolic"), QStringLiteral("network-wireless-connected-75-symbolic") }; + icon_names = &i_wifi_good; + break; + case NETWORK_WIFI_EXCELENT: + static const QStringList i_wifi_excelent = { QStringLiteral("network-wireless-signal-excellent-symbolic"), QStringLiteral("network-wireless-connected-100-symbolic") }; + icon_names = &i_wifi_excelent; + break; + case NETWORK_VPN: + static const QStringList i_network_vpn = { QStringLiteral("network-vpn") }; + icon_names = &i_network_vpn; + break; + case SECURITY_LOW: + static const QStringList i_security_low = { QStringLiteral("security-low-symbolic") }; + icon_names = &i_security_low; + break; + case SECURITY_HIGH: + static const QStringList i_security_high = { QStringLiteral("security-high-symbolic") }; + icon_names = &i_security_high; + break; + case PREFERENCES_NETWORK: + static const QStringList i_preferences_network = { QStringLiteral("preferences-system-network") }; + icon_names = &i_preferences_network; + break; + }; + for (auto const & name : *icon_names) + { + QIcon icon{QIcon::fromTheme(name)}; + if (!icon.isNull()) + return icon; + } + //TODO: fallback!?! + return QIcon::fromTheme(QStringLiteral("network-transmit")); + } + + Icon wifiSignalIcon(const int signal) + { + if (0 >= signal) + return icons::NETWORK_WIFI_NONE; + else if (25 >= signal) + return icons::NETWORK_WIFI_WEAK; + else if (50 >= signal) + return icons::NETWORK_WIFI_OK; + else if (75 >= signal) + return icons::NETWORK_WIFI_GOOD; + else + return icons::NETWORK_WIFI_EXCELENT; + } +} diff --git a/src/icons.h b/src/icons.h new file mode 100644 index 0000000..d7b355d --- /dev/null +++ b/src/icons.h @@ -0,0 +1,53 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(ICONS_H) +#define ICONS_H + +#include + +namespace icons +{ + enum Icon { + NETWORK_OFFLINE + , NETWORK_WIRED + , NETWORK_WIRED_DISCONNECTED + , NETWORK_WIFI_ACQUIRING + , NETWORK_WIFI_NONE + , NETWORK_WIFI_WEAK + , NETWORK_WIFI_OK + , NETWORK_WIFI_GOOD + , NETWORK_WIFI_EXCELENT + , NETWORK_WIFI_DISCONNECTED + , NETWORK_VPN + + , SECURITY_LOW + , SECURITY_HIGH + + , PREFERENCES_NETWORK + }; + + QIcon getIcon(Icon ico); + Icon wifiSignalIcon(const int signal); +} + +#endif diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..2ded612 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,31 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "log.h" +#include + +#if defined(NDEBUG) +Q_LOGGING_CATEGORY(NM_TRAY, "nm-tray", QtInfoMsg) +#else +Q_LOGGING_CATEGORY(NM_TRAY, "nm-tray") +#endif + diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..13dcb67 --- /dev/null +++ b/src/log.h @@ -0,0 +1,31 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(LOG_H) +#define LOG_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(NM_TRAY) + +#endif //LOG_H + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f7f0661 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,40 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include + +#include "tray.h" + +#include "icons.h" + +int main(int argc, char * argv[]) +{ + QApplication app{argc, argv}; + app.setOrganizationName(QStringLiteral("nm-tray")); + app.setApplicationName(QStringLiteral("nm-tray")); + app.setWindowIcon(icons::getIcon(icons::PREFERENCES_NETWORK)); + app.setQuitOnLastWindowClosed(false); + + Tray tray; + + return app.exec(); +} diff --git a/src/menuview.cpp b/src/menuview.cpp new file mode 100644 index 0000000..ee47bbd --- /dev/null +++ b/src/menuview.cpp @@ -0,0 +1,159 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2016~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ + +#include "menuview.h" +#include "nmmodel.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + class SingleActivateStyle : public QProxyStyle + { + public: + using QProxyStyle::QProxyStyle; + virtual int styleHint(StyleHint hint, const QStyleOption * option = 0, const QWidget * widget = 0, QStyleHintReturn * returnData = 0) const override + { + if(hint == QStyle::SH_ItemView_ActivateItemOnSingleClick) + return 1; + return QProxyStyle::styleHint(hint, option, widget, returnData); + + } + }; + + class MultiIconDelegate : public QStyledItemDelegate + { + public: + using QStyledItemDelegate::QStyledItemDelegate; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + Q_ASSERT(index.isValid()); + + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + //add the security overlay icon + QIcon sec_icon = qvariant_cast(index.data(NmModel::IconSecurityRole)); + if (!sec_icon.isNull()) + { + QPixmap res_pixmap = opt.icon.pixmap(opt.decorationSize); + QPainter p{&res_pixmap}; + QSize sec_size = res_pixmap.size(); + sec_size /= 2; + QPoint sec_pos = res_pixmap.rect().bottomRight(); + sec_pos -= QPoint{sec_size.width(), sec_size.height()}; + p.drawPixmap(sec_pos, sec_icon.pixmap(sec_size)); + + opt.icon = QIcon{res_pixmap}; + + } + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, option.widget); + } + }; + +} + +MenuView::MenuView(QAbstractItemModel * model, QWidget * parent /*= nullptr*/) + : QListView(parent) + , mProxy{new QSortFilterProxyModel{this}} + , mMaxItemsToShow(10) +{ + setEditTriggers(NoEditTriggers); + setSizeAdjustPolicy(AdjustToContents); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionBehavior(SelectRows); + setSelectionMode(NoSelection); + setFrameShape(QFrame::HLine); + setFrameShadow(QFrame::Plain); + + setStyle(new SingleActivateStyle); + mProxy->setSourceModel(model); + mProxy->setDynamicSortFilter(true); + mProxy->setFilterRole(Qt::DisplayRole); + mProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + mProxy->setSortRole(Qt::DisplayRole); + mProxy->setSortCaseSensitivity(Qt::CaseInsensitive); + mProxy->sort(0); + { + QScopedPointer guard{selectionModel()}; + setModel(mProxy); + } + { + QScopedPointer guard{itemDelegate()}; + setItemDelegate(new MultiIconDelegate{this}); + } +} + +void MenuView::setFilter(QString const & filter) +{ + mProxy->setFilterFixedString(filter); + const int count = mProxy->rowCount(); + if (0 < count) + { + if (count > mMaxItemsToShow) + { + setCurrentIndex(mProxy->index(mMaxItemsToShow - 1, 0)); + verticalScrollBar()->triggerAction(QScrollBar::SliderToMinimum); + } else + { + setCurrentIndex(mProxy->index(count - 1, 0)); + } + } +} + +void MenuView::setMaxItemsToShow(int max) +{ + mMaxItemsToShow = max; +} + +void MenuView::activateCurrent() +{ + QModelIndex const index = currentIndex(); + if (index.isValid()) + emit activated(index); +} + +QSize MenuView::viewportSizeHint() const +{ + const int count = mProxy->rowCount(); + QSize s{0, 0}; + if (0 < count) + { + const bool scrollable = mMaxItemsToShow < count; + s.setWidth(sizeHintForColumn(0) + (scrollable ? verticalScrollBar()->sizeHint().width() : 0)); + s.setHeight(sizeHintForRow(0) * (scrollable ? mMaxItemsToShow : count)); + } + return s; +} + +QSize MenuView::minimumSizeHint() const +{ + return QSize{0, 0}; +} diff --git a/src/menuview.h b/src/menuview.h new file mode 100644 index 0000000..10da052 --- /dev/null +++ b/src/menuview.h @@ -0,0 +1,62 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2016~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ + +#if !defined(MENU_VIEW_H) +#define MENU_VIEW_H + +#include + +class QAbstractItemModel; +class QSortFilterProxyModel; + +class MenuView : public QListView +{ + Q_OBJECT +public: + MenuView(QAbstractItemModel * model, QWidget * parent = nullptr); + + /*! \brief Sets the filter for entries to be presented + */ + void setFilter(QString const & filter); + /*! \brief Set the maximum number of items/results to show + */ + void setMaxItemsToShow(int max); + /*! \brief Set the maximum width of item to show + */ + void setMaxItemWidth(int max); + +public Q_SLOTS: + /*! \brief Trigger action on currently active item + */ + void activateCurrent(); + +protected: + virtual QSize viewportSizeHint() const override; + virtual QSize minimumSizeHint() const override; + +private: + QSortFilterProxyModel * mProxy; + int mMaxItemsToShow; +}; + +#endif //MENU_VIEW_H diff --git a/src/nmlist.cpp b/src/nmlist.cpp new file mode 100644 index 0000000..1a78cdf --- /dev/null +++ b/src/nmlist.cpp @@ -0,0 +1,93 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "nmlist.h" +#include "ui_nmlist.h" +#include "nmmodel.h" +#include "nmproxy.h" +#include + +template +void installDblClick(QAbstractItemView * view, QAbstractItemModel * m) +{ + T * net_model = qobject_cast(m); + if (net_model) + { + QAbstractProxyModel * proxy = qobject_cast(view->model()); + Q_ASSERT(nullptr != proxy && proxy->sourceModel() == net_model); + QObject::connect(view, &QAbstractItemView::doubleClicked, [net_model, proxy] (QModelIndex const & i) { + QModelIndex source_i = proxy->mapToSource(i); + switch (static_cast(net_model->data(source_i, NmModel::ItemTypeRole).toInt())) + { + case NmModel::ActiveConnectionType: + net_model->deactivateConnection(source_i); + break; + case NmModel::WifiNetworkType: + case NmModel::ConnectionType: + net_model->activateConnection(source_i); + break; + default: + //do nothing + break; + } + }); + } +} + +NmList::NmList(QString const & title, QAbstractItemModel * m, QWidget *parent) + : QDialog(parent) + , ui(new Ui::NmList) +{ + ui->setupUi(this); + setWindowTitle(title); + + Q_ASSERT(qobject_cast(m)); + QSortFilterProxyModel * proxy_sort = new QSortFilterProxyModel{this}; + proxy_sort->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy_sort->sort(0); + proxy_sort->setSourceModel(m); + ui->treeView->setModel(proxy_sort); + installDblClick(ui->treeView, m); + + NmProxy * proxy = new NmProxy(this); + proxy->setNmModel(qobject_cast(m), NmModel::ActiveConnectionType); + proxy_sort = new QSortFilterProxyModel{this}; + proxy_sort->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy_sort->sort(0); + proxy_sort->setSourceModel(proxy); + ui->listActive->setModel(proxy_sort); + installDblClick(ui->listActive, proxy); + + proxy = new NmProxy(this); + proxy->setNmModel(qobject_cast(m), NmModel::WifiNetworkType); + proxy_sort = new QSortFilterProxyModel{this}; + proxy_sort->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy_sort->sort(0); + proxy_sort->setSourceModel(proxy); + ui->listWifi->setModel(proxy_sort); + installDblClick(ui->listWifi, proxy); +} + +NmList::~NmList() +{ + delete ui; +} diff --git a/src/nmlist.h b/src/nmlist.h new file mode 100644 index 0000000..e35f472 --- /dev/null +++ b/src/nmlist.h @@ -0,0 +1,46 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#ifndef NMLIST_H +#define NMLIST_H + +#include + +namespace Ui { +class NmList; +} + +class QAbstractItemModel; + +class NmList : public QDialog +{ + Q_OBJECT + +public: + explicit NmList(QString const & title, QAbstractItemModel * m, QWidget *parent = nullptr); + ~NmList(); + +private: + Ui::NmList *ui; +}; + +#endif // NMLIST_H diff --git a/src/nmlist.ui b/src/nmlist.ui new file mode 100644 index 0000000..70ea020 --- /dev/null +++ b/src/nmlist.ui @@ -0,0 +1,55 @@ + + + NmList + + + + 0 + 0 + 565 + 507 + + + + Dialog + + + + + + All information + + + + + + + + + + Active connections + + + + + + + + + + Available wireless + + + + + + + false + + + + + + + + diff --git a/src/nmmodel.cpp b/src/nmmodel.cpp new file mode 100644 index 0000000..beee319 --- /dev/null +++ b/src/nmmodel.cpp @@ -0,0 +1,1570 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "nmmodel.h" +#include "nmmodel_p.h" +#include "icons.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + NetworkManager::ConnectionSettings::Ptr assembleWpaXPskSettings(const NetworkManager::AccessPoint::Ptr accessPoint) + { + //TODO: enhance getting the password from user + bool ok; + QString password = QInputDialog::getText(nullptr, NmModel::tr("nm-tray - wireless password") + , NmModel::tr("Password is needed for connection to '%1':").arg(accessPoint->ssid()) + , QLineEdit::Password, QString(), &ok); + if (!ok) + return NetworkManager::ConnectionSettings::Ptr{nullptr}; + + NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings{NetworkManager::ConnectionSettings::Wireless}}; + settings->setId(accessPoint->ssid()); + settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid()); + settings->setAutoconnect(true); + //Note: workaround for wrongly (randomly) initialized gateway-ping-timeout + settings->setGatewayPingTimeout(0); + + NetworkManager::WirelessSetting::Ptr wifi_sett + = settings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + wifi_sett->setInitialized(true); + wifi_sett->setSsid(accessPoint->ssid().toUtf8()); + wifi_sett->setSecurity("802-11-wireless-security"); + + NetworkManager::WirelessSecuritySetting::Ptr security_sett + = settings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast(); + security_sett->setInitialized(true); + if (NetworkManager::AccessPoint::Adhoc == accessPoint->mode()) + { + wifi_sett->setMode(NetworkManager::WirelessSetting::Adhoc); + security_sett->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaNone); + } else + { + security_sett->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk); + } + security_sett->setPsk(password); + return settings; + } +} + +NmModelPrivate::NmModelPrivate() +{ + insertActiveConnections(); + insertConnections(); + insertDevices(); + insertWifiNetworks(); + + //initialize NetworkManager signals + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &NmModelPrivate::onDeviceAdded); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &NmModelPrivate::onDeviceRemoved); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &NmModelPrivate::onActiveConnectionAdded); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &NmModelPrivate::onActiveConnectionRemoved); + connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &NmModelPrivate::onConnectionAdded); + connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, static_cast(&NmModelPrivate::onConnectionRemoved)); + + // Note: the connectionRemoved is never emitted in case network-manager service stop, + // we need remove the connections manually. + connect(NetworkManager::notifier(), &NetworkManager::Notifier::serviceDisappeared, this, &NmModelPrivate::clearConnections); + +//qCDebug(NM_TRAY) << mActiveConns.size() << mConnections.size() << mDevices.size(); +} + +NmModelPrivate::~NmModelPrivate() +{ +} + +void NmModelPrivate::removeActiveConnection(int pos) +{ + //active connections signals + NetworkManager::ActiveConnection::Ptr conn = mActiveConns.takeAt(pos); + conn->disconnect(this); +} + +void NmModelPrivate::clearActiveConnections() +{ + while (0 < mActiveConns.size()) + removeActiveConnection(0); +} + +void NmModelPrivate::addActiveConnection(NetworkManager::ActiveConnection::Ptr conn) +{ + mActiveConns.push_back(conn); + connect(conn.data(), &NetworkManager::ActiveConnection::connectionChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::default4Changed, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::default6Changed, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::dhcp4ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::dhcp6ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::ipV4ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::ipV6ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::idChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::typeChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::masterChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::specificObjectChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::stateChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::vpnChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::uuidChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(conn.data(), &NetworkManager::ActiveConnection::devicesChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + if (conn->vpn()) + { + connect(qobject_cast(conn.data()), &NetworkManager::VpnConnection::bannerChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + connect(qobject_cast(conn.data()), &NetworkManager::VpnConnection::stateChanged, this, &NmModelPrivate::onActiveConnectionUpdated); + } +} + +void NmModelPrivate::insertActiveConnections() +{ + for (auto const & conn : NetworkManager::activeConnections()) + addActiveConnection(conn); +} + +void NmModelPrivate::removeConnection(int pos) +{ + //connections signals + NetworkManager::Connection::Ptr conn = mConnections.takeAt(pos); + conn->disconnect(this); +} + +void NmModelPrivate::clearConnections() +{ + while (0 < mConnections.size()) + removeConnection(0); +} + +void NmModelPrivate::addConnection(NetworkManager::Connection::Ptr conn) +{ + mConnections.push_back(conn); + //connections signals + connect(conn.data(), &NetworkManager::Connection::updated, this, &NmModelPrivate::onConnectionUpdated); + connect(conn.data(), &NetworkManager::Connection::removed, this, static_cast(&NmModelPrivate::onConnectionRemoved)); +} + +void NmModelPrivate::insertConnections() +{ + for (auto const & conn : NetworkManager::listConnections()) + addConnection(conn); +} + +void NmModelPrivate::removeDevice(int pos) +{ + //connections signals + NetworkManager::Device::Ptr device = mDevices.takeAt(pos); + device->disconnect(this); +} + +void NmModelPrivate::clearDevices() +{ + while (0 < mDevices.size()) + removeDevice(0); +} + +void NmModelPrivate::addDevice(NetworkManager::Device::Ptr device) +{ + mDevices.push_back(device); + //device signals + connect(device.data(), &NetworkManager::Device::stateChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::activeConnectionChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::autoconnectChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::availableConnectionChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::availableConnectionAppeared, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::capabilitiesChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::dhcp4ConfigChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::dhcp6ConfigChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::driverChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::driverVersionChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::firmwareMissingChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::firmwareVersionChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::interfaceNameChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::ipV4AddressChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::ipV4ConfigChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::ipV6ConfigChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::ipInterfaceChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::managedChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::physicalPortIdChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::mtuChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::nmPluginMissingChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::meteredChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::connectionStateChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::stateReasonChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(device.data(), &NetworkManager::Device::udiChanged, this, &NmModelPrivate::onDeviceUpdated); + switch (device->type()) + { + case NetworkManager::Ethernet: + connect(qobject_cast(device.data()), &NetworkManager::WiredDevice::bitRateChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WiredDevice::carrierChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WiredDevice::hardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WiredDevice::permanentHardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated); + break; + + case NetworkManager::Device::Wifi: + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::bitRateChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::activeAccessPointChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::modeChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::wirelessCapabilitiesChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::hardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::permanentHardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::wirelessPropertiesChanged, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::accessPointAppeared, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::accessPointDisappeared, this, &NmModelPrivate::onDeviceUpdated); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::networkAppeared, this, &NmModelPrivate::onWifiNetworkAppeared); + connect(qobject_cast(device.data()), &NetworkManager::WirelessDevice::networkDisappeared, this, &NmModelPrivate::onWifiNetworkDisappeared); + break; + default: + //TODO: other device types! + break; + } +} + +void NmModelPrivate::insertDevices() +{ + for (auto const & device : NetworkManager::networkInterfaces()) + addDevice(device); +} + +void NmModelPrivate::removeWifiNetwork(int pos) +{ + //network signals + NetworkManager::WirelessNetwork::Ptr net = mWifiNets.takeAt(pos); + net->disconnect(this); +} + +void NmModelPrivate::clearWifiNetworks() +{ + while (0 < mWifiNets.size()) + removeWifiNetwork(0); +} + +void NmModelPrivate::addWifiNetwork(NetworkManager::WirelessNetwork::Ptr net) +{ + mWifiNets.push_back(net); + //device signals + connect(net.data(), &NetworkManager::WirelessNetwork::signalStrengthChanged, this, &NmModelPrivate::onWifiNetworkUpdated); + connect(net.data(), &NetworkManager::WirelessNetwork::referenceAccessPointChanged, this, &NmModelPrivate::onWifiNetworkUpdated); + connect(net.data(), &NetworkManager::WirelessNetwork::disappeared, this, &NmModelPrivate::onWifiNetworkUpdated); +} + +void NmModelPrivate::insertWifiNetworks() +{ + for (auto const & device : mDevices) + { + if (NetworkManager::Device::Wifi == device->type()) + { + NetworkManager::WirelessDevice::Ptr w_dev = device.objectCast(); + for (auto const & net : w_dev->networks()) + { + if (!net.isNull()) + { + addWifiNetwork(net); + } + } + } + } +} + +NetworkManager::ActiveConnection::Ptr NmModelPrivate::findActiveConnection(QString const & path) +{ + auto i = std::find_if(mActiveConns.cbegin(), mActiveConns.cend(), [&path] (NetworkManager::ActiveConnection::Ptr const & conn) -> bool { + return conn->path() == path; + }); + return mActiveConns.cend() == i ? NetworkManager::ActiveConnection::Ptr{} : *i; +} + +template +NetworkManager::Device::Ptr NmModelPrivate::findDevice(Predicate const & pred) +{ + auto i = std::find_if(mDevices.cbegin(), mDevices.cend(), pred); + return mDevices.cend() == i ? NetworkManager::Device::Ptr{} : *i; +} + +NetworkManager::Device::Ptr NmModelPrivate::findDeviceUni(QString const & uni) +{ + return findDevice([&uni] (NetworkManager::Device::Ptr const & dev) { return dev->uni() == uni; }); +} + +NetworkManager::Device::Ptr NmModelPrivate::findDeviceInterface(QString const & interfaceName) +{ + return findDevice([&interfaceName] (NetworkManager::Device::Ptr const & dev) { return dev->interfaceName() == interfaceName; }); +} + +NetworkManager::WirelessNetwork::Ptr NmModelPrivate::findWifiNetwork(QString const & ssid, QString const & devUni) +{ + auto i = std::find_if(mWifiNets.cbegin(), mWifiNets.cend(), [&ssid, &devUni] (NetworkManager::WirelessNetwork::Ptr const & net) -> bool { + return net->ssid() == ssid && net->device() == devUni; + }); + return mWifiNets.cend() == i ? NetworkManager::WirelessNetwork::Ptr{} : *i; +} + +void NmModelPrivate::onConnectionUpdated() +{ + emit connectionUpdate(qobject_cast(sender())); +} + +void NmModelPrivate::onConnectionRemoved() +{ + emit connectionRemove(qobject_cast(sender())); +} + +void NmModelPrivate::onActiveConnectionUpdated() +{ + emit activeConnectionUpdate(qobject_cast(sender())); +} + +void NmModelPrivate::onDeviceUpdated() +{ + emit deviceUpdate(qobject_cast(sender())); +} + +void NmModelPrivate::onWifiNetworkAppeared(QString const & ssid) +{ + NetworkManager::Device * dev = qobject_cast(sender()); + emit wifiNetworkAdd(dev, ssid); + emit deviceUpdate(dev); +} + +void NmModelPrivate::onWifiNetworkDisappeared(QString const & ssid) +{ + NetworkManager::Device * dev = qobject_cast(sender()); + emit wifiNetworkRemove(dev, ssid); + emit deviceUpdate(dev); +} + +void NmModelPrivate::onWifiNetworkUpdated() +{ + emit wifiNetworkUpdate(qobject_cast(sender())); +} + +void NmModelPrivate::onDeviceAdded(QString const & uni) +{ + NetworkManager::Device::Ptr dev = NetworkManager::findNetworkInterface(uni); + if (!dev.isNull()) + { + Q_ASSERT(dev->isValid()); + emit deviceAdd(dev); + } +} + +void NmModelPrivate::onDeviceRemoved(QString const & uni) +{ + NetworkManager::Device::Ptr dev = findDeviceUni(uni); + if (!dev.isNull()) + { + Q_ASSERT(dev->isValid()); + emit deviceRemove(dev.data()); + } +} + +void NmModelPrivate::onActiveConnectionAdded(QString const & path) +{ + NetworkManager::ActiveConnection::Ptr conn = NetworkManager::findActiveConnection(path);//XXX: const QString &uni + if (!conn.isNull()) + { + Q_ASSERT(conn->isValid()); + emit activeConnectionAdd(conn); + } +} + +void NmModelPrivate::onActiveConnectionRemoved(QString const & path) +{ + NetworkManager::ActiveConnection::Ptr conn = findActiveConnection(path);//XXX: const QString &uni + if (!conn.isNull()) + { + Q_ASSERT(conn->isValid()); + emit activeConnectionRemove(conn.data()); + } +} + +void NmModelPrivate::onActiveConnectionsChanged() +{ + emit activeConnectionsReset(); +} + +void NmModelPrivate::onConnectionAdded(QString const & path) +{ + NetworkManager::Connection::Ptr conn = NetworkManager::findConnection(path); + if (!conn.isNull()) + { + Q_ASSERT(conn->isValid()); + emit connectionAdd(conn); + } +} + +void NmModelPrivate::onConnectionRemoved(QString const & path) +{ + NetworkManager::Connection::Ptr conn = NetworkManager::findConnection(path); + if (!conn.isNull()) + { + Q_ASSERT(conn->isValid()); + emit connectionRemove(conn.data()); + } +} + + + + +NmModel::NmModel(QObject * parent) + : QAbstractItemModel(parent) + , d{new NmModelPrivate} +{ + connect(d.data(), &NmModelPrivate::connectionAdd, [this] (NetworkManager::Connection::Ptr conn) { +//qCDebug(NM_TRAY) << "connectionAdd" << conn->name(); + if (0 > d->mConnections.indexOf(conn)) + { + const int cnt = d->mConnections.size(); + beginInsertRows(createIndex(1, 0, ITEM_CONNECTION), cnt, cnt); + d->addConnection(conn); + endInsertRows(); + } else + { + //TODO: onConnectionUpdate + } + }); + connect(d.data(), &NmModelPrivate::connectionUpdate, [this] (NetworkManager::Connection * conn) { +//qCDebug(NM_TRAY) << "connectionUpdate" << conn->name(); + auto i = std::find(d->mConnections.cbegin(), d->mConnections.cend(), conn); + if (d->mConnections.cend() != i) + { + QModelIndex index = createIndex(i - d->mConnections.cbegin(), 0, ITEM_CONNECTION_LEAF); + emit dataChanged(index, index); + } + }); + connect(d.data(), &NmModelPrivate::connectionRemove, [this] (NetworkManager::Connection * conn) { +//qCDebug(NM_TRAY) << "connectionRemove" << conn->name(); + auto i = std::find(d->mConnections.cbegin(), d->mConnections.cend(), conn); + if (d->mConnections.cend() != i) + { + const int pos = i - d->mConnections.cbegin(); + beginRemoveRows(createIndex(1, 0, ITEM_CONNECTION), pos, pos); + d->removeConnection(pos); + endRemoveRows(); + } + }); + connect(d.data(), &NmModelPrivate::activeConnectionAdd, [this] (NetworkManager::ActiveConnection::Ptr conn) { +//qCDebug(NM_TRAY) << "activecCnnectionAdd" << conn->connection()->name(); + if (0 > d->mActiveConns.indexOf(conn)) + { + const int cnt = d->mActiveConns.size(); + beginInsertRows(createIndex(0, 0, ITEM_ACTIVE), cnt, cnt); + d->addActiveConnection(conn); + endInsertRows(); + } else + { + //TODO: onActiveConnectionUpdate + } + }); + connect(d.data(), &NmModelPrivate::activeConnectionUpdate, [this] (NetworkManager::ActiveConnection * conn) { +//qCDebug(NM_TRAY) << "activecCnnectionUpdate" << conn->connection()->name(); + auto i = std::find(d->mActiveConns.cbegin(), d->mActiveConns.cend(), conn); + if (d->mActiveConns.cend() != i) + { + QModelIndex index = createIndex(i - d->mActiveConns.cbegin(), 0, ITEM_ACTIVE_LEAF); + emit dataChanged(index, index); + } + }); + connect(d.data(), &NmModelPrivate::activeConnectionRemove, [this] (NetworkManager::ActiveConnection * conn) { +//qCDebug(NM_TRAY) << "activecCnnectionRemove" << conn->connection()->name(); + auto i = std::find(d->mActiveConns.cbegin(), d->mActiveConns.cend(), conn); + if (d->mActiveConns.cend() != i) + { + const int pos = i - d->mActiveConns.cbegin(); + beginRemoveRows(createIndex(0, 0, ITEM_ACTIVE), pos, pos); + d->removeActiveConnection(pos); + endRemoveRows(); + } + }); + connect(d.data(), &NmModelPrivate::activeConnectionsReset, [this] () { +//qCDebug(NM_TRAY) << "activecCnnectionReset"; + const int cnt = d->mActiveConns.size(); + if (0 < cnt) + { + beginRemoveRows(createIndex(0, 0, ITEM_ACTIVE), 0, d->mActiveConns.size() - 1); + d->clearActiveConnections(); + endRemoveRows(); + } + const int new_cnt = NetworkManager::activeConnections().size(); + if (0 < new_cnt) + { + beginInsertRows(createIndex(0, 0, ITEM_ACTIVE), 0, new_cnt - 1); + d->insertActiveConnections(); + endInsertRows(); + } + }); + connect(d.data(), &NmModelPrivate::deviceAdd, [this] (NetworkManager::Device::Ptr dev) { +//qCDebug(NM_TRAY) << "deviceAdd" << dev->interfaceName(); + if (0 > d->mDevices.indexOf(dev)) + { + const int cnt = d->mDevices.size(); + beginInsertRows(createIndex(2, 0, ITEM_DEVICE), cnt, cnt); + d->addDevice(dev); + endInsertRows(); + } else + { + //TODO: onDeviceUpdate + } + }); + connect(d.data(), &NmModelPrivate::deviceUpdate, [this] (NetworkManager::Device * dev) { +//qCDebug(NM_TRAY) << "deviceUpdate" << dev << dev->interfaceName(); + auto i = std::find(d->mDevices.cbegin(), d->mDevices.cend(), dev); + if (d->mDevices.cend() != i) + { + QModelIndex index = createIndex(i - d->mDevices.cbegin(), 0, ITEM_DEVICE_LEAF); + emit dataChanged(index, index); + } + }); + connect(d.data(), &NmModelPrivate::deviceRemove, [this] (NetworkManager::Device * dev) { +//qCDebug(NM_TRAY) << "deviceRemove" << dev->interfaceName(); + auto i = std::find(d->mDevices.cbegin(), d->mDevices.cend(), dev); + if (d->mDevices.cend() != i) + { + const int pos = i - d->mDevices.cbegin(); + beginRemoveRows(createIndex(2, 0, ITEM_DEVICE), pos, pos); + d->removeDevice(pos); + endRemoveRows(); + } + }); + connect(d.data(), &NmModelPrivate::wifiNetworkAdd, [this] (NetworkManager::Device * dev, QString const & ssid) { +//qCDebug(NM_TRAY) << "wifiNetworkAdd" << dev << dev->interfaceName() << ssid; + NetworkManager::WirelessDevice * w_dev = qobject_cast(dev); + NetworkManager::WirelessNetwork::Ptr net = w_dev->findNetwork(ssid); + if (!net.isNull()) + { + if (0 > d->mWifiNets.indexOf(net)) + { + const int cnt = d->mWifiNets.size(); + beginInsertRows(createIndex(3, 0, ITEM_WIFINET), cnt, cnt); + d->addWifiNetwork(net); + endInsertRows(); + } else + { + //TODO: onWifiNetworkUpdate + } + } + }); + connect(d.data(), &NmModelPrivate::wifiNetworkUpdate, [this] (NetworkManager::WirelessNetwork * net) { +//qCDebug(NM_TRAY) << "wifiNetworkUpdate" << net << net->ssid(); + auto i = std::find(d->mWifiNets.cbegin(), d->mWifiNets.cend(), net); + if (d->mWifiNets.cend() != i) + { + if (net->accessPoints().isEmpty()) + { + //remove + auto pos = i - d->mWifiNets.cbegin(); + beginRemoveRows(createIndex(3, 0, ITEM_WIFINET), pos, pos); + d->removeWifiNetwork(pos); + endRemoveRows(); + } else + { + //update + QModelIndex index = createIndex(i - d->mWifiNets.cbegin(), 0, ITEM_WIFINET_LEAF); + emit dataChanged(index, index); + //XXX: active connection + auto dev = d->findDeviceUni((*i)->device()); + if (!dev.isNull()) + { + auto active = dev->activeConnection(); + if (!active.isNull()) + { + QModelIndex index = createIndex(d->mActiveConns.indexOf(active), 0, ITEM_ACTIVE_LEAF); + emit dataChanged(index, index); + } + } + } + } + }); + connect(d.data(), &NmModelPrivate::wifiNetworkRemove, [this] (NetworkManager::Device * dev, QString const & ssid) { +//qCDebug(NM_TRAY) << "wifiNetworkRemove" << dev << dev->interfaceName() << ssid; + NetworkManager::WirelessNetwork::Ptr net = d->findWifiNetwork(ssid, dev->uni()); + if (!net.isNull()) + { + auto pos = d->mWifiNets.indexOf(net); + if (0 <= pos) + { + beginRemoveRows(createIndex(3, 0, ITEM_WIFINET), pos, pos); + d->removeWifiNetwork(pos); + endRemoveRows(); + } + } + }); +//qCDebug(NM_TRAY) << __FUNCTION__ << "finished"; +} + +NmModel::~NmModel() +{ +} + +/* Model hierarchy + * root + * - mActiveConns + * - mConnections + * - mDevices + */ +int NmModel::rowCount(const QModelIndex &parent/* = QModelIndex()*/) const +{ + int cnt = 0; + if (!parent.isValid()) + cnt = 1; + else + { + const int id = parent.internalId(); + if (ITEM_ROOT == id) + cnt = 4; + else if (ITEM_ACTIVE == id) + cnt = d->mActiveConns.size(); + else if (ITEM_CONNECTION == id) + cnt = d->mConnections.size(); + else if (ITEM_DEVICE == id) + cnt = d->mDevices.size(); + else if (ITEM_WIFINET == id) + cnt = d->mWifiNets.size(); + } + +//qCDebug(NM_TRAY) << __FUNCTION__ << parent << cnt; + return cnt; +} + +int NmModel::columnCount(const QModelIndex & /*parent = QModelIndex()*/) const +{ +//qCDebug(NM_TRAY) << __FUNCTION__ << parent << 1; + //XXX: more columns for wifi connections (for name && icons...)?? + return 1; +} + +bool NmModel::isValidDataIndex(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_WIFINET: + return true; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns.size() > index.row() && 0 == index.column(); + case ITEM_CONNECTION_LEAF: + return d->mConnections.size() > index.row() && 0 == index.column(); + case ITEM_DEVICE_LEAF: + return d->mDevices.size() > index.row() && 0 == index.column(); + case ITEM_WIFINET_LEAF: + return d->mWifiNets.size() > index.row() && 0 == index.column(); + } + return false; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_WIFINET: + case ITEM_DEVICE: + return HelperType; + case ITEM_ACTIVE_LEAF: + return ActiveConnectionType; + case ITEM_CONNECTION_LEAF: + return ConnectionType; + case ITEM_DEVICE_LEAF: + return DeviceType; + case ITEM_WIFINET_LEAF: + return WifiNetworkType; + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + return NmModel::tr("root"); + case ITEM_ACTIVE: + return NmModel::tr("active connection(s)"); + case ITEM_CONNECTION: + return NmModel::tr("connection(s)"); + case ITEM_DEVICE: + return NmModel::tr("device(s)"); + case ITEM_WIFINET: + return NmModel::tr("wifi network(s)"); + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->connection()->name(); + case ITEM_CONNECTION_LEAF: + return d->mConnections[index.row()]->name(); + case ITEM_DEVICE_LEAF: + return d->mDevices[index.row()]->interfaceName(); + case ITEM_WIFINET_LEAF: + return d->mWifiNets[index.row()]->referenceAccessPoint()->ssid(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_WIFINET: + return QVariant{}; + case ITEM_DEVICE_LEAF: + //TODO: other types + switch (d->mDevices[index.row()]->type()) + { + case NetworkManager::Device::Wifi: + return NetworkManager::ConnectionSettings::Wireless; + case NetworkManager::Device::Wimax: + return NetworkManager::ConnectionSettings::Wimax; + default: + return NetworkManager::ConnectionSettings::Wired; + } + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->connection()->settings()->connectionType(); + case ITEM_CONNECTION_LEAF: + return d->mConnections[index.row()]->settings()->connectionType(); + case ITEM_WIFINET_LEAF: + return NetworkManager::ConnectionSettings::Wireless; + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + const QVariant conn_type = dataRole(index); + if (!conn_type.isValid()) + { + return QVariant{}; + } + return NetworkManager::ConnectionSettings::typeAsString(static_cast(conn_type.toInt())); +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_CONNECTION_LEAF: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + return -1; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->state(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_CONNECTION_LEAF: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + return QVariant{}; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->master(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_CONNECTION_LEAF: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + return QVariant{}; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->devices(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + auto const internal_id = static_cast(index.internalId()); + switch (internal_id) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_WIFINET: + case ITEM_DEVICE_LEAF: + return -1; + case ITEM_CONNECTION_LEAF: + case ITEM_ACTIVE_LEAF: + { + NetworkManager::ConnectionSettings::Ptr settings = ITEM_CONNECTION_LEAF == internal_id + ? d->mConnections[index.row()]->settings() + : d->mActiveConns[index.row()]->connection()->settings(); + if (NetworkManager::ConnectionSettings::Wireless == settings->connectionType()) + { + NetworkManager::WirelessSecuritySetting::Ptr w_sett = settings->setting(NetworkManager::Setting::WirelessSecurity).staticCast(); + if (w_sett.isNull()) + return icons::SECURITY_LOW; + else if (NetworkManager::WirelessSecuritySetting::WpaNone != w_sett->keyMgmt()) + return icons::SECURITY_HIGH; + else + return icons::SECURITY_LOW; + } + return -1; + } + case ITEM_WIFINET_LEAF: + return d->mWifiNets[index.row()]->referenceAccessPoint()->capabilities().testFlag(NetworkManager::AccessPoint::Privacy) + ? icons::SECURITY_HIGH + : icons::SECURITY_LOW; + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + const auto type = dataRole(index).toInt(); + if (0 <= type) + return icons::getIcon(static_cast(type)); + else + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + return -1; + case ITEM_ACTIVE_LEAF: + { + NetworkManager::ConnectionSettings::Ptr sett = d->mActiveConns[index.row()]->connection()->settings(); + //TODO: other type with signals!?! + if (NetworkManager::ConnectionSettings::Wireless == sett->connectionType()) + { + NetworkManager::WirelessSetting::Ptr w_sett = sett->setting(NetworkManager::Setting::Wireless).staticCast(); + Q_ASSERT(!w_sett.isNull()); + for (auto const & dev_uni : d->mActiveConns[index.row()]->devices()) + { + auto dev = NetworkManager::findNetworkInterface(dev_uni); + if (!dev.isNull() && dev->isValid()) + { + auto w_dev = dev.objectCast(); + if (!w_dev.isNull()) + { + auto wifi_net = w_dev->findNetwork(w_sett->ssid()); + if (!wifi_net.isNull()) + return wifi_net->signalStrength(); + } + } + } + } + return -1; + } + case ITEM_CONNECTION_LEAF: + //TODO: implement + return -1; + case ITEM_WIFINET_LEAF: + return d->mWifiNets[index.row()]->signalStrength(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + return QVariant{}; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->uuid(); + case ITEM_CONNECTION_LEAF: + return d->mConnections[index.row()]->uuid(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + return QVariant{}; + case ITEM_ACTIVE_LEAF: + return d->mActiveConns[index.row()]->path(); + case ITEM_CONNECTION_LEAF: + return d->mConnections[index.row()]->path(); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + NetworkManager::Device::Ptr dev; + NetworkManager::ConnectionSettings::Ptr settings; + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_DEVICE_LEAF: + case ITEM_WIFINET: + case ITEM_WIFINET_LEAF: + case ITEM_CONNECTION_LEAF: + return QVariant{}; + break; + case ITEM_ACTIVE_LEAF: + { + auto const a_conn = d->mActiveConns[index.row()]; + auto const dev_list = a_conn->devices(); + //TODO: work with all devices?!? + if (!dev_list.isEmpty()) + dev = d->findDeviceUni(dev_list.first()); + Q_ASSERT(!a_conn->connection().isNull()); + settings = a_conn->connection()->settings(); + } + break; + } + //TODO: we probably shouldn't assemble a string information (with styling) here and leave it to the consumer + QString info; + QDebug str{&info}; + str.noquote(); + str.nospace(); + if (!dev.isNull()) + { + auto m_enum = NetworkManager::Device::staticMetaObject.enumerator(NetworkManager::Device::staticMetaObject.indexOfEnumerator("Type")); + + QString hw_address = NmModel::tr("unknown", "hardware address"); + QString security; + int bit_rate = -1; + switch (dev->type()) + { + case NetworkManager::Device::Adsl: + break; + case NetworkManager::Device::Bond: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hwAddress(); + } + break; + case NetworkManager::Device::Bridge: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hwAddress(); + } + break; + case NetworkManager::Device::Gre: + break; + case NetworkManager::Device::Generic: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hardwareAddress(); + } + break; + case NetworkManager::Device::InfiniBand: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hwAddress(); + } + break; + case NetworkManager::Device::MacVlan: + case NetworkManager::Device::Modem: + break; + case NetworkManager::Device::Bluetooth: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hardwareAddress(); + } + break; + case NetworkManager::Device::OlpcMesh: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hardwareAddress(); + } + break; + case NetworkManager::Device::Team: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hwAddress(); + } + break; + case NetworkManager::Device::Tun: + case NetworkManager::Device::Veth: + break; + case NetworkManager::Device::Vlan: + { + auto spec_dev = dev->as(); + hw_address = spec_dev->hwAddress(); + } + break; + case NetworkManager::Device::Ethernet: + { + auto spec_dev = dev->as(); + Q_ASSERT(nullptr != spec_dev); + hw_address = spec_dev->hardwareAddress(); + bit_rate = spec_dev->bitRate(); + } + break; + case NetworkManager::Device::Wifi: + { + auto spec_dev = dev->as(); + Q_ASSERT(nullptr != spec_dev); + hw_address = spec_dev->hardwareAddress(); + bit_rate = spec_dev->bitRate(); + NetworkManager::Setting::Ptr setting = settings->setting(NetworkManager::Setting::WirelessSecurity); + if (!setting.isNull()) + { + QVariantMap const map = setting->toMap(); + if (map.contains(QLatin1String(NM_SETTING_WIRELESS_SECURITY_KEY_MGMT))) + security = map.value(QLatin1String(NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)).toString(); + } + } + break; + case NetworkManager::Device::Wimax: + //Wimax support was dropped in network manager 1.2.0 + //we should never get here in runtime with nm >= 1.2.0 + { + auto spec_dev = dev->as(); + Q_ASSERT(nullptr != spec_dev); + hw_address = spec_dev->hardwareAddress(); + //bit_rate = spec_dev->bitRate(); + } + break; + case NetworkManager::Device::UnknownType: + case NetworkManager::Device::Unused1: + case NetworkManager::Device::Unused2: + break; + + } + str << QStringLiteral("") + << QStringLiteral("") + << QStringLiteral("") + << QStringLiteral("") + << QStringLiteral("") + << QStringLiteral(""); + if (!security.isEmpty()) + { + str << QStringLiteral(""); + } + //IP4/6 + QString const ip4 = NmModel::tr("IPv4", "Active connection information"); + QString const ip6 = NmModel::tr("IPv6", "Active connection information"); + for (auto const & ip : { std::make_tuple(ip4, dev->ipV4Config()) , std::make_tuple(ip6, dev->ipV6Config()) }) + { + NetworkManager::IpConfig ip_config = std::get<1>(ip); + if (ip_config.isValid()) + { + str << QStringLiteral(""); + int i = 1; + for (QNetworkAddressEntry const & address : ip_config.addresses()) + { + QString suffix = (i > 1 ? QString{"(%1)"}.arg(i) : QString{}); + str << QStringLiteral("") + << QStringLiteral("") + ; + ++i; + } + + QString const gtw = ip_config.gateway(); + if (!gtw.isEmpty()) + { + str << QStringLiteral(""); + } + i = 0; + for (auto const & nameserver : ip_config.nameservers()) + { + str << QStringLiteral(""); + } + } + } + str << QStringLiteral("
") << NmModel::tr("General", "Active connection information") << QStringLiteral("
") << NmModel::tr("Interface", "Active connection information") << QStringLiteral(": ") + << m_enum.valueToKey(dev->type()) << QStringLiteral(" (") << dev->interfaceName() << QStringLiteral(")
") << NmModel::tr("Hardware Address", "Active connection information") << QStringLiteral(": ") + << hw_address << QStringLiteral("
") << NmModel::tr("Driver", "Active connection information") << QStringLiteral(": ") + << dev->driver() << QStringLiteral("
") << NmModel::tr("Speed", "Active connection information") << QStringLiteral(": "); + if (0 <= bit_rate) + str << bit_rate << NmModel::tr(" Kb/s"); + else + str << NmModel::tr("unknown", "Speed"); + str << QStringLiteral("
") << NmModel::tr("Security", "Active connection information") << QStringLiteral(": ") + << security << QStringLiteral("
") << std::get<0>(ip) << QStringLiteral("
") << NmModel::tr("IP Address", "Active connection information") << suffix << QStringLiteral(": ") + << address.ip().toString() << QStringLiteral("
") << NmModel::tr("Subnet Mask", "Active connection information") << suffix << QStringLiteral(": ") + << address.netmask().toString() << QStringLiteral("
") << NmModel::tr("Default route", "Active connection information") << QStringLiteral(": ") + << ip_config.gateway() << QStringLiteral("
") << NmModel::tr("DNS(%1)", "Active connection information").arg(++i) << QStringLiteral(": ") + << nameserver.toString() << QStringLiteral("
"); + } + return info; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_WIFINET: + return -1; + case ITEM_ACTIVE_LEAF: + case ITEM_CONNECTION_LEAF: + case ITEM_DEVICE_LEAF: + { + auto type = static_cast(dataRole(index).toInt()); + auto state = static_cast(dataRole(index).toInt()); + //TODO other types? + switch (type) + { + case NetworkManager::ConnectionSettings::Wireless: + case NetworkManager::ConnectionSettings::Wimax: + if (0 > state) + return icons::NETWORK_WIFI_DISCONNECTED; + switch (state) + { + case NetworkManager::ActiveConnection::Activating: + return icons::NETWORK_WIFI_ACQUIRING; + case NetworkManager::ActiveConnection::Activated: + return icons::wifiSignalIcon(dataRole(index).toInt()); + case NetworkManager::ActiveConnection::Unknown: + case NetworkManager::ActiveConnection::Deactivating: + case NetworkManager::ActiveConnection::Deactivated: + return icons::NETWORK_WIFI_DISCONNECTED; + } + break; + case NetworkManager::ConnectionSettings::Vpn: + return icons::NETWORK_VPN; + default: + return NetworkManager::ActiveConnection::Activated == state ? icons::NETWORK_WIRED : icons::NETWORK_WIRED_DISCONNECTED; + } + } + case ITEM_WIFINET_LEAF: + return icons::wifiSignalIcon(d->mWifiNets[index.row()]->signalStrength()); + } + return QVariant{}; +} + +template <> +QVariant NmModel::dataRole(const QModelIndex & index) const +{ + return icons::getIcon(static_cast(dataRole(index).toInt())); +} + +QVariant NmModel::data(const QModelIndex &index, int role) const +{ +//qCDebug(NM_TRAY) << __FUNCTION__ << index << role; + Q_ASSERT(isValidDataIndex(index)); + QVariant ret; + if (index.isValid()) + switch (role) + { + case ItemTypeRole: + ret = dataRole(index); + break; + case Qt::DisplayRole: + case NameRole: + ret = dataRole(index); + break; + case ConnectionTypeRole: + ret = dataRole(index); + break; + case ConnectionTypeStringRole: + ret = dataRole(index); + break; + case ActiveConnectionStateRole: + ret = dataRole(index); + break; + case ActiveConnectionMasterRole: + ret = dataRole(index); + break; + case ActiveConnectionDevicesRole: + ret = dataRole(index); + break; + case SignalRole: + ret = dataRole(index); + break; + case ConnectionUuidRole: + ret = dataRole(index); + break; + case ConnectionPathRole: + ret = dataRole(index); + break; + case ActiveConnectionInfoRole: + ret = dataRole(index); + break; + case IconTypeRole: + ret = dataRole(index); + break; + case Qt::DecorationRole: + case IconRole: + ret = dataRole(index); + break; + case IconSecurityTypeRole: + ret = dataRole(index); + break; + case IconSecurityRole: + ret = dataRole(index); + break; + default: + ret = QVariant{}; + break; + } +//qCDebug(NM_TRAY) << __FUNCTION__ << "ret" << index << role << ret; + return ret; +} + +QModelIndex NmModel::index(int row, int column, const QModelIndex &parent/* = QModelIndex()*/) const +{ +//qCDebug(NM_TRAY) << __FUNCTION__ << row << column << parent; + if (!hasIndex(row, column, parent)) + return QModelIndex{}; + const int id = parent.internalId(); + ItemId int_id; + if (!parent.isValid()) + { + Q_ASSERT(0 == row && 0 == column); + int_id = ITEM_ROOT; + } else if (ITEM_ROOT == id) + { + Q_ASSERT(4 > row && 0 == column); + switch (row) + { + case 0: int_id = ITEM_ACTIVE; break; + case 1: int_id = ITEM_CONNECTION; break; + case 2: int_id = ITEM_DEVICE; break; + case 3: int_id = ITEM_WIFINET; break; + } + } else if (ITEM_ACTIVE == id) + { + Q_ASSERT(d->mActiveConns.size() > row); + int_id = ITEM_ACTIVE_LEAF; + } + else if (ITEM_CONNECTION == id) + { + Q_ASSERT(d->mConnections.size() > row); + int_id = ITEM_CONNECTION_LEAF; + } + else if (ITEM_DEVICE == id) + { + Q_ASSERT(d->mDevices.size() > row); + int_id = ITEM_DEVICE_LEAF; + } + else if (ITEM_WIFINET == id) + { + Q_ASSERT(d->mWifiNets.size() > row); + int_id = ITEM_WIFINET_LEAF; + } + else + { + Q_ASSERT(false); //should never get here + return QModelIndex{}; + } + +//qCDebug(NM_TRAY) << __FUNCTION__ << "ret: " << row << column << int_id; + return createIndex(row, column, int_id); +} + +QModelIndex NmModel::parent(const QModelIndex &index) const +{ + QModelIndex parent_i; + if (index.isValid()) + { + switch (static_cast(index.internalId())) + { + case ITEM_ROOT: + break; + case ITEM_ACTIVE: + case ITEM_CONNECTION: + case ITEM_DEVICE: + case ITEM_WIFINET: + parent_i = createIndex(0, 0, ITEM_ROOT); + break; + case ITEM_ACTIVE_LEAF: + parent_i = createIndex(0, 0, ITEM_ACTIVE); + break; + case ITEM_CONNECTION_LEAF: + parent_i = createIndex(1, 0, ITEM_CONNECTION); + break; + case ITEM_DEVICE_LEAF: + parent_i = createIndex(2, 0, ITEM_DEVICE); + break; + case ITEM_WIFINET_LEAF: + parent_i = createIndex(3, 0, ITEM_WIFINET); + break; + } + } + +//qCDebug(NM_TRAY) << __FUNCTION__ << index << parent_i; + return parent_i; +} + +QModelIndex NmModel::indexTypeRoot(ItemType type) const +{ + QModelIndex i; + switch (type) + { + case HelperType: + i = createIndex(0, 0, ITEM_ROOT); + break; + case ActiveConnectionType: + i = createIndex(0, 0, ITEM_ACTIVE); + break; + case ConnectionType: + i = createIndex(1, 0, ITEM_CONNECTION); + break; + case DeviceType: + i = createIndex(2, 0, ITEM_DEVICE); + break; + case WifiNetworkType: + i = createIndex(3, 0, ITEM_WIFINET); + break; + } + return i; +} + +void NmModel::activateConnection(QModelIndex const & index) +{ + ItemId id = static_cast(index.internalId()); + if (!isValidDataIndex(index) || (ITEM_CONNECTION_LEAF != id && ITEM_WIFINET_LEAF != id)) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << "got invalid index for connection activation" << index; + return; + } + QString conn_uni, dev_uni; + QString conn_name, dev_name; + QString spec_object; + NMVariantMapMap map_settings; + bool add_connection = false; + switch (id) + { + case ITEM_CONNECTION_LEAF: + { + auto const & conn = d->mConnections[index.row()]; + conn_uni = conn->path(); + conn_name = conn->name(); + if (NetworkManager::ConnectionSettings::Vpn == conn->settings()->connectionType()) + { + spec_object = dev_uni = QStringLiteral("/"); + /* + // find first non-vpn active connection + const auto act_i = std::find_if(d->mActiveConns.cbegin(), d->mActiveConns.cend(), [] (NetworkManager::ActiveConnection::Ptr const & conn) -> bool + { + return nullptr != dynamic_cast(conn.data()); + }); + if (act_i != d->mActiveConns.cend() && !(*act_i)->devices().empty()) + { + dev_uni = (*act_i)->devices().front(); + spec_object = (*act_i)->path(); + } + */ + + } else + { + dev_name = conn->settings()->interfaceName(); + for (auto const & dev : d->mDevices) + for (auto const & dev_conn : dev->availableConnections()) + if (dev_conn == conn) + { + dev_uni = dev->uni(); + dev_name = dev->interfaceName(); + } + if (dev_uni.isEmpty() && !dev_name.isEmpty()) + { + auto dev = d->findDeviceInterface(dev_name); + if (!dev.isNull()) + dev_uni = dev->uni(); + } + } + if (dev_uni.isEmpty()) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << QStringLiteral("can't find device '%1' to activate connection '%2' on").arg(dev_name).arg(conn->name()); + return; + } + } + break; + case ITEM_WIFINET_LEAF: + { + auto const & net = d->mWifiNets[index.row()]; + auto access_point = net->referenceAccessPoint(); + Q_ASSERT(!access_point.isNull()); + dev_uni = net->device(); + auto dev = d->findDeviceUni(dev_uni); + Q_ASSERT(!dev.isNull()); + auto spec_dev = dev->as(); + Q_ASSERT(nullptr != spec_dev); + conn_uni = access_point->uni(); + conn_name = access_point->ssid(); + //find the device name + NetworkManager::Connection::Ptr conn; + dev_name = dev->interfaceName(); + for (auto const & dev_conn : dev->availableConnections()) + if (dev_conn->settings()->id() == conn_name) + { + conn = dev_conn; + } + if (conn.isNull()) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << QStringLiteral("can't find connection for '%1' on device '%2', will create new...").arg(conn_name).arg(dev_name); + add_connection = true; + spec_object = conn_uni; + NetworkManager::WirelessSecurityType sec_type = NetworkManager::findBestWirelessSecurity(spec_dev->wirelessCapabilities() + , true, (spec_dev->mode() == NetworkManager::WirelessDevice::Adhoc) + , access_point->capabilities(), access_point->wpaFlags(), access_point->rsnFlags()); + switch (sec_type) + { + case NetworkManager::UnknownSecurity: + qCWarning(NM_TRAY).noquote() << QStringLiteral("unknown security to use for '%1'").arg(conn_name); + case NetworkManager::NoneSecurity: + //nothing to do + break; + case NetworkManager::WpaPsk: + case NetworkManager::Wpa2Psk: + if (NetworkManager::ConnectionSettings::Ptr settings = assembleWpaXPskSettings(access_point)) + { + map_settings = settings->toMap(); + } else + { + qCWarning(NM_TRAY).noquote() << QStringLiteral("connection settings assembly for '%1' failed, abandoning activation...") + .arg(conn_name); + return; + } + break; + //TODO: other types... + } + } else + { + conn_uni = conn->path(); + } + } + break; + default: + Q_ASSERT(false); + } +qCDebug(NM_TRAY) << __FUNCTION__ << conn_uni << dev_uni << conn_name << dev_name << spec_object; + //TODO: check vpn type etc.. + QDBusPendingCallWatcher * watcher; + if (add_connection) + watcher = new QDBusPendingCallWatcher{NetworkManager::addAndActivateConnection(map_settings, dev_uni, spec_object), this}; + else + watcher = new QDBusPendingCallWatcher{NetworkManager::activateConnection(conn_uni, dev_uni, spec_object), this}; + connect(watcher, &QDBusPendingCallWatcher::finished, [conn_name, dev_name] (QDBusPendingCallWatcher * watcher) { + if (watcher->isError() || !watcher->isValid()) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << QStringLiteral("activation of connection '%1' on interface '%2' failed: %3").arg(conn_name) + .arg(dev_name).arg(watcher->error().message()); + } + watcher->deleteLater(); + }); +} + +void NmModel::deactivateConnection(QModelIndex const & index) +{ + ItemId id = static_cast(index.internalId()); + if (!isValidDataIndex(index) || ITEM_ACTIVE_LEAF != id) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << "got invalid index for connection deactivation" << index; + return; + } + + auto const & active = d->mActiveConns[index.row()]; +qCDebug(NM_TRAY) << __FUNCTION__ << active->path(); + QDBusPendingReply<> reply = NetworkManager::deactivateConnection(active->path()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, [active] (QDBusPendingCallWatcher * watcher) { + if (watcher->isError() || !watcher->isValid()) + { + //TODO: in what form should we output the warning messages + qCWarning(NM_TRAY).noquote() << QStringLiteral("deactivation of connection '%1' failed: %3").arg(active->connection()->name()) + .arg(watcher->error().message()); + } + watcher->deleteLater(); + }); +} diff --git a/src/nmmodel.h b/src/nmmodel.h new file mode 100644 index 0000000..fa94b33 --- /dev/null +++ b/src/nmmodel.h @@ -0,0 +1,98 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(NMMODEL_H) +#define NMMODEL_H + +#include + +class NmModelPrivate; + +class NmModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum ItemType + { + HelperType + , ActiveConnectionType + , ConnectionType + , DeviceType + , WifiNetworkType + }; + enum ItemRole + { + ItemTypeRole = Qt::UserRole + 1 + , NameRole + + , IconTypeRole + , IconRole + , ConnectionTypeRole + , ActiveConnectionTypeRole = ConnectionTypeRole + , ConnectionTypeStringRole + , ActiveConnectionTypeStringRole = ConnectionTypeStringRole + , ConnectionUuidRole + , ActiveConnectionUuidRole = ConnectionUuidRole + , ConnectionPathRole + , ActiveConnectionPathRole = ConnectionPathRole + , ActiveConnectionInfoRole + , ActiveConnectionStateRole + , ActiveConnectionMasterRole + , ActiveConnectionDevicesRole + , IconSecurityTypeRole + , IconSecurityRole + + , SignalRole + }; + +public: + explicit NmModel(QObject * parent = nullptr); + ~NmModel(); + + //QAbstractItemModel methods + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex &index) const override; + + virtual QVariant data(const QModelIndex &index, int role) const override; + + QModelIndex indexTypeRoot(ItemType type) const; + +public Q_SLOTS: + //NetworkManager management methods + void activateConnection(QModelIndex const & index); + void deactivateConnection(QModelIndex const & index); + +private: + bool isValidDataIndex(const QModelIndex & index) const; + template + QVariant dataRole(const QModelIndex & index) const; + +private: + QScopedPointer d; +}; + + +#endif //NMMODEL_H diff --git a/src/nmmodel_p.h b/src/nmmodel_p.h new file mode 100644 index 0000000..fe53a28 --- /dev/null +++ b/src/nmmodel_p.h @@ -0,0 +1,129 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(NMMODEL_P_H) +#define NMMODEL_P_H + +#include +#include +#include + +class NmModelPrivate : public QObject +{ + Q_OBJECT +public: + NmModelPrivate(); + ~NmModelPrivate(); + + void removeActiveConnection(int pos); + void clearActiveConnections(); + void insertActiveConnections(); + void addActiveConnection(NetworkManager::ActiveConnection::Ptr conn); + + void removeConnection(int pos); + void clearConnections(); + void insertConnections(); + void addConnection(NetworkManager::Connection::Ptr conn); + + void removeDevice(int pos); + void clearDevices(); + void insertDevices(); + void addDevice(NetworkManager::Device::Ptr conn); + + void removeWifiNetwork(int pos); + void clearWifiNetworks(); + void insertWifiNetworks(); + void addWifiNetwork(NetworkManager::WirelessNetwork::Ptr net); + + NetworkManager::ActiveConnection::Ptr findActiveConnection(QString const & path); + template + NetworkManager::Device::Ptr findDevice(Predicate const & pred); + NetworkManager::Device::Ptr findDeviceUni(QString const & uni); + NetworkManager::Device::Ptr findDeviceInterface(QString const & interfaceName); + NetworkManager::WirelessNetwork::Ptr findWifiNetwork(QString const & ssid, QString const & devUni); + +Q_SIGNALS: + void connectionAdd(NetworkManager::Connection::Ptr conn); + void connectionUpdate(NetworkManager::Connection * conn); + void connectionRemove(NetworkManager::Connection * conn); + void activeConnectionAdd(NetworkManager::ActiveConnection::Ptr conn); + void activeConnectionUpdate(NetworkManager::ActiveConnection * conn); + void activeConnectionRemove(NetworkManager::ActiveConnection * conn); + void activeConnectionsReset(); + void deviceAdd(NetworkManager::Device::Ptr dev); + void deviceUpdate(NetworkManager::Device * dev); + void deviceRemove(NetworkManager::Device * dev); + void wifiNetworkAdd(NetworkManager::Device * dev, QString const & ssid); + void wifiNetworkUpdate(NetworkManager::WirelessNetwork * net); + void wifiNetworkRemove(NetworkManager::Device * dev, QString const & ssid); + + +private Q_SLOT: + //connection + void onConnectionUpdated(); + void onConnectionRemoved(); + + //active connection + void onActiveConnectionUpdated(); + + //device + void onDeviceUpdated(); + void onWifiNetworkAppeared(QString const & ssid); + void onWifiNetworkDisappeared(QString const & ssid); + + //wifi network + void onWifiNetworkUpdated(); + + //notifier + void onDeviceAdded(QString const & uni); + void onDeviceRemoved(QString const & uni); + void onActiveConnectionAdded(QString const & path); + void onActiveConnectionRemoved(QString const & path); + void onActiveConnectionsChanged(); + + //settings notifier + void onConnectionAdded(QString const & path); + void onConnectionRemoved(QString const & path); + +public: + NetworkManager::ActiveConnection::List mActiveConns; + NetworkManager::Connection::List mConnections; + NetworkManager::Device::List mDevices; + NetworkManager::WirelessNetwork::List mWifiNets; + +}; + + +enum ItemId +{ + ITEM_ROOT = 0x0 + , ITEM_ACTIVE = 0x01 + , ITEM_ACTIVE_LEAF = 0x11 + , ITEM_CONNECTION = 0x2 + , ITEM_CONNECTION_LEAF = 0x21 + , ITEM_DEVICE = 0x3 + , ITEM_DEVICE_LEAF = 0x31 + , ITEM_WIFINET = 0x4 + , ITEM_WIFINET_LEAF = 0x41 +}; + +#endif //NMMODEL_P_H diff --git a/src/nmproxy.cpp b/src/nmproxy.cpp new file mode 100644 index 0000000..6cc3c32 --- /dev/null +++ b/src/nmproxy.cpp @@ -0,0 +1,259 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "nmproxy.h" +#include "log.h" + +void NmProxy::setSourceModel(QAbstractItemModel * sourceModel) +{ + //we operate only on our known model + Q_ASSERT(sourceModel->metaObject() == &NmModel::staticMetaObject); + Q_ASSERT(root.isValid()); + beginResetModel(); + + if (nullptr != this->sourceModel()) + { + disconnect(this->sourceModel(), &QAbstractItemModel::dataChanged, this, &NmProxy::onSourceDataChanged); + disconnect(this->sourceModel(), &QAbstractItemModel::headerDataChanged, this, &NmProxy::onSourceHeaderDataChanged); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsAboutToBeInserted, this, &NmProxy::onSourceRowsAboutToBeInserted); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsInserted, this, &NmProxy::onSourceRowsInserted); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &NmProxy::onSourceRowsAboutToBeRemoved); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &NmProxy::onSourceRowsRemoved); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsAboutToBeInserted, this, &NmProxy::onSourceColumnsAboutToBeInserted); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsInserted, this, &NmProxy::onSourceColumnsInserted); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsAboutToBeRemoved, this, &NmProxy::onSourceColumnsAboutToBeRemoved); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsRemoved, this, &NmProxy::onSourceColumnsRemoved); + disconnect(this->sourceModel(), &QAbstractItemModel::modelAboutToBeReset, this, &NmProxy::onSourceModelAboutToBeReset); + disconnect(this->sourceModel(), &QAbstractItemModel::modelReset, this, &NmProxy::onSourceModelReset); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsAboutToBeMoved, this, &NmProxy::onSourceRowsAboutToBeMoved); + disconnect(this->sourceModel(), &QAbstractItemModel::rowsMoved, this, &NmProxy::onSourceRowsMoved); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsAboutToBeMoved, this, &NmProxy::onSourceColumnsAboutToBeMoved); + disconnect(this->sourceModel(), &QAbstractItemModel::columnsMoved, this, &NmProxy::onSourceColumnsMoved); + } + + QAbstractProxyModel::setSourceModel(sourceModel); + + connect(sourceModel, &QAbstractItemModel::dataChanged, this, &NmProxy::onSourceDataChanged); + connect(sourceModel, &QAbstractItemModel::headerDataChanged, this, &NmProxy::onSourceHeaderDataChanged); + connect(sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &NmProxy::onSourceRowsAboutToBeInserted); + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &NmProxy::onSourceRowsInserted); + connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &NmProxy::onSourceRowsAboutToBeRemoved); + connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, &NmProxy::onSourceRowsRemoved); + connect(sourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, &NmProxy::onSourceColumnsAboutToBeInserted); + connect(sourceModel, &QAbstractItemModel::columnsInserted, this, &NmProxy::onSourceColumnsInserted); + connect(sourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, &NmProxy::onSourceColumnsAboutToBeRemoved); + connect(sourceModel, &QAbstractItemModel::columnsRemoved, this, &NmProxy::onSourceColumnsRemoved); + connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &NmProxy::onSourceModelAboutToBeReset); + connect(sourceModel, &QAbstractItemModel::modelReset, this, &NmProxy::onSourceModelReset); + connect(sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &NmProxy::onSourceRowsAboutToBeMoved); + connect(sourceModel, &QAbstractItemModel::rowsMoved, this, &NmProxy::onSourceRowsMoved); + connect(sourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this, &NmProxy::onSourceColumnsAboutToBeMoved); + connect(sourceModel, &QAbstractItemModel::columnsMoved, this, &NmProxy::onSourceColumnsMoved); + + endResetModel(); +} + +void NmProxy::setNmModel(NmModel * model, NmModel::ItemType shownType) +{ + root = model->indexTypeRoot(shownType); + setSourceModel(model); +//qCDebug(NM_TRAY) << __FUNCTION__ << model->indexTypeRoot(shownType) << "->" << root; +} + +QModelIndex NmProxy::index(int row, int column, const QModelIndex & proxyParent) const +{ + QModelIndex i; + if (hasIndex(row, column, proxyParent)) + i = createIndex(row, column); +//qCDebug(NM_TRAY) << __FUNCTION__ << row << column << proxyParent << "->" << i; + return i; +} + +QModelIndex NmProxy::parent(const QModelIndex &/*proxyIndex*/) const +{ + //showing only one level/list: leaf -> invalid, root -> invalid + QModelIndex i; +//qCDebug(NM_TRAY) << __FUNCTION__ << proxyIndex << "->" << i; + return i; +} + +int NmProxy::rowCount(const QModelIndex &proxyParent) const +{ + //showing only one level/list: leaf -> source root rowcount, root -> 0 + if (proxyParent.isValid()) + return 0; + else + return sourceModel()->rowCount(root); +} + +int NmProxy::columnCount(const QModelIndex &proxyParent) const +{ + return sourceModel()->columnCount(mapToSource(proxyParent)); +} + +void NmProxy::activateConnection(QModelIndex const & index) const +{ + qobject_cast(sourceModel())->activateConnection(mapToSource(index)); +} + +void NmProxy::deactivateConnection(QModelIndex const & index) const +{ + qobject_cast(sourceModel())->deactivateConnection(mapToSource(index)); +} + +QModelIndex NmProxy::mapToSource(const QModelIndex & proxyIndex) const +{ + QModelIndex i; + if (proxyIndex.isValid()) + i = sourceModel()->index(proxyIndex.row(), proxyIndex.column(), root); +//qCDebug(NM_TRAY) << __FUNCTION__ << proxyIndex << "->" << i; + return i; +} + +QModelIndex NmProxy::mapFromSource(const QModelIndex & sourceIndex) const +{ + QModelIndex i; + if (sourceIndex.isValid() && root != sourceIndex) + i = createIndex(sourceIndex.row(), sourceIndex.column()); +//qCDebug(NM_TRAY) << __FUNCTION__ << sourceIndex << "->" << i; + return i; +} + +void NmProxy::onSourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) +{ + if (root == topLeft.parent()) + emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles); +} + +void NmProxy::onSourceHeaderDataChanged(Qt::Orientation orientation, int first, int last) +{ + emit headerDataChanged(orientation, first, last); +} + +void NmProxy::onSourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last) +{ + if (root == parent) + beginInsertRows(mapFromSource(parent), first, last); +} + +void NmProxy::onSourceRowsInserted(const QModelIndex &parent, int, int) +{ + if (root == parent) + endInsertRows(); +} + +void NmProxy::onSourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + if (root == parent) + beginRemoveRows(mapFromSource(parent), first, last); +} + +void NmProxy::onSourceRowsRemoved(const QModelIndex &parent, int, int) +{ + if (root == parent) + endRemoveRows(); +} + +void NmProxy::onSourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last) +{ + if (root == parent) + beginInsertColumns(mapFromSource(parent), first, last); +} + +void NmProxy::onSourceColumnsInserted(const QModelIndex &parent, int, int) +{ + if (root == parent) + endInsertColumns(); +} + + +void NmProxy::onSourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + if (root == parent) + beginRemoveColumns(mapFromSource(parent), first, last); +} + +void NmProxy::onSourceColumnsRemoved(const QModelIndex &parent, int, int) +{ + if (root == parent) + endRemoveColumns(); +} + +void NmProxy::onSourceModelAboutToBeReset() +{ + beginResetModel(); +} + +void NmProxy::onSourceModelReset() +{ + endResetModel(); +} + +void NmProxy::onSourceRowsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) +{ + if (root == sourceParent) + { + if (root == destinationParent) + beginMoveRows(mapFromSource(sourceParent), sourceStart, sourceEnd, destinationParent, destinationRow); + else + beginRemoveRows(mapFromSource(sourceParent), sourceStart, sourceEnd); + } else if (root == destinationParent) + beginInsertRows(mapFromSource(destinationParent), destinationRow, destinationRow + (sourceEnd - sourceStart)); +} + +void NmProxy::onSourceRowsMoved( const QModelIndex &sourceParent, int, int, const QModelIndex &destinationParent, int) +{ + if (root == sourceParent) + { + if (root == destinationParent) + endMoveRows(); + else + endRemoveRows(); + } else if (root == destinationParent) + endInsertRows(); +} + + +void NmProxy::onSourceColumnsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) +{ + if (root == sourceParent) + { + if (root == destinationParent) + beginMoveColumns(mapFromSource(sourceParent), sourceStart, sourceEnd, destinationParent, destinationColumn); + else + beginRemoveColumns(mapFromSource(sourceParent), sourceStart, sourceEnd); + } else if (root == destinationParent) + beginInsertColumns(mapFromSource(destinationParent), destinationColumn, destinationColumn + (sourceEnd - sourceStart)); +} + +void NmProxy::onSourceColumnsMoved( const QModelIndex &sourceParent, int, int, const QModelIndex &destinationParent, int) +{ + if (root == sourceParent) + { + if (root == destinationParent) + endMoveColumns(); + else + endRemoveColumns(); + } else if (root == destinationParent) + endInsertColumns(); +} + diff --git a/src/nmproxy.h b/src/nmproxy.h new file mode 100644 index 0000000..e38341d --- /dev/null +++ b/src/nmproxy.h @@ -0,0 +1,79 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(NMPROXY_H) +#define NMPROXY_H + +#include +#include "nmmodel.h" + +class NmProxy : public QAbstractProxyModel +{ + Q_OBJECT +public: + using QAbstractProxyModel::QAbstractProxyModel; + virtual void setSourceModel(QAbstractItemModel * sourceModel) override; + void setNmModel(NmModel * model, NmModel::ItemType shownType); + + virtual QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex &index) const override; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + void activateConnection(QModelIndex const & index) const; + void deactivateConnection(QModelIndex const & index) const; + +protected: + virtual QModelIndex mapToSource(const QModelIndex & proxyIndex) const override; + virtual QModelIndex mapFromSource(const QModelIndex & sourceIndex) const override; + +private Q_SLOTS: + void onSourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); + void onSourceHeaderDataChanged(Qt::Orientation orientation, int first, int last); + + void onSourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void onSourceRowsInserted(const QModelIndex &parent, int first, int last); + + void onSourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void onSourceRowsRemoved(const QModelIndex &parent, int first, int last); + + void onSourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void onSourceColumnsInserted(const QModelIndex &parent, int first, int last); + + void onSourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void onSourceColumnsRemoved(const QModelIndex &parent, int first, int last); + + void onSourceModelAboutToBeReset(); + void onSourceModelReset(); + + void onSourceRowsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow); + void onSourceRowsMoved( const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); + + void onSourceColumnsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn); + void onSourceColumnsMoved( const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column); + +private: + QModelIndex root; +}; + +#endif //NMPROXY_H diff --git a/src/translate.cpp b/src/translate.cpp new file mode 100644 index 0000000..d532173 --- /dev/null +++ b/src/translate.cpp @@ -0,0 +1,49 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include +#include +#include +#include + +#if !defined(TRANSLATION_DIR) +#define TRANSLATION_DIR "/usr/share/nm-tray" +#endif + +void translate() +{ + const QString locale = QLocale::system().name(); + QTranslator * qt_trans = new QTranslator{qApp}; + if (qt_trans->load(QLatin1String("qt_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + qApp->installTranslator(qt_trans); + else + delete qt_trans; + + QString trans_dir = qgetenv("NM_TRAY_TRANSLATION_DIR").constData(); + QTranslator * my_trans = new QTranslator{qApp}; + if (my_trans->load(QLatin1String("nm-tray_") + locale, trans_dir.isEmpty() ? TRANSLATION_DIR : trans_dir)) + qApp->installTranslator(my_trans); + else + delete my_trans; +} + +Q_COREAPP_STARTUP_FUNCTION(translate) diff --git a/src/tray.cpp b/src/tray.cpp new file mode 100644 index 0000000..2fec52d --- /dev/null +++ b/src/tray.cpp @@ -0,0 +1,397 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#include "tray.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "icons.h" +#include "nmmodel.h" +#include "nmproxy.h" +#include "log.h" +#include "dbus/org.freedesktop.Notifications.h" + +#include "nmlist.h" +#include "connectioninfo.h" +#include "windowmenu.h" + +// config keys +static const QString ENABLE_NOTIFICATIONS = QStringLiteral("enableNotifications"); + +class TrayPrivate +{ +public: + TrayPrivate(); + void updateState(QModelIndex const & index, bool removing); + void primaryConnectionUpdate(); + void setShown(QPersistentModelIndex const & index); + void updateIcon(); + void refreshIcon(); + void openCloseDialog(QDialog * dialog); + void notify(QModelIndex const & index, bool removing); + +public: + QSystemTrayIcon mTrayIcon; + QMenu mContextMenu; + QTimer mStateTimer; + QAction * mActEnableNetwork; + QAction * mActEnableWifi; + QAction * mActConnInfo; + QAction * mActDebugInfo; + NmModel mNmModel; + NmProxy mActiveConnections; + QPersistentModelIndex mPrimaryConnection; + QPersistentModelIndex mShownConnection; + QList mConnectionsToNotify; //!< just "created" connections to which notification wasn't sent yet + icons::Icon mIconCurrent; + icons::Icon mIcon2Show; + QTimer mIconTimer; + QScopedPointer mConnDialog; + QScopedPointer mInfoDialog; + + org::freedesktop::Notifications mNotification; + + + // configuration + bool mEnableNotifications; //!< should info about connection establishment etc. be send by org.freedesktop.Notifications +}; + +TrayPrivate::TrayPrivate() + : mNotification{QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus()} +{ + mActiveConnections.setNmModel(&mNmModel, NmModel::ActiveConnectionType); +} + +void TrayPrivate::updateState(QModelIndex const & index, bool removing) +{ + notify(index, removing); + + const auto state = static_cast(mActiveConnections.data(index, NmModel::ActiveConnectionStateRole).toInt()); + const bool is_primary = mPrimaryConnection == index; +//qCDebug(NM_TRAY) << __FUNCTION__ << index << removing << mActiveConnections.data(index, NmModel::NameRole) << mActiveConnections.data(index, NmModel::ConnectionUuidRole).toString() << is_primary << mActiveConnections.data(index, NmModel::ConnectionTypeRole).toInt() << state; + + if (removing || NetworkManager::ActiveConnection::Deactivated == state || NetworkManager::ActiveConnection::Deactivating == state) + { + if (is_primary) + { + mPrimaryConnection = QModelIndex{}; + setShown(mPrimaryConnection); + } else if (mShownConnection == index) + { + setShown(mPrimaryConnection); + } + } else + { + if (is_primary || NetworkManager::ActiveConnection::Activating == state) + { + setShown(index); + } else if (mShownConnection == index) + { + setShown(mPrimaryConnection); + } + } + //TODO: optimize this text assembly (to not do it every time)? + if (mPrimaryConnection.isValid()) + { + mTrayIcon.setToolTip(Tray::tr("
Connection %1(%2) active
") + .arg(mPrimaryConnection.data(NmModel::NameRole).toString()) + .arg(mPrimaryConnection.data(NmModel::ActiveConnectionTypeStringRole).toString()) + ); + } else + { + mTrayIcon.setToolTip(Tray::tr("
No active connection
")); + } +} + +void TrayPrivate::primaryConnectionUpdate() +{ + NetworkManager::ActiveConnection::Ptr prim_conn = NetworkManager::primaryConnection(); + if (!prim_conn || !prim_conn->isValid()) + { + mPrimaryConnection = QModelIndex{}; + setShown(mPrimaryConnection); + return; + } + +//qCDebug(NM_TRAY) << __FUNCTION__ << prim_conn->uuid(); + + QModelIndexList l = mActiveConnections.match(mActiveConnections.index(0, 0, QModelIndex{}), NmModel::ActiveConnectionUuidRole, prim_conn->uuid(), -1, Qt::MatchExactly); +//qCDebug(NM_TRAY) << __FUNCTION__ << l.size(); + //nothing to do if the connection not populated in model yet + if (0 >= l.size()) + return; + Q_ASSERT(1 == l.size()); + mPrimaryConnection = l.first(); + updateState(mPrimaryConnection, false); +} + +void TrayPrivate::setShown(QPersistentModelIndex const & index) +{ + mShownConnection = index; + mIcon2Show = mShownConnection.isValid() + ? static_cast(mActiveConnections.data(mShownConnection, NmModel::IconTypeRole).toInt()) : icons::NETWORK_OFFLINE; + //postpone setting the icon (for case we change the icon in till our event is finished) + mIconTimer.start(); +} + +void TrayPrivate::updateIcon() +{ + if (mIconCurrent == mIcon2Show) + return; + + mIconCurrent = mIcon2Show; + refreshIcon(); +} + +void TrayPrivate::refreshIcon() +{ + //Note: the icons::getIcon chooses the right icon from list of possible candidates + // -> we need to refresh the icon in case of icon theme change + mTrayIcon.setIcon(icons::getIcon(mIconCurrent)); +} + +void TrayPrivate::openCloseDialog(QDialog * dialog) +{ + if (dialog->isHidden() || dialog->isMinimized()) + { + dialog->showNormal(); + dialog->activateWindow(); + dialog->raise(); + } else + dialog->close(); +} + +void TrayPrivate::notify(QModelIndex const & index, bool removing) +{ + if (!mEnableNotifications) + { + return; + } + + QString summary, body; + if (removing) + { + mConnectionsToNotify.removeOne(index); + summary = Tray::tr("Connection lost"); + body = Tray::tr("We have just lost the connection to %1 '%2'."); + } else + { + const int notif_i = mConnectionsToNotify.indexOf(index); + // do nothing if not just added or the connection is not activated yet + if (-1 == notif_i + || NetworkManager::ActiveConnection::Activated != static_cast(mActiveConnections.data(index, NmModel::ActiveConnectionStateRole).toInt()) + ) + { + return; + } + mConnectionsToNotify.removeAt(notif_i); // fire the notification only once + summary = Tray::tr("Connection established"); + body = Tray::tr("We have just established the connection to %1 '%2'."); + } + + // TODO: do somehow check the result? + mNotification.Notify(Tray::tr("NetworkManager(nm-tray)") + , 0 + , icons::getIcon(static_cast(mActiveConnections.data(index, NmModel::IconTypeRole).toInt())).name() + , summary + , body.arg(mActiveConnections.data(index, NmModel::ConnectionTypeStringRole).toString()).arg(mActiveConnections.data(index, NmModel::NameRole).toString()) + , {} + , {} + , -1); +} + + +Tray::Tray(QObject *parent/* = nullptr*/) + : QObject{parent} + , d{new TrayPrivate} +{ + d->mEnableNotifications = QSettings{}.value(ENABLE_NOTIFICATIONS, true).toBool(); + + connect(&d->mTrayIcon, &QSystemTrayIcon::activated, this, &Tray::onActivated); + + //postpone the update in case of signals flood + connect(&d->mStateTimer, &QTimer::timeout, this, &Tray::setActionsStates); + d->mStateTimer.setSingleShot(true); + d->mStateTimer.setInterval(200); + + d->mIconCurrent = static_cast(-1); + d->setShown(QModelIndex{}); + d->refreshIcon(); //force setting the icon instantly + + //postpone updating of the icon + connect(&d->mIconTimer, &QTimer::timeout, [this] { d->updateIcon(); }); + d->mIconTimer.setSingleShot(true); + d->mIconTimer.setInterval(0); + + d->mActEnableNetwork = d->mContextMenu.addAction(Tray::tr("Enable Networking")); + d->mActEnableWifi = d->mContextMenu.addAction(Tray::tr("Enable Wi-fi")); + d->mContextMenu.addSeparator(); + QAction * enable_notifications = d->mContextMenu.addAction(Tray::tr("Enable notifications")); + d->mContextMenu.addSeparator(); + d->mActConnInfo = d->mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("dialog-information")), Tray::tr("Connection information")); + d->mActDebugInfo = d->mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("dialog-information")), Tray::tr("Debug information")); + connect(d->mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("document-edit")), Tray::tr("Edit connections...")), &QAction::triggered + , this, &Tray::onEditConnectionsTriggered); + d->mContextMenu.addSeparator(); + connect(d->mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("help-about")), Tray::tr("About")), &QAction::triggered + , this, &Tray::onAboutTriggered); + connect(d->mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("application-exit")), Tray::tr("Quit")), &QAction::triggered + , this, &Tray::onQuitTriggered); + //for listening on the QEvent::ThemeChange (is delivered only to QWidget objects) + d->mContextMenu.installEventFilter(this); + + d->mActEnableNetwork->setCheckable(true); + d->mActEnableWifi->setCheckable(true); + enable_notifications->setCheckable(true); + enable_notifications->setChecked(d->mEnableNotifications); + connect(d->mActEnableNetwork, &QAction::triggered, [this] (bool checked) { NetworkManager::setNetworkingEnabled(checked); }); + connect(d->mActEnableWifi, &QAction::triggered, [this] (bool checked) { NetworkManager::setWirelessEnabled(checked); }); + connect(enable_notifications, &QAction::triggered, [this] (bool checked) { d->mEnableNotifications = checked; QSettings{}.setValue(ENABLE_NOTIFICATIONS, checked); }); + connect(d->mActConnInfo, &QAction::triggered, [this] (bool ) { + if (d->mInfoDialog.isNull()) + { + d->mInfoDialog.reset(new ConnectionInfo{&d->mNmModel}); + connect(d->mInfoDialog.data(), &QDialog::finished, [this] { + d->mInfoDialog.reset(nullptr); + }); + } + d->openCloseDialog(d->mInfoDialog.data()); + }); + connect(d->mActDebugInfo, &QAction::triggered, [this] (bool ) { + if (d->mConnDialog.isNull()) + { + d->mConnDialog.reset(new NmList{Tray::tr("nm-tray info"), &d->mNmModel}); + connect(d->mConnDialog.data(), &QDialog::finished, [this] { + d->mConnDialog.reset(nullptr); + }); + } + d->openCloseDialog(d->mConnDialog.data()); + }); + + // Note: Force all the updates as the NetworkManager::Notifier signals aren't + // emitted at application startup. + d->primaryConnectionUpdate(); + setActionsStates(); + + connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, &d->mStateTimer, static_cast(&QTimer::start)); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, &d->mStateTimer, static_cast(&QTimer::start)); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessHardwareEnabledChanged, &d->mStateTimer, static_cast(&QTimer::start)); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionChanged, this, &Tray::onPrimaryConnectionChanged); + + connect(&d->mActiveConnections, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const & parent, int first, int last) { + for (int i = first; i <= last; ++i) + { + const QModelIndex index = d->mActiveConnections.index(i, 0, parent); +//qCDebug(NM_TRAY) << "rowsInserted" << index; + if (d->mEnableNotifications) + { + d->mConnectionsToNotify.append(index); + } + d->updateState(index, false); + } + }); + connect(&d->mActiveConnections, &QAbstractItemModel::rowsAboutToBeRemoved, [this] (QModelIndex const & parent, int first, int last) { +//qCDebug(NM_TRAY) << "rowsAboutToBeRemoved"; + for (int i = first; i <= last; ++i) + d->updateState(d->mActiveConnections.index(i, 0, parent), true); + }); + connect(&d->mActiveConnections, &QAbstractItemModel::dataChanged, [this] (const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & /*roles*/) { +//qCDebug(NM_TRAY) << "dataChanged"; + for (auto const & i : QItemSelection{topLeft, bottomRight}.indexes()) + d->updateState(i, false); + }); + + d->mTrayIcon.setContextMenu(&d->mContextMenu); + QTimer::singleShot(0, [this] { d->mTrayIcon.show(); }); +} + +Tray::~Tray() +{ +} + +bool Tray::eventFilter(QObject * object, QEvent * event) +{ + Q_ASSERT(&d->mContextMenu == object); + if (QEvent::ThemeChange == event->type()) + d->refreshIcon(); + return false; +} + +void Tray::onEditConnectionsTriggered() +{ + // Note: let this object dangle, if the process isn't finished until our application is closed + QProcess * editor = new QProcess; + editor->setProcessChannelMode(QProcess::ForwardedChannels); + // TODO: allow the command to be configurable!? + qCInfo(NM_TRAY) << "starting the nm-connection-editor"; + editor->start(QStringLiteral("nm-connection-editor")); + editor->closeWriteChannel(); + connect(editor, static_cast(&QProcess::finished), [editor] { + qCInfo(NM_TRAY) << "the nm-connection-editor finished"; + editor->deleteLater(); + }); +} + +void Tray::onAboutTriggered() +{ + QMessageBox::about(nullptr, Tray::tr("%1 about").arg(QStringLiteral("nm-tray")) + , Tray::tr("nm-tray is a simple Qt based" + " frontend for NetworkManager.

" + "Version: %1").arg(NM_TRAY_VERSION)); +} + + +void Tray::onQuitTriggered() +{ + QApplication::instance()->quit(); +} + + +void Tray::onActivated() +{ + QMenu * menu = new WindowMenu(&d->mNmModel); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(QCursor::pos()); +} + +void Tray::setActionsStates() +{ + const bool net_enabled = NetworkManager::isNetworkingEnabled(); + d->mActEnableNetwork->setChecked(net_enabled); + + d->mActEnableWifi->setChecked(NetworkManager::isWirelessEnabled()); + d->mActEnableWifi->setEnabled(NetworkManager::isNetworkingEnabled() && NetworkManager::isWirelessHardwareEnabled()); + + d->mActConnInfo->setEnabled(net_enabled); +} + +void Tray::onPrimaryConnectionChanged(QString const & /*uni*/) +{ + d->primaryConnectionUpdate(); +} diff --git a/src/tray.h b/src/tray.h new file mode 100644 index 0000000..e7791e5 --- /dev/null +++ b/src/tray.h @@ -0,0 +1,58 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2015~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ +#if !defined(TRAY_H) +#define TRAY_H + +#include +#include + +class TrayPrivate; + +class Tray : public QObject +{ +Q_OBJECT + +public: + Tray(QObject *parent = nullptr); + ~Tray(); + +protected: + virtual bool eventFilter(QObject * object, QEvent * event) override; + +private Q_SLOTS: + //menu + void onEditConnectionsTriggered(); + void onAboutTriggered(); + void onQuitTriggered(); + void onActivated(); + + //NetworkManager + void setActionsStates(); + void onPrimaryConnectionChanged(QString const & uni); + +private: + QScopedPointer d; +}; + + +#endif //TRAY_H diff --git a/src/windowmenu.cpp b/src/windowmenu.cpp new file mode 100644 index 0000000..da1c64c --- /dev/null +++ b/src/windowmenu.cpp @@ -0,0 +1,182 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2016~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ + +#include "windowmenu.h" +#include "nmmodel.h" +#include "nmproxy.h" +#include "menuview.h" + +#include +#include +#include + +class WindowMenuPrivate +{ +public: + NmModel * mNmModel; + + QScopedPointer mWirelessModel; + QWidgetAction * mWirelessAction; + + QScopedPointer mActiveModel; + QWidgetAction * mActiveAction; + + QScopedPointer mConnectionModel; + QWidgetAction * mConnectionAction; + + QAction * mMakeDirtyAction; + QTimer mDelaySizeRefreshTimer; + + WindowMenuPrivate(WindowMenu * q); + template + void onActivated(QModelIndex const & index + , QAbstractItemModel const * topParent + , F const & functor); + void forceSizeRefresh(); + void onViewRowChange(QAction * viewAction, QAbstractItemModel const * model); +private: + WindowMenu * q_ptr; + Q_DECLARE_PUBLIC(WindowMenu); +}; + +WindowMenuPrivate::WindowMenuPrivate(WindowMenu * q) + : q_ptr{q} +{ +} + +template +void WindowMenuPrivate::onActivated(QModelIndex const & index + , QAbstractItemModel const * topParent + , F const & functor) +{ + QModelIndex i = index; + for (QAbstractProxyModel const * proxy = qobject_cast(index.model()) + ; nullptr != proxy && topParent != proxy + ; proxy = qobject_cast(proxy->sourceModel()) + ) + { + i = proxy->mapToSource(i); + } + functor(i); +} + +void WindowMenuPrivate::forceSizeRefresh() +{ + Q_Q(WindowMenu); + if (!q->isVisible()) + { + return; + } + + const QSize old_size = q->size(); + //TODO: how to force the menu to recalculate it's size in a more elegant way? + q->addAction(mMakeDirtyAction); + q->removeAction(mMakeDirtyAction); + // ensure to be visible (should the resize make it out of screen) + if (old_size != q->size()) + { + q->popup(q->geometry().topLeft()); + } +} + +void WindowMenuPrivate::onViewRowChange(QAction * viewAction, QAbstractItemModel const * model) +{ + viewAction->setVisible(model->rowCount(QModelIndex{}) > 0); + mDelaySizeRefreshTimer.start(); +} + + + + +WindowMenu::WindowMenu(NmModel * nmModel, QWidget * parent /*= nullptr*/) + : QMenu{parent} + , d_ptr{new WindowMenuPrivate{this}} +{ + Q_D(WindowMenu); + d->mNmModel = nmModel; + + //active proxy & widgets + d->mActiveModel.reset(new NmProxy); + d->mActiveModel->setNmModel(d->mNmModel, NmModel::ActiveConnectionType); + MenuView * active_view = new MenuView{d->mActiveModel.data()}; + connect(active_view, &QAbstractItemView::activated, [this, d] (const QModelIndex & index) { + d->onActivated(index, d->mActiveModel.data(), std::bind(&NmProxy::deactivateConnection, d->mActiveModel.data(), std::placeholders::_1)); + close(); + }); + + d->mActiveAction = new QWidgetAction{this}; + d->mActiveAction->setDefaultWidget(active_view); + connect(d->mActiveModel.data(), &QAbstractItemModel::modelReset, [d] { d->onViewRowChange(d->mActiveAction, d->mActiveModel.data()); }); + connect(d->mActiveModel.data(), &QAbstractItemModel::rowsInserted, [d] { d->onViewRowChange(d->mActiveAction, d->mActiveModel.data()); }); + connect(d->mActiveModel.data(), &QAbstractItemModel::rowsRemoved, [d] { d->onViewRowChange(d->mActiveAction, d->mActiveModel.data()); }); + + //wireless proxy & widgets + d->mWirelessModel.reset(new NmProxy); + d->mWirelessModel->setNmModel(d->mNmModel, NmModel::WifiNetworkType); + MenuView * wifi_view = new MenuView{d->mWirelessModel.data()}; + connect(wifi_view, &QAbstractItemView::activated, [this, d] (const QModelIndex & index) { + d->onActivated(index, d->mWirelessModel.data(), std::bind(&NmProxy::activateConnection, d->mWirelessModel.data(), std::placeholders::_1)); + close(); + }); + + d->mWirelessAction = new QWidgetAction{this}; + d->mWirelessAction->setDefaultWidget(wifi_view); + connect(d->mWirelessModel.data(), &QAbstractItemModel::modelReset, [d] { d->onViewRowChange(d->mWirelessAction, d->mWirelessModel.data()); }); + connect(d->mWirelessModel.data(), &QAbstractItemModel::rowsInserted, [d] { d->onViewRowChange(d->mWirelessAction, d->mWirelessModel.data()); }); + connect(d->mWirelessModel.data(), &QAbstractItemModel::rowsRemoved, [d] { d->onViewRowChange(d->mWirelessAction, d->mWirelessModel.data()); }); + + //connection proxy & widgets + d->mConnectionModel.reset(new NmProxy); + d->mConnectionModel->setNmModel(d->mNmModel, NmModel::ConnectionType); + MenuView * connection_view = new MenuView{d->mConnectionModel.data()}; + connect(connection_view, &QAbstractItemView::activated, [this, d] (const QModelIndex & index) { + d->onActivated(index, d->mConnectionModel.data(), std::bind(&NmProxy::activateConnection, d->mConnectionModel.data(), std::placeholders::_1)); + close(); + }); + + d->mConnectionAction = new QWidgetAction{this}; + d->mConnectionAction->setDefaultWidget(connection_view); + connect(d->mConnectionModel.data(), &QAbstractItemModel::modelReset, [d] { d->onViewRowChange(d->mConnectionAction, d->mConnectionModel.data()); }); + connect(d->mConnectionModel.data(), &QAbstractItemModel::rowsInserted, [d] { d->onViewRowChange(d->mConnectionAction, d->mConnectionModel.data()); }); + connect(d->mConnectionModel.data(), &QAbstractItemModel::rowsRemoved, [d] { d->onViewRowChange(d->mConnectionAction, d->mConnectionModel.data()); }); + + addAction(tr("Active connection(s)"))->setEnabled(false); + addAction(d->mActiveAction); + addAction(tr("Wi-Fi network(s)"))->setEnabled(false); + addAction(d->mWirelessAction); + addAction(tr("Known connection(s)"))->setEnabled(false); + addAction(d->mConnectionAction); + + d->mMakeDirtyAction = new QAction{this}; + d->mDelaySizeRefreshTimer.setInterval(200); + d->mDelaySizeRefreshTimer.setSingleShot(true); + connect(&d->mDelaySizeRefreshTimer, &QTimer::timeout, [d] { d->forceSizeRefresh(); }); + + d->onViewRowChange(d->mActiveAction, d->mActiveModel.data()); + d->onViewRowChange(d->mWirelessAction, d->mWirelessModel.data()); + d->onViewRowChange(d->mConnectionAction, d->mConnectionModel.data()); +} + +WindowMenu::~WindowMenu() +{ +} diff --git a/src/windowmenu.h b/src/windowmenu.h new file mode 100644 index 0000000..2f4c973 --- /dev/null +++ b/src/windowmenu.h @@ -0,0 +1,46 @@ +/*COPYRIGHT_HEADER + +This file is a part of nm-tray. + +Copyright (c) + 2016~now Palo Kisa + +nm-tray is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +COPYRIGHT_HEADER*/ + +#if !defined(WINDOW_MENU_H) +#define WINDOW_MENU_H + +#include + +class WindowMenuPrivate; +class NmModel; + +class WindowMenu : public QMenu +{ + Q_OBJECT +public: + WindowMenu(NmModel * nmModel, QWidget * parent = nullptr); + ~WindowMenu(); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(WindowMenu); +}; + + +#endif //WINDOW_MENU_H + diff --git a/translations/nm-tray.ts b/translations/nm-tray.ts new file mode 100644 index 0000000..720cf7b --- /dev/null +++ b/translations/nm-tray.ts @@ -0,0 +1,253 @@ + + + + + ConnectionInfo + + + Connection information + + + + + NmList + + + Dialog + + + + + All information + + + + + Active connections + + + + + Available wireless + + + + + NmModel + + + root + + + + + active connection(s) + + + + + connection(s) + + + + + device(s) + + + + + wifi network(s) + + + + + unknown + hardware address + + + + + nm-tray - wireless password + + + + + Password is needed for connection to '%1': + + + + + General + Active connection information + + + + + Interface + Active connection information + + + + + Hardware Address + Active connection information + + + + + Driver + Active connection information + + + + + Speed + Active connection information + + + + + Kb/s + + + + + unknown + Speed + + + + + Security + Active connection information + + + + + IPv4 + Active connection information + + + + + IPv6 + Active connection information + + + + + IP Address + Active connection information + + + + + Subnet Mask + Active connection information + + + + + Default route + Active connection information + + + + + DNS(%1) + Active connection information + + + + + Tray + + + Connection lost + + + + + We have just lost the connection to %1 '%2'. + + + + + Connection established + + + + + We have just established the connection to %1 '%2'. + + + + + NetworkManager(nm-tray) + + + + + Enable Networking + + + + + Enable Wi-fi + + + + + Enable notifications + + + + + Connection information + + + + + Debug information + + + + + About + + + + + Quit + + + + + %1 about + + + + + <strong><a href="https://github.com/palinek/nm-tray">nm-tray</a></strong> is a simple Qt based frontend for <a href="https://wiki.gnome.org/Projects/NetworkManager">NetworkManager</a>.<br/><br/>Version: %1 + + + + + nm-tray info + + + + + WindowMenu + + + Active connection(s) + + + + + Wi-Fi network(s) + + + + diff --git a/translations/nm-tray_de.ts b/translations/nm-tray_de.ts new file mode 100644 index 0000000..ca57e7b --- /dev/null +++ b/translations/nm-tray_de.ts @@ -0,0 +1,260 @@ + + + + + ConnectionInfo + + + Connection information + Verbindungsinformationen + + + + NmList + + + Dialog + Did not translate as "Dialog" seems to get replaced with string "nm-tray info" from context "Tray". + Dialog + + + + All information + Verfügbare Informationen + + + + Active connections + Aktive Verbindungen + + + + Available wireless + Verfügbare drahtlose Verbindungen + + + + NmModel + + + root + Translated as "network" hence not literally at all. Not sure what's best here but this seems a bit more reasonable to me. + Netzwerk + + + + active connection(s) + Aktive Verbindungen + + + + connection(s) + Verbindungen + + + + device(s) + Geräte + + + + wifi network(s) + Drahtlose Netzwerke + + + + unknown + hardware address + unbekannt + + + + nm-tray - wireless password + + + + + Password is needed for connection to '%1': + + + + + General + Active connection information + Allgemeines + + + + Interface + Active connection information + Schnittstelle + + + + Hardware Address + Active connection information + Hardware-Adresse + + + + Driver + Active connection information + Treiber + + + + Speed + Active connection information + Übertragungsrate + + + + Kb/s + Kb/s + + + + unknown + Speed + unbekannt + + + + Security + Active connection information + Sicherheit + + + + IPv4 + Active connection information + + + + + IPv6 + Active connection information + + + + + IP Address + Active connection information + + + + + Subnet Mask + Active connection information + + + + + Default route + Active connection information + + + + + DNS(%1) + Active connection information + + + + + Tray + + + Connection lost + + + + + We have just lost the connection to %1 '%2'. + + + + + Connection established + + + + + We have just established the connection to %1 '%2'. + + + + + NetworkManager(nm-tray) + + + + + Enable Networking + Netzwerk aktivieren + + + + Enable Wi-fi + Drahtlose Verbindungen aktivieren + + + + Enable notifications + + + + + Connection information + Verbindungsinformationen + + + + Debug information + + + + + About + Über + + + + Quit + Beenden + + + + %1 about + %1 über + + + + <strong><a href="https://github.com/palinek/nm-tray">nm-tray</a></strong> is a simple Qt based frontend for <a href="https://wiki.gnome.org/Projects/NetworkManager">NetworkManager</a>.<br/><br/>Version: %1 + + + + This is the about nm-tray! + Pimped this a little bit. Hope that's okay. + nm-tray ist eine auf Qt basierende Nutzeroberfläche für den NetworkManager zur Ausführung in der Leiste von Desktop-Umgebungen. + + + + nm-tray info + nm-tray Informationen + + + + WindowMenu + + + Active connection(s) + + + + + Wi-Fi network(s) + + + + diff --git a/translations/nm-tray_pl.ts b/translations/nm-tray_pl.ts new file mode 100644 index 0000000..c638ebd --- /dev/null +++ b/translations/nm-tray_pl.ts @@ -0,0 +1,253 @@ + + + + + ConnectionInfo + + + Connection information + Informacje o połączeniu + + + + NmList + + + Dialog + Okno dialogowe + + + + All information + Wszystkie informacje + + + + Active connections + Aktywne połączenia + + + + Available wireless + Dostępne połączenia bezprzewodowe + + + + NmModel + + + root + root + + + + active connection(s) + aktywne połączenia + + + + connection(s) + połączenia + + + + device(s) + urządzenia + + + + wifi network(s) + sieci bezprzewodowe + + + + unknown + hardware address + nieznany + + + + nm-tray - wireless password + nm-tray - hasło połączenia bezprzewodowego + + + + Password is needed for connection to '%1': + Potrzebne jest hasło, aby połączyć się z '%1': + + + + General + Active connection information + Ogólne + + + + Interface + Active connection information + Interfejs + + + + Hardware Address + Active connection information + Adres urządzenia + + + + Driver + Active connection information + Informacje o aktywnym połączeniu + + + + Speed + Active connection information + Prędkość + + + + Kb/s + Kb/s + + + + unknown + Speed + nieznana + + + + Security + Active connection information + Bezpieczeństwo + + + + IPv4 + Active connection information + IPv4 + + + + IPv6 + Active connection information + IPv6 + + + + IP Address + Active connection information + Adres IP + + + + Subnet Mask + Active connection information + Maska podsieci + + + + Default route + Active connection information + Brama domyślna + + + + DNS(%1) + Active connection information + DNS(%1) + + + + Tray + + + Connection lost + Utracono połączenie + + + + We have just lost the connection to %1 '%2'. + Utracono połączenie z %1 '%2'. + + + + Connection established + Ustanowiono połączenie + + + + We have just established the connection to %1 '%2'. + Ustanowiono połączenie z %1 '%2'. + + + + NetworkManager(nm-tray) + NetworkManager(nm-tray) + + + + Enable Networking + Włącz sieci + + + + Enable Wi-fi + Włącz Wi-Fi + + + + Enable notifications + Włącz powiadomienia + + + + Connection information + Informacje o połączeniu + + + + Debug information + Informacje debugowania + + + + About + O programie + + + + Quit + Zakończ + + + + %1 about + o %1 + + + + <strong><a href="https://github.com/palinek/nm-tray">nm-tray</a></strong> is a simple Qt based frontend for <a href="https://wiki.gnome.org/Projects/NetworkManager">NetworkManager</a>.<br/><br/>Version: %1 + <strong><a href="https://github.com/palinek/nm-tray">nm-tray</a></strong> jest prostą, napisaną w Qt nakładką dla <a href="https://wiki.gnome.org/Projects/NetworkManager">NetworkManagera</a>.<br/><br/>Wersja: %1 + + + + nm-tray info + Informacje o nm-tray + + + + WindowMenu + + + Active connection(s) + Aktywne połączenia + + + + Wi-Fi network(s) + Sieci Wi-Fi + + + diff --git a/translations/nm-tray_sk.ts b/translations/nm-tray_sk.ts new file mode 100644 index 0000000..173c920 --- /dev/null +++ b/translations/nm-tray_sk.ts @@ -0,0 +1,254 @@ + + + + + ConnectionInfo + + + Connection information + Informácie o spojení + + + + NmList + + + Dialog + + + + + All information + Všetky informácie + + + + Active connections + Aktívne spojenia + + + + Available wireless + Dostupné bezdôtové + + + + NmModel + + + root + + + + + active connection(s) + aktívne spojenie/a + + + + connection(s) + spojenie/a + + + + device(s) + zariadenie/a + + + + wifi network(s) + wifi sieť/siete + + + + unknown + hardware address + neznáme + + + + nm-tray - wireless password + + + + + Password is needed for connection to '%1': + + + + + General + Active connection information + Základné + + + + Interface + Active connection information + Rozhranie + + + + Hardware Address + Active connection information + Hardvérová adresa + + + + Driver + Active connection information + Ovládač + + + + Speed + Active connection information + Rýchlosť + + + + Kb/s + + + + + unknown + Speed + neznáme + + + + Security + Active connection information + Bezpečnosť + + + + IPv4 + Active connection information + + + + + IPv6 + Active connection information + + + + + IP Address + Active connection information + + + + + Subnet Mask + Active connection information + + + + + Default route + Active connection information + + + + + DNS(%1) + Active connection information + + + + + Tray + + + Connection lost + + + + + We have just lost the connection to %1 '%2'. + + + + + Connection established + + + + + We have just established the connection to %1 '%2'. + + + + + NetworkManager(nm-tray) + + + + + Enable Networking + don't know how to correctly translate + Zapnúť sieť + + + + Enable Wi-fi + Zapnúť Wi-fi + + + + Enable notifications + + + + + Connection information + Informácie o spojení + + + + Debug information + + + + + About + O ... + + + + Quit + Vypnúť + + + + %1 about + O %1 + + + + <strong><a href="https://github.com/palinek/nm-tray">nm-tray</a></strong> is a simple Qt based frontend for <a href="https://wiki.gnome.org/Projects/NetworkManager">NetworkManager</a>.<br/><br/>Version: %1 + + + + + nm-tray info + nm-tray info + + + + WindowMenu + + + Active connection(s) + + + + + Wi-Fi network(s) + + + +