├── .gitignore ├── .pylintrc ├── COMPILE.md ├── HISTORY.md ├── LICENSE ├── Makefile ├── README.md ├── bin ├── sagnc-service-restart └── smart-agnc ├── run.sh ├── setup.cfg ├── setup.py ├── share ├── applications │ └── smart-agnc.desktop ├── icons │ ├── HighContrast │ │ └── 24x24 │ │ │ └── apps │ │ │ ├── smart-agnc-disabled.svg │ │ │ └── smart-agnc-enabled.svg │ ├── hicolor │ │ ├── 128x128 │ │ │ └── apps │ │ │ │ └── smart-agnc.svg │ │ └── 24x24 │ │ │ └── apps │ │ │ ├── smart-agnc-disabled.svg │ │ │ └── smart-agnc-enabled.svg │ ├── ubuntu-mono-dark │ │ └── apps │ │ │ └── 24 │ │ │ ├── smart-agnc-disabled.svg │ │ │ └── smart-agnc-enabled.svg │ └── ubuntu-mono-light │ │ └── apps │ │ └── 24 │ │ ├── smart-agnc-disabled.svg │ │ └── smart-agnc-enabled.svg ├── locale │ ├── en_US │ │ └── LC_MESSAGES │ │ │ └── smart_agnc.po │ ├── es_AR │ │ └── LC_MESSAGES │ │ │ └── smart_agnc.po │ └── pt_BR │ │ └── LC_MESSAGES │ │ └── smart_agnc.po └── polkit-1 │ └── actions │ └── org.smart-agnc.sagnc-service-restart.policy ├── src ├── c-bind │ ├── c-bind.c │ ├── commands.c │ └── commands.h └── smart_agnc │ ├── __init__.py │ ├── __main__.py │ ├── about_win.py │ ├── agn_binder.py │ ├── agn_monitor.py │ ├── agn_notifier.py │ ├── config.py │ ├── conn_info_win.py │ ├── menu.py │ ├── new_password_win.py │ ├── settings_win.py │ ├── tray_icon.py │ ├── update.py │ ├── utils.py │ └── windows.py └── stdeb.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | *.mo 2 | *.png 3 | *.pyc 4 | bin/sagnc-bind 5 | build 6 | deb_dist 7 | dist 8 | src/dist 9 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [VARIABLES] 2 | dummy-variables-rgx=unused 3 | additional-builtins=_ 4 | -------------------------------------------------------------------------------- /COMPILE.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | Have the AT&T Global Network Client working and install the appropriate 4 | libraries to build the program. 5 | 6 | ### In Ubuntu 7 | 8 | sudo apt-get install gettext librsvg2-bin gcc libc6-dev-x32 python-iniparse 9 | 10 | ### In CentOS 11 | 12 | sudo yum install gettext librsvg2 gcc glibc-devel.i686 python-iniparse 13 | 14 | ## Compile & run 15 | 16 | 1. Extract into any folder. 17 | 2. Go to the extracted folder and compile with `make`. 18 | 3. Execute `run.sh`. 19 | 4. (optional) Add `run.sh` to the "Startup Applications" list. 20 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## v0.1.4 2 | 3 | ## v0.1.3 (2015-02-18) 4 | 5 | * Added help and close buttons to about window 6 | * Added alert messages translations 7 | * Multi-threading for AGNC communications 8 | * Multi-threading for checking updates 9 | * Using iniparse library to preserve comments in configuration file 10 | * Added pt_BR translation 11 | 12 | ## v0.1.2 (2015-01-28) 13 | 14 | * Restarting AGNC daemon when DAEMON_DEAD state is received 15 | * Restarting AGNC daemon in background process 16 | * Windows start centered 17 | * Logs now rotate depending on their file size 18 | 19 | ## v0.1.1 (2015-01-26) 20 | 21 | * Restarting AGNC daemon when necessary improves stability 22 | * Connection information window redesign 23 | * Added an About dialog 24 | * Notify OS when finished loading 25 | * Checks for new versions 26 | 27 | ## v0.1.0 (2015-01-22) 28 | 29 | * All main features implemented, so bumping version to 0.1.0 30 | * Added proxy settings 31 | * Added logs to improve feedback quality 32 | 33 | ## v0.0.8 (2015-01-20) 34 | 35 | * Packages for everybody! 36 | * fixed python 2.4 module compatibility 37 | * improved agnc subprocess' stability 38 | 39 | ## v0.0.7 (2015-01-17) 40 | 41 | * Turned the project into a python module 42 | * Alert messages with better syntax 43 | * Automatic AGNC services restart when they misbehave 44 | * Optional exit button 45 | 46 | ## v0.0.6 (2015-01-12) 47 | 48 | * External scripts configuration supports `~` in the path 49 | * Automatic and manual change password dialog 50 | * AgnBinder events don't overlap (fixes IP: None bug) 51 | 52 | ## v0.0.5 (2015-01-10) 53 | 54 | * Makefile is now compatible with RedHat and Ubuntu's libraries 55 | * External script's output now shows up through notify 56 | * Timeout for the connecting process, defined in config (default: 40 secs) 57 | * Multiple icons sets! 58 | 59 | ## v0.0.4 (2015-01-08) 60 | 61 | * Different icons for different color schemes 62 | * Using python's own logging class 63 | * Translation into es_AR now available 64 | * Encoding password in configuration file 65 | 66 | ## v0.0.3 (2015-01-07) 67 | 68 | * Formatted values inside the "VPN Connection Information" window 69 | * Shows VPN's assigned IP inside popup menu 70 | * Configuration file supports external edits 71 | * Ability to execute external scripts on certain conditions 72 | 73 | ## v0.0.2 (2015-01-06) 74 | 75 | * Added a tray icon! 76 | * Improved installation instructions 77 | * Improved agnc python interface stability 78 | * Using GNU GPL v2 License 79 | 80 | ## v0.0.1 (2015-01-05) 81 | 82 | * Window to configure your VPN credentials 83 | * Connect and disconnect from VPN 84 | * Automatic VPN reconnection 85 | * Tray icon supports Ubuntu's appindicator and GTK's StatusIcon 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TRANSLATIONS_IN = $(wildcard share/locale/*/LC_MESSAGES/*.po) 2 | TRANSLATIONS = $(patsubst %.po,%.mo,$(TRANSLATIONS_IN)) 3 | PYC_FILES = $(wildcard src/smart_agnc/*.pyc) 4 | SVG_FILES = $(wildcard share/icons/*/*/*/*.svg) 5 | PNG_FILES = $(patsubst %.svg,%.png,$(SVG_FILES)) 6 | 7 | all: $(TRANSLATIONS) $(PNG_FILES) 8 | gcc -m32 -s -ggdb -std=gnu99 -I/opt/agns/include -pthread -L/opt/agns/lib \ 9 | -o bin/sagnc-bind src/c-bind/*.c -l:libagnc.so.1.0.0 -l:libagnLogc.so.1.0.0 10 | 11 | clean: 12 | rm -rf bin/sagnc-bind deb_dist dist MANIFEST smart-agnc-*.tar.gz \ 13 | $(TRANSLATIONS) $(PYC_FILES) $(PNG_FILES) 14 | 15 | %.png: %.svg 16 | rsvg-convert -o $@ $^ 17 | 18 | %.mo: %.po 19 | msgfmt -c -o $@ $^ 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart AT&T Global Network Client 2 | 3 | Smart AT&T Global Network Client improves upon the AT&T Global Network Client by 4 | * showing the status of the VPN connection with just a glance at the tray icon 5 | * reconnecting the VPN connection when it drops 6 | * providing quick connection through the tray icon 7 | * extending it your way by adding custom scripts. 8 | 9 | ## Prerequisites 10 | 11 | Have the AT&T Global Network Client installed. 12 | 13 | ## Installation 14 | 15 | Download and install the package you need from the 16 | [releases](//github.com/knoid/smart-agnc/releases) page. 17 | 18 | ## Hidden features 19 | 20 | Some of this features require modifying the configuration file in 21 | `~/.smart-agnc/config`. This file uses `.ini` format with sections and keys. 22 | 23 | ### Execute a script when a certain connection state is met 24 | 25 | Add a new section called `scripts` and set there any numeric status code as the 26 | key and a file path as the value. For example, to execute `my_script.sh` when 27 | the connection is established you would need: 28 | 29 | [vpn] 30 | ... 31 | 32 | [scripts] 33 | 400 = /home/my_user/my_script.sh 34 | 35 | You can find every connection state in 36 | [agn_binder.py](src/smart_agnc/agn_binder.py). 37 | 38 | ### Customize maximum timeout for connection attempt 39 | 40 | The maximum time out for establishing a VPN connection by the client can be 41 | defined in the configuration file. Add `timeout` key followed by timeout, in 42 | seconds, in the `vpn` section. 43 | 44 | If a connection is not established within the max number of seconds timeout, 45 | a disconnect event is issued to restart the connection process. 46 | 47 | ### Exit button 48 | 49 | Adding the option `--exit-button` when starting the app will add an exit button 50 | to the context menu. 51 | 52 | ## Disclaimer 53 | 54 | By using this application, you agree to share the version you are running and 55 | your country of residence; but don't worry, we only collect this information to 56 | know the real level of adoption this tool has and then be able to improve your 57 | experience accordingly. 58 | 59 | I'm not a C programmer, so collaborations will be greatly appreciated. 60 | -------------------------------------------------------------------------------- /bin/sagnc-service-restart: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /etc/init.d/agnclientd stop 4 | /etc/init.d/agnLogd stop 5 | sleep 2 6 | /etc/init.d/agnLogd start 7 | /etc/init.d/agnclientd start 8 | -------------------------------------------------------------------------------- /bin/smart-agnc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python -m smart_agnc.__main__ $* 4 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "`dirname \"$0\"`"/src 4 | python -m smart_agnc.__main__ $* 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | requires = notify-python 3 | python-iniparse 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.command.bdist_rpm import bdist_rpm 2 | from distutils.command.install_data import install_data 3 | from distutils.command.sdist import sdist 4 | from distutils.core import setup 5 | import os 6 | import platform 7 | 8 | name = 'smart-agnc' 9 | package = name.replace('-', '_') 10 | 11 | 12 | class PreSourceBuild(sdist): 13 | def run(self): 14 | manifest_files = get_files('bin') + get_files('share') + \ 15 | get_files(os.path.join('src', package)) + \ 16 | [('', ['setup.py'])] 17 | with open('MANIFEST', 'w') as f: 18 | for unused_path, files in manifest_files: 19 | for fpath in files: 20 | if not (fpath.endswith('.svg') or fpath.endswith('.po')): 21 | f.write(fpath + '\n') 22 | 23 | sdist.run(self) 24 | 25 | 26 | class PreInstall(install_data): 27 | 28 | data_files = [] 29 | 30 | def run(self): 31 | if os.path.isdir('debian'): 32 | with open('debian/control', 'r') as f: 33 | lines = f.readlines() 34 | with open('debian/control', 'w') as f: 35 | for line in lines: 36 | if line.startswith('Architecture'): 37 | if '64' in platform.processor(): 38 | line = line.replace('all', 'amd64') 39 | else: 40 | line = line.replace('all', 'i386') 41 | f.write(line) 42 | 43 | install_data.run(self) 44 | 45 | 46 | class PreBdistRpm(bdist_rpm): 47 | def run(self): 48 | self.post_uninstall = self.post_install = 'post-install.sh' 49 | with open(self.post_install, 'w') as pi: 50 | for dirname in os.listdir(os.path.join('share', 'icons')): 51 | pi.write('gtk-update-icon-cache -q -t -f %s\n' % 52 | os.path.join('/usr', 'share', 'icons', dirname)) 53 | 54 | bdist_rpm.run(self) 55 | 56 | 57 | def get_files(path, prefix=''): 58 | all_files = [] 59 | for root, unused_subdirs, files in os.walk(path): 60 | dir_files = [] 61 | for filename in files: 62 | dir_files.append(os.path.join(root, filename)) 63 | if len(dir_files) > 0: 64 | all_files.append((os.path.join(prefix, root), dir_files)) 65 | return all_files 66 | 67 | setup( 68 | name=name, 69 | version='0.1.3', 70 | description='Smart AT&T Global Network Client', 71 | #long_description=readme, 72 | author='Ariel Barabas', 73 | author_email='ariel.baras@gmail.com', 74 | url='https://github.com/knoid/smart-agnc', 75 | download_url='https://github.com/knoid/smart-agnc/releases', 76 | packages=[package], 77 | scripts=['bin/smart-agnc', 78 | 'bin/sagnc-service-restart', 'bin/sagnc-bind'], 79 | license='GNUv2', 80 | cmdclass={ 81 | 'sdist': PreSourceBuild, 82 | 'install_data': PreInstall, 83 | 'bdist_rpm': PreBdistRpm 84 | }, 85 | data_files=get_files('share', '/usr'), 86 | package_dir={package: os.path.join('src', package)} 87 | ) 88 | -------------------------------------------------------------------------------- /share/applications/smart-agnc.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=2.0 3 | Encoding=UTF-8 4 | Name=Smart AT&T Global Network Client 5 | Comment=Smarter remote access VPN client 6 | Exec=/usr/bin/smart-agnc --exit-button 7 | Icon=/usr/share/icons/hicolor/128x128/apps/smart-agnc.png 8 | Terminal=false 9 | X-MultipleArgs=false 10 | Type=Application 11 | Categories=Application;Network; 12 | StartupNotify=true 13 | -------------------------------------------------------------------------------- /share/icons/HighContrast/24x24/apps/smart-agnc-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 31 | 40 | 44 | 48 | 52 | 53 | 62 | 66 | 70 | 74 | 75 | 76 | 80 | 84 | 87 | 91 | 95 | 96 | 99 | 103 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /share/icons/HighContrast/24x24/apps/smart-agnc-enabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 31 | 40 | 44 | 48 | 52 | 53 | 62 | 66 | 70 | 74 | 75 | 76 | 80 | 84 | 85 | -------------------------------------------------------------------------------- /share/icons/hicolor/128x128/apps/smart-agnc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 127 | 128 | -------------------------------------------------------------------------------- /share/icons/hicolor/24x24/apps/smart-agnc-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 31 | 40 | 44 | 48 | 52 | 53 | 62 | 66 | 70 | 74 | 75 | 76 | 80 | 84 | 85 | -------------------------------------------------------------------------------- /share/icons/hicolor/24x24/apps/smart-agnc-enabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 31 | 40 | 44 | 48 | 52 | 53 | 62 | 66 | 70 | 74 | 75 | 76 | 80 | 84 | 85 | -------------------------------------------------------------------------------- /share/icons/ubuntu-mono-dark/apps/24/smart-agnc-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /share/icons/ubuntu-mono-dark/apps/24/smart-agnc-enabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | AT&T 26 | 27 | 28 | 29 | 33 | 34 | -------------------------------------------------------------------------------- /share/icons/ubuntu-mono-light/apps/24/smart-agnc-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | AT&T 28 | 29 | 30 | 31 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /share/icons/ubuntu-mono-light/apps/24/smart-agnc-enabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | AT&T 17 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | AT&T 28 | 29 | 30 | 31 | 35 | 36 | -------------------------------------------------------------------------------- /share/locale/en_US/LC_MESSAGES/smart_agnc.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: smart-agnc\n" 4 | "POT-Creation-Date: 2015-02-02 09:23-0300\n" 5 | "PO-Revision-Date: 2015-02-02 09:24-0300\n" 6 | "Last-Translator: Ariel Barabas \n" 7 | "Language-Team: \n" 8 | "Language: en_US\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.5.4\n" 13 | "X-Poedit-KeywordsList: _;gettext;gettext_noop\n" 14 | "X-Poedit-Basepath: ../../../..\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Poedit-SearchPath-0: src/smart_agnc\n" 17 | 18 | #: src/smart_agnc/settings_win.py:24 19 | msgid "Configuration" 20 | msgstr "Configuration" 21 | 22 | #: src/smart_agnc/settings_win.py:26 23 | msgid "Account" 24 | msgstr "Account" 25 | 26 | #: src/smart_agnc/settings_win.py:29 27 | msgid "Username" 28 | msgstr "Username" 29 | 30 | #: src/smart_agnc/settings_win.py:32 src/smart_agnc/settings_win.py:91 31 | msgid "Password" 32 | msgstr "Password" 33 | 34 | #: src/smart_agnc/settings_win.py:36 src/smart_agnc/settings_win.py:95 35 | msgid "Show password" 36 | msgstr "Show password" 37 | 38 | #: src/smart_agnc/settings_win.py:41 39 | msgid "Enable proxy settings" 40 | msgstr "Enable proxy settings" 41 | 42 | #: src/smart_agnc/settings_win.py:52 43 | msgid "Change Password" 44 | msgstr "Change Password" 45 | 46 | #: src/smart_agnc/settings_win.py:53 47 | msgid "You should disconnect first" 48 | msgstr "You should disconnect first" 49 | 50 | #: src/smart_agnc/settings_win.py:85 51 | msgid "Server" 52 | msgstr "Server" 53 | 54 | #: src/smart_agnc/settings_win.py:88 55 | msgid "User" 56 | msgstr "User" 57 | 58 | #: src/smart_agnc/conn_info_win.py:17 59 | msgid "Connection Information" 60 | msgstr "Connection Information" 61 | 62 | #: src/smart_agnc/conn_info_win.py:24 src/smart_agnc/conn_info_win.py:28 63 | #: src/smart_agnc/agn_notifier.py:132 64 | msgid "Not available." 65 | msgstr "Not available." 66 | 67 | #: src/smart_agnc/conn_info_win.py:30 68 | msgid "Yes." 69 | msgstr "Yes." 70 | 71 | #: src/smart_agnc/conn_info_win.py:30 72 | msgid "No." 73 | msgstr "No." 74 | 75 | #: src/smart_agnc/conn_info_win.py:54 76 | msgid "BytesReceivedAtStartOfConnection" 77 | msgstr "Bytes received at start of connection" 78 | 79 | #: src/smart_agnc/conn_info_win.py:55 80 | msgid "BytesSentAtStartOfConnection" 81 | msgstr "Bytes sent at start of connection" 82 | 83 | #: src/smart_agnc/conn_info_win.py:56 84 | msgid "ConnectType" 85 | msgstr "Connect type" 86 | 87 | #: src/smart_agnc/conn_info_win.py:57 88 | msgid "InternetIPAddress" 89 | msgstr "Internet IP address" 90 | 91 | #: src/smart_agnc/conn_info_win.py:58 92 | msgid "InternetGatewayIPAddress" 93 | msgstr "Internet gateway IP address" 94 | 95 | #: src/smart_agnc/conn_info_win.py:59 96 | msgid "InternetAdapter" 97 | msgstr "Internet adapter" 98 | 99 | #: src/smart_agnc/conn_info_win.py:60 100 | msgid "InternetMACAddress" 101 | msgstr "Internet MAC address" 102 | 103 | #: src/smart_agnc/conn_info_win.py:61 104 | msgid "StatusCode" 105 | msgstr "Status code" 106 | 107 | #: src/smart_agnc/conn_info_win.py:62 108 | msgid "StatusText" 109 | msgstr "Status text" 110 | 111 | #: src/smart_agnc/conn_info_win.py:63 112 | msgid "TimeCompleted" 113 | msgstr "Completed time" 114 | 115 | #: src/smart_agnc/conn_info_win.py:64 116 | msgid "TimeConnected" 117 | msgstr "Connected time" 118 | 119 | #: src/smart_agnc/conn_info_win.py:65 120 | msgid "TimeStarted" 121 | msgstr "Started time" 122 | 123 | #: src/smart_agnc/conn_info_win.py:66 124 | msgid "VPNCompressionActive" 125 | msgstr "Is VPN compression active?" 126 | 127 | #: src/smart_agnc/conn_info_win.py:67 128 | msgid "VPNIPAddress" 129 | msgstr "VPN IP address" 130 | 131 | #: src/smart_agnc/conn_info_win.py:68 132 | msgid "VPNServerIPAddress" 133 | msgstr "VPN server IP address" 134 | 135 | #: src/smart_agnc/conn_info_win.py:69 136 | msgid "VPNTunnelAdapter" 137 | msgstr "VPN tunnel adapter" 138 | 139 | #: src/smart_agnc/new_password_win.py:18 140 | msgid "Smart AGNC: Set new password" 141 | msgstr "Smart AGNC: Set new password" 142 | 143 | #: src/smart_agnc/new_password_win.py:20 144 | msgid "New password:" 145 | msgstr "New password:" 146 | 147 | #: src/smart_agnc/new_password_win.py:24 148 | msgid "Repeat password:" 149 | msgstr "Repeat password:" 150 | 151 | #: src/smart_agnc/new_password_win.py:46 152 | #, python-format 153 | msgid "" 154 | "The password `%s` has been generated for you. Would you like to use it? Be " 155 | "sure to copy it first." 156 | msgstr "" 157 | "The password `%s` has been generated for you. Would you like to use it? Be " 158 | "sure to copy it first." 159 | 160 | #: src/smart_agnc/new_password_win.py:48 161 | msgid "Smart AGNC: Time for a new password" 162 | msgstr "Smart AGNC: Time for a new password" 163 | 164 | #: src/smart_agnc/new_password_win.py:67 165 | msgid "Password must contain numbers, uppercase and lowercase letters." 166 | msgstr "Password must contain numbers, uppercase and lowercase letters." 167 | 168 | #: src/smart_agnc/new_password_win.py:70 169 | msgid "Passwords don't match." 170 | msgstr "Passwords don't match." 171 | 172 | #: src/smart_agnc/new_password_win.py:74 173 | msgid "Error" 174 | msgstr "Error" 175 | 176 | #: src/smart_agnc/about_win.py:24 177 | msgid "About Smart AGNC" 178 | msgstr "About Smart AGNC" 179 | 180 | #: src/smart_agnc/about_win.py:33 181 | #, python-format 182 | msgid "" 183 | "Please go to %s\n" 184 | "and click on Like if you've found this app to be useful." 185 | msgstr "" 186 | "Please go to %s\n" 187 | "and click on Like if you've found this app to be useful." 188 | 189 | #: src/smart_agnc/about_win.py:38 190 | msgid "Author" 191 | msgstr "Author" 192 | 193 | #: src/smart_agnc/about_win.py:39 194 | msgid "Version" 195 | msgstr "Version" 196 | 197 | #: src/smart_agnc/about_win.py:44 198 | msgid "Help" 199 | msgstr "Help" 200 | 201 | #: src/smart_agnc/about_win.py:48 202 | msgid "Close" 203 | msgstr "Close" 204 | 205 | #: src/smart_agnc/agn_monitor.py:84 src/smart_agnc/agn_notifier.py:121 206 | msgid "AGNC Services should be restarted." 207 | msgstr "AGNC Services should be restarted." 208 | 209 | #: src/smart_agnc/menu.py:38 210 | msgid "Restart AGNC Services" 211 | msgstr "Restart AGNC Services" 212 | 213 | #: src/smart_agnc/menu.py:48 214 | msgid "Keep alive" 215 | msgstr "Keep alive" 216 | 217 | #: src/smart_agnc/menu.py:58 218 | msgid "VPN Connection Information" 219 | msgstr "VPN Connection Information" 220 | 221 | #: src/smart_agnc/menu.py:63 222 | msgid "Edit Account settings..." 223 | msgstr "Edit Account settings..." 224 | 225 | #: src/smart_agnc/menu.py:73 226 | msgid "Download new version" 227 | msgstr "Download new version" 228 | 229 | #: src/smart_agnc/menu.py:78 230 | msgid "About" 231 | msgstr "About" 232 | 233 | #: src/smart_agnc/menu.py:84 234 | msgid "Quit" 235 | msgstr "Quit" 236 | 237 | #: src/smart_agnc/agn_notifier.py:89 238 | msgid "There is a new version available!" 239 | msgstr "There is a new version available!" 240 | 241 | #: src/smart_agnc/agn_notifier.py:127 242 | msgid "Connect" 243 | msgstr "Connect" 244 | 245 | #: src/smart_agnc/agn_notifier.py:129 246 | msgid "Disconnect" 247 | msgstr "Disconnect" 248 | 249 | #: src/smart_agnc/agn_notifier.py:138 250 | #, python-format 251 | msgid "IP: %s" 252 | msgstr "IP: %s" 253 | 254 | #: src/smart_agnc/agn_notifier.py:148 255 | msgid "It is time to change your password!" 256 | msgstr "It is time to change your password!" 257 | 258 | #: src/smart_agnc/agn_notifier.py:154 259 | msgid "Invalid credentials" 260 | msgstr "Invalid credentials" 261 | 262 | #: src/smart_agnc/agn_notifier.py:160 263 | msgid "Unknown error!" 264 | msgstr "Unknown error!" 265 | 266 | #: src/smart_agnc/agn_notifier.py:215 267 | msgid "VPN Disconnected" 268 | msgstr "VPN Disconnected" 269 | 270 | #: src/smart_agnc/agn_notifier.py:219 271 | msgid "Complete credentials to connect" 272 | msgstr "Complete credentials to connect" 273 | -------------------------------------------------------------------------------- /share/locale/es_AR/LC_MESSAGES/smart_agnc.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: smart-agnc\n" 4 | "POT-Creation-Date: 2015-02-02 09:23-0300\n" 5 | "PO-Revision-Date: 2015-02-02 09:24-0300\n" 6 | "Last-Translator: Ariel Barabas \n" 7 | "Language-Team: \n" 8 | "Language: es_AR\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.5.4\n" 13 | "X-Poedit-KeywordsList: _;gettext;gettext_noop\n" 14 | "X-Poedit-Basepath: ../../../..\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Poedit-SearchPath-0: src/smart_agnc\n" 17 | 18 | #: src/smart_agnc/settings_win.py:24 19 | msgid "Configuration" 20 | msgstr "Configuración" 21 | 22 | #: src/smart_agnc/settings_win.py:26 23 | msgid "Account" 24 | msgstr "Cuenta" 25 | 26 | #: src/smart_agnc/settings_win.py:29 27 | msgid "Username" 28 | msgstr "Usuario" 29 | 30 | #: src/smart_agnc/settings_win.py:32 src/smart_agnc/settings_win.py:91 31 | msgid "Password" 32 | msgstr "Contraseña" 33 | 34 | #: src/smart_agnc/settings_win.py:36 src/smart_agnc/settings_win.py:95 35 | msgid "Show password" 36 | msgstr "Mostrar contraseña" 37 | 38 | #: src/smart_agnc/settings_win.py:41 39 | msgid "Enable proxy settings" 40 | msgstr "Habilitar configuración del proxy" 41 | 42 | #: src/smart_agnc/settings_win.py:52 43 | msgid "Change Password" 44 | msgstr "Cambiar Contraseña" 45 | 46 | #: src/smart_agnc/settings_win.py:53 47 | msgid "You should disconnect first" 48 | msgstr "Primero debes desconectarte" 49 | 50 | #: src/smart_agnc/settings_win.py:85 51 | msgid "Server" 52 | msgstr "Servidor" 53 | 54 | #: src/smart_agnc/settings_win.py:88 55 | msgid "User" 56 | msgstr "Usuario" 57 | 58 | #: src/smart_agnc/conn_info_win.py:17 59 | msgid "Connection Information" 60 | msgstr "Información de Conexión" 61 | 62 | #: src/smart_agnc/conn_info_win.py:24 src/smart_agnc/conn_info_win.py:28 63 | #: src/smart_agnc/agn_notifier.py:132 64 | msgid "Not available." 65 | msgstr "No disponible." 66 | 67 | #: src/smart_agnc/conn_info_win.py:30 68 | msgid "Yes." 69 | msgstr "Si." 70 | 71 | #: src/smart_agnc/conn_info_win.py:30 72 | msgid "No." 73 | msgstr "No." 74 | 75 | #: src/smart_agnc/conn_info_win.py:54 76 | msgid "BytesReceivedAtStartOfConnection" 77 | msgstr "Bytes recibidos al comienzo de la conexión" 78 | 79 | #: src/smart_agnc/conn_info_win.py:55 80 | msgid "BytesSentAtStartOfConnection" 81 | msgstr "Bytes enviados al comienzo de la conexión" 82 | 83 | #: src/smart_agnc/conn_info_win.py:56 84 | msgid "ConnectType" 85 | msgstr "Tipo de conexión" 86 | 87 | #: src/smart_agnc/conn_info_win.py:57 88 | msgid "InternetIPAddress" 89 | msgstr "Dirección IP de Internet" 90 | 91 | #: src/smart_agnc/conn_info_win.py:58 92 | msgid "InternetGatewayIPAddress" 93 | msgstr "Dirección IP del gateway" 94 | 95 | #: src/smart_agnc/conn_info_win.py:59 96 | msgid "InternetAdapter" 97 | msgstr "Adaptador de Internet" 98 | 99 | #: src/smart_agnc/conn_info_win.py:60 100 | msgid "InternetMACAddress" 101 | msgstr "Dirección MAC de Internet" 102 | 103 | #: src/smart_agnc/conn_info_win.py:61 104 | msgid "StatusCode" 105 | msgstr "Código de estado" 106 | 107 | #: src/smart_agnc/conn_info_win.py:62 108 | msgid "StatusText" 109 | msgstr "Estado" 110 | 111 | #: src/smart_agnc/conn_info_win.py:63 112 | msgid "TimeCompleted" 113 | msgstr "Tiempo final" 114 | 115 | #: src/smart_agnc/conn_info_win.py:64 116 | msgid "TimeConnected" 117 | msgstr "Tiempo al conectar" 118 | 119 | #: src/smart_agnc/conn_info_win.py:65 120 | msgid "TimeStarted" 121 | msgstr "Tiempo de comienzo" 122 | 123 | #: src/smart_agnc/conn_info_win.py:66 124 | msgid "VPNCompressionActive" 125 | msgstr "Compresión de datos en VPN activada" 126 | 127 | #: src/smart_agnc/conn_info_win.py:67 128 | msgid "VPNIPAddress" 129 | msgstr "Dirección IP en la VPN" 130 | 131 | #: src/smart_agnc/conn_info_win.py:68 132 | msgid "VPNServerIPAddress" 133 | msgstr "Dirección IP del servidor de VPN" 134 | 135 | #: src/smart_agnc/conn_info_win.py:69 136 | msgid "VPNTunnelAdapter" 137 | msgstr "Adaptador del túnel de VPN" 138 | 139 | #: src/smart_agnc/new_password_win.py:18 140 | msgid "Smart AGNC: Set new password" 141 | msgstr "Smart AGNC: Configura una nueva contraseña" 142 | 143 | #: src/smart_agnc/new_password_win.py:20 144 | msgid "New password:" 145 | msgstr "Nueva contraseña:" 146 | 147 | #: src/smart_agnc/new_password_win.py:24 148 | msgid "Repeat password:" 149 | msgstr "Repite la contraseña:" 150 | 151 | #: src/smart_agnc/new_password_win.py:46 152 | #, python-format 153 | msgid "" 154 | "The password `%s` has been generated for you. Would you like to use it? Be " 155 | "sure to copy it first." 156 | msgstr "" 157 | "La contraseña `%s` fue generada para ti. ¿Quieres usarla? Asegúrate de " 158 | "copiarla primero." 159 | 160 | #: src/smart_agnc/new_password_win.py:48 161 | msgid "Smart AGNC: Time for a new password" 162 | msgstr "Smart AGNC: Es tiempo de una nueva contraseña" 163 | 164 | #: src/smart_agnc/new_password_win.py:67 165 | msgid "Password must contain numbers, uppercase and lowercase letters." 166 | msgstr "La contraseña debe contener números y letras mayúsculas y minúsculas." 167 | 168 | #: src/smart_agnc/new_password_win.py:70 169 | msgid "Passwords don't match." 170 | msgstr "Las contraseñas no coinciden" 171 | 172 | #: src/smart_agnc/new_password_win.py:74 173 | msgid "Error" 174 | msgstr "Error" 175 | 176 | #: src/smart_agnc/about_win.py:24 177 | msgid "About Smart AGNC" 178 | msgstr "Acerca de Smart AGNC" 179 | 180 | #: src/smart_agnc/about_win.py:33 181 | #, python-format 182 | msgid "" 183 | "Please go to %s\n" 184 | "and click on Like if you've found this app to be useful." 185 | msgstr "" 186 | "Por favor ve a %s\n" 187 | "y haz click en Like si esta aplicación te pareció útil." 188 | 189 | #: src/smart_agnc/about_win.py:38 190 | msgid "Author" 191 | msgstr "Autor" 192 | 193 | #: src/smart_agnc/about_win.py:39 194 | msgid "Version" 195 | msgstr "Versión" 196 | 197 | #: src/smart_agnc/about_win.py:44 198 | msgid "Help" 199 | msgstr "Ayuda" 200 | 201 | #: src/smart_agnc/about_win.py:48 202 | msgid "Close" 203 | msgstr "Cerrar" 204 | 205 | #: src/smart_agnc/agn_monitor.py:84 src/smart_agnc/agn_notifier.py:121 206 | msgid "AGNC Services should be restarted." 207 | msgstr "Los servicios del AGNC deben ser reiniciados." 208 | 209 | #: src/smart_agnc/menu.py:38 210 | msgid "Restart AGNC Services" 211 | msgstr "Reiniciar servicios AGNC" 212 | 213 | #: src/smart_agnc/menu.py:48 214 | msgid "Keep alive" 215 | msgstr "Mantener activo" 216 | 217 | #: src/smart_agnc/menu.py:58 218 | msgid "VPN Connection Information" 219 | msgstr "Información de Conexión de VPN" 220 | 221 | #: src/smart_agnc/menu.py:63 222 | msgid "Edit Account settings..." 223 | msgstr "Configuración de Cuenta..." 224 | 225 | #: src/smart_agnc/menu.py:73 226 | msgid "Download new version" 227 | msgstr "Bajar la nueva versión" 228 | 229 | #: src/smart_agnc/menu.py:78 230 | msgid "About" 231 | msgstr "Acerca de" 232 | 233 | #: src/smart_agnc/menu.py:84 234 | msgid "Quit" 235 | msgstr "Salir" 236 | 237 | #: src/smart_agnc/agn_notifier.py:89 238 | msgid "There is a new version available!" 239 | msgstr "Hay una nueva versión disponible!" 240 | 241 | #: src/smart_agnc/agn_notifier.py:127 242 | msgid "Connect" 243 | msgstr "Conectar" 244 | 245 | #: src/smart_agnc/agn_notifier.py:129 246 | msgid "Disconnect" 247 | msgstr "Desconectar" 248 | 249 | #: src/smart_agnc/agn_notifier.py:138 250 | #, python-format 251 | msgid "IP: %s" 252 | msgstr "IP: %s" 253 | 254 | #: src/smart_agnc/agn_notifier.py:148 255 | msgid "It is time to change your password!" 256 | msgstr "Es tiempo de cambiar tu contraseña!" 257 | 258 | #: src/smart_agnc/agn_notifier.py:154 259 | msgid "Invalid credentials" 260 | msgstr "Credenciales inválidas" 261 | 262 | #: src/smart_agnc/agn_notifier.py:160 263 | msgid "Unknown error!" 264 | msgstr "Error desconocido!" 265 | 266 | #: src/smart_agnc/agn_notifier.py:215 267 | msgid "VPN Disconnected" 268 | msgstr "VPN desconectada" 269 | 270 | #: src/smart_agnc/agn_notifier.py:219 271 | msgid "Complete credentials to connect" 272 | msgstr "Ingrese sus credenciales para conectase" 273 | -------------------------------------------------------------------------------- /share/locale/pt_BR/LC_MESSAGES/smart_agnc.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: smart-agnc\n" 4 | "POT-Creation-Date: 2015-02-02 09:23-0300\n" 5 | "PO-Revision-Date: 2015-02-02 09:24-0300\n" 6 | "Last-Translator: Henrique Tancredi \n" 7 | "Language-Team: \n" 8 | "Language: pt_BR\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.5.4\n" 13 | "X-Poedit-KeywordsList: _;gettext;gettext_noop\n" 14 | "X-Poedit-Basepath: ../../../..\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Poedit-SearchPath-0: src/smart_agnc\n" 17 | 18 | #: src/smart_agnc/settings_win.py:24 19 | msgid "Configuration" 20 | msgstr "Configuração" 21 | 22 | #: src/smart_agnc/settings_win.py:26 23 | msgid "Account" 24 | msgstr "Conta" 25 | 26 | #: src/smart_agnc/settings_win.py:29 27 | msgid "Username" 28 | msgstr "Usuário" 29 | 30 | #: src/smart_agnc/settings_win.py:32 src/smart_agnc/settings_win.py:91 31 | msgid "Password" 32 | msgstr "Senha" 33 | 34 | #: src/smart_agnc/settings_win.py:36 src/smart_agnc/settings_win.py:95 35 | msgid "Show password" 36 | msgstr "Mostrar senha" 37 | 38 | #: src/smart_agnc/settings_win.py:41 39 | msgid "Enable proxy settings" 40 | msgstr "Habilitar configuração de proxy" 41 | 42 | #: src/smart_agnc/settings_win.py:52 43 | msgid "Change Password" 44 | msgstr "Trocar Senha" 45 | 46 | #: src/smart_agnc/settings_win.py:53 47 | msgid "You should disconnect first" 48 | msgstr "Você deve desconectar primeiro" 49 | 50 | #: src/smart_agnc/settings_win.py:85 51 | msgid "Server" 52 | msgstr "Servidor" 53 | 54 | #: src/smart_agnc/settings_win.py:88 55 | msgid "User" 56 | msgstr "Usuário" 57 | 58 | #: src/smart_agnc/conn_info_win.py:17 59 | msgid "Connection Information" 60 | msgstr "Informações da Conexão" 61 | 62 | #: src/smart_agnc/conn_info_win.py:24 src/smart_agnc/conn_info_win.py:28 63 | #: src/smart_agnc/agn_notifier.py:132 64 | msgid "Not available." 65 | msgstr "Não está disponível" 66 | 67 | #: src/smart_agnc/conn_info_win.py:30 68 | msgid "Yes." 69 | msgstr "Sim." 70 | 71 | #: src/smart_agnc/conn_info_win.py:30 72 | msgid "No." 73 | msgstr "Não." 74 | 75 | #: src/smart_agnc/conn_info_win.py:54 76 | msgid "BytesReceivedAtStartOfConnection" 77 | msgstr "Bytes recibidos no início da conexão" 78 | 79 | #: src/smart_agnc/conn_info_win.py:55 80 | msgid "BytesSentAtStartOfConnection" 81 | msgstr "Bytes enviados no início da conexão" 82 | 83 | #: src/smart_agnc/conn_info_win.py:56 84 | msgid "ConnectType" 85 | msgstr "Tipo de conexão" 86 | 87 | #: src/smart_agnc/conn_info_win.py:57 88 | msgid "InternetIPAddress" 89 | msgstr "Endereço IP de Internet" 90 | 91 | #: src/smart_agnc/conn_info_win.py:58 92 | msgid "InternetGatewayIPAddress" 93 | msgstr "Endereço IP do Gateway" 94 | 95 | #: src/smart_agnc/conn_info_win.py:59 96 | msgid "InternetAdapter" 97 | msgstr "Adaptador de Internet" 98 | 99 | #: src/smart_agnc/conn_info_win.py:60 100 | msgid "InternetMACAddress" 101 | msgstr "Endereço MAC de Internet" 102 | 103 | #: src/smart_agnc/conn_info_win.py:61 104 | msgid "StatusCode" 105 | msgstr "Código de estado" 106 | 107 | #: src/smart_agnc/conn_info_win.py:62 108 | msgid "StatusText" 109 | msgstr "Estado" 110 | 111 | #: src/smart_agnc/conn_info_win.py:63 112 | msgid "TimeCompleted" 113 | msgstr "Tempo final" 114 | 115 | #: src/smart_agnc/conn_info_win.py:64 116 | msgid "TimeConnected" 117 | msgstr "Tempo conectado" 118 | 119 | #: src/smart_agnc/conn_info_win.py:65 120 | msgid "TimeStarted" 121 | msgstr "Tempo de início" 122 | 123 | #: src/smart_agnc/conn_info_win.py:66 124 | msgid "VPNCompressionActive" 125 | msgstr "Compressão de dados com VPN ativa" 126 | 127 | #: src/smart_agnc/conn_info_win.py:67 128 | msgid "VPNIPAddress" 129 | msgstr "Endereço IP da VPN" 130 | 131 | #: src/smart_agnc/conn_info_win.py:68 132 | msgid "VPNServerIPAddress" 133 | msgstr "Endereço IP do servidor VPN" 134 | 135 | #: src/smart_agnc/conn_info_win.py:69 136 | msgid "VPNTunnelAdapter" 137 | msgstr "Adaptador do túnel VPN" 138 | 139 | #: src/smart_agnc/new_password_win.py:18 140 | msgid "Smart AGNC: Set new password" 141 | msgstr "Smart AGNC: Insira uma nova senha" 142 | 143 | #: src/smart_agnc/new_password_win.py:20 144 | msgid "New password:" 145 | msgstr "Nova senha:" 146 | 147 | #: src/smart_agnc/new_password_win.py:24 148 | msgid "Repeat password:" 149 | msgstr "Repita sua senha:" 150 | 151 | #: src/smart_agnc/new_password_win.py:46 152 | #, python-format 153 | msgid "" 154 | "The password `%s` has been generated for you. Would you like to use it? Be " 155 | "sure to copy it first." 156 | msgstr "" 157 | "A senha `%s` foi criada para você. Você quer usar-la? Não se esqueça de " 158 | "copiar-la primeiro." 159 | 160 | #: src/smart_agnc/new_password_win.py:48 161 | msgid "Smart AGNC: Time for a new password" 162 | msgstr "Smart AGNC: Chegou a hora de trocar sua senha" 163 | 164 | #: src/smart_agnc/new_password_win.py:67 165 | msgid "Password must contain numbers, uppercase and lowercase letters." 166 | msgstr "A senha deve ter números e letras maiúsculas e minúsculas." 167 | 168 | #: src/smart_agnc/new_password_win.py:70 169 | msgid "Passwords don't match." 170 | msgstr "As senhas não são iguais." 171 | 172 | #: src/smart_agnc/new_password_win.py:74 173 | msgid "Error" 174 | msgstr "Erro" 175 | 176 | #: src/smart_agnc/about_win.py:24 177 | msgid "About Smart AGNC" 178 | msgstr "Sobre o Smart AGNC" 179 | 180 | #: src/smart_agnc/about_win.py:33 181 | #, python-format 182 | msgid "" 183 | "Please go to %s\n" 184 | "and click on Like if you've found this app to be useful." 185 | msgstr "" 186 | "Por favor vá até %s\n" 187 | "e clique em Like se considera este aplicativo útil." 188 | 189 | #: src/smart_agnc/about_win.py:38 190 | msgid "Author" 191 | msgstr "Autor" 192 | 193 | #: src/smart_agnc/about_win.py:39 194 | msgid "Version" 195 | msgstr "Versão" 196 | 197 | #: src/smart_agnc/about_win.py:44 198 | msgid "Help" 199 | msgstr "Ajuda" 200 | 201 | #: src/smart_agnc/about_win.py:48 202 | msgid "Close" 203 | msgstr "Fechar" 204 | 205 | #: src/smart_agnc/agn_monitor.py:84 src/smart_agnc/agn_notifier.py:121 206 | msgid "AGNC Services should be restarted." 207 | msgstr "Os serviços AGNC devem ser reiniciados." 208 | 209 | #: src/smart_agnc/menu.py:38 210 | msgid "Restart AGNC Services" 211 | msgstr "Reiniciar serviços AGNC" 212 | 213 | #: src/smart_agnc/menu.py:48 214 | msgid "Keep alive" 215 | msgstr "Manter ativo" 216 | 217 | #: src/smart_agnc/menu.py:58 218 | msgid "VPN Connection Information" 219 | msgstr "Informações sobre a Conexão VPN" 220 | 221 | #: src/smart_agnc/menu.py:63 222 | msgid "Edit Account settings..." 223 | msgstr "Editar preferencias de sua conta..." 224 | 225 | #: src/smart_agnc/menu.py:73 226 | msgid "Download new version" 227 | msgstr "Download da nova versão" 228 | 229 | #: src/smart_agnc/menu.py:78 230 | msgid "About" 231 | msgstr "Sobre" 232 | 233 | #: src/smart_agnc/menu.py:84 234 | msgid "Quit" 235 | msgstr "Sair" 236 | 237 | #: src/smart_agnc/agn_notifier.py:89 238 | msgid "There is a new version available!" 239 | msgstr "Existe uma nova versão disponível!" 240 | 241 | #: src/smart_agnc/agn_notifier.py:127 242 | msgid "Connect" 243 | msgstr "Conectar" 244 | 245 | #: src/smart_agnc/agn_notifier.py:129 246 | msgid "Disconnect" 247 | msgstr "Desconectar" 248 | 249 | #: src/smart_agnc/agn_notifier.py:138 250 | #, python-format 251 | msgid "IP: %s" 252 | msgstr "IP: %s" 253 | 254 | #: src/smart_agnc/agn_notifier.py:148 255 | msgid "It is time to change your password!" 256 | msgstr "Chegou a hora de você trocar sua senha!" 257 | 258 | #: src/smart_agnc/agn_notifier.py:154 259 | msgid "Invalid credentials" 260 | msgstr "Crendenciais inválidas" 261 | 262 | #: src/smart_agnc/agn_notifier.py:160 263 | msgid "Unknown error!" 264 | msgstr "Erro desconhecido!" 265 | 266 | #: src/smart_agnc/agn_notifier.py:215 267 | msgid "VPN Disconnected" 268 | msgstr "VPN desconectada" 269 | 270 | #: src/smart_agnc/agn_notifier.py:219 271 | msgid "Complete credentials to connect" 272 | msgstr "Insira suas credencias para se conectar" 273 | -------------------------------------------------------------------------------- /share/polkit-1/actions/org.smart-agnc.sagnc-service-restart.policy: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Smart AT&T Global Network Client 8 | https://github.com/knoid/smart-agnc 9 | 10 | 11 | Restart AGNC Services 12 | audio-x-generic 13 | 14 | no 15 | no 16 | yes 17 | 18 | /usr/bin/sagnc-service-restart 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/c-bind/c-bind.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "commands.h" 12 | 13 | #define MAX_COMMANDS 6 14 | 15 | int state_change_callback(int parameter) { 16 | fprintf(stderr, "state-change\t%d\n", parameter); 17 | return EXIT_SUCCESS; 18 | } 19 | 20 | int action_requested_callback(int parameter) { 21 | fprintf(stderr, "action-requested\t%d\n", parameter); 22 | return EXIT_SUCCESS; 23 | } 24 | 25 | const cmd_function_t cmd_functions[MAX_COMMANDS] = { 26 | cmd_exit, 27 | cmd_action_connect, 28 | cmd_action_disconnect, 29 | cmd_get_connect_attempt_info, 30 | cmd_get_state, 31 | cmd_get_user_info 32 | }; 33 | 34 | int main(int argc, char *argv[]) { 35 | setbuf(stdout, NULL); 36 | 37 | agnHandle_t handle; 38 | 39 | int poll_ret; 40 | struct pollfd fd; 41 | 42 | char *line, *char_nargs; 43 | size_t nbytes = 100; 44 | int commandId, nargs, i; 45 | char **args; 46 | ssize_t bytes_read; 47 | 48 | agnLogcOpen(AGN_LOG_ID_GENERIC); 49 | agnLogcLevel(AGN_LOG_DEBUG); 50 | 51 | handle = agncOpen(agnComponentIdAGNClient, 52 | state_change_callback, action_requested_callback); 53 | 54 | for (;;) { 55 | 56 | // Needs to be set in every loop because `poll` may modify it. 57 | fd.fd = 0; 58 | fd.events = POLLIN; 59 | fd.revents = 0; 60 | 61 | commandId = nargs = -1; 62 | 63 | // wait for input on stdin until timeout 64 | poll_ret = poll(&fd, 1, 500); 65 | 66 | // read input 67 | if (poll_ret > 0) { 68 | line = NULL; 69 | bytes_read = getline(&line, &nbytes, stdin); 70 | if (bytes_read > 3) { // number + space + number + line break = 4 71 | commandId = strtol(strtok(line, " "), NULL, 10); 72 | char_nargs = strtok(NULL, " "); 73 | if (char_nargs != NULL) { 74 | nargs = strtol(char_nargs, NULL, 10); 75 | } 76 | } 77 | free(line); 78 | } 79 | 80 | // check valid input 81 | if (-1 < commandId && commandId < MAX_COMMANDS && nargs >= 0) { 82 | 83 | args = (char**) malloc(sizeof(char*) * nargs); 84 | for (i = 0; i < nargs; i++) { 85 | line = NULL; 86 | bytes_read = getline(&line, &nbytes, stdin); 87 | 88 | // remove trailing line end 89 | args[i] = (char*) malloc(sizeof(char) * bytes_read); 90 | line[bytes_read - 1] = '\0'; 91 | strcpy(args[i], line); 92 | 93 | free(line); 94 | } 95 | 96 | if (cmd_functions[commandId](handle, nargs, args) == EXIT_FAILURE) { 97 | break; 98 | } 99 | 100 | for (i = 0; i < nargs; i++) { 101 | free(args[i]); 102 | } 103 | free(args); 104 | 105 | } else if (poll_ret > 0) { 106 | break; 107 | } 108 | } 109 | 110 | agncClose(handle); 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /src/c-bind/commands.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "commands.h" 7 | 8 | void replace_newlines(char* text) { 9 | char* newLine = text; 10 | while (newLine = strchr(newLine, '\n')) { 11 | newLine[0] = '|'; 12 | } 13 | } 14 | 15 | int cmd_exit(agnHandle_t handle, int argc, char *argv[]) { 16 | return EXIT_FAILURE; 17 | } 18 | 19 | int cmd_action_connect(agnHandle_t handle, int argc, char *argv[]) { 20 | agnUser_t user; 21 | 22 | user.ulStructSize = sizeof(agnUser_t); 23 | strcpy(user.szAccount, argv[0]); 24 | strcpy(user.szUsername, argv[1]); 25 | strcpy(user.szPassword, argv[2]); 26 | strcpy(user.szNewPassword, argv[3]); 27 | strcpy(user.szSMXServer, argv[4]); 28 | if (5 < argc) { 29 | strcpy(user.szProxyServer, argv[5]); 30 | strcpy(user.szProxyUser, argv[6]); 31 | strcpy(user.szProxyPassword, argv[7]); 32 | } else { 33 | strcpy(user.szProxyServer, ""); 34 | strcpy(user.szProxyUser, ""); 35 | strcpy(user.szProxyPassword, ""); 36 | } 37 | 38 | agncPostActionRequest(handle, agnActionConnect, &user, sizeof(agnUser_t)); 39 | 40 | return EXIT_SUCCESS; 41 | } 42 | 43 | int cmd_action_disconnect(agnHandle_t handle, int argc, char *argv[]) { 44 | agncPostActionRequest(handle, agnActionDisconnect, NULL, 0); 45 | return EXIT_SUCCESS; 46 | } 47 | 48 | int cmd_get_connect_attempt_info(agnHandle_t handle, int argc, char *argv[]) { 49 | agnConnectAttempt_t attempt; 50 | attempt.ulStructSize = sizeof(agnConnectAttempt_t); 51 | if (0 == agncGetConnectAttemptInfo(&attempt)) { 52 | replace_newlines(attempt.szStatusText); 53 | printf("agnConnectAttempt\tszStatusText\t%s\n", attempt.szStatusText); 54 | printf("agnConnectAttempt\tTimeStarted\t%lu\n", attempt.TimeStarted); 55 | printf("agnConnectAttempt\tTimeConnected\t%lu\n", attempt.TimeConnected); 56 | printf("agnConnectAttempt\tTimeCompleted\t%lu\n", attempt.TimeCompleted); 57 | printf("agnConnectAttempt\tVPNIPAddress\t%lu\n", attempt.VPNIPAddress); 58 | printf("agnConnectAttempt\tVPNServerIPAddress\t%lu\n", attempt.VPNServerIPAddress); 59 | printf("agnConnectAttempt\tszVPNTunnelAdapter\t%s\n", attempt.szVPNTunnelAdapter); 60 | printf("agnConnectAttempt\tConnectType\t%d\n", attempt.ConnectType); 61 | printf("agnConnectAttempt\tStatusCode\t%d\n", attempt.StatusCode); 62 | printf("agnConnectAttempt\tInternetIPAddress\t%lu\n", attempt.InternetIPAddress); 63 | printf("agnConnectAttempt\tInternetGatewayIPAddress\t%lu\n", attempt.InternetGatewayIPAddress); 64 | printf("agnConnectAttempt\tszInternetAdapter\t%s\n", attempt.szInternetAdapter); 65 | printf("agnConnectAttempt\tszInternetMACAddress\t%s\n", attempt.szInternetMACAddress); 66 | printf("agnConnectAttempt\tVPNCompressionActive\t%d\n", attempt.VPNCompressionActive); 67 | printf("agnConnectAttempt\tBytesReceivedAtStartOfConnection\t%lu\n", attempt.BytesReceivedAtStartOfConnection); 68 | printf("agnConnectAttempt\tBytesSentAtStartOfConnection\t%lu\n", attempt.BytesSentAtStartOfConnection); 69 | printf("EOF\n"); 70 | } 71 | return EXIT_SUCCESS; 72 | } 73 | 74 | int cmd_get_state(agnHandle_t handle, int argc, char *argv[]) { 75 | printf("%d\nEOF\n", agncGetState()); 76 | return EXIT_SUCCESS; 77 | } 78 | 79 | int cmd_get_user_info(agnHandle_t handle, int argc, char *argv[]) { 80 | agnUser_t user; 81 | user.ulStructSize = sizeof(agnUser_t); 82 | if (0 == agncGetUserInfo(&user)) { 83 | printf("agnUser\tszAccount\t%s\n", user.szAccount); 84 | printf("agnUser\tszNewPassword\t%s\n", user.szNewPassword); 85 | printf("agnUser\tszPassword\t%s\n", user.szPassword); 86 | printf("agnUser\tszProxyPassword\t%s\n", user.szProxyPassword); 87 | printf("agnUser\tszProxyServer\t%s\n", user.szProxyServer); 88 | printf("agnUser\tszProxyUser\t%s\n", user.szProxyUser); 89 | printf("agnUser\tszSMXServer\t%s\n", user.szSMXServer); 90 | printf("agnUser\tszUsername\t%s\n", user.szUsername); 91 | printf("EOF\n"); 92 | } 93 | return EXIT_SUCCESS; 94 | } 95 | -------------------------------------------------------------------------------- /src/c-bind/commands.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMANDS_H_ 2 | #define COMMANDS_H_ 3 | 4 | int cmd_exit(agnHandle_t handle, int argc, char *argv[]); 5 | 6 | int cmd_action_connect(agnHandle_t handle, int argc, char *argv[]); 7 | 8 | int cmd_action_disconnect(agnHandle_t handle, int argc, char *argv[]); 9 | 10 | int cmd_get_connect_attempt_info(agnHandle_t handle, int argc, char *argv[]); 11 | 12 | int cmd_get_state(agnHandle_t handle, int argc, char *argv[]); 13 | 14 | int cmd_get_user_info(agnHandle_t handle, int argc, char *argv[]); 15 | 16 | typedef int (*cmd_function_t)(agnHandle_t, int, char**); 17 | 18 | #endif /* COMMANDS_H_ */ 19 | -------------------------------------------------------------------------------- /src/smart_agnc/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This app will create a seamless VPN connection between you and your enterprise 5 | by adding a tray icon and keeping you connected whenever possible. 6 | """ 7 | 8 | import gtk 9 | import logging 10 | import os 11 | import pynotify 12 | 13 | __version__ = '0.1.3' 14 | __author__ = 'Ariel Barabas' 15 | 16 | module_dir = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | if module_dir.startswith('/usr/'): # we are in production 19 | root_path = '/usr' 20 | else: 21 | root_path = os.path.abspath(os.path.join(module_dir, '..', '..')) 22 | os.environ["PATH"] = os.path.join(root_path, 'bin') + os.pathsep + \ 23 | os.environ["PATH"] 24 | 25 | config_dir = os.path.expanduser('~/.smart-agnc') 26 | logs_dir = os.path.join(config_dir, 'logs') 27 | config_file = os.path.join(config_dir, 'config') 28 | share_dir = os.path.join(root_path, 'share') 29 | 30 | icons_dir = os.path.join(share_dir, 'icons') 31 | gtk.icon_theme_get_default().append_search_path(icons_dir) 32 | -------------------------------------------------------------------------------- /src/smart_agnc/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gettext 4 | import gobject 5 | import gtk 6 | import logging 7 | from logging.handlers import RotatingFileHandler 8 | from optparse import OptionParser 9 | import os 10 | import sys 11 | 12 | from . import config_dir, config_file, logs_dir, share_dir 13 | from agn_binder import AgnBinder 14 | from agn_monitor import AgnMonitor 15 | from agn_notifier import AgnNotifier 16 | from config import UserPreferences 17 | import utils 18 | 19 | # move config file to .smart-agnc folder (remove in July) 20 | if os.path.isfile(config_dir): 21 | os.rename(config_dir, config_dir + '_backup') 22 | os.mkdir(config_dir) 23 | os.rename(config_dir + '_backup', config_file) 24 | elif not os.path.isdir(config_dir): 25 | os.mkdir(config_dir) 26 | if not os.path.isdir(logs_dir): 27 | os.mkdir(logs_dir) 28 | 29 | i18n_dir = os.path.join(share_dir, 'locale') 30 | gettext.install(__package__, i18n_dir) 31 | 32 | optp = OptionParser() 33 | optp.add_option('--exit-button', dest='exit_button', action='store_true', 34 | help='Add an exit button to the icon\'s context menu') 35 | optp.add_option('--no-check-update', dest='check_update', action='store_false', 36 | help='Disable checking for application updates.') 37 | optp.add_option('-v', '--verbose', dest='verbose', action='count', 38 | help='Increase verbosity (specify multiple times for more)') 39 | opts, args = optp.parse_args() 40 | 41 | if opts.check_update is None: 42 | opts.check_update = True 43 | 44 | log_level = logging.WARNING # default 45 | if opts.verbose == 1: 46 | log_level = logging.INFO 47 | elif opts.verbose >= 2: 48 | log_level = logging.DEBUG 49 | 50 | # setting up two loggers 51 | streamLog = logging.StreamHandler(sys.stdout) 52 | streamLog.setLevel(log_level) 53 | 54 | fileLog = RotatingFileHandler(os.path.join(logs_dir, 'log'), 'a', 1 << 20, 55 | backupCount=5, encoding='utf-8') 56 | fileLog.setLevel(logging.INFO) 57 | 58 | formatter_keys = ('asctime', 'name', 'levelname', 'message') 59 | formatter_string = '|'.join(['%%(%s)s' % key for key in formatter_keys]) 60 | formatter = logging.Formatter(formatter_string) 61 | streamLog.setFormatter(formatter) 62 | fileLog.setFormatter(formatter) 63 | 64 | logger = logging.getLogger(__package__) 65 | logger.setLevel(logging.DEBUG) 66 | logger.addHandler(streamLog) 67 | logger.addHandler(fileLog) 68 | logger.info('new run') 69 | 70 | gobject.threads_init() 71 | 72 | EVENTS = utils.EventsManager() 73 | AGN_BINDER = AgnBinder(EVENTS) 74 | CONFIG = UserPreferences({'keepalive': True, 'timeout': 40}, AGN_BINDER) 75 | AGN_MONITOR = AgnMonitor(AGN_BINDER, CONFIG, EVENTS) 76 | AgnNotifier(CONFIG, AGN_BINDER, AGN_MONITOR, EVENTS, opts) 77 | 78 | gtk.main() 79 | -------------------------------------------------------------------------------- /src/smart_agnc/about_win.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gtk 4 | import pango 5 | import webbrowser 6 | 7 | from . import __author__, __version__ 8 | from windows import _Window, _WindowCentered 9 | 10 | _title_ = __package__.replace('_', '-') 11 | 12 | tap_url = 'https://tap.innovate.ibm.com/app/3768' 13 | tap_url = '%s' % (tap_url, tap_url) 14 | 15 | readme_url = 'https://github.com/knoid/%s/blob/v%s/README.md' % \ 16 | (_title_, __version__) 17 | 18 | 19 | class AboutWindow(_WindowCentered, _Window): 20 | 21 | def __init__(self): 22 | super(AboutWindow, self).__init__() 23 | 24 | self.set_title(_('About Smart AGNC')) 25 | 26 | self.vbox = vbox = gtk.VBox(False, 10) 27 | self.add(vbox) 28 | 29 | vbox.add(gtk.image_new_from_icon_name(_title_, gtk.ICON_SIZE_DIALOG)) 30 | vbox.add(gtk.Label('Smart AT&T Global Network Client')) 31 | 32 | label = gtk.Label() 33 | label.set_markup(_('Please go to %s\nand click on Like if you\'ve ' + 34 | 'found this app to be useful.') % tap_url) 35 | label.set_justify(gtk.JUSTIFY_CENTER) 36 | vbox.add(label) 37 | 38 | self.add_attr(_('Author'), __author__) 39 | self.add_attr(_('Version'), __version__) 40 | 41 | hbox = gtk.HBox(True, 100) 42 | vbox.add(hbox) 43 | 44 | btn_help = gtk.Button(_('Help')) 45 | btn_help.connect('clicked', lambda b: webbrowser.open(readme_url)) 46 | hbox.add(btn_help) 47 | 48 | btn_close = gtk.Button(_('Close')) 49 | btn_close.connect('clicked', lambda b: self.hide()) 50 | hbox.add(btn_close) 51 | 52 | vbox.show_all() 53 | 54 | def add_attr(self, label, value): 55 | hbox = gtk.HBox(True, 10) 56 | self.vbox.add(hbox) 57 | 58 | lbl_key = gtk.Label(label) 59 | lbl_key.set_alignment(1, 0.5) 60 | lbl_key.modify_font(pango.FontDescription('bold')) 61 | hbox.add(lbl_key) 62 | 63 | lbl_value = gtk.Label(value) 64 | lbl_value.set_alignment(0, 0.5) 65 | hbox.add(lbl_value) 66 | -------------------------------------------------------------------------------- /src/smart_agnc/agn_binder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import atexit 4 | from distutils.spawn import find_executable 5 | import fcntl 6 | from functools import wraps 7 | import dbus 8 | import gobject 9 | import logging 10 | import os 11 | import socket 12 | import struct 13 | from subprocess import call, PIPE, Popen 14 | import threading 15 | import time 16 | 17 | STATE_UNKNOWN = 0 18 | STATE_UNCHANGED = 1 19 | STATE_NOT_CONNECTED = 10 20 | STATE_DAEMON_DEAD = 15 21 | STATE_BEFORE_CONNECT = 100 22 | STATE_SMX_AUTHENTICATING = 200 23 | STATE_VPN_CONNECTING = 300 24 | STATE_VPN_RECONNECTED = 350 25 | STATE_CONNECTED = 400 26 | STATE_DISCONNECTING = 500 27 | STATE_AFTER_CONNECT = 600 28 | 29 | SERVICE_MANAGER_ADDRESS = '204.146.172.230' # AT&T Production RIG 30 | 31 | logger = logging.getLogger(__name__) 32 | lock = threading.Lock() 33 | 34 | 35 | def retry(max_retries=5): 36 | def outer(func): 37 | @wraps(func) 38 | def wrapper(*args, **kwargs): 39 | assert max_retries > 0 40 | logger.info(func.__name__) 41 | lock.acquire() 42 | x = max_retries 43 | while x >= 0: 44 | try: 45 | ret = func(*args, **kwargs) 46 | lock.release() 47 | return ret 48 | except IOError: 49 | x -= 1 50 | 51 | lock.release() 52 | 53 | # should not come to this 54 | if can_restart_agnc_services(): 55 | restart_agnc_services() 56 | return wrapper 57 | return outer 58 | 59 | 60 | class AgnBinder(object): 61 | """ 62 | Provides an interface between python and AGNC's daemon. 63 | """ 64 | 65 | proc = None 66 | 67 | # event source id 68 | __io_watch = 0 69 | 70 | def __init__(self, events): 71 | self.events = events 72 | self.setup_process() 73 | atexit.register(self.proc.kill) 74 | events.emit('agn-state-change', self.get_state()) 75 | 76 | def setup_process(self): 77 | if self.__io_watch > 0: 78 | self.proc.kill() 79 | gobject.source_remove(self.__io_watch) 80 | self.__io_watch = 0 81 | 82 | self.proc = proc = Popen([find_executable('sagnc-bind')], 83 | stdout=PIPE, stderr=PIPE, stdin=PIPE) 84 | 85 | # https://www.domenkozar.com/2009/09/13/read-popenstdout-object- \ 86 | # asynchronously-or-why-low-level-knowledge-holes-are-killing-me/ 87 | for stream in (proc.stdout, proc.stderr): 88 | fdn = stream.fileno() 89 | file_flags = fcntl.fcntl(fdn, fcntl.F_GETFL) 90 | fcntl.fcntl(fdn, fcntl.F_SETFL, file_flags | os.O_NDELAY) 91 | 92 | self.__io_watch = gobject.io_add_watch(proc.stderr, gobject.IO_IN, 93 | self.__get_event__) 94 | 95 | def __get_event__(self, stderr, unused): 96 | line = __get_line__(stderr) 97 | if line == '': 98 | return False 99 | try: 100 | (change_type, param) = line.split('\t') 101 | self.events.emit('agn-' + change_type, int(param)) 102 | except ValueError: 103 | pass 104 | return True 105 | 106 | def __next_line__(self): 107 | tries = 5 108 | while True: 109 | try: 110 | return __get_line__(self.proc.stdout) 111 | except IOError as err: 112 | tries -= 1 113 | if tries > 0: 114 | logger.debug(err) 115 | time.sleep(0.1) 116 | continue 117 | else: 118 | logger.warning(err) 119 | self.setup_process() 120 | raise 121 | 122 | def __get_lines__(self): 123 | lines = [] 124 | while True: 125 | line = self.__next_line__() 126 | logger.debug('vpn > %s', line) 127 | if line == 'EOF': 128 | return lines 129 | else: 130 | lines.append(line) 131 | 132 | def __get_object_response__(self): 133 | res = {} 134 | for line in self.__get_lines__(): 135 | (unused, prop, value) = line.split('\t') 136 | if prop.startswith('sz'): 137 | prop = prop[2:] 138 | value = value.replace('|', '\n') 139 | else: 140 | value = int(value) 141 | res[prop] = value 142 | return res 143 | 144 | def __send__(self, num, args=None): 145 | if not args: 146 | args = [] 147 | 148 | stdin = '\n'.join([str(num) + ' ' + str(len(args))] + args) 149 | logger.debug('vpn < %s', stdin) 150 | try: 151 | self.proc.stdin.write(stdin + '\n') 152 | except IOError: 153 | logger.info('Subprocess is dead, spawning a new one.') 154 | self.setup_process() 155 | self.__send__(num, args) 156 | 157 | @retry() 158 | def exit(self): 159 | """ 160 | Exit subprocess. 161 | """ 162 | self.__send__(0) 163 | 164 | @retry() 165 | def action_connect(self, account, username, password, new_password='', 166 | proxy=None): 167 | """ 168 | Issues a connect action to AGNC's daemon with the provided account, 169 | username and password against AT&T's production servers. 170 | """ 171 | args = [account, username, password, new_password, 172 | SERVICE_MANAGER_ADDRESS] 173 | if proxy: 174 | args += [proxy['server'], proxy['user'], proxy['password']] 175 | self.__send__(1, args) 176 | 177 | @retry() 178 | def action_disconnect(self): 179 | """ 180 | Issues a disconnect action to the daemon. 181 | """ 182 | self.__send__(2) 183 | 184 | @retry() 185 | def get_connect_attempt_info(self): 186 | """ 187 | Returns a dictionary with AGNC's connect attempt information. 188 | """ 189 | self.__send__(3) 190 | return self.__get_object_response__() 191 | 192 | @retry() 193 | def get_state(self): 194 | """ 195 | Returns an int with AGNC's current state 196 | """ 197 | self.__send__(4) 198 | return int(self.__get_lines__()[0]) 199 | 200 | @retry() 201 | def get_user_info(self): 202 | """ 203 | Returns a dictionary with AGNC's user information. 204 | """ 205 | self.__send__(5) 206 | return self.__get_object_response__() 207 | 208 | 209 | def long2ip(long_ip): 210 | """ 211 | Convert a long to an IP string 212 | """ 213 | packed = struct.pack(' disconnect 49 | if ab.STATE_BEFORE_CONNECT < state < ab.STATE_VPN_RECONNECTED and \ 50 | self.connecting_timeout < time.time(): 51 | logger.info('Timeout! Disconnecting...') 52 | self.binder.action_disconnect() 53 | 54 | if self.fail_connect > SERVICES_RESTART_AFTER: 55 | self.fail_connect = 0 56 | utils.restart_agnc_services() 57 | else: 58 | self.fail_connect += 1 59 | 60 | return 61 | 62 | if ab.STATE_BEFORE_CONNECT < state <= ab.STATE_CONNECTED: 63 | # I'm connected 64 | 65 | if ab.STATE_VPN_RECONNECTED <= state: 66 | menu.item_restart_service.hide() 67 | self.fail_connect = 0 68 | 69 | if self.want_to < state: # I want to get disconnected 70 | self.binder.action_disconnect() 71 | 72 | else: 73 | # I'm disconnected 74 | 75 | if self.want_to > state: # I want to get connected 76 | 77 | vpn, proxy = self.config.get_connection_values() 78 | use_proxy = self.config.getboolean('proxy', 'enabled') 79 | if len(vpn) == 3 and (not use_proxy or len(proxy) == 3): 80 | 81 | if force or self.config.getboolean('vpn', 'keepalive'): 82 | self.vpn_connect(vpn, proxy) 83 | else: 84 | utils.alert(_('VPN Disconnected')) 85 | self.want_to = ab.STATE_NOT_CONNECTED 86 | 87 | else: 88 | utils.alert(_('Complete credentials to connect')) 89 | self.events.emit('configuration-error', state) 90 | self.want_to = ab.STATE_NOT_CONNECTED 91 | 92 | def set_want_to(self, state): 93 | self.want_to = state 94 | connection_timeout = self.config.getint('vpn', 'timeout') 95 | self.connecting_timeout = time.time() + connection_timeout 96 | 97 | def vpn_connect(self, vpn, proxy=None, new_password=''): 98 | timeout_secs = self.config.getint('vpn', 'timeout') 99 | if not self.config.getboolean('proxy', 'enabled'): 100 | proxy = None 101 | self.connecting_timeout = time.time() + timeout_secs 102 | self.binder.action_connect(vpn['account'], vpn['username'], 103 | vpn['password'], new_password, proxy) 104 | -------------------------------------------------------------------------------- /src/smart_agnc/agn_notifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import base64 5 | import gobject 6 | import gtk 7 | import logging 8 | import os 9 | import pynotify 10 | from subprocess import Popen, PIPE 11 | 12 | # local imports 13 | import agn_binder as ab 14 | from conn_info_win import ConnectionInformationWindow 15 | import menu 16 | from new_password_win import NewPasswordWindow 17 | from settings_win import ConfigurationWindow 18 | from tray_icon import TrayIcon 19 | import update 20 | import utils 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | class AgnNotifier(TrayIcon): 26 | """AgnNotifier""" 27 | 28 | _id = 'at&t-smart' 29 | 30 | changing_password = False 31 | 32 | def __init__(self, user_config, vpn, monitor, events, opts): 33 | super(AgnNotifier, self).__init__(self._id) 34 | pynotify.init(self._id) 35 | 36 | self.config = user_config 37 | self.events = events 38 | 39 | self.new_password_win = NewPasswordWindow() 40 | self.new_password_win.connect('new-password', self.set_new_password) 41 | 42 | self.monitor = monitor 43 | self.vpn = vpn 44 | events.connect('agn-state-change', self.on_vpn_state_change) 45 | events.connect('agn-state-change', self.trigger_external_script) 46 | events.connect('configuration-error', self.do_configure) 47 | 48 | vpn_values, proxy_values = self.config.get_connection_values() 49 | self.config_win = ConfigurationWindow( 50 | events, vpn_values, user_config.getboolean('proxy', 'enabled'), 51 | proxy_values, self.new_password_win) 52 | self.config_win.connect('save', self.do_save) 53 | 54 | self.conn_info_win = ConnectionInformationWindow() 55 | self.set_menu(menu.create( 56 | conn_toggle=self.do_toggle_connection, 57 | keepalive_init_state=self.config.getboolean('vpn', 'keepalive'), 58 | keepalive_toggle=self.do_toggle_keepalive, 59 | conn_info=self.do_conn_info, 60 | configure=self.do_configure, 61 | restart_agnc_services=self.do_restart_agnc_services, 62 | exit_button=opts.exit_button)) 63 | 64 | if opts.check_update: 65 | update.check_periodically(self.upgrade_available) 66 | 67 | gtk.gdk.notify_startup_complete() 68 | 69 | @utils.async(True) # blocks repetead calls so they all get called 70 | def trigger_external_script(self, unused_events, state): 71 | logger.info('trigger_external_script, state=%d', state) 72 | script_path = self.config.get('scripts', str(state)) 73 | if len(script_path) > 0: 74 | output = [script_path] 75 | try: 76 | proc = Popen([os.path.expanduser(script_path)], 77 | close_fds=True, stdout=PIPE, stderr=PIPE) 78 | except OSError as err: 79 | output.append(str(err)) 80 | proc = False 81 | if proc: # the external process was successfully initialized 82 | output += [s.rstrip() for s in proc.communicate() if s] 83 | if len(output) > 1: 84 | utils.alert('\n'.join(output)) 85 | 86 | @utils.async(True) # blocks repetead calls so they all get called 87 | def on_vpn_state_change(self, unused_events, new_state): 88 | logger.info('on_vpn_state_change, new_state=%d', new_state) 89 | 90 | # ignoring higher states than STATE_CONNECTED we can be sure that 'the 91 | # higher the state, the more `connected` we are' remains True 92 | if new_state > ab.STATE_CONNECTED: 93 | return 94 | 95 | if new_state == ab.STATE_DAEMON_DEAD: 96 | utils.restart_agnc_services() 97 | 98 | attempt = self.vpn.get_connect_attempt_info() 99 | menu.item_conn_status.set_label(attempt['StatusText']) 100 | 101 | toggle_btn_text = _('Connect') 102 | if self.monitor.want_to == ab.STATE_CONNECTED: 103 | toggle_btn_text = _('Disconnect') 104 | menu.item_conn_toggle.set_label(toggle_btn_text) 105 | 106 | ip_address = _('Not available.') 107 | icon_state = 'disabled' 108 | if new_state > ab.STATE_VPN_CONNECTING: 109 | ip_address = ab.long2ip(attempt['VPNIPAddress']) 110 | icon_state = 'enabled' 111 | 112 | if self.changing_password: 113 | encoded_password = base64.b64encode(self.changing_password) 114 | self.config.set('vpn', 'password', encoded_password) 115 | self.config.write_to_disk() 116 | self.changing_password = False 117 | 118 | menu.item_conn_ip.set_label(_('IP: %s') % ip_address) 119 | self.set_icon(icon_state) 120 | 121 | # 12 = password expired 122 | # 16 = incorrect new password 123 | if attempt['StatusCode'] in [12, 16]: 124 | if self.monitor.want_to == ab.STATE_CONNECTED: 125 | utils.alert(_('It is time to change your password!')) 126 | gobject.idle_add(self.new_password_win.request_new_password) 127 | self.monitor.set_want_to(ab.STATE_NOT_CONNECTED) 128 | 129 | # 8 = Invalid credentials 130 | elif 8 == attempt['StatusCode']: 131 | self.monitor.set_want_to(ab.STATE_NOT_CONNECTED) 132 | utils.alert(_('Invalid credentials')) 133 | self.events.emit('configuration-error', attempt['StatusCode']) 134 | 135 | # 502 = User-requested disconnect 136 | elif 0 < attempt['StatusCode'] and 502 != attempt['StatusCode']: 137 | self.monitor.set_want_to(ab.STATE_NOT_CONNECTED) 138 | utils.alert(_('Unknown error!') + '\n' + attempt['StatusText']) 139 | self.events.emit('configuration-error', attempt['StatusCode']) 140 | 141 | def set_new_password(self, unused_win, new_password): 142 | vpn, proxy = self.config.get_connection_values() 143 | self.changing_password = new_password 144 | self.monitor.vpn_connect(vpn, proxy, new_password) 145 | self.monitor.set_want_to(ab.STATE_CONNECTED) 146 | 147 | def upgrade_available(self): 148 | utils.alert(_('There is a new version available!')) 149 | menu.item_new_version.show() 150 | 151 | def do_configure(self, unused_m_item=None, unused_state=None): 152 | self.config_win.present() 153 | 154 | def do_conn_info(self, unused_m_item=None): 155 | attempt = self.vpn.get_connect_attempt_info() 156 | self.conn_info_win.set_dict(attempt) 157 | self.conn_info_win.present() 158 | 159 | def do_restart_agnc_services(self, unused_m_item=None): 160 | ab.restart_agnc_services() 161 | 162 | def do_save(self, config_win, 163 | v_account, v_username, v_password, 164 | use_proxy, p_server, p_user, p_password): 165 | config_win.hide() 166 | self.config.set('vpn', 'account', v_account) 167 | self.config.set('vpn', 'username', v_username) 168 | self.config.set('vpn', 'password', base64.b64encode(v_password)) 169 | self.config.setboolean('proxy', 'enabled', use_proxy) 170 | self.config.set('proxy', 'server', p_server) 171 | self.config.set('proxy', 'user', p_user) 172 | self.config.set('proxy', 'password', base64.b64encode(p_password)) 173 | self.config.write_to_disk() 174 | self.monitor.set_want_to(ab.STATE_CONNECTED) 175 | self.monitor.check_connection() 176 | 177 | def do_toggle_connection(self, unused_m_item=None): 178 | if self.monitor.want_to == ab.STATE_CONNECTED: 179 | self.monitor.set_want_to(ab.STATE_NOT_CONNECTED) 180 | self.vpn.action_disconnect() 181 | self.on_vpn_state_change(self.vpn, ab.STATE_NOT_CONNECTED) 182 | else: 183 | self.monitor.set_want_to(ab.STATE_CONNECTED) 184 | self.monitor.check_connection(True) 185 | 186 | def do_toggle_keepalive(self, m_item): 187 | self.config.setboolean('vpn', 'keepalive', m_item.get_active()) 188 | self.config.write_to_disk() 189 | -------------------------------------------------------------------------------- /src/smart_agnc/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """this file is intended to handle the configuration files and data""" 4 | 5 | # system imports 6 | import base64 7 | import iniparse.compat as ConfigParser 8 | import logging 9 | import os 10 | import re 11 | 12 | # local imports 13 | from . import config_file 14 | 15 | non_printables = ''.join([unichr(x) for x in (range(0, 32) + range(127, 160))]) 16 | non_printables_re = re.compile('[%s]' % re.escape(non_printables)) 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class UserPreferences(object): 21 | """ 22 | This is just an even safer implementation of the SafeConfigParser that 23 | tries to read and write without throwing exceptions. 24 | """ 25 | 26 | _modification_time = -1 27 | scp = None 28 | 29 | def __init__(self, defaults, vpn): 30 | self.defaults = defaults 31 | self.vpn = vpn 32 | self._read_config() 33 | 34 | def _read_config(self): 35 | try: 36 | new_modification_time = os.path.getmtime(config_file) 37 | except os.error: 38 | new_modification_time = 0 # force ConfigParser setup 39 | 40 | if new_modification_time > self._modification_time: 41 | self._modification_time = new_modification_time 42 | 43 | self.scp = ConfigParser.SafeConfigParser() 44 | self.scp.read(config_file) 45 | 46 | def write_to_disk(self): 47 | with open(config_file, 'wb') as fileh: 48 | self.scp.write(fileh) 49 | 50 | def get_connection_values(self): 51 | section_keys = (('vpn', ['account', 'username', 'password']), 52 | ('proxy', ['server', 'user', 'password'])) 53 | user_info = None 54 | res = [] 55 | 56 | for section, keys in section_keys: 57 | values = {} 58 | for key in keys: 59 | values[key] = self.get(section, key) 60 | 61 | if len(values['password']) > 0: 62 | try: 63 | decoded_password = base64.b64decode(values['password']) 64 | except TypeError: 65 | decoded_password = '' 66 | if len(decoded_password) > 0 and is_printable(decoded_password): 67 | # password was base64 encoded 68 | values['password'] = decoded_password 69 | else: 70 | logger.warning('Password was not encoded in config file.') 71 | 72 | if len(values) == 0: 73 | if not user_info: 74 | user_info = self.vpn.get_user_info() 75 | for key in keys: 76 | agnc_key = 'Proxy' * (section == 'proxy') + key.title() 77 | values[key] = user_info[agnc_key] 78 | 79 | res.append(filter_empty(values)) 80 | 81 | return res 82 | 83 | def set(self, section, key, value): 84 | self._read_config() 85 | try: 86 | return self.scp.set(section, key, value) 87 | except ConfigParser.NoSectionError: 88 | self.scp.add_section(section) 89 | self.set(section, key, value) 90 | 91 | def setboolean(self, section, key, value): 92 | return self.set(section, key, 'on' if value else 'off') 93 | 94 | def get(self, section, key, default=''): 95 | try: 96 | return self.scp.get(section, key) 97 | except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 98 | if key in self.defaults: 99 | return self.defaults[key] 100 | else: 101 | return default 102 | 103 | def getboolean(self, section, key): 104 | try: 105 | return self.scp.getboolean(section, key) 106 | except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 107 | return self.get(section, key, False) 108 | 109 | def getint(self, section, key): 110 | try: 111 | return self.scp.getint(section, key) 112 | except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 113 | return self.get(section, key, 0) 114 | 115 | 116 | def is_printable(string): 117 | return non_printables_re.search(string) is None 118 | 119 | 120 | def filter_empty(values): 121 | return dict((key, val) for key, val in values.items() if len(val) > 0) 122 | -------------------------------------------------------------------------------- /src/smart_agnc/conn_info_win.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import time 5 | 6 | from windows import _WindowCentered, _WindowForm 7 | from agn_binder import long2ip 8 | 9 | 10 | class ConnectionInformationWindow(_WindowCentered, _WindowForm): 11 | 12 | def __init__(self): 13 | super(ConnectionInformationWindow, self).__init__() 14 | 15 | self.fields = {} 16 | 17 | self.set_title(_('Connection Information')) 18 | self.table.set_property('n-columns', 2) 19 | self.table.show_all() 20 | 21 | def set_dict(self, data): 22 | for key, value in sorted(data.iteritems()): 23 | if 'IP' in key: 24 | label = long2ip(value) if value else _('Not available.') 25 | elif 'Bytes' in key: 26 | label = bytes2human(value) 27 | elif 'Time' in key: 28 | label = time.ctime(value) if value else _('Not available.') 29 | elif 'Active' in key: 30 | label = _('Yes.') if value else _('No.') 31 | else: 32 | label = str(value) 33 | 34 | if key not in self.fields: 35 | self._make_label(_(key), 0, len(self.fields)).show() 36 | txt = self._make_label('', 1, len(self.fields)) 37 | txt.set_selectable(True) 38 | txt.show() 39 | self.fields[key] = txt 40 | self.fields[key].set_label(label) 41 | 42 | 43 | def bytes2human(num, format_str='%3.1f %sB'): 44 | """http://stackoverflow.com/questions/1094841""" 45 | for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: 46 | if abs(num) < 1024.0: 47 | return format_str % (num, unit) 48 | num /= 1024.0 49 | return format_str % (num, 'Yi') 50 | 51 | 52 | def gettext_keys(_): 53 | """This is here so this keywords appear on the language files editor.""" 54 | _('BytesReceivedAtStartOfConnection') 55 | _('BytesSentAtStartOfConnection') 56 | _('ConnectType') 57 | _('InternetIPAddress') 58 | _('InternetGatewayIPAddress') 59 | _('InternetAdapter') 60 | _('InternetMACAddress') 61 | _('StatusCode') 62 | _('StatusText') 63 | _('TimeCompleted') 64 | _('TimeConnected') 65 | _('TimeStarted') 66 | _('VPNCompressionActive') 67 | _('VPNIPAddress') 68 | _('VPNServerIPAddress') 69 | _('VPNTunnelAdapter') 70 | -------------------------------------------------------------------------------- /src/smart_agnc/menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gtk 4 | import webbrowser 5 | 6 | from about_win import AboutWindow 7 | 8 | item_conn_status = gtk.MenuItem() 9 | item_conn_ip = gtk.MenuItem() 10 | item_conn_toggle = gtk.MenuItem() 11 | item_restart_service = gtk.MenuItem() 12 | item_new_version = gtk.MenuItem() 13 | 14 | releases_url = 'https://github.com/knoid/smart-agnc/releases' 15 | 16 | 17 | def create(conn_toggle, keepalive_init_state, keepalive_toggle, conn_info, 18 | configure, restart_agnc_services, exit_button): 19 | 20 | about_win = AboutWindow() 21 | 22 | menu = gtk.Menu() 23 | 24 | m_item = item_conn_status 25 | m_item.set_sensitive(False) 26 | menu.append(m_item) 27 | 28 | m_item = item_conn_ip 29 | m_item.set_sensitive(False) 30 | menu.append(m_item) 31 | 32 | # Separator 33 | m_item = gtk.SeparatorMenuItem() 34 | menu.append(m_item) 35 | 36 | # Restart AGNC services (only visible after a few connection timeouts) 37 | m_item = item_restart_service 38 | m_item.set_label('>> %s <<' % _('Restart AGNC Services')) 39 | m_item.connect('activate', restart_agnc_services) 40 | menu.append(m_item) 41 | 42 | # Toggle connection state 43 | m_item = item_conn_toggle 44 | m_item.connect('activate', conn_toggle) 45 | menu.append(m_item) 46 | 47 | # Auto reconnect checkbox 48 | m_item = gtk.CheckMenuItem(_('Keep alive')) 49 | m_item.set_active(keepalive_init_state) 50 | m_item.connect('activate', keepalive_toggle) 51 | menu.append(m_item) 52 | 53 | # Separator 54 | m_item = gtk.SeparatorMenuItem() 55 | menu.append(m_item) 56 | 57 | # Configuration menu item 58 | m_item = gtk.MenuItem(_('VPN Connection Information')) 59 | m_item.connect('activate', conn_info) 60 | menu.append(m_item) 61 | 62 | # Configuration menu item 63 | m_item = gtk.MenuItem(_('Edit Account settings...')) 64 | m_item.connect('activate', configure) 65 | menu.append(m_item) 66 | 67 | # Separator 68 | m_item = gtk.SeparatorMenuItem() 69 | menu.append(m_item) 70 | 71 | # New version 72 | m_item = item_new_version 73 | m_item.set_label(_('Download new version')) 74 | m_item.connect('activate', lambda mi: webbrowser.open(releases_url)) 75 | menu.append(m_item) 76 | 77 | # About 78 | m_item = gtk.MenuItem(_('About')) 79 | m_item.connect('activate', lambda mi: about_win.present()) 80 | menu.append(m_item) 81 | 82 | # Exit button 83 | if exit_button: 84 | m_item = gtk.MenuItem(_('Quit')) 85 | m_item.connect('activate', gtk.main_quit) 86 | menu.append(m_item) 87 | 88 | menu.show_all() 89 | item_restart_service.hide() 90 | item_new_version.hide() 91 | 92 | return menu 93 | -------------------------------------------------------------------------------- /src/smart_agnc/new_password_win.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import base64 5 | import gobject 6 | import gtk 7 | import os 8 | import re 9 | 10 | from windows import _WindowForm 11 | 12 | 13 | class NewPasswordWindow(_WindowForm): 14 | 15 | def __init__(self): 16 | super(NewPasswordWindow, self).__init__() 17 | 18 | self.set_title(_('Smart AGNC: Set new password')) 19 | 20 | self._make_label(_('New password:'), 0, 0) 21 | self.txt_password1 = self._make_entry(1, 0) 22 | self.txt_password1.set_visibility(False) 23 | 24 | self._make_label(_('Repeat password:'), 0, 1) 25 | self.txt_password2 = self._make_entry(1, 1) 26 | self.txt_password2.set_visibility(False) 27 | 28 | close_save = gtk.Button(stock=gtk.STOCK_SAVE) 29 | self._attach(close_save, 1, 3) 30 | close_save.connect('clicked', self.__on_submit__) 31 | 32 | self.table.show_all() 33 | self.connect('form-submit', self.__on_submit__) 34 | 35 | def present(self): 36 | self.txt_password1.set_text('') 37 | self.txt_password2.set_text('') 38 | super(NewPasswordWindow, self).present() 39 | 40 | def request_new_password(self): 41 | new_password = random_string() 42 | 43 | dialog = gtk.MessageDialog( 44 | None, 45 | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, 46 | _('The password `%s` has been generated for you. Would you like ' + 47 | 'to use it? Be sure to copy it first.') % new_password) 48 | dialog.set_title(_('Smart AGNC: Time for a new password')) 49 | 50 | response = dialog.run() 51 | dialog.destroy() 52 | if response == gtk.RESPONSE_OK: 53 | self.emit('new-password', new_password) 54 | else: 55 | self.set_position(gtk.WIN_POS_CENTER) 56 | self.set_transient_for(None) 57 | self.present() 58 | 59 | def __on_submit__(self, unused_widget): 60 | password1 = self.txt_password1.get_text() 61 | password2 = self.txt_password2.get_text() 62 | if password1 == password2 and is_valid_password(password1): 63 | self.emit('new-password', password1) 64 | self.hide() 65 | else: 66 | if password1 == password2: 67 | d_text = _('Password must contain numbers, uppercase and ' + 68 | 'lowercase letters.') 69 | else: 70 | d_text = _('Passwords don\'t match.') 71 | dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, 72 | gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK, 73 | d_text) 74 | dialog.set_title(_('Error')) 75 | dialog.run() 76 | dialog.destroy() 77 | 78 | gobject.signal_new('new-password', NewPasswordWindow, 79 | gobject.SIGNAL_RUN_CLEANUP, None, (str, )) 80 | 81 | 82 | def random_string(length=8): 83 | r_string = '' 84 | while not is_valid_password(r_string): 85 | r_string = base64.urlsafe_b64encode(os.urandom(length))[:length] 86 | return r_string 87 | 88 | test_number = re.compile('[0-9]') 89 | 90 | 91 | def is_valid_password(password): 92 | return len(password) > 7 and password != password.lower() \ 93 | and test_number.search(password) 94 | -------------------------------------------------------------------------------- /src/smart_agnc/settings_win.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import gobject 5 | import gtk 6 | 7 | from windows import _WindowCentered, _WindowForm 8 | import agn_binder as ab 9 | 10 | 11 | class ConfigurationWindow(_WindowCentered, _WindowForm): 12 | 13 | txt_account = None 14 | txt_username = None 15 | txt_password = None 16 | 17 | txt_proxy_server = None 18 | txt_proxy_user = None 19 | txt_proxy_password = None 20 | 21 | def __init__(self, events, vpn, use_proxy, proxy, change_password_win): 22 | super(ConfigurationWindow, self).__init__() 23 | 24 | self.set_title(_('Configuration')) 25 | 26 | self._make_label(_('Account'), 0, 0) 27 | self.txt_account = self._make_entry(1, 0) 28 | 29 | self._make_label(_('Username'), 0, 1) 30 | self.txt_username = self._make_entry(1, 1) 31 | 32 | self._make_label(_('Password'), 0, 2) 33 | self.txt_password = self._make_entry(1, 2) 34 | self.txt_password.set_visibility(False) 35 | 36 | check_button = gtk.CheckButton(_('Show password')) 37 | self._attach(check_button, 2, 2) 38 | check_button.connect('toggled', self.on_password_visiblity, 39 | self.txt_password) 40 | 41 | self.check_proxy = gtk.CheckButton(_('Enable proxy settings')) 42 | self.check_proxy.set_active(use_proxy) 43 | self.check_proxy.set_alignment(0, 0.5) 44 | self._attach(self.check_proxy, 1, 3, 2) 45 | self.check_proxy.connect('toggled', self.on_proxy_toggled) 46 | 47 | self.close_save = close_save = gtk.Button(stock=gtk.STOCK_SAVE) 48 | close_save.set_flags(gtk.CAN_DEFAULT) 49 | close_save.connect('clicked', self.do_btn_save) 50 | self._attach(close_save, 1, 5) 51 | 52 | self.change_password = button = gtk.Button(_('Change Password')) 53 | button.set_tooltip_text(_('You should disconnect first')) 54 | button.connect('clicked', self.do_change_password, change_password_win) 55 | self._attach(button, 2, 5) 56 | 57 | self.table.show_all() 58 | self.connect('form-submit', self.__on_submit__) 59 | self.connect('hide', self.__on_hide__, change_password_win) 60 | 61 | self.proxy_table = self.init_proxy_table() 62 | self._attach(self.proxy_table, 0, 4, 3) 63 | 64 | events.connect('agn-state-change', self.on_agn_state_change) 65 | self.on_proxy_toggled(self.check_proxy) 66 | 67 | self.vpn_mapper = { 68 | 'account': self.txt_account, 69 | 'username': self.txt_username, 70 | 'password': self.txt_password 71 | } 72 | self.proxy_mapper = { 73 | 'server': self.txt_proxy_server, 74 | 'user': self.txt_proxy_user, 75 | 'password': self.txt_proxy_password 76 | } 77 | 78 | self.set_values(vpn, use_proxy, proxy) 79 | 80 | def init_proxy_table(self): 81 | table = gtk.Table() 82 | table.set_col_spacings(10) 83 | table.set_row_spacings(10) 84 | self._make_label(_('Server'), 0, 0, table) 85 | self.txt_proxy_server = self._make_entry(1, 0, table) 86 | 87 | self._make_label(_('User'), 0, 1, table) 88 | self.txt_proxy_user = self._make_entry(1, 1, table) 89 | 90 | self._make_label(_('Password'), 0, 2, table) 91 | self.txt_proxy_password = self._make_entry(1, 2, table) 92 | self.txt_proxy_password.set_visibility(False) 93 | 94 | check_button = gtk.CheckButton(_('Show password')) 95 | self._attach(check_button, 2, 2, table=table) 96 | check_button.connect('toggled', self.on_password_visiblity, 97 | self.txt_proxy_password) 98 | 99 | table.show_all() 100 | return table 101 | 102 | def set_values(self, vpn, use_proxy, proxy): 103 | for mapper, values in ((self.vpn_mapper, vpn), 104 | (self.proxy_mapper, proxy)): 105 | for key, entry in mapper.iteritems(): 106 | if key in values: 107 | entry.set_text(values[key]) 108 | 109 | self.check_proxy.set_active(use_proxy) 110 | self.on_proxy_toggled(self.check_proxy) 111 | 112 | def do_btn_save(self, unused): 113 | ret = {} 114 | mappers = [('vpn', self.vpn_mapper), ('proxy', self.proxy_mapper)] 115 | for sect, mapper in mappers: 116 | items = mapper.iteritems() 117 | ret[sect] = (dict([(key, txt.get_text()) for key, txt in items])) 118 | self.emit('save', 119 | ret['vpn']['account'], 120 | ret['vpn']['username'], 121 | ret['vpn']['password'], 122 | self.check_proxy.get_active(), 123 | ret['proxy']['server'], 124 | ret['proxy']['user'], 125 | ret['proxy']['password']) 126 | 127 | def do_change_password(self, unused_btn, change_password_win): 128 | change_password_win.set_position(gtk.WIN_POS_NONE) 129 | change_password_win.set_transient_for(self) 130 | change_password_win.present() 131 | 132 | def on_agn_state_change(self, unused_vpn, state): 133 | enabled = state < ab.STATE_BEFORE_CONNECT 134 | self.change_password.set_sensitive(enabled) 135 | self.change_password.set_has_tooltip(not enabled) 136 | 137 | def on_password_visiblity(self, widget, password_field): 138 | password_field.set_visibility(widget.get_active()) 139 | 140 | def on_proxy_toggled(self, widget): 141 | if widget.get_active(): 142 | self.proxy_table.show() 143 | else: 144 | self.proxy_table.hide() 145 | 146 | def __on_hide__(self, unused, change_password_win): 147 | if change_password_win.get_transient_for() is self: 148 | change_password_win.hide() 149 | 150 | def __on_submit__(self, unused): 151 | self.do_btn_save(False) 152 | 153 | 154 | gobject.signal_new('save', ConfigurationWindow, gobject.SIGNAL_RUN_FIRST, 155 | None, (str, str, str, bool, str, str, str, )) 156 | -------------------------------------------------------------------------------- /src/smart_agnc/tray_icon.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gtk 4 | 5 | try: 6 | import appindicator 7 | HAS_APPINDICATOR = True 8 | except ImportError: 9 | HAS_APPINDICATOR = False 10 | 11 | from . import icons_dir 12 | 13 | if HAS_APPINDICATOR: 14 | 15 | class TrayIcon(appindicator.Indicator): 16 | 17 | def __init__(self, ti_id): 18 | super(TrayIcon, self).__init__( 19 | ti_id, '', appindicator.CATEGORY_SYSTEM_SERVICES) 20 | 21 | self.set_status(appindicator.STATUS_ACTIVE) 22 | self.set_attention_icon('indicator-messages-new') 23 | self.set_icon_theme_path(icons_dir) 24 | 25 | def set_icon(self, state): 26 | super(TrayIcon, self).set_icon('smart-agnc-' + state) 27 | 28 | else: 29 | 30 | class TrayIcon(gtk.StatusIcon): 31 | 32 | menu = None 33 | 34 | def __init__(self, ti_id): 35 | super(TrayIcon, self).__init__() 36 | self.ti_id = ti_id 37 | self.connect('button-press-event', self.__on_click__) 38 | 39 | def set_icon(self, state): 40 | self.set_from_icon_name('smart-agnc-' + state) 41 | 42 | def set_menu(self, menu): 43 | self.menu = menu 44 | 45 | def __on_click__(self, unused, evt): 46 | if self.menu: 47 | self.menu.popup(None, None, 48 | self.__position_menu__, evt.button, evt.time) 49 | 50 | def __position_menu__(self, menu): 51 | return gtk.status_icon_position_menu(menu, self) 52 | -------------------------------------------------------------------------------- /src/smart_agnc/update.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | from distutils.version import StrictVersion 5 | import gobject 6 | import json 7 | import logging 8 | import urllib2 9 | 10 | # local imports 11 | from . import __version__ 12 | import utils 13 | 14 | logger = logging.getLogger(__name__) 15 | current_version = StrictVersion(__version__) 16 | # short link to https://api.github.com/repos/knoid/smart-agnc/releases 17 | gh_api = 'http://bit.ly/5pezDr' 18 | two_hours = 1000 * 60 * 60 * 2 19 | 20 | 21 | def check_periodically(upgrade_available_callback): 22 | gobject.timeout_add(two_hours, check_updates, upgrade_available_callback) 23 | 24 | 25 | @utils.async() 26 | def check_updates(callback): 27 | 28 | # a return value from an async func wont reach the caller, so we just 29 | # schedule the next update check. 30 | check_periodically(callback) 31 | 32 | logger.info('Checking updates') 33 | req = urllib2.Request(gh_api) 34 | 35 | # Github API requirements 36 | req.add_header('Accept', 'application/vnd.github.v3+json') 37 | req.add_header('User-Agent', __package__ + '_' + __version__) 38 | 39 | # Bitly requirement 40 | req.add_header('Referer', 'http://' + __package__ + '/' + __version__) 41 | 42 | try: 43 | content = urllib2.urlopen(req) 44 | except urllib2.URLError: 45 | content = None 46 | 47 | if content: 48 | for release in json.loads(content.read()): 49 | if StrictVersion(release['tag_name'][1:]) > current_version: 50 | return callback() 51 | 52 | logger.info('No new updates') 53 | -------------------------------------------------------------------------------- /src/smart_agnc/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import functools 5 | import gobject 6 | import logging 7 | import pynotify 8 | import threading 9 | 10 | # local imports 11 | import agn_binder as ab 12 | import menu 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | def alert(msg): 18 | notice = pynotify.Notification('Smart AT&T Client', msg) 19 | notice.show() 20 | logger.warning('Alert: %s', msg.replace('\n', '\n > ')) 21 | 22 | 23 | def async(blocking=False): 24 | lock = threading.Lock() 25 | 26 | def outer(func): 27 | @functools.wraps(func) 28 | def wrapper(*args, **kwargs): 29 | LockingThread(blocking, lock, 30 | target=func, args=args, kwargs=kwargs).start() 31 | return wrapper 32 | return outer 33 | 34 | 35 | class EventsManager(gobject.GObject): 36 | 37 | __gsignals__ = { 38 | 'agn-action-requested': ( 39 | gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 40 | gobject.TYPE_INT]), 41 | 'agn-state-change': ( 42 | gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 43 | gobject.TYPE_INT]), 44 | 'configuration-error': ( 45 | gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [ 46 | gobject.TYPE_INT]) 47 | } 48 | 49 | def __init__(self): 50 | super(EventsManager, self).__init__() 51 | 52 | def emit(self, *args): 53 | gobject.idle_add(super(EventsManager, self).emit, *args) 54 | 55 | 56 | class LockingThread(threading.Thread): 57 | 58 | def __init__(self, blocking, lock, **kwargs): 59 | super(LockingThread, self).__init__(**kwargs) 60 | self.blocking = blocking 61 | self.daemon = True 62 | self.lock = lock 63 | 64 | def run(self): 65 | if self.lock.acquire(self.blocking): 66 | super(LockingThread, self).run() 67 | self.lock.release() 68 | 69 | 70 | def restart_agnc_services(): 71 | if ab.can_restart_agnc_services(): 72 | menu.item_restart_service.set_label(_('Restarting AGNC services')) 73 | ab.restart_agnc_services() 74 | else: 75 | alert(_('AGNC Services should be restarted.')) 76 | menu.item_restart_service.show() 77 | -------------------------------------------------------------------------------- /src/smart_agnc/windows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # system imports 4 | import gobject 5 | import gtk 6 | 7 | 8 | class _Window(gtk.Window): 9 | """ 10 | Abstract Window class that predefines the behaviour of every window. It 11 | lets you close with Esc and submit with Return. It also adds a new signal 12 | called `form-submit` to handle the Return key. 13 | """ 14 | 15 | def __init__(self): 16 | super(_Window, self).__init__() 17 | 18 | self.set_resizable(False) 19 | self.set_border_width(10) 20 | self.connect('delete-event', __prevent_destroy__) 21 | self.connect('key-press-event', __on_key__) 22 | 23 | gobject.signal_new('form-submit', _Window, gobject.SIGNAL_RUN_FIRST, None, ()) 24 | 25 | 26 | def __on_key__(widget, evt): 27 | if evt.keyval == gtk.keysyms.Escape: 28 | widget.hide() 29 | 30 | if evt.keyval == gtk.keysyms.Return: 31 | widget.emit('form-submit') 32 | 33 | 34 | def __prevent_destroy__(widget, _=None): 35 | widget.hide() 36 | return True 37 | 38 | 39 | class _WindowCentered(object): 40 | 41 | def present(self): 42 | self.set_position(gtk.WIN_POS_CENTER) 43 | super(_Window, self).present() 44 | 45 | 46 | class _WindowForm(_Window): 47 | 48 | def __init__(self): 49 | super(_WindowForm, self).__init__() 50 | 51 | table = gtk.Table(rows=4, columns=3) 52 | table.set_col_spacings(10) 53 | table.set_row_spacings(10) 54 | self.add(table) 55 | 56 | self.table = table 57 | 58 | def _attach(self, widget, left, top, width=1, height=1, table=None): 59 | """ 60 | Attaches a widget into a position inside the table occupying one cell. 61 | Returns the inserted widget 62 | """ 63 | if not table: 64 | table = self.table 65 | table.attach(widget, left, left + width, top, top + height) 66 | return widget 67 | 68 | def _make_entry(self, left, top, table=None): 69 | """ 70 | Returns and attaches an entry with some defaults appropriate to AGNC. 71 | """ 72 | entry = gtk.Entry() 73 | entry.set_max_length(128) 74 | entry.set_width_chars(20) 75 | return self._attach(entry, left, top, table=table) 76 | 77 | def _make_label(self, txt, left, top, table=None): 78 | """ 79 | Attaches a label to the table. Returns the new label. 80 | """ 81 | label = gtk.Label(txt) 82 | label.set_alignment(0, 0.5) 83 | return self._attach(label, left, top, table=table) 84 | -------------------------------------------------------------------------------- /stdeb.cfg: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | Package: smart-agnc 3 | Suite: trusty 4 | Depends: ${shlibs:Depends}, agnclient, python-appindicator, python-notify, 5 | python-iniparse 6 | Build-Depends: gettext, librsvg2-bin 7 | Dpkg-Shlibdeps-Params: --ignore-missing-info 8 | --------------------------------------------------------------------------------