├── AUTHORS ├── COPYING ├── ChangeLog ├── INSTALL ├── LICENSE ├── Makefile.am ├── NEWS ├── README.md ├── autogen.sh ├── configure.ac ├── data ├── org.agronick.relay.gschema.xml └── relay.desktop ├── relay_icon.xcf └── src ├── Makefile.am ├── assets ├── channel-loading.svg ├── connect-server.png ├── help-info-symbolic.svg ├── list-add-symbolic.svg ├── list-remove-symbolic.svg ├── mail-unread.svg ├── manage-servers.png ├── paste.png ├── saved-server.png ├── server-icon-light.svg ├── server-icon.svg ├── system-users.svg ├── user-idle.svg └── user-offline.svg ├── channel_tab.vala ├── config.vapi ├── connection.vala ├── drag_file.vala ├── irc.vala ├── main_entry.vala ├── main_window.vala ├── message.vala ├── relay.vala ├── rich_text.vala ├── server_manager.vala ├── settings.vala ├── sqlclient.vala └── ui ├── relay.png ├── relay.svg ├── relay.ui ├── server_window.ui └── settings_window.ui /AUTHORS: -------------------------------------------------------------------------------- 1 | I started making this program in June of 2015 as a way to give back to the Elementary OS project. I hope you enjoy using it. 2 | 3 | Copyright (C) 2015 Kyle Agronick 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | {description} 294 | Copyright (C) {year} {fullname} 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 | {signature of Ty Coon}, 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 | 341 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015-08-08 Kyle Agronick 2 | 3 | Press up and down to review previous messages 4 | 5 | 2015-07-25 Kyle Agronick 6 | 7 | Save a conversation just by right clicking the screen 8 | 9 | 2015-07-24 Kyle Agronick 10 | 11 | Added users search box 12 | 13 | 2015-07-14 Kyle Agronick 14 | 15 | Added settings and color picking 16 | Bug fixes 17 | 18 | 2015-07-08 Kyle Agronick 19 | 20 | Added integration with Hastebin 21 | Better error handling 22 | Bug fixes 23 | 24 | 2015-07-06 Kyle Agronick 25 | 26 | Stopped the tabs from becoming small 27 | Put an icon on the sidebar indicating if the channel/server is open or now 28 | Right click any tab to change tabs 29 | 30 | 2015-07-05 Kyle Agronick 31 | 32 | Added the ability to click a user name on the user popover or the chat window 33 | and have their name fill the main text entry 34 | 35 | 2015-07-06 Kyle Agronick 36 | 37 | Added the ability to click a user name on the user popover or the chat window 38 | and have their name fill the main text entry 39 | 40 | 2015-06-30 Kyle Agronick 41 | 42 | Packaging for inital public beta release 43 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | DEPENDENCIES 3 | -------------------------------------------------------------------------------- 4 | These are listed as Ubuntu packages. (They are reported to be the same on Arch 5 | Linux as well) You may need to adjust for your distribution. 6 | To compile you will need the following dependencies: 7 | 8 | automake 9 | libglib2.0-dev 10 | libx11-dev 11 | valac-0.26 12 | libgranite-dev 13 | libsqlite3-dev 14 | libgee-0.8-dev 15 | libgtk-3-dev 16 | libunity-dev 17 | libtool-bin or libtool depending on the distro 18 | 19 | If you are on a distribution that uses apt-get you can use the following command. 20 | (sudo apt-get install libtool-bin libtool libgtk-3-dev libgee-0.8-dev libsqlite3-dev libgranite-dev valac-0.26 libx11-dev libglib2.0-dev automake libunity-dev) 21 | 22 | (Other versions of valac may work. But newer versions seems to have a bug that prevents compiling) 23 | 24 | -------------------------------------------------------------------------------- 25 | BUILDING AND INSTALLING 26 | -------------------------------------------------------------------------------- 27 | mkdir build 28 | cd build 29 | ../autogen.sh 30 | make 31 | sudo make install 32 | 33 | If you want to use the program without installing you must install the settings gsettings xml file in /data/org.agronick.relay.gschema.xml 34 | Running make install does this automatically. 35 | -------------------------------------------------------------------------------- /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 | {description} 294 | Copyright (C) {year} {fullname} 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 | {signature of Ty Coon}, 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 | 341 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | ## Created by Anjuta 3 | 4 | SUBDIRS = src 5 | 6 | ACLOCAL_AMFLAGS=-I m4 7 | 8 | dist_doc_DATA = \ 9 | README.md \ 10 | COPYING \ 11 | AUTHORS \ 12 | ChangeLog \ 13 | INSTALL 14 | 15 | # Remove doc directory on uninstall 16 | uninstall-local: 17 | -rm -r $(docdir) 18 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | set ttf-inconsolata as depends 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![logo](https://cdn.rawgit.com/agronick/Relay/master/src/ui/relay.svg) Relay 2 | *Created by Kyle Agronick * 3 | 4 | This was a fun little project I made a few years ago. I don't have the time right now to maintain it. If anyone wants to take it over I'd be happy to give them access. 5 | 6 | Relay is an IRC client that attemps to be small, quick, easy to use, elegant, and functional. 7 | The Github and Launchpad repos are mirrored. You can find up-to-date information about Relay on Launchpad and Github. 8 | 9 | If Relay displays with a light interface please open up an issue and let me know what displays when you type 10 | `echo $XDG_CURRENT_DESKTOP`. I will need to add some code to detect and load a suitable dark theme preloaded 11 | in your distro. 12 | 13 | ![screenshot](http://bit.ly/1M6dYGZ) 14 | 15 | * **Installation and Licensing** 16 | Please consult the `INSTALL` and `COPYING` files for more information. 17 | If you would rather not install from source you can use the following 18 | commands on a platform that uses apt-get: 19 | 20 | ``` 21 | sudo apt-add-repository "ppa:agronick/relay" 22 | sudo apt-get update 23 | sudo apt-get install relay 24 | ``` 25 | 26 | * **Website** 27 | The project's oficial website is https://poisonpacket.wordpress.com/relay/ 28 | 29 | * **Bugs** 30 | Please report bugs at https://github.com/agronick/Relay 31 | 32 | * **Translations** 33 | To contribute translations please visit http://translations.launchpad.net/relay 34 | 35 | * **Donations** 36 | If you want to support the project, please consider a donation at https://poisonpacket.wordpress.com/relay/ 37 | 38 | * **Further documentation** 39 | For more documentation visit https://poisonpacket.wordpress.com/relay/ 40 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run this to generate all the initial makefiles, etc. 3 | 4 | srcdir=`dirname $0` 5 | test -z "$srcdir" && srcdir=. 6 | 7 | DIE=0 8 | 9 | if [ -n "$GNOME2_DIR" ]; then 10 | ACLOCAL_FLAGS="-I $GNOME2_DIR/share/aclocal $ACLOCAL_FLAGS" 11 | LD_LIBRARY_PATH="$GNOME2_DIR/lib:$LD_LIBRARY_PATH" 12 | PATH="$GNOME2_DIR/bin:$PATH" 13 | export PATH 14 | export LD_LIBRARY_PATH 15 | fi 16 | 17 | (test -f $srcdir/configure.ac) || { 18 | echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" 19 | echo " top-level package directory" 20 | exit 1 21 | } 22 | 23 | (autoconf --version) < /dev/null > /dev/null 2>&1 || { 24 | echo 25 | echo "**Error**: You must have \`autoconf' installed." 26 | echo "Download the appropriate package for your distribution," 27 | echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" 28 | DIE=1 29 | } 30 | 31 | (grep "^IT_PROG_INTLTOOL" $srcdir/configure.ac >/dev/null) && { 32 | (intltoolize --version) < /dev/null > /dev/null 2>&1 || { 33 | echo 34 | echo "**Error**: You must have \`intltool' installed." 35 | echo "You can get it from:" 36 | echo " ftp://ftp.gnome.org/pub/GNOME/" 37 | DIE=1 38 | } 39 | } 40 | 41 | (grep "^AM_PROG_XML_I18N_TOOLS" $srcdir/configure.ac >/dev/null) && { 42 | (xml-i18n-toolize --version) < /dev/null > /dev/null 2>&1 || { 43 | echo 44 | echo "**Error**: You must have \`xml-i18n-toolize' installed." 45 | echo "You can get it from:" 46 | echo " ftp://ftp.gnome.org/pub/GNOME/" 47 | DIE=1 48 | } 49 | } 50 | 51 | (grep "^LT_INIT" $srcdir/configure.ac >/dev/null) && { 52 | (libtool --version) < /dev/null > /dev/null 2>&1 || { 53 | echo 54 | echo "**Error**: You must have \`libtool' installed." 55 | echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" 56 | DIE=1 57 | } 58 | } 59 | 60 | (grep "^AM_GLIB_GNU_GETTEXT" $srcdir/configure.ac >/dev/null) && { 61 | (grep "sed.*POTFILES" $srcdir/configure.ac) > /dev/null || \ 62 | (glib-gettextize --version) < /dev/null > /dev/null 2>&1 || { 63 | echo 64 | echo "**Error**: You must have \`glib' installed." 65 | echo "You can get it from: ftp://ftp.gtk.org/pub/gtk" 66 | DIE=1 67 | } 68 | } 69 | 70 | (automake --version) < /dev/null > /dev/null 2>&1 || { 71 | echo 72 | echo "**Error**: You must have \`automake' installed." 73 | echo "You can get it from: ftp://ftp.gnu.org/pub/gnu/" 74 | DIE=1 75 | NO_AUTOMAKE=yes 76 | } 77 | 78 | 79 | # if no automake, don't bother testing for aclocal 80 | test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || { 81 | echo 82 | echo "**Error**: Missing \`aclocal'. The version of \`automake'" 83 | echo "installed doesn't appear recent enough." 84 | echo "You can get automake from ftp://ftp.gnu.org/pub/gnu/" 85 | DIE=1 86 | } 87 | 88 | if test "$DIE" -eq 1; then 89 | exit 1 90 | fi 91 | 92 | if test -z "$*"; then 93 | echo "**Warning**: I am going to run \`configure' with no arguments." 94 | echo "If you wish to pass any to it, please specify them on the" 95 | echo \`$0\'" command line." 96 | echo 97 | fi 98 | 99 | case $CC in 100 | xlc ) 101 | am_opt=--include-deps;; 102 | esac 103 | 104 | for coin in `find $srcdir -path $srcdir/CVS -prune -o -name configure.ac -print` 105 | do 106 | dr=`dirname $coin` 107 | if test -f $dr/NO-AUTO-GEN; then 108 | echo skipping $dr -- flagged as no auto-gen 109 | else 110 | echo processing $dr 111 | ( cd $dr 112 | 113 | aclocalinclude="$ACLOCAL_FLAGS" 114 | 115 | if grep "^AM_GLIB_GNU_GETTEXT" configure.ac >/dev/null; then 116 | echo "Creating $dr/aclocal.m4 ..." 117 | test -r $dr/aclocal.m4 || touch $dr/aclocal.m4 118 | echo "Running glib-gettextize... Ignore non-fatal messages." 119 | echo "no" | glib-gettextize --force --copy 120 | echo "Making $dr/aclocal.m4 writable ..." 121 | test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4 122 | fi 123 | if grep "^IT_PROG_INTLTOOL" configure.ac >/dev/null; then 124 | echo "Running intltoolize..." 125 | intltoolize --copy --force --automake 126 | fi 127 | if grep "^AM_PROG_XML_I18N_TOOLS" configure.ac >/dev/null; then 128 | echo "Running xml-i18n-toolize..." 129 | xml-i18n-toolize --copy --force --automake 130 | fi 131 | if grep "^LT_INIT" configure.ac >/dev/null; then 132 | if test -z "$NO_LIBTOOLIZE" ; then 133 | echo "Running libtoolize..." 134 | libtoolize --force --copy 135 | fi 136 | fi 137 | echo "Running aclocal $aclocalinclude ..." 138 | aclocal $aclocalinclude 139 | if grep "^A[CM]_CONFIG_HEADER" configure.ac >/dev/null; then 140 | echo "Running autoheader..." 141 | autoheader 142 | fi 143 | echo "Running automake --gnu $am_opt ..." 144 | automake --add-missing --copy --gnu $am_opt 145 | echo "Running autoconf ..." 146 | autoconf 147 | ) 148 | fi 149 | done 150 | 151 | if test x$NOCONFIGURE = x; then 152 | echo Running $srcdir/configure "$@" ... 153 | $srcdir/configure "$@" \ 154 | && echo Now type \`make\' to compile. || exit 1 155 | else 156 | echo Skipping configure process. 157 | fi 158 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl Process this file with autoconf to produce a configure script. 2 | dnl Created by Anjuta application wizard. 3 | 4 | AC_INIT(relay, 2.0.16) 5 | 6 | AC_CONFIG_HEADERS([config.h]) 7 | 8 | AM_INIT_AUTOMAKE([1.11 foreign]) 9 | 10 | AC_CONFIG_MACRO_DIR([m4]) 11 | 12 | AM_SILENT_RULES([yes]) 13 | 14 | AC_PROG_CC 15 | 16 | GLIB_GSETTINGS 17 | 18 | 19 | LT_INIT 20 | 21 | dnl Check for vala 22 | AM_PROG_VALAC([0.10.0]) 23 | 24 | 25 | PKG_CHECK_MODULES(RELAY, granite 26 | sqlite3 27 | gee-0.8 28 | glib-2.0 29 | unity 30 | [gtk+-3.0 ]) 31 | 32 | 33 | AC_OUTPUT([ 34 | Makefile 35 | src/Makefile 36 | ]) -------------------------------------------------------------------------------- /data/org.agronick.relay.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | false 9 | 10 | 11 | false 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | 19 | 20 | true 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | 30 | "default" 31 | 32 | 33 | "default" 34 | 35 | 36 | "default" 37 | 38 | 39 | "default" 40 | 41 | 42 | "default" 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /data/relay.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Encoding=UTF-8 4 | Name=Relay 5 | Comment=IRC Client for the Modern Desktop 6 | Exec=relay 7 | Icon=relay 8 | Terminal=false 9 | Type=Application 10 | StartupNotify=false 11 | Categories=Network;IRCClient;Chat; -------------------------------------------------------------------------------- /relay_icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/relay_icon.xcf -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | ## Created by Anjuta 4 | 5 | uidir = $(pkgdatadir)/ui 6 | ui_DATA = ui/relay.ui ui/server_window.ui ui/relay.svg ui/settings_window.ui 7 | 8 | assetsdir = $(pkgdatadir)/assets 9 | assets_DATA = ui/relay.png \ 10 | assets/connect-server.png \ 11 | assets/help-info-symbolic.svg \ 12 | assets/mail-unread.svg \ 13 | assets/manage-servers.png \ 14 | assets/paste.png \ 15 | assets/saved-server.png \ 16 | assets/server-icon.svg \ 17 | assets/server-icon-light.svg \ 18 | assets/system-users.svg \ 19 | assets/user-idle.svg \ 20 | assets/user-offline.svg \ 21 | assets/list-add-symbolic.svg \ 22 | assets/list-remove-symbolic.svg \ 23 | assets/channel-loading.svg 24 | 25 | appicondir = $(datadir)/icons/hicolor/scalable/apps 26 | appicon_DATA = ui/relay.svg 27 | 28 | desktopdir = $(datadir)/applications 29 | desktop_DATA = ../data/relay.desktop 30 | 31 | gsettings_SCHEMAS = ../data/org.agronick.relay.gschema.xml 32 | @GSETTINGS_RULES@ 33 | 34 | AM_CPPFLAGS = \ 35 | -DPACKAGE_LOCALE_DIR=\""$(localedir)"\" \ 36 | -DPACKAGE_SRC_DIR=\""$(srcdir)"\" \ 37 | -DPACKAGE_DATA_DIR=\""$(pkgdatadir)"\" \ 38 | -DGETTEXT_PACKAGE=\""relay"\"\ 39 | $(RELAY_CFLAGS) 40 | 41 | AM_CFLAGS =\ 42 | -Wall\ 43 | -g 44 | 45 | bin_PROGRAMS = relay 46 | 47 | relay_SOURCES = \ 48 | config.vapi \ 49 | main_window.vala \ 50 | sqlclient.vala \ 51 | server_manager.vala \ 52 | channel_tab.vala \ 53 | message.vala \ 54 | irc.vala \ 55 | relay.vala \ 56 | connection.vala \ 57 | rich_text.vala \ 58 | drag_file.vala \ 59 | org.github.agronick.relay \ 60 | settings.vala \ 61 | main_entry.vala 62 | 63 | relay_VALAFLAGS = \ 64 | -X -DGETTEXT_PACKAGE="relay" --target-glib 2.32 --pkg unity --pkg gee-0.8 --pkg x11 --pkg gtk+-3.0 --pkg glib-2.0 --pkg granite --pkg sqlite3 65 | 66 | relay_LDFLAGS = \ 67 | -lm -lX11 -lglib-2.0 68 | 69 | relay_LDADD = $(RELAY_LIBS) 70 | 71 | EXTRA_DIST = $(ui_DATA) $(assets_DATA) 72 | 73 | UPDATE_DESKTOP = sudo update-desktop-database $(desktopdir) || : 74 | 75 | install-data-hook: 76 | $(UPDATE_DESKTOP) || :; \ 77 | sudo rm $(datadir)/icons/hicolor/scalable/apps/relay.png || :; \ 78 | sudo rm $(pkgdatadir)/ui/relay.png || :; \ 79 | sudo gtk-update-icon-cache -f -t $(datadir)/icons/hicolor || : 80 | 81 | uninstall-local: 82 | -rm -r $(uidir) 83 | -rm -r $(imagedir) 84 | -rm -r $(pkgdatadir) 85 | 86 | -------------------------------------------------------------------------------- /src/assets/connect-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/src/assets/connect-server.png -------------------------------------------------------------------------------- /src/assets/help-info-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | elementary Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | elementary Symbolic Icon Theme 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/assets/list-add-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | elementary Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | elementary Symbolic Icon Theme 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/assets/list-remove-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | elementary Symbolic Icon Theme 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | elementary Symbolic Icon Theme 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/assets/mail-unread.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 41 | 50 | 52 | 56 | 60 | 64 | 68 | 69 | 71 | 75 | 79 | 83 | 87 | 88 | 90 | 94 | 98 | 99 | 109 | 118 | 128 | 139 | 140 | 142 | 143 | 145 | image/svg+xml 146 | 148 | 149 | 150 | 151 | 152 | 157 | 162 | 167 | 168 | -------------------------------------------------------------------------------- /src/assets/manage-servers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/src/assets/manage-servers.png -------------------------------------------------------------------------------- /src/assets/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/src/assets/paste.png -------------------------------------------------------------------------------- /src/assets/saved-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/src/assets/saved-server.png -------------------------------------------------------------------------------- /src/assets/server-icon-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 52 | 57 | 62 | 67 | 72 | 77 | 78 | -------------------------------------------------------------------------------- /src/assets/server-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 52 | 57 | 58 | -------------------------------------------------------------------------------- /src/assets/system-users.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 31 | 35 | 39 | 40 | 42 | 46 | 50 | 51 | 53 | 57 | 61 | 62 | 72 | 81 | 90 | 99 | 109 | 118 | 127 | 137 | 146 | 155 | 165 | 174 | 175 | 177 | 180 | 185 | 190 | 195 | 200 | 201 | 203 | 208 | 213 | 218 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /src/assets/user-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | 35 | 36 | 38 | 42 | 46 | 47 | 49 | 53 | 57 | 61 | 65 | 66 | 68 | 72 | 76 | 80 | 84 | 85 | 87 | 91 | 95 | 99 | 103 | 104 | 113 | 123 | 132 | 142 | 143 | 147 | 151 | 155 | 156 | -------------------------------------------------------------------------------- /src/channel_tab.vala: -------------------------------------------------------------------------------- 1 | 2 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 3 | /* 4 | * channel_tab.vala 5 | * Copyright (C) 2015 Kyle Agronick 6 | * 7 | * KyRC is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * KyRC is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program. If not, see . 19 | */ 20 | 21 | using Granite; 22 | using Gtk; 23 | using Gee; 24 | using Gdk; 25 | 26 | public class ChannelTab : GLib.Object { 27 | public int tab_index { get; set; } 28 | public Connection connection { get; set; } 29 | public string channel_name { 30 | get{return tab.label;} 31 | set{tab.label = value;} 32 | } 33 | public Granite.Widgets.Tab tab; 34 | public bool is_server_tab = false; 35 | public bool has_subject = false; 36 | public string channel_subject = ""; 37 | public bool is_locked = false; 38 | public LinkedList users = new LinkedList(); 39 | public LinkedList ops = new LinkedList(); 40 | public LinkedList half_ops = new LinkedList(); 41 | public LinkedList owners = new LinkedList(); 42 | public LinkedList blocked_users = new LinkedList(); 43 | private TextView output; 44 | public int message_count = 0; 45 | private LinkedList pending_msg = new LinkedList(); 46 | private LinkedList pending_err = new LinkedList(); 47 | public string channel_url = ""; 48 | public bool needs_spacer = false; 49 | 50 | public static TimeVal timeval = TimeVal(); 51 | public static int timestamp_seconds = 180; 52 | private long last_timestamp = 0; 53 | private string last_user = ""; 54 | string date_format = "%A, %B %e"; 55 | string time_format = Granite.DateTime.get_default_time_format(true, true); 56 | 57 | TextTag user_other_tag; 58 | TextTag user_self_tag; 59 | TextTag std_message_tag; 60 | TextTag full_width_tag; 61 | TextTag error_tag; 62 | TextTag link_tag; 63 | TextTag name_hilight_tag; 64 | TextTag timestamp_tag; 65 | TextTag spacing_tag; 66 | TextTag other_name_hilight_tag; 67 | TextTag spacer_line_tag; 68 | 69 | public signal void new_subject(int tab_id, string subject); 70 | 71 | public void add_text (string msg) { 72 | connection.send_output(msg); 73 | } 74 | 75 | public ChannelTab (Connection param_server) { 76 | connection = param_server; 77 | } 78 | 79 | public void set_tab (Widgets.Tab t, int index) { 80 | tab_index = index; 81 | tab = t; 82 | } 83 | 84 | public void set_topic (string subject, bool append = false) { 85 | channel_subject = append ? channel_subject + subject + "\n": subject; 86 | has_subject = true; 87 | Idle.add( () => { 88 | new_subject(tab_index, channel_subject); 89 | return false; 90 | }); 91 | } 92 | 93 | public void add_block_list (string name) { 94 | if (name == null || name.strip().length < 2) 95 | return; 96 | 97 | blocked_users.add(name); 98 | 99 | if (IRC.user_prefixes.index_of_char(name[0]) != -1) 100 | blocked_users.add(IRC.remove_user_prefix(name)); 101 | 102 | } 103 | 104 | public void remove_block_list (string name) { 105 | blocked_users.remove(name); 106 | 107 | if (IRC.user_prefixes.index_of_char(name[0]) != -1) 108 | blocked_users.remove(IRC.remove_user_prefix(name)); 109 | } 110 | 111 | public void add_users_message (Message message) { 112 | var names = message.message.split(" "); 113 | foreach (var name in names) { 114 | if (name.strip().length < 2) 115 | continue; 116 | 117 | add_user(name, false); 118 | } 119 | } 120 | 121 | public string fix_user_name (string? name) { 122 | if (name == null) 123 | return ""; 124 | if (IRC.user_prefixes.index_of_char(name[0]) != -1) 125 | return name.substring(1); 126 | else 127 | return name; 128 | } 129 | 130 | public LinkedList> get_all_user_lists() { 131 | LinkedList> user_types = new LinkedList>(); 132 | user_types.add(owners); 133 | user_types.add(ops); 134 | user_types.add(half_ops); 135 | user_types.add(users); 136 | return user_types; 137 | } 138 | 139 | public void user_name_change(string _old_name, string _new_name) { 140 | string old_name = fix_user_name(_old_name); 141 | string new_name = fix_user_name(_new_name); 142 | 143 | foreach (LinkedList list in get_all_user_lists()) { 144 | int index = list.index_of(old_name); 145 | if (index != -1) { 146 | list.remove(old_name); 147 | add_name_ordered(ref list, new_name, true); 148 | } 149 | } 150 | 151 | if (connection.server.nickname == old_name || connection.server.nickname == new_name) 152 | connection.server.nickname = new_name; 153 | 154 | if (MainWindow.settings.get_bool("show_join") || new_name == connection.server.nickname) 155 | add_with_tag(old_name + _(" is now known as ") + new_name + "\n", full_width_tag); 156 | } 157 | 158 | public void user_leave_channel(string _name, string msg) { 159 | string name = fix_user_name(_name); 160 | if (users.contains(name)) { 161 | last_user = ""; 162 | if (MainWindow.settings.get_bool("show_join")) { 163 | space(); 164 | string colon = (msg.strip().length > 0) ? ": " + msg : ""; 165 | add_with_tag(name + _(" has left") + colon + "\n", full_width_tag); 166 | } 167 | } 168 | if (!users.remove(name)) 169 | if (!ops.remove(name)) 170 | if (!half_ops.remove(name)) 171 | owners.remove(name); 172 | } 173 | 174 | public void user_join_channel(string name) { 175 | last_user = ""; 176 | string uname = add_user(name, true); 177 | if (uname == null) 178 | return; 179 | if (MainWindow.settings.get_bool("show_join")) { 180 | space(); 181 | add_with_tag(uname + _(" has joined: ") + channel_name + "\n", full_width_tag); 182 | } 183 | } 184 | 185 | public string? add_user(string? user, bool sorted = true) { 186 | if (user == null) 187 | return null; 188 | 189 | bool op = (user[0] == '@'); 190 | bool halfop = (user[0] == '%'); 191 | bool owner = (user[0] == '~'); 192 | string uname = fix_user_name(user); 193 | 194 | if (op && !ops.contains(uname)) 195 | add_name_ordered(ref ops, uname, sorted); 196 | else if (halfop && !half_ops.contains(uname)) 197 | add_name_ordered(ref half_ops, uname, sorted); 198 | else if (owner && !owners.contains(uname)) 199 | add_name_ordered(ref owners, uname, sorted); 200 | else if (!users.contains(uname)) 201 | add_name_ordered(ref users, uname, sorted); 202 | 203 | return uname; 204 | } 205 | 206 | public void sort_names () { 207 | Relay.sort_clean(ref owners); 208 | Relay.sort_clean(ref ops); 209 | Relay.sort_clean(ref half_ops); 210 | Relay.sort_clean(ref users); 211 | } 212 | 213 | public void add_name_ordered (ref LinkedList list, string name, bool ordered) { 214 | if (!ordered) { 215 | list.add(name); 216 | return; 217 | } 218 | 219 | int i = 0; 220 | foreach (var item in list) { 221 | int compare = Relay.compare(name, item); 222 | if (compare < 1) { 223 | list.insert(i, name); 224 | return; 225 | } 226 | i++; 227 | } 228 | list.insert(i, name); 229 | } 230 | 231 | public void set_output(TextView _output) { 232 | output = _output; 233 | output.buffer.changed.connect(do_autoscroll); 234 | 235 | user_other_tag = output.buffer.create_tag("user_other"); 236 | user_self_tag = output.buffer.create_tag("user_self"); 237 | std_message_tag = output.buffer.create_tag("std_message"); 238 | full_width_tag = output.buffer.create_tag("full_width"); 239 | error_tag = output.buffer.create_tag("error"); 240 | link_tag = output.buffer.create_tag("link"); 241 | name_hilight_tag = output.buffer.create_tag("name_hilight"); 242 | timestamp_tag = output.buffer.create_tag("timestamp"); 243 | spacing_tag = output.buffer.create_tag("spacing"); 244 | other_name_hilight_tag = output.buffer.create_tag("other_name"); 245 | spacer_line_tag = output.buffer.create_tag("spacer_line"); 246 | 247 | update_tag_table(); 248 | 249 | var _pending_msg = pending_msg; 250 | var _pending_err = pending_err; 251 | pending_msg = null; 252 | pending_err = null; 253 | foreach (var msg in _pending_msg) 254 | display_message(msg); 255 | 256 | foreach (var msg in _pending_err) 257 | display_error(msg); 258 | } 259 | 260 | public TextView get_output () { 261 | return output; 262 | } 263 | 264 | public void send_text_out (string message) { 265 | string formatted_message = ""; 266 | if (message.length > 4 && message[0:4] == "/msg") { 267 | formatted_message = parse_message_cmd(message); 268 | } else if (message.length > 5 && message[0:5] == "/nick") { 269 | formatted_message = parse_nick_change(message); 270 | } else { 271 | if (is_server_tab) { 272 | formatted_message = format_server_msg(message); 273 | } else { 274 | formatted_message = format_channel_msg(message); 275 | } 276 | if (formatted_message.strip().length == 0) 277 | return; 278 | } 279 | connection.send_output(formatted_message); 280 | } 281 | 282 | public string format_channel_msg (string message) { 283 | if (message[0] == '/') 284 | return message.substring(1); 285 | return "PRIVMSG " + channel_name + " :" + message; 286 | } 287 | 288 | public string parse_nick_change (string? message) { 289 | if (message == null) 290 | return ""; 291 | string[] split = message.split(" "); 292 | connection.server.nickname = split[1]; 293 | return "NICK " + split[1]; 294 | } 295 | 296 | public string format_server_msg (string message) { 297 | if (message[0] != '/') { 298 | add_with_tag(_("Start you command with a / to send it \n"), error_tag); 299 | return ""; 300 | } 301 | return message.substring(1); 302 | } 303 | 304 | public void display_message (Message message) { 305 | if (pending_msg != null) { 306 | pending_msg.add(message); 307 | return; 308 | } 309 | 310 | message.message = message.get_msg_txt(); 311 | message.message += "\n"; 312 | 313 | switch (message.command) { 314 | case "PRIVMSG": 315 | handle_private_message(message); 316 | break; 317 | case "NOTICE": 318 | case IRC.RPL_MOTD: 319 | case IRC.RPL_MOTDSTART: 320 | add_with_tag(message.message, full_width_tag); 321 | break; 322 | default: 323 | add_with_tag(message.message, full_width_tag); 324 | break; 325 | } 326 | } 327 | 328 | public void display_error (Message message) { 329 | if (pending_err != null) { 330 | pending_err.add(message); 331 | return; 332 | } 333 | tab.working = false; 334 | message.message += "\n"; 335 | add_with_tag(message.message, error_tag); 336 | } 337 | 338 | public string space(int count = 1) { 339 | string txt = " \n"; 340 | for (int i = 0; i < count - 1; i++) 341 | txt += txt; 342 | add_with_tag(txt, spacing_tag); 343 | return txt; 344 | } 345 | 346 | public void handle_private_message (Message message) { 347 | if (blocked_users.contains(fix_user_name(message.user_name))) 348 | return; 349 | 350 | string user = message.user_name_get(); 351 | if (!is_server_tab && user == last_user) 352 | user = ""; 353 | else { 354 | if (!make_timestamp()) 355 | space(); 356 | last_user = user; 357 | } 358 | 359 | add_with_tag(user, message.internal ? user_self_tag : user_other_tag); 360 | add_with_tag(message.message, std_message_tag); 361 | } 362 | 363 | private bool make_timestamp() { 364 | if (!MainWindow.settings.get_bool("show_datestamp")) 365 | return false; 366 | 367 | timeval.get_current_time(); 368 | long current = timeval.tv_sec; 369 | if (current - last_timestamp > timestamp_seconds) { 370 | var local = new GLib.DateTime.now_local(); 371 | string datetime = local.format(date_format + " " + time_format) + "\n"; 372 | add_with_tag(datetime, timestamp_tag); 373 | last_timestamp = current; 374 | return true; 375 | } 376 | return false; 377 | } 378 | 379 | public void do_autoscroll () { 380 | ScrolledWindow sw = (ScrolledWindow) output.get_parent(); 381 | Adjustment position = sw.get_vadjustment(); 382 | if (!(position is Adjustment)) 383 | return; 384 | if (position.value > position.upper - position.page_size - 75) { 385 | Idle.add( () => { 386 | position.set_value(position.upper - position.page_size); 387 | sw.set_vadjustment(position); 388 | return false; 389 | }); 390 | } 391 | } 392 | 393 | private void add_with_tag (string? text, TextTag? tag, int retry_count = 0) { 394 | if (text == null || 395 | tag == null || 396 | retry_count > 4 || 397 | (text.strip() == "" && tag != spacing_tag)) 398 | return; 399 | 400 | 401 | var rich_text = new RichText(text); 402 | if (tag == full_width_tag || tag == std_message_tag) { 403 | rich_text.parse_links(); 404 | if (tag == std_message_tag) { 405 | foreach (var usr in users) 406 | rich_text.parse_name(usr); 407 | } 408 | } 409 | 410 | while (is_locked) { 411 | Thread.usleep(111); 412 | } 413 | Idle.add( () => { 414 | is_locked = true; 415 | TextIter? end; 416 | output.buffer.get_end_iter(out end); 417 | if (end == null) { 418 | add_with_tag(text, tag, retry_count++); 419 | return false; 420 | } 421 | output.buffer.insert_text(ref end, text, text.length); 422 | TextIter start = end; 423 | start.backward_chars(text.length); 424 | output.buffer.apply_tag(tag, start, end); 425 | if (rich_text.has_links) { 426 | for (int i = 0; i < rich_text.link_locations_start.size; i++) 427 | { 428 | output.buffer.get_end_iter(out end); 429 | start = end; 430 | start.set_offset(start.get_offset() - rich_text.link_locations_start[i]); 431 | end.set_offset(end.get_offset() - rich_text.link_locations_end[i]); 432 | output.buffer.apply_tag(link_tag, start, end); 433 | } 434 | } 435 | if (rich_text.has_names) { 436 | for (int i = 0; i < rich_text.names.size; i++) 437 | { 438 | output.buffer.get_end_iter(out end); 439 | start = end; 440 | start.set_offset(start.get_offset() - rich_text.name_location_start[i]); 441 | end.set_offset(end.get_offset() - rich_text.name_location_end[i]); 442 | TextTag utag = (rich_text.names[i] == connection.server.nickname) ? name_hilight_tag : other_name_hilight_tag; 443 | utag.set_data("uname", output.buffer.get_text(start, end, false)); 444 | output.buffer.apply_tag( 445 | utag, 446 | start, end); 447 | } 448 | } 449 | is_locked = false; 450 | return false; 451 | }); 452 | is_locked = false; 453 | } 454 | 455 | 456 | public void update_tag_table () { 457 | var color = Gdk.RGBA(); 458 | color.parse(MainWindow.settings.get_color("user-other-color")); 459 | user_other_tag.foreground_rgba = color; 460 | user_other_tag.left_margin = 0; 461 | user_other_tag.weight = Pango.Weight.SEMIBOLD; 462 | user_other_tag.event.connect(user_name_clicked); 463 | 464 | color.parse(MainWindow.settings.get_color("user-self-color")); 465 | user_self_tag.foreground_rgba = color; 466 | user_self_tag.left_margin = 0; 467 | user_self_tag.weight = Pango.Weight.SEMIBOLD; 468 | 469 | color.parse(MainWindow.settings.get_color("message-color")); 470 | std_message_tag.foreground_rgba = color; 471 | std_message_tag.indent = 0; 472 | 473 | full_width_tag.left_margin = 0; 474 | 475 | color.parse(Relay.is_light_theme ? "#752712" : "#C54725"); 476 | error_tag.foreground_rgba = color; 477 | error_tag.left_margin = 0; 478 | 479 | color.parse(MainWindow.settings.get_color("link-color")); 480 | link_tag.foreground_rgba = color; 481 | link_tag.underline_set = true; 482 | link_tag.event.connect(link_clicked); 483 | 484 | color.parse(Relay.is_light_theme ? "#3E749B" :"#2B94E0"); 485 | name_hilight_tag.foreground_rgba = color; 486 | name_hilight_tag.weight = Pango.Weight.SEMIBOLD; 487 | 488 | color.parse(Relay.is_light_theme ? "#748E16" :"#DEFF67"); 489 | other_name_hilight_tag.foreground_rgba = color; 490 | other_name_hilight_tag.event.connect(user_name_clicked); 491 | 492 | color.parse(MainWindow.settings.get_color("timestamp-color")); 493 | timestamp_tag.foreground_rgba = color; 494 | timestamp_tag.justification = Justification.RIGHT; 495 | timestamp_tag.size_points = 8; 496 | timestamp_tag.family = "Liberation Sans"; 497 | timestamp_tag.pixels_above_lines = 7; 498 | timestamp_tag.pixels_below_lines = 3; 499 | 500 | spacing_tag.size_points = 1; 501 | spacer_line_tag.justification = Justification.FILL; 502 | spacer_line_tag.underline = Pango.Underline.SINGLE; 503 | spacer_line_tag.left_margin = 15; 504 | spacer_line_tag.right_margin = 15; 505 | spacer_line_tag.foreground_rgba = output.get_style_context().get_background_color(StateFlags.NORMAL); 506 | color.parse(Relay.is_light_theme? "#C3C3C3" : "#505254"); 507 | spacer_line_tag.paragraph_background_rgba = color; 508 | spacer_line_tag.size_points = 0.5; 509 | } 510 | 511 | private int[] last_spacer_range = new int[2]; 512 | public void add_spacer_line () { 513 | Idle.add( () => { 514 | TextIter start; 515 | TextIter end; 516 | 517 | if (last_spacer_range[1] != 0) { 518 | output.get_buffer().get_iter_at_offset(out start, last_spacer_range[0]); 519 | output.get_buffer().get_iter_at_offset(out end, last_spacer_range[1]); 520 | output.buffer.delete(ref start, ref end); 521 | } 522 | 523 | output.buffer.get_end_iter(out start); 524 | space(3); 525 | add_with_tag("- \n", spacer_line_tag); 526 | space(2); 527 | last_spacer_range[0] = start.get_offset(); 528 | last_spacer_range[1] = last_spacer_range[0] + 17; //8 = space() * 3 + spacer_line_tag 529 | 530 | return false; 531 | }); 532 | } 533 | 534 | public bool user_name_clicked (GLib.Object event_object, Gdk.Event event, TextIter end) { 535 | if (event.type == EventType.BUTTON_RELEASE) { 536 | 537 | string name = get_tag_selection((TextView) event_object, ref end); 538 | 539 | if (name == "") 540 | return false; 541 | 542 | name += ": "; 543 | 544 | if (MainWindow.current_tab == tab_index) 545 | MainWindow.fill_input(name); 546 | } 547 | return false; 548 | } 549 | 550 | public bool link_clicked (GLib.Object event_object, Gdk.Event event, TextIter end) { 551 | if (event.type == EventType.BUTTON_RELEASE) { 552 | TextView tv = (TextView) event_object; 553 | TextIter start = end; 554 | 555 | string link = get_tag_selection(tv, ref end); 556 | 557 | if (link == "") 558 | return false; 559 | 560 | Granite.Services.System.open_uri(link); 561 | } 562 | return false; 563 | } 564 | 565 | public string get_tag_selection(TextView tv, ref TextIter end) { 566 | 567 | TextIter start = end; 568 | SList tags = end.get_tags(); 569 | TextTag utag = null; 570 | foreach (var tag in tags) { 571 | debug(tag.name); 572 | if (tag.name == "link" || 573 | tag.name == "other_name" || 574 | tag.name == "user_other") 575 | utag = tag; 576 | } 577 | 578 | if (utag == null) 579 | return ""; 580 | 581 | while (!end.ends_tag(utag)) 582 | end.forward_char(); 583 | 584 | while (!start.begins_tag(utag)) 585 | start.backward_char(); 586 | 587 | return tv.buffer.get_text(start, end, false).strip(); 588 | } 589 | 590 | private string parse_message_cmd(string message) { 591 | string[] split = message.split(" "); 592 | string[] slice = split[2:split.length]; 593 | string msg = "PRIVMSG " + split[1] + " :" + string.joinv(" ", slice); 594 | return msg; 595 | } 596 | 597 | public int get_char_count() { 598 | if (output == null) 599 | return 0; 600 | return output.buffer.get_char_count(); 601 | } 602 | } 603 | 604 | -------------------------------------------------------------------------------- /src/config.vapi: -------------------------------------------------------------------------------- 1 | [CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] 2 | namespace Config { 3 | public const string GETTEXT_PACKAGE; 4 | public const string SPRITE_DIR; 5 | public const string BACKGROUND_DIR; 6 | public const string PACKAGE_DATA_DIR; 7 | public const string PACKAGE_LOCALE_DIR; 8 | public const string PACKAGE_NAME; 9 | public const string PACKAGE_VERSION; 10 | public const string VERSION; 11 | } -------------------------------------------------------------------------------- /src/connection.vala: -------------------------------------------------------------------------------- 1 | 2 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 3 | /* 4 | * connection.vala 5 | * Copyright (C) 2015 Kyle Agronick 6 | * 7 | * KyRC is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * KyRC is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program. If not, see . 19 | */ 20 | 21 | using GLib; 22 | using Gtk; 23 | using Gee; 24 | 25 | public class Connection : Object 26 | { 27 | 28 | public static const uint16 DEFAULT_PORT = 6667; 29 | private DataInputStream input_stream; 30 | private DataOutputStream output_stream; 31 | public ChannelTab server_tab; 32 | public HashMap channel_tabs = new HashMap(); 33 | public LinkedList channel_autoconnect = new LinkedList(); 34 | public bool exit = false; 35 | public bool error_state = false; 36 | public bool autoconnect_ran = false; 37 | public SqlClient.Server server; 38 | 39 | public signal void new_tab(ChannelTab tab, string name); 40 | public signal void new_message(ChannelTab? tab, Message message, bool is_error = false); 41 | public signal void change_channel_state(string chan_name, string state); 42 | 43 | public bool connect_to_server (SqlClient.Server _server) { 44 | server = _server; 45 | server_tab = add_channel_tab(server.host); 46 | server_tab.is_server_tab = true; 47 | 48 | new Thread("Connection " + server.host, do_connect); 49 | 50 | return true; 51 | } 52 | 53 | private int do_connect () { 54 | try{ 55 | Resolver resolver = Resolver.get_default (); 56 | GLib.List addresses = resolver.lookup_by_name(server.host, null); 57 | InetAddress address = addresses.nth_data (0); 58 | 59 | 60 | SocketClient client = new SocketClient (); 61 | client.set_tls(server.encryption); 62 | client.set_tls_validation_flags(TlsCertificateFlags.UNKNOWN_CA); 63 | // Resolve hostname to IP address: 64 | SocketConnection conn = client.connect (new InetSocketAddress (address, (uint16) server.port)); 65 | input_stream = new DataInputStream (conn.input_stream); 66 | output_stream = new DataOutputStream (conn.output_stream); 67 | 68 | if (server.username.strip().length == 0) 69 | server.username = Environment.get_user_name(); 70 | if (server.nickname.strip().length == 0) 71 | server.nickname = Environment.get_user_name(); 72 | if (server.realname.strip().length == 0) 73 | server.realname = server.nickname; 74 | 75 | do_register(); 76 | 77 | string? line = ""; 78 | do{ 79 | size_t size; 80 | try{ 81 | line = input_stream.read_line(out size); 82 | debug("Raw input " + line); 83 | handle_input(line); 84 | }catch(IOError e) { 85 | error_state = true; 86 | warning("IO error while reading " + e.message); 87 | } 88 | }while (line != null && !exit); 89 | } catch (GLib.Error err) { 90 | var e = (owned) err; 91 | warning("Could not connect " + e.message); 92 | error_state = true; 93 | Idle.add( ()=> { 94 | Relay.show_error_window(e.message); 95 | server_tab.tab.close(); 96 | foreach (string tab in channel_autoconnect) 97 | turn_off_icon(tab); 98 | return false; 99 | }); 100 | return 0; 101 | } 102 | 103 | return 1; 104 | } 105 | 106 | public ChannelTab? add_channel_tab (string? _name, bool primsg = false) { 107 | string? name = _name; 108 | if (name == null || name.strip() == "" || server == null) 109 | return null; 110 | if (name == server.username || (name == server.nickname && !primsg) || name == "IRC") 111 | return server_tab; 112 | if (channel_tabs.has_key(name)) 113 | return channel_tabs[name]; 114 | var newTab = new ChannelTab(this); 115 | new_tab(newTab, name); 116 | channel_tabs[name] = newTab; 117 | return newTab; 118 | } 119 | 120 | //Same as add_channel_tab but won't create if not found 121 | private ChannelTab? find_channel_tab (string? name) { 122 | if (name == null) 123 | return null; 124 | else if (channel_tabs.has_key(name)) 125 | return channel_tabs[name]; 126 | 127 | return server_tab; 128 | } 129 | 130 | private void handle_input (string? msg) { 131 | if (msg == null) { 132 | stop(); 133 | return; 134 | } 135 | 136 | Message message = new Message (msg); 137 | switch (message.command) { 138 | case "PING": 139 | handle_ping(ref message); 140 | return; 141 | case "PONG": 142 | info(msg); 143 | return; 144 | case IRC.PRIVATE_MESSAGE: 145 | ChannelTab tab = add_channel_tab(message.parameters[0]); 146 | if (tab == server_tab && message.parameters[0] == server.nickname) { 147 | tab = add_channel_tab(message.user_name, true); 148 | } 149 | if (tab != null) 150 | new_message(tab, message); 151 | return; 152 | case IRC.RPL_TOPIC: 153 | ChannelTab tab = find_channel_tab(message.parameters[1]); 154 | if (tab != null && tab != server_tab) 155 | tab.set_topic(message.get_msg_txt()); 156 | return; 157 | case IRC.RPL_CHANNEL_URL: 158 | ChannelTab tab = find_channel_tab(message.parameters[1]); 159 | if (tab != null && tab != server_tab) 160 | tab.channel_url = message.message; 161 | return; 162 | case IRC.RPL_LUSERCLIENT: 163 | case "NOTICE": 164 | case IRC.RPL_MOTD: 165 | case IRC.RPL_MOTDSTART: 166 | case IRC.RPL_YOURHOST: 167 | case IRC.RPL_LUSEROP: 168 | case IRC.RPL_LUSERUNKNOWN: 169 | case IRC.RPL_LUSERCHANNELS: 170 | case IRC.RPL_UMODEIS: //maybe atab 171 | case IRC.RPL_SERVLIST: 172 | case IRC.RPL_ENDOFSTATS: 173 | case IRC.RPL_STATSLINKINFO: 174 | server_tab.display_message(message); 175 | return; 176 | case IRC.RPL_CREATED: 177 | case IRC.RPL_LUSERME: 178 | server_tab.set_topic(message.get_msg_txt(), true); 179 | new_message(server_tab, message); 180 | return; 181 | case IRC.RPL_WELCOME: 182 | do_autoconnect(); 183 | run_on_connect_cmds(); 184 | if (channel_autoconnect.size == 0) 185 | server_tab.tab.working = false; 186 | break; 187 | case IRC.RPL_NAMREPLY: 188 | var tab = add_channel_tab(message.parameters[2]); 189 | if (tab == null || tab == server_tab) 190 | return; 191 | tab.add_users_message(message); 192 | break; 193 | case IRC.QUIT_MSG: 194 | case IRC.PART_MSG: 195 | foreach(var t in channel_tabs.entries) { 196 | if (!t.value.is_server_tab && t != null && message.user_name != null && message.user_name.length > 0) 197 | t.value.user_leave_channel(message.user_name, message.get_msg_txt()); 198 | } 199 | return; 200 | case IRC.USER_NAME_CHANGED: 201 | foreach(var t in channel_tabs.entries) { 202 | if (t != null && message.user_name != null && message.user_name.length > 0) 203 | t.value.user_name_change(message.user_name, message.get_msg_txt()); 204 | } 205 | return; 206 | case IRC.JOIN_MSG: 207 | var tab = find_channel_tab(message.get_msg_txt()); 208 | if (tab == server_tab) 209 | tab = find_channel_tab(message.parameters[0]); 210 | if (tab != server_tab && tab != null && message.user_name != null && message.user_name.length > 0) 211 | tab.user_join_channel(message.user_name); 212 | return; 213 | case IRC.RPL_ENDOFNAMES: 214 | var tab = find_channel_tab(message.parameters[1]); 215 | if (tab != null) 216 | tab.sort_names(); 217 | return; 218 | //Errors 219 | case IRC.ERR_NICKNAMEINUSE: 220 | case IRC.ERR_NONICKNAMEGIVEN: 221 | string error_msg = message.get_msg_txt(); 222 | if (message.get_msg_txt().length < 3) 223 | error_msg = _("The name you chose is in use."); 224 | error_msg = server.host + "\n" + error_msg; 225 | name_in_use(error_msg); 226 | return; 227 | case IRC.ERR_LINKCHANNEL: 228 | //Channel forwarding 229 | if (message.parameters.length < 2) 230 | return; 231 | turn_off_icon(message.parameters[1]); 232 | return; 233 | case IRC.ERR_NOSUCHNICK: 234 | case IRC.ERR_NOSUCHCHANNEL: 235 | case IRC.ERR_WASNOSUCHNICK: 236 | case IRC.ERR_UNKNOWNCOMMAND: 237 | case IRC.ERR_NOMOTD: 238 | case IRC.ERR_USERNOTINCHANNEL: 239 | case IRC.ERR_NOTONCHANNEL: 240 | case IRC.ERR_NOTREGISTERED: 241 | case IRC.ERR_NEEDMOREPARAMS: 242 | case IRC.ERR_UNKNOWNMODE: 243 | case IRC.ERR_ALREADYONCHANNEL: 244 | case IRC.ERR_CHANOPRIVSNEEDED: 245 | case IRC.ERR_NONONREG: 246 | new_message(find_channel_tab(message.parameters[0]), message, true); 247 | return; 248 | default: 249 | if (message.command == null) 250 | message.command = "0"; 251 | else { 252 | int mode = int.parse(message.command); 253 | if (message.command == "ERROR" || 254 | (mode <= 533 && mode >= 400) || 255 | (mode >= 712 && mode <= 715) || 256 | (mode >= 972)) { 257 | new_message(server_tab, message, true); 258 | return; 259 | } 260 | } 261 | debug("Unhandled message: " + msg); 262 | return; 263 | } 264 | } 265 | 266 | public void do_register () { 267 | send_output("PASS " + ((server.password.length > 0) ? server.password : "-p")); 268 | send_output("NICK " + server.nickname); 269 | send_output("USER " + server.username + " 0 * :" + server.realname); 270 | send_output("MODE " + server.username + " +i"); 271 | } 272 | 273 | public void do_autoconnect () { 274 | autoconnect_ran = true; 275 | foreach (var chan in channel_autoconnect) { 276 | join(chan); 277 | } 278 | Gdk.threads_add_timeout_seconds(25, ()=> { 279 | foreach (var chan in channel_autoconnect) { 280 | change_channel_state(chan, "stuck"); 281 | } 282 | return false; 283 | }); 284 | } 285 | 286 | public void turn_off_icon (string channel) { 287 | change_channel_state(channel, "inactive"); 288 | } 289 | 290 | public void run_on_connect_cmds() { 291 | if (server.connect_cmds != null && server.connect_cmds.length > 0) { 292 | string[] cmds = server.connect_cmds.split("\n"); 293 | foreach(string run in cmds) { 294 | if (run.length > 1) 295 | server_tab.send_text_out(run); 296 | } 297 | } 298 | } 299 | 300 | public void name_in_use (string message) { 301 | debug("At name in use"); 302 | Idle.add( ()=> { 303 | var dialog = new Dialog.with_buttons(_("Nickname in use"), MainWindow.window, 304 | DialogFlags.DESTROY_WITH_PARENT, 305 | _("Connect"), Gtk.ResponseType.ACCEPT, 306 | _("Cancel"), Gtk.ResponseType.CANCEL); 307 | Gtk.Box content = dialog.get_content_area() as Gtk.Box; 308 | content.pack_start(new Label(_(message)), false, false, 5); 309 | var server_name = new Entry(); 310 | server_name.placeholder_text = _("New username"); 311 | server_name.activate.connect(() => { 312 | dialog.response(Gtk.ResponseType.ACCEPT); 313 | }); 314 | content.pack_start(server_name, false, false, 5); 315 | dialog.show_all(); 316 | dialog.response.connect((id) => { 317 | switch (id){ 318 | case Gtk.ResponseType.ACCEPT: 319 | string name = server_name.get_text().strip(); 320 | if (name.length > 0) { 321 | server.nickname = server.username = server_name.get_text(); 322 | if (server.realname.length == 0) 323 | server.realname = server.nickname; 324 | do_register(); 325 | dialog.close(); 326 | } 327 | break; 328 | case Gtk.ResponseType.CANCEL: 329 | dialog.close(); 330 | server_tab.tab.close(); 331 | foreach (var tab in channel_tabs.entries) { 332 | tab.value.tab.close(); 333 | } 334 | break; 335 | } 336 | }); 337 | return false; 338 | }); 339 | } 340 | 341 | private void handle_ping (ref Message msg) { 342 | send_output("PONG " + msg.message); 343 | } 344 | 345 | public void join (string channel) { 346 | send_output("JOIN " + channel); 347 | } 348 | 349 | public void send_output (string output) { 350 | if (!is_stream_out(output_stream)) 351 | return; 352 | debug("Sending out " + output + " " + server.host + "\n"); 353 | try{ 354 | output_stream.put_string(output + "\r\n"); 355 | }catch(GLib.Error e){ 356 | Relay.show_error_window(e.message); 357 | } 358 | } 359 | 360 | public bool is_stream_out (DataOutputStream? output) { 361 | return !(!(output is DataOutputStream) || output == null || output.is_closed()); 362 | } 363 | 364 | public bool is_stream_in (DataInputStream? input) { 365 | return !(!(input is DataInputStream) || input == null || input.is_closed()); 366 | } 367 | 368 | public void do_exit () { 369 | exit = true; 370 | 371 | foreach (string chan in channel_autoconnect) 372 | turn_off_icon(chan); 373 | 374 | send_output("QUIT :" + _("Relay, an IRC client for the modern desktop")); 375 | stop(); 376 | } 377 | 378 | public void stop () { 379 | exit = true; 380 | 381 | if (is_stream_in (input_stream)) { 382 | input_stream.clear_pending(); 383 | try{ 384 | input_stream.close(); 385 | } catch (GLib.IOError e){} 386 | } 387 | if (is_stream_out(output_stream)) { 388 | try{ 389 | output_stream.clear_pending(); 390 | output_stream.flush(); 391 | output_stream.close(); 392 | } catch (GLib.Error e){} 393 | } 394 | debug("Sucessfully stopped"); 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/drag_file.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * drag_file.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * Relay is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Relay is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | */ 19 | using Gtk; 20 | using GLib; 21 | 22 | internal class DragFile : GLib.Object { 23 | 24 | private const string HASTEBIN_HOST = "hastebin.com"; 25 | private Button paste; 26 | private Spinner spinner; 27 | 28 | public signal void file_uploaded(string url); 29 | 30 | public const Gtk.TargetEntry[] TARGETS = { 31 | {"text/uri-list",0,0} 32 | }; 33 | 34 | public void attach_spinner (Box box) { 35 | spinner = new Spinner(); 36 | spinner.active = true; 37 | spinner.hide(); 38 | box.pack_start(spinner); 39 | } 40 | 41 | public void attach_button (Button _paste) { 42 | paste = _paste; 43 | } 44 | 45 | public void reset_ui () { 46 | Idle.add( ()=> { 47 | paste.show(); 48 | spinner.hide(); 49 | return false; 50 | }); 51 | } 52 | 53 | /* Method definitions */ 54 | public void drop_file (Gdk.DragContext context, int x, int y, SelectionData selection_data, uint info, uint time_) { 55 | paste.hide(); 56 | spinner.show(); 57 | 58 | foreach (string uri in selection_data.get_uris()) { 59 | new Thread("Pastebin post", ()=>{ 60 | try{ 61 | string contents = ""; 62 | var path = File.new_for_uri(uri); 63 | 64 | int64 file_size = path.query_info ("*", FileQueryInfoFlags.NONE).get_size (); 65 | 66 | if (file_size > 500000) { 67 | Relay.show_error_window(_("The file " + uri + " is too large")); 68 | return 1; 69 | } 70 | 71 | var dis = new DataInputStream(path.read()); 72 | string line; 73 | // Read lines until end of file (null) is reached 74 | while ((line = dis.read_line (null)) != null) { 75 | contents += line + "\n"; 76 | } 77 | 78 | SocketClient client = new SocketClient (); 79 | Resolver resolver = Resolver.get_default (); 80 | GLib.List addresses = resolver.lookup_by_name(HASTEBIN_HOST, null); 81 | InetAddress address = addresses.nth_data (0); 82 | SocketConnection conn = client.connect (new InetSocketAddress (address, 80)); 83 | var input_stream = new DataInputStream (conn.input_stream); 84 | var output_stream = new DataOutputStream (conn.output_stream); 85 | 86 | output_stream.put_string("POST /documents HTTP/1.0\r\n"); 87 | output_stream.put_string("Host: hastebin.com\r\n"); 88 | output_stream.put_string("Accept: */*\r\n"); 89 | output_stream.put_string("Content-Length: " + contents.length.to_string() + "\r\n"); 90 | output_stream.put_string("Content-Type: application/x-www-form-urlencoded\r\n"); 91 | output_stream.put_string("\r\n"); 92 | output_stream.put_string(contents); 93 | output_stream.flush(); 94 | 95 | size_t length; 96 | string? output = ""; 97 | string json = ""; 98 | do{ 99 | output = input_stream.read_line(out length); 100 | if (output != null && output[0] == '{' && output[output.length - 1] == '}') 101 | json = output; 102 | } while (output != null); 103 | 104 | /* 105 | * Doing this without a JSON lib so users doin't 106 | * need to install another package. The JSON 107 | * returned only has one element. Its simple. 108 | */ 109 | var parts = json.split(":"); 110 | if (parts != null && parts.length > 1) 111 | json = parts[1][1:parts[1].length - 2]; 112 | else { 113 | Relay.show_error_window(_("The message returned was not formed correctly.")); 114 | return 1; 115 | } 116 | 117 | Idle.add( ()=>{ 118 | MainWindow.paste.show(); 119 | file_uploaded("http://" + HASTEBIN_HOST + "/" + json); 120 | reset_ui(); 121 | return false; 122 | }); 123 | } catch (GLib.Error e) { 124 | reset_ui(); 125 | Relay.show_error_window(e.message); 126 | warning("Could not upload " + e.message); 127 | } 128 | return 0; 129 | }); 130 | } 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/irc.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * irc.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * KyRC is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * KyRC is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | */ 19 | 20 | public class IRC{ 21 | public const uint16 DEFAULT_PORT = 6667; 22 | public const int USER_LENGTH = 18; 23 | public const int USER_WIDTH = 126; 24 | public const string user_prefixes = "~&@%+"; 25 | public const string spacers = " ,:/\\{}[];$#@!)(*&^\n\t\r"; 26 | 27 | //Replies 28 | public const string RPL_WELCOME = "001"; 29 | public const string RPL_YOURHOST = "002"; 30 | public const string RPL_CREATED = "003"; 31 | public const string RPL_MYINFO = "004"; 32 | public const string RPL_BOUNCE = "005"; 33 | public const string RPL_AWAY = "301"; 34 | public const string RPL_UNAWAY = "305"; 35 | public const string RPL_NOWAWAY = "306"; 36 | public const string RPL_WHOISUSER = "311"; 37 | public const string RPL_WHOISSERVER = "312"; 38 | public const string RPL_WHOISOPERATOR = "313"; 39 | public const string RPL_WHOISCHANNELS = "319"; 40 | public const string RPL_WHOISIDLE = "317"; 41 | public const string RPL_ENDOFWHOIS = "318"; 42 | public const string RPL_LIST = "322"; 43 | public const string RPL_LISTEND = "323"; 44 | public const string RPL_CHANNELMODEIS = "324"; 45 | public const string RPL_CHANNEL_URL = "328"; 46 | public const string RPL_AUTHEDAS = "330"; 47 | public const string RPL_NOTOPIC = "331"; 48 | public const string RPL_TOPIC = "332"; 49 | public const string RPL_VERSION = "351"; 50 | public const string RPL_NAMREPLY = "353"; 51 | public const string RPL_ENDOFNAMES = "366"; 52 | public const string RPL_MOTD = "372"; 53 | public const string RPL_MOTDSTART = "375"; 54 | public const string RPL_ENDOFMOTD = "376"; 55 | public const string RPL_LUSEROP = "252"; 56 | public const string RPL_UMODEIS = "221"; 57 | public const string RPL_SERVLIST = "234"; 58 | public const string RPL_ENDOFSTATS = "219"; 59 | public const string RPL_STATSCOMMANDS = "212"; 60 | public const string RPL_STATSLINKINFO = "211"; 61 | // " :operator(s) online" 62 | public const string RPL_LUSERUNKNOWN = "253"; 63 | //" :unknown connection(s)" 64 | public const string RPL_LUSERCHANNELS = "254"; 65 | //" :channels formed" 66 | public const string RPL_LUSERME = "255"; 67 | //":I have clients and servers" 68 | public const string RPL_LUSERCLIENT = "251"; 69 | //:There are users and services on servers" 70 | public const string PRIVATE_MESSAGE = "PRIVMSG"; 71 | public const string USER_NAME_CHANGED = "NICK"; 72 | public const string QUIT_MSG = "QUIT"; 73 | public const string JOIN_MSG = "JOIN"; 74 | public const string PART_MSG = "PART"; 75 | 76 | //Errors 77 | public const string ERR_NOSUCHNICK = "401"; 78 | public const string ERR_NOSUCHCHANNEL = "403"; 79 | public const string ERR_WASNOSUCHNICK = "406"; 80 | public const string ERR_UNKNOWNCOMMAND = "421"; 81 | public const string ERR_NOMOTD = "422"; 82 | public const string ERR_NONICKNAMEGIVEN = "431"; 83 | public const string ERR_NICKNAMEINUSE = "433"; 84 | public const string ERR_USERNOTINCHANNEL = "441"; 85 | public const string ERR_NOTONCHANNEL = "442"; 86 | public const string ERR_NOTREGISTERED = "451"; 87 | public const string ERR_NEEDMOREPARAMS = "461"; 88 | public const string ERR_LINKCHANNEL = "470"; 89 | public const string ERR_UNKNOWNMODE = "472"; 90 | public const string ERR_ALREADYONCHANNEL = "479"; 91 | public const string ERR_CHANOPRIVSNEEDED = "482"; 92 | public const string ERR_NONONREG = "486"; 93 | 94 | public static string remove_user_prefix (string name) { 95 | if (user_prefixes.index_of_char(name[0]) != -1) 96 | return name.substring(1); 97 | return name; 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/main_entry.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * main-entry.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * Relay is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Relay is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | */ 19 | using Gee; 20 | using Gtk; 21 | using Gdk; 22 | 23 | public class MainEntry : Entry { 24 | 25 | int last_pos = -1; 26 | ArrayList history = new ArrayList(); 27 | 28 | public MainEntry () { 29 | key_press_event.connect(press_event); 30 | activate.connect(do_activate); 31 | } 32 | 33 | public bool press_event (EventKey event) { 34 | string? keyval = Gdk.keyval_name(event.keyval); 35 | switch (keyval) { 36 | case "Up": 37 | up_arrow(); 38 | return true; 39 | break; 40 | case "Down": 41 | down_arrow(); 42 | break; 43 | } 44 | return false; 45 | } 46 | 47 | private void up_arrow () { 48 | if (last_pos == -1) 49 | last_pos = history.size - 1; 50 | else if (last_pos == 0) 51 | return; 52 | else 53 | last_pos--; 54 | 55 | 56 | write_history(last_pos); 57 | } 58 | 59 | private void down_arrow () { 60 | if (history.size > last_pos + 1) 61 | last_pos++; 62 | else { 63 | set_text(""); 64 | return; 65 | } 66 | 67 | write_history(last_pos); 68 | } 69 | 70 | private void write_history (int index) { 71 | if (index > history.size || index < 0) 72 | return; 73 | 74 | set_text(history[index]); 75 | set_position(get_text().length); 76 | } 77 | 78 | public void do_activate () { 79 | if (get_text().strip() == "") 80 | return; 81 | 82 | history.add(get_text()); 83 | last_pos = -1; 84 | } 85 | } -------------------------------------------------------------------------------- /src/message.vala: -------------------------------------------------------------------------------- 1 | 2 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 3 | /* 4 | * message.vala 5 | * Copyright (C) 2015 Kyle Agronick 6 | * 7 | * KyRC is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * KyRC is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program. If not, see . 19 | */ 20 | 21 | using GLib; 22 | using Gee; 23 | 24 | public class Message : GLib.Object { 25 | public string? message { get; set; } 26 | public string prefix { get; set; } 27 | public string command { get; set; } 28 | public string[] parameters { get; set; } 29 | public string user_name = ""; 30 | public bool internal = false; 31 | public bool usr_private_message = false; 32 | private static Regex? regex; 33 | private static Regex? fix_message; 34 | 35 | private static string regex_string = """^(:(?\S+) )?(?\S+)( (?!:)(?.+?))?( :(?.+))?$"""; 36 | private static string replace_string = """[\x00-\x1F\x80-\xFF]"""; 37 | public static string[] user_cmds = {IRC.PRIVATE_MESSAGE, IRC.JOIN_MSG, IRC.USER_NAME_CHANGED, IRC.QUIT_MSG, IRC.PART_MSG}; 38 | 39 | public Message (string _message = "") { 40 | if (regex == null) { 41 | try { 42 | regex = new Regex(regex_string, RegexCompileFlags.OPTIMIZE ); 43 | } catch (RegexError e){ 44 | error("There was a regex error that should never happen"); 45 | } 46 | } 47 | if (_message.length == 0) 48 | return; 49 | try{ 50 | message = _message.validate() ? _message : _message.escape("\b\f\n\r\t\'"); 51 | }catch(RegexError e) { 52 | message = _message; 53 | } 54 | parse_regex(); 55 | } 56 | 57 | public string get_prefix_name () { 58 | if (prefix.index_of_char('!') == -1) 59 | return ""; 60 | if (command == IRC.PRIVATE_MESSAGE) 61 | usr_private_message = true; 62 | if (prefix != null) 63 | return prefix.split("!")[0]; 64 | return ""; 65 | } 66 | 67 | public void user_name_set (string? name) { 68 | if (name != null) 69 | user_name = name; 70 | } 71 | 72 | //Use this function to add padding to the user name 73 | public string user_name_get () { 74 | string name = user_name; 75 | if (name.length >= IRC.USER_LENGTH) 76 | name = user_name.substring(0, IRC.USER_LENGTH - 3) + "..."; 77 | int length = IRC.USER_LENGTH - name.length; 78 | return name + string.nfill(length, ' '); 79 | } 80 | 81 | public string get_msg_txt() { 82 | return (message == null) ? "" : message.strip(); 83 | } 84 | 85 | public void parse_regex () { 86 | try{ 87 | if (message == null) 88 | return; 89 | 90 | regex.replace_eval (message, -1, 0, 0, (mi, s) => { 91 | prefix = mi.fetch_named ("prefix"); 92 | command = mi.fetch_named ("command"); 93 | if (mi.fetch_named ("params") != null) 94 | parameters = mi.fetch_named ("params").split(" "); 95 | message = mi.fetch_named ("trail"); 96 | 97 | if (message != null) 98 | message = message.replace("\t", ""); 99 | if (prefix != null) { 100 | if (command in user_cmds) 101 | user_name_set(prefix.split("!")[0]); 102 | } 103 | 104 | return false; 105 | }); 106 | }catch (RegexError e){ 107 | warning("Regex error with " + message); 108 | } 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/relay.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * relay.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * relay is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * relay is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | 19 | 20 | TERMS OF USE - EASING EQUATIONS 21 | pen source under the http://www.opensource.org/licenses/bsd-license.php BSD License.
22 | 23 | Copyright © 2001 Robert Penner 24 | All rights reserved. 25 | Redistribution and use in source and binary forms, 26 | with or without modification, are permitted provided that the following 27 | conditions are met: 28 | 29 | Redistributions of source code must retain the above copyright notice, this list of 30 | conditions and the following disclaimer. 31 | Redistributions in binary form must reproduce the above copyright notice, this list of 32 | conditions and the following disclaimer in the documentation and/or 33 | other materials provided with the distribution. 34 | 35 | Neither the name of the author nor the names of contributors may be used to 36 | endorse or promote products derived from this software without specific 37 | prior written permission. 38 | 39 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 40 | AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 41 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 42 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 43 | NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY 44 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 45 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 46 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 47 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 48 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 49 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 50 | POSSIBILITY OF SUCH DAMAGE. 51 | */ 52 | using X; 53 | using GLib; 54 | using Gee; 55 | using Gtk; 56 | 57 | public class Relay : Granite.Application { 58 | 59 | private MainWindow window = null; 60 | public string[] args; 61 | public static bool has_activated = false; 62 | public static bool on_elementary = false; 63 | public static bool on_ubuntu = false; 64 | public static bool on_kde = false; 65 | public static bool is_light_theme = false; 66 | public static string path; 67 | public static bool no_theme; 68 | 69 | 70 | construct { 71 | 72 | program_name = "Relay"; 73 | exec_name = "relay"; 74 | 75 | build_data_dir = Config.PACKAGE_DATA_DIR; 76 | build_pkg_data_dir = Config.GETTEXT_PACKAGE; 77 | build_version = Config.VERSION; 78 | 79 | app_years = "2015"; 80 | app_icon = "relay"; 81 | app_launcher = "relay.desktop"; 82 | application_id = "net.launchpad.relay"; 83 | 84 | main_url = "https://poisonpacket.wordpress.com/relay/"; 85 | bug_url = "https://bugs.launchpad.net/relay"; 86 | help_url = "https://poisonpacket.wordpress.com/relay/"; 87 | translate_url = "https://translations.launchpad.net/relay"; 88 | 89 | about_authors = { "Kyle Agronick " }; 90 | about_documenters = { "Kyle Agronick " }; 91 | about_artists = { "Kyle Agronick (App) " }; 92 | about_comments = "IRC Client for the Modern Desktop"; 93 | about_translators = "translator-credits"; 94 | about_license_type = Gtk.License.GPL_3_0; 95 | 96 | set_options(); 97 | 98 | Intl.setlocale(LocaleCategory.MESSAGES, ""); 99 | Intl.textdomain(Config.GETTEXT_PACKAGE); 100 | Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "utf-8"); 101 | Intl.bindtextdomain(Config.GETTEXT_PACKAGE, "./locale"); 102 | 103 | } 104 | 105 | public const GLib.OptionEntry[] app_options = { 106 | { "no-theme", 't', 0, OptionArg.NONE, out no_theme, "Disable switching to a different theme", null }, 107 | { null } 108 | }; 109 | 110 | public static void main (string[] args) { 111 | 112 | path = args[0]; 113 | 114 | var context = new OptionContext (); 115 | context.add_main_entries (Relay.app_options, "relay"); 116 | 117 | try { 118 | context.parse (ref args); 119 | } catch (Error e) { 120 | warning (e.message); 121 | } 122 | 123 | X.init_threads (); 124 | 125 | GLib.Log.set_default_handler(handle_log); 126 | 127 | var main = new Relay(); 128 | main.run(args); 129 | } 130 | 131 | public override void activate () { 132 | 133 | if (has_activated) { 134 | MainWindow.window.present(); 135 | return; 136 | } 137 | 138 | has_activated = true; 139 | 140 | check_env(); 141 | 142 | if (!no_theme) { 143 | var e_theme = File.new_for_path("/usr/share/themes/elementary/"); 144 | if (e_theme.query_exists()) 145 | Gtk.Settings.get_default().gtk_theme_name = "elementary"; 146 | else if (on_ubuntu) 147 | Gtk.Settings.get_default().gtk_theme_name = "Adwaita"; 148 | else if (on_kde) 149 | Gtk.Settings.get_default().gtk_theme_name = "oxygen-gtk"; 150 | } else { 151 | debug("Not attempting to switch theme."); 152 | } 153 | 154 | Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true; 155 | 156 | window = new MainWindow(this); 157 | Gtk.main (); 158 | } 159 | 160 | public static string get_asset_file (string name) { 161 | string[] checks = {"./" + name, 162 | "src/" + name, 163 | "../src/" + name, 164 | "./*/src/" + name, 165 | Config.PACKAGE_DATA_DIR + "/" + name,}; 166 | foreach(string check in checks) { 167 | File file = File.new_for_path (check); 168 | if (file.query_exists()) 169 | return check; 170 | } 171 | error("Unable to find asset file: " + name); 172 | } 173 | 174 | public static void handle_log (string? log_domain, LogLevelFlags log_levels, string message) { 175 | string prefix = ""; 176 | string suffix = "\x1b[39;49m " ; 177 | switch(log_levels) { 178 | case LogLevelFlags.LEVEL_DEBUG: 179 | prefix = "\x1b[94mDebug: "; 180 | break; 181 | case LogLevelFlags.LEVEL_INFO: 182 | prefix = "\x1b[92mInfo: "; 183 | break; 184 | case LogLevelFlags.LEVEL_WARNING: 185 | prefix = "\x1b[93mWarning: "; 186 | break; 187 | case LogLevelFlags.LEVEL_ERROR: 188 | prefix = "\x1b[91mError: "; 189 | break; 190 | default: 191 | prefix = message; 192 | break; 193 | } 194 | GLib.stdout.printf(prefix + message + suffix + "\n"); 195 | } 196 | 197 | private void check_env () { 198 | string output; 199 | output = GLib.Environment.get_variable("XDG_CURRENT_DESKTOP"); 200 | 201 | if (output != null && output.contains ("Pantheon")) { 202 | on_elementary = true; 203 | }else if (output != null && (output.contains ("Unity") || output.contains ("XFCE"))) 204 | on_ubuntu = true; 205 | else if (output == "KDE") 206 | on_kde = true; 207 | } 208 | 209 | private static bool error_open = false; 210 | public static void show_error_window (string error_msg) { 211 | if (error_open) 212 | return; 213 | Idle.add( ()=> { 214 | Gtk.MessageDialog dialog = new Gtk.MessageDialog (MainWindow.window, 215 | Gtk.DialogFlags.MODAL, 216 | Gtk.MessageType.WARNING, 217 | Gtk.ButtonsType.OK, 218 | _(error_msg)); 219 | dialog.response.connect ((response_id) => { 220 | error_open = false; 221 | dialog.destroy(); 222 | }); 223 | dialog.show (); 224 | error_open = true; 225 | return false; 226 | }); 227 | } 228 | 229 | //Dark themes need light text 230 | public static bool set_color_mode (Gdk.RGBA color) { 231 | is_light_theme = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue < 0.4; 232 | return is_light_theme; 233 | } 234 | 235 | public static double ease_out_elastic (float t,float b , float c, float d) { 236 | if (t==0) return b; if ((t/=d)==1) return b+c; 237 | float p=d*0.3f; 238 | float a=c; 239 | float s=p/4; 240 | double val = ((a*Math.pow(2,-16*t)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + c + b; 241 | return val; 242 | } 243 | 244 | public static float ease_in_bounce (float t,float b , float c, float d) { 245 | return c - ease_out_bounce(d-t, 0, c, d) + b; 246 | } 247 | 248 | public static float ease_out_bounce (float t,float b , float c, float d) { 249 | if ((t/=d) < (1/2.75f)) { 250 | return c*(7.5625f*t*t) + b; 251 | } else if (t < (2/2.75f)) { 252 | float postFix = t-=(1.5f/2.75f); 253 | return c*(7.5625f*postFix*t + 0.75f) + b; 254 | } else if (t < (2.5/2.75)) { 255 | float postFix = t-=(2.25f/2.75f); 256 | return c*(7.5625f*postFix*t + 0.9375f) + b; 257 | } else { 258 | float postFix = t-=(2.625f/2.75f); 259 | return c*(7.5625f*postFix*t + 0.984375f) + b; 260 | } 261 | } 262 | 263 | public static void sort_clean (ref LinkedList list) { 264 | list.sort(Relay.compare); 265 | } 266 | 267 | 268 | public static int compare(string a, string b) { 269 | return GLib.strcmp(a.down(), b.down()); 270 | } 271 | } 272 | 273 | -------------------------------------------------------------------------------- /src/rich_text.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * rich_text.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * relay is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * relay is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | */ 19 | 20 | using Gee; 21 | 22 | public class RichText : GLib.Object { 23 | 24 | string text; 25 | 26 | private static Regex? parse_url; 27 | private static const string url_string = """(http|https?|ftp):\/\/[^\s\/$.?#].[^\s]*"""; 28 | public LinkedList link_locations_start; 29 | public LinkedList link_locations_end; 30 | public LinkedList name_location_start = new LinkedList(); 31 | public LinkedList name_location_end = new LinkedList(); 32 | public LinkedList names = new LinkedList(); 33 | public bool has_links = false; 34 | public bool has_names = false; 35 | 36 | public RichText (string _text) { 37 | text = _text; 38 | 39 | try{ 40 | if (parse_url == null) 41 | parse_url = new Regex(url_string, RegexCompileFlags.OPTIMIZE); 42 | } catch (RegexError e) {} 43 | } 44 | 45 | public void parse_links () { 46 | link_locations_start = new LinkedList(); 47 | link_locations_end = new LinkedList(); 48 | 49 | MatchInfo match_info; 50 | string lookup = text; 51 | int last_offset = 0; 52 | 53 | while (parse_url.match_all (lookup, 0, out match_info)) { 54 | int start; 55 | int end; 56 | match_info.fetch_pos(0, out start, out end); 57 | link_locations_start.add(text.length - (start + last_offset)); 58 | link_locations_end.add(text.length - (end + last_offset)); 59 | last_offset += end; 60 | lookup = lookup.substring(end); 61 | has_links = true; 62 | } 63 | } 64 | 65 | public void parse_name (string? name) { 66 | if (name == null || name.length == 0) 67 | return; 68 | int location = text.index_of(name); 69 | while (location > -1 && ((location - 1 == 0 || IRC.spacers.index_of_char(text.get_char(location - 1)) != -1 ) && 70 | (location + name.length == text.length - 1 || IRC.spacers.index_of_char(text.get_char(location + name.length)) != -1))) { 71 | 72 | name_location_start.add(text.length - location - name.length); 73 | name_location_end.add((text.length - location)); 74 | names.add(name); 75 | location += name.length; 76 | has_names = true; 77 | location = text.index_of(name, location); 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/server_manager.vala: -------------------------------------------------------------------------------- 1 | 2 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 3 | /* 4 | * server_manager.vala 5 | * Copyright (C) 2015 Kyle Agronick 6 | * 7 | * KyRC is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * KyRC is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program. If not, see . 19 | */ 20 | 21 | using Gdk; 22 | using Gtk; 23 | using Gee; 24 | 25 | public class ServerManager : Object 26 | { 27 | public Gtk.Window window; 28 | Entry new_channel; 29 | ListBox channels; 30 | ListBox servers; 31 | ListBoxRow select_row = null; 32 | SqlClient sqlclient = SqlClient.get_instance(); 33 | Button add_channel; 34 | Button cancel; 35 | 36 | Entry host; 37 | SpinButton port; 38 | Entry real; 39 | Entry user; 40 | Entry pass; 41 | Entry nick; 42 | Switch encrypt; 43 | TextView connect_cmds; 44 | Grid form; 45 | SqlClient.Server current_server = null; 46 | bool none_selected = false; 47 | public char[] CHANNEL_CHAR = {'&', '#', '+', '!'}; 48 | 49 | public signal void close(); 50 | 51 | public bool open_window () { 52 | if (window is Widget && window.visible) { 53 | window.present(); 54 | return true; 55 | } 56 | current_server = null; 57 | var builder = new Builder(); 58 | try{ 59 | builder.add_from_file(Relay.get_asset_file(MainWindow.UI_FILE_SERVERS)); 60 | } catch (Error e){ 61 | error("Unable to load UI file " + Relay.get_asset_file(MainWindow.UI_FILE_SERVERS)); 62 | } 63 | 64 | window = builder.get_object ("window") as Gtk.Window; 65 | var box = builder.get_object ("port_wrap") as Box; 66 | var remove_channel = builder.get_object ("remove_channel") as Button; 67 | var server_btns = builder.get_object ("server_buttons") as Box; 68 | cancel = builder.get_object ("cancel") as Button; 69 | add_channel = builder.get_object ("add_channel") as Button; 70 | new_channel = builder.get_object ("channel_name") as Entry; 71 | var channels_wrapper = builder.get_object ("channel_wrapper") as ScrolledWindow; 72 | channels = new ListBox(); 73 | channels_wrapper.add(channels); 74 | servers = builder.get_object ("servers") as ListBox; 75 | host = builder.get_object ("host") as Entry; 76 | real = builder.get_object ("real") as Entry; 77 | user = builder.get_object ("user") as Entry; 78 | pass = builder.get_object ("pass") as Entry; 79 | nick = builder.get_object ("nick") as Entry; 80 | connect_cmds = builder.get_object ("connect_cmds") as TextView; 81 | encrypt = builder.get_object ("encrypt") as Switch; 82 | form = builder.get_object ("form") as Grid; 83 | 84 | var channels_placeholder = new Label(_("No Channels")); 85 | channels.set_placeholder(channels_placeholder); 86 | channels_placeholder.show_all(); 87 | 88 | servers.set_selection_mode(SelectionMode.BROWSE); 89 | servers.row_activated.connect(save_changes); 90 | servers.row_activated.connect(populate_fields); 91 | servers.row_selected.connect(clear_row); 92 | 93 | var add_server = new Gtk.Button(); 94 | add_server.image = new Image.from_file(Relay.get_asset_file("assets/list-add-symbolic.svg")); 95 | var remove_server = new Gtk.Button(); 96 | remove_server.image = new Image.from_file(Relay.get_asset_file("assets/list-remove-symbolic.svg")); 97 | server_btns.pack_end(add_server, false, false, 0); 98 | server_btns.pack_end(remove_server, false, false, 0); 99 | 100 | channels.set_selection_mode(SelectionMode.BROWSE); 101 | servers.set_size_request(175,50); 102 | 103 | 104 | cancel.button_release_event.connect(cancel_clicked); 105 | add_channel.button_release_event.connect(add_channel_clicked); 106 | add_server.button_release_event.connect(add_server_clicked); 107 | remove_server.button_release_event.connect(remove_server_clicked); 108 | remove_channel.button_release_event.connect(remove_channel_clicked); 109 | host.focus_out_event.connect(host_text_changed); 110 | 111 | 112 | var chn_adj = new Adjustment(1, 1, 500, 1, 2, 100); 113 | channels.set_adjustment(chn_adj); 114 | 115 | 116 | port = new Gtk.SpinButton.with_range (0, 65535, 1); 117 | box.pack_start(port, false, false, 0); 118 | 119 | add_servers(); 120 | window.show_all (); 121 | set_forms_active(false); 122 | 123 | window.destroy.connect( ()=> { 124 | close(); 125 | }); 126 | 127 | return false; 128 | } 129 | 130 | private void clear_row (ListBoxRow? row) { 131 | if (!(row is Widget)) { 132 | none_selected = true; 133 | }else if (none_selected) { 134 | current_server = null; 135 | select_row = null; 136 | none_selected = false; 137 | } 138 | } 139 | 140 | 141 | public void save_changes (ListBoxRow? row) { 142 | if (current_server == null || select_row == null || row == null) 143 | return; 144 | 145 | string hostname = host.get_text().strip(); 146 | 147 | if (hostname == "") 148 | return; 149 | 150 | var exists = sqlclient.get_server(hostname); 151 | if (exists != null && exists.id != current_server.id) 152 | return; 153 | 154 | 155 | current_server.host = hostname; 156 | current_server.realname = real.get_text(); 157 | current_server.username = user.get_text(); 158 | current_server.password = pass.get_text(); 159 | current_server.nickname = nick.get_text(); 160 | current_server.port = port.get_value_as_int(); 161 | current_server.encryption = encrypt.get_active(); 162 | current_server.connect_cmds = connect_cmds.buffer.text; 163 | 164 | current_server.update(); 165 | 166 | 167 | if (select_row != null) { 168 | int index = select_row.get_index(); 169 | var newrow = get_list_box_row (hostname); 170 | servers.remove(select_row); 171 | servers.insert(newrow, index); 172 | servers.show_all(); 173 | } 174 | } 175 | 176 | private bool host_text_changed (EventFocus event) { 177 | string message = ""; 178 | string name = host.get_text().strip(); 179 | if (name.length == 0) { 180 | message = "Host can not be empty. Your changes will not be saved."; 181 | } 182 | var exists = sqlclient.get_server(name); 183 | if (exists != null && exists.id != current_server.id) { 184 | message = "A server with that host already exists. Your changes will not be saved."; 185 | } 186 | if (message != "") { 187 | Gtk.MessageDialog msg = new Gtk.MessageDialog (window, 188 | Gtk.DialogFlags.MODAL, 189 | Gtk.MessageType.WARNING, 190 | Gtk.ButtonsType.OK, 191 | "%s", 192 | message); 193 | msg.response.connect ((response_id) => { 194 | host.grab_focus(); 195 | msg.destroy(); 196 | }); 197 | msg.show (); 198 | } 199 | return false; 200 | } 201 | 202 | public void populate_fields (ListBoxRow? row) { 203 | if (row == null) { 204 | current_server = null; 205 | select_row = null; 206 | } 207 | 208 | set_forms_active(true); 209 | 210 | select_row = row; 211 | 212 | SqlClient.Server svr = sqlclient.get_server(row.name); 213 | if (svr == null) 214 | svr = new SqlClient.Server(); 215 | 216 | current_server = svr; 217 | 218 | host.set_text(svr.host); 219 | user.set_text(svr.username); 220 | real.set_text(svr.realname); 221 | pass.set_text(svr.password); 222 | port.set_value(svr.port); 223 | nick.set_text(svr.nickname); 224 | encrypt.state_set(svr.encryption); 225 | connect_cmds.buffer.text = svr.connect_cmds; 226 | 227 | foreach (Widget lbr in channels.get_children()) { 228 | channels.remove(lbr); 229 | } 230 | 231 | foreach (SqlClient.Channel chn in svr.channels) { 232 | var lbr = get_channel_list_box_row(chn); 233 | channels.insert(lbr, -1); 234 | } 235 | 236 | channels.show_all(); 237 | } 238 | 239 | private ListBoxRow get_channel_list_box_row (SqlClient.Channel channel) { 240 | var box = new Box(Orientation.HORIZONTAL, 1); 241 | var lbr = new ListBoxRow(); 242 | var lbl = new Label(channel.channel); 243 | var ac_switch = new Switch(); 244 | var switch_box = new Box(Orientation.HORIZONTAL, 1); 245 | lbl.set_halign(Align.START); 246 | switch_box.pack_start(ac_switch); 247 | box.pack_start(lbl, true, true, 0); 248 | box.pack_end(switch_box, false, false, 0); 249 | ac_switch.state = channel.autoconnect; 250 | ac_switch.state_set.connect(channel.update_autoconnect); 251 | lbr.add(box); 252 | lbr.show_all(); 253 | box.set_data("ac_switch", ac_switch); 254 | lbr.name = channel.channel; 255 | return lbr; 256 | } 257 | 258 | private ListBoxRow get_list_box_row (string name) { 259 | var lbr = new ListBoxRow(); 260 | var lbl = new Label(name); 261 | lbr.add(lbl); 262 | lbr.set_halign(Align.FILL); 263 | lbl.set_halign(Align.START); 264 | lbr.name = name; 265 | return lbr; 266 | } 267 | 268 | 269 | private void add_servers () { 270 | ListBoxRow first_row = null; 271 | foreach (var svr in SqlClient.servers.entries) { 272 | var server = svr.value; 273 | var lbr = get_list_box_row(server.host); 274 | servers.insert(lbr, -1); 275 | if (first_row == null) 276 | first_row = lbr; 277 | } 278 | 279 | } 280 | 281 | private bool remove_channel_clicked (Gdk.EventButton event) { 282 | var widget = channels.get_selected_row(); 283 | channels.remove(widget); 284 | var channel = current_server.find_channel_by_name(widget.name); 285 | if (channel == null) 286 | return false; 287 | channel.delete_channel(); 288 | current_server.channels = SqlClient.servers[current_server.id].channels; 289 | return false; 290 | } 291 | 292 | static string CHANNEL_ERROR = _("A channel name must begin with one of the following characters: %c, %c, %c, %c."); 293 | private bool add_channel_clicked (Gdk.EventButton event) { 294 | string chan_name = new_channel.get_text().strip(); 295 | if (chan_name.length == 0 || current_server.find_channel_by_name(chan_name) != null) 296 | return false; 297 | 298 | if (!(chan_name[0] in CHANNEL_CHAR)) { 299 | Gtk.MessageDialog msg = new Gtk.MessageDialog (window, 300 | Gtk.DialogFlags.MODAL, 301 | Gtk.MessageType.WARNING, 302 | Gtk.ButtonsType.OK, 303 | CHANNEL_ERROR, 304 | CHANNEL_CHAR[0], 305 | CHANNEL_CHAR[1], 306 | CHANNEL_CHAR[2], 307 | CHANNEL_CHAR[3]); 308 | msg.response.connect ((response_id) => { 309 | host.grab_focus(); 310 | msg.destroy(); 311 | }); 312 | msg.show (); 313 | return false; 314 | } 315 | var channel = new SqlClient.Channel(); 316 | channel.server_id = current_server.id; 317 | channel.channel = chan_name; 318 | channel.add_channel(); 319 | 320 | var lbr = get_channel_list_box_row(channel); 321 | new_channel.set_text(""); 322 | channels.add(lbr); 323 | channels.show_all(); 324 | current_server.channels = SqlClient.servers[current_server.id].channels; 325 | return false; 326 | } 327 | 328 | private bool remove_server_clicked (Gdk.EventButton event) { 329 | var widget = servers.get_selected_row(); 330 | if (widget == null) 331 | return true; 332 | servers.remove(widget); 333 | set_forms_active(false); 334 | current_server.remove_server(); 335 | current_server = null; 336 | return true; 337 | } 338 | 339 | private void set_forms_active (bool active) { 340 | var children = form.get_children(); 341 | foreach (var child in children) { 342 | child.set_sensitive(active); 343 | } 344 | cancel.get_parent().set_sensitive(true); 345 | } 346 | 347 | 348 | private bool add_server_clicked (Gdk.EventButton event) { 349 | save_changes (select_row); 350 | current_server = new SqlClient.Server(); 351 | current_server.username = current_server.nickname = Environment.get_user_name(); 352 | int id = current_server.add_server_empty(); 353 | current_server.id = id; 354 | current_server.host = "New Server " + id.to_string(); 355 | 356 | var lbr = get_list_box_row (current_server.host); 357 | servers.insert(lbr, -1); 358 | servers.select_row(lbr); 359 | servers.show_all(); 360 | populate_fields (lbr); 361 | host.grab_focus(); 362 | port.set_value((int) Connection.DEFAULT_PORT); 363 | 364 | return false; 365 | } 366 | 367 | 368 | private bool cancel_clicked (Gdk.EventButton event) { 369 | save_changes (select_row); 370 | window.close(); 371 | return false; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/settings.vala: -------------------------------------------------------------------------------- 1 | /* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ 2 | /* 3 | * settings.vala 4 | * Copyright (C) 2015 Kyle Agronick 5 | * 6 | * Relay is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License as published by the 8 | * Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Relay is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with this program. If not, see . 18 | */ 19 | using GLib; 20 | using Gtk; 21 | using Gee; 22 | using Gdk; 23 | using Pango; 24 | 25 | public class Settings : GLib.Object { 26 | 27 | const string[] switch_names = {"show_animations", "show_join", "show_sidebar", "open_server", "show_datestamp", "change_tab", "show_tabs", "show_line"}; 28 | const string[] color_names = {"user-self-color", "user-other-color", "message-color", "link-color", "timestamp-color"}; 29 | HashMap colors_defaults = new HashMap(); 30 | GLib.Settings settings; 31 | Gtk.Window window = null; 32 | 33 | public signal void changed_color(); 34 | public signal void show_hide_tabs(bool show); 35 | 36 | construct { 37 | settings = new GLib.Settings("org.agronick.relay"); 38 | set_colors_defaults(); 39 | } 40 | 41 | public bool show_window () { 42 | if (window is Widget && window.visible) { 43 | window.present(); 44 | return true; 45 | } 46 | var builder = new Builder(); 47 | try { 48 | builder.add_from_file(Relay.get_asset_file(MainWindow.UI_FILE_SETTINGS)); 49 | } catch (Error e){ 50 | error("Unable to load UI file " + Relay.get_asset_file(MainWindow.UI_FILE_SETTINGS)); 51 | } 52 | window = builder.get_object ("window") as Gtk.Window; 53 | 54 | foreach (string name in switch_names) { 55 | var switches = builder.get_object (name) as Switch; 56 | settings.bind(name.replace("_", "-"), switches, "active", SettingsBindFlags.DEFAULT); 57 | } 58 | 59 | var show_tabs = builder.get_object ("show_tabs") as Switch; 60 | show_tabs.state_set.connect( (state)=> { 61 | show_hide_tabs(state); 62 | return true; 63 | }); 64 | 65 | foreach (string type in color_names) { 66 | var colors = builder.get_object (type) as Entry; 67 | settings.bind(type, colors, "text", SettingsBindFlags.DEFAULT); 68 | colors.button_press_event.connect((event) => { 69 | if (event.button != 1) 70 | return true; 71 | var picker = new ColorChooserDialog(_("Color Picker"), window); 72 | picker.response.connect( (id)=> { 73 | if (id == ResponseType.OK) 74 | colors.text = RGBA_to_hex(picker.get_rgba()); 75 | picker.close(); 76 | }); 77 | 78 | var color = RGBA(); 79 | color.parse(colors.text); 80 | picker.set_rgba(color); 81 | picker.show_all(); 82 | return true; 83 | }); 84 | 85 | colors.changed.connect( ()=> { 86 | if (colors.text == "default") 87 | colors.text = colors_defaults[type]; 88 | 89 | var bg = RGBA(); 90 | bg.parse(colors.text); 91 | colors.override_color(StateFlags.NORMAL , bg); 92 | changed_color(); 93 | }); 94 | 95 | var reset = builder.get_object (type + "-reset") as Button; 96 | reset.button_release_event.connect( (event)=> { 97 | colors.text = "default"; 98 | return false; 99 | }); 100 | 101 | var change_back = colors.text; 102 | colors.text = ""; 103 | colors.text = change_back; 104 | 105 | var bold = new Pango.FontDescription(); 106 | bold.set_weight(Weight.BOLD); 107 | colors.override_font(bold); 108 | } 109 | 110 | var close = builder.get_object("close") as Button; 111 | close.button_release_event.connect( (event)=> { 112 | window.close(); 113 | return true; 114 | }); 115 | 116 | window.show_all(); 117 | 118 | return true; 119 | } 120 | 121 | public string get_color (string color) { 122 | var val = settings.get_string(color); 123 | return (val == "default") ? colors_defaults[color] : val; 124 | } 125 | 126 | public bool get_bool (string name) { 127 | return settings.get_boolean(name.replace("_","-")); 128 | } 129 | 130 | public static string RGBA_to_hex (RGBA rgba) { 131 | string s = 132 | "#%02x%02x%02x%02x" 133 | .printf((uint)(Math.round(rgba.red*255)), 134 | (uint)(Math.round(rgba.green*255)), 135 | (uint)(Math.round(rgba.blue*255)), 136 | (uint)(Math.round(rgba.alpha*255))) 137 | .up(); 138 | return s; 139 | } 140 | 141 | 142 | public void set_colors_defaults () { 143 | colors_defaults["user-self-color"] = Relay.is_light_theme ? "#3B1C73" : "#AE81FF"; 144 | colors_defaults["user-other-color"] = Relay.is_light_theme ? "#1D6A77" : "#4EC9DE"; 145 | colors_defaults["message-color"] = Relay.is_light_theme ? "#505050" : "#F8F8F2"; 146 | colors_defaults["link-color"] = Relay.is_light_theme ? "#0000FF" : "#3D81C4"; 147 | colors_defaults["timestamp-color"] = Relay.is_light_theme ? "#181818" : "#D5D5D5"; 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/sqlclient.vala: -------------------------------------------------------------------------------- 1 | 2 | /* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ 3 | /* 4 | * sqlclient.vala 5 | * Copyright (C) 2015 Kyle Agronick 6 | * 7 | * KyRC is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * KyRC is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 | * See the GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program. If not, see . 19 | */ 20 | 21 | using GLib; 22 | using GLib.Environment; 23 | using Sqlite; 24 | using Gee; 25 | 26 | public class SqlClient : Object 27 | { 28 | public const string DB_FILE = "relay02.db"; 29 | static SqlClient self = null; 30 | public static Sqlite.Database db; 31 | public static HashMap servers = new HashMap(); 32 | 33 | private SqlClient () { 34 | init(); 35 | } 36 | 37 | public static SqlClient get_instance () { 38 | if (self == null) 39 | self = new SqlClient(); 40 | 41 | return self; 42 | } 43 | 44 | public HashMap get_servers() { 45 | return servers; 46 | } 47 | 48 | private void init () { 49 | string confbase = GLib.Environment.get_user_config_dir() + "/relay"; 50 | File dir = File.new_for_path(confbase); 51 | try{ 52 | if (!dir.query_exists()) 53 | dir.make_directory(); 54 | }catch(Error e){ 55 | error("Unable to create database. Can not write to " + confbase + ". Program will not function."); 56 | } 57 | 58 | string conffile = confbase + "/" + DB_FILE; 59 | 60 | int ec = Sqlite.Database.open_v2(conffile, out db); 61 | if (ec != Sqlite.OK) { 62 | stderr.printf ("Can't open database: %d: %s\n", db.errcode (), db.errmsg ()); 63 | } 64 | 65 | add_tables(); 66 | refresh(); 67 | } 68 | 69 | public void refresh () { 70 | servers.clear(); 71 | db.exec("SELECT * from servers ORDER BY servers.host", refresh_callback); 72 | db.exec("SELECT * from channels ORDER BY channels.channel", refresh_callback_channel); 73 | } 74 | 75 | public Server? get_server (string name) { 76 | foreach (var svr in servers.entries) { 77 | if (svr.value.host == name) 78 | return svr.value; 79 | } 80 | return null; 81 | } 82 | 83 | public Server? get_server_id (int id) { 84 | if (servers.has_key(id)) 85 | return servers[id]; 86 | return null; 87 | } 88 | 89 | public static Channel? find_channel (Server current_server, string name) { 90 | foreach (var chan in current_server.channels) { 91 | if (chan.server_id == current_server.id && chan.channel == name) 92 | return chan; 93 | } 94 | return null; 95 | } 96 | 97 | public int refresh_callback (int n_columns, string[] values, string[] column_names) { 98 | var server = new Server(); 99 | for (int i = 0; i < n_columns; i++) { 100 | switch(column_names[i]) { 101 | case "id": 102 | server.id = int.parse(values[i]); 103 | break; 104 | case "host": 105 | server.host = values[i]; 106 | break; 107 | case "port": 108 | server.port = int.parse(values[i]); 109 | break; 110 | case "nickname": 111 | server.nickname = values[i]; 112 | break; 113 | case "realname": 114 | server.realname = values[i]; 115 | break; 116 | case "username": 117 | server.username = values[i]; 118 | break; 119 | case "password": 120 | server.password = values[i]; 121 | break; 122 | case "on_connect": 123 | server.on_connect = values[i]; 124 | break; 125 | case "encryption": 126 | server.encryption = to_bool(values[i]); 127 | break; 128 | case "validate_server": 129 | server.validate_server = to_bool(values[i]); 130 | break; 131 | case "autoconnect": 132 | server.autoconnect = to_bool(values[i]); 133 | break; 134 | case "connect_cmds": 135 | server.connect_cmds = values[i]; 136 | break; 137 | default: 138 | warning("Not able to handle col: " + column_names[i]); 139 | break; 140 | } 141 | } 142 | servers[server.id] = server; 143 | return 0; 144 | } 145 | 146 | public int refresh_callback_channel (int n_columns, string[] values, string[] column_names) { 147 | Server svr = null; 148 | for (int i = 0; i < n_columns; i++) { 149 | if (column_names[i] == "server_id") { 150 | svr = get_server_id( int.parse(values[i])); 151 | } 152 | } 153 | 154 | if (svr == null) 155 | return 0; 156 | 157 | Channel chn = new Channel(); 158 | for (int i = 0; i < n_columns; i++) { 159 | switch(column_names[i]) { 160 | case "id": 161 | chn.id = int.parse(values[i]); 162 | break; 163 | case "server_id": 164 | chn.server_id = int.parse(values[i]); 165 | break; 166 | case "channel": 167 | chn.channel = values[i]; 168 | break; 169 | case "autoconnect": 170 | chn.autoconnect = to_bool(values[i]); 171 | break; 172 | } 173 | } 174 | 175 | 176 | svr.channels.add(chn); 177 | 178 | return 0; 179 | } 180 | 181 | public static bool to_bool (string? input) { 182 | if (input == null) 183 | return false; 184 | return (input == "1"); 185 | } 186 | 187 | public static int bool_to (bool b) { 188 | return b ? 1 : 0; 189 | } 190 | 191 | public class Server{ 192 | 193 | public const string[] keys = {"host", "port", "nickname", "realname", "username", "password", "on_connect", "encryption", "validate_server", "autoconnect", "connect_cmds"}; 194 | 195 | public int id = -1; 196 | public string host = ""; 197 | public int port = Connection.DEFAULT_PORT; 198 | public string nickname = ""; 199 | public string realname = ""; 200 | public string username = ""; 201 | public string password = ""; 202 | public string on_connect = ""; 203 | public bool encryption = false; 204 | public bool autoconnect = false; 205 | public bool validate_server = false; 206 | public string connect_cmds = ""; 207 | public LinkedList channels = new LinkedList(); 208 | 209 | public int add_server_empty () { 210 | string sql = "INSERT INTO servers (host, port) VALUES('', " + Connection.DEFAULT_PORT.to_string() + ")"; 211 | db.exec(sql); 212 | this.id = (int)db.last_insert_rowid (); 213 | servers[this.id]=this; 214 | return this.id; 215 | } 216 | 217 | public int add_server () { 218 | this.id = add_server_empty (); 219 | servers[id]=this; 220 | update(); 221 | return id; 222 | } 223 | 224 | public Channel? find_channel_by_name (string name) { 225 | foreach(Channel chan in channels) { 226 | if (chan.channel == name) 227 | return chan; 228 | } 229 | return null; 230 | } 231 | 232 | public LinkedList get_autoconnect_channels() { 233 | var returns = new LinkedList(); 234 | foreach(var channel in channels) { 235 | if (channel.autoconnect) 236 | returns.add(channel.channel); 237 | } 238 | return returns; 239 | } 240 | 241 | public int update () { 242 | var svr = this; 243 | Sqlite.Statement stmt; 244 | int ok; 245 | string sql = "SELECT id FROM servers WHERE id = " + svr.id.to_string(); 246 | bool exists = false; 247 | db.exec(sql, (n_columns, values, column_names) => { 248 | exists = true; 249 | return 0; 250 | }); 251 | 252 | 253 | string keys = ""; 254 | foreach (string i in Server.keys) { 255 | //Skip over ID 256 | if (keys == "") { 257 | keys = " "; 258 | 259 | } 260 | keys += i + "=$" + i + ", "; 261 | } 262 | 263 | keys = keys[0:-2]; 264 | 265 | 266 | if (exists) { 267 | sql = "UPDATE servers SET " + keys + " WHERE id = " + svr.id.to_string(); 268 | 269 | ok = db.prepare_v2(sql, sql.length, out stmt); 270 | if (ok != Sqlite.OK) { 271 | critical (db.errmsg ()); 272 | return -1; 273 | } 274 | 275 | stmt.bind_text(1, svr.host); 276 | stmt.bind_int(2, svr.port); 277 | stmt.bind_text(3, svr.nickname); 278 | stmt.bind_text(4, svr.realname); 279 | stmt.bind_text(5, svr.username); 280 | stmt.bind_text(6, svr.password); 281 | stmt.bind_text(7, svr.on_connect); 282 | stmt.bind_int(8, bool_to(svr.encryption)); 283 | stmt.bind_int(9, bool_to(svr.validate_server)); 284 | stmt.bind_int(10, bool_to(svr.autoconnect)); 285 | stmt.bind_text(11, svr.connect_cmds); 286 | 287 | stmt.step(); 288 | 289 | SqlClient.servers[svr.id] = svr; 290 | 291 | 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | public void remove_server () { 298 | servers.unset(this.id); 299 | string sql = "DELETE FROM servers WHERE id=" + this.id.to_string(); 300 | db.exec(sql); 301 | sql = "DELETE FROM channels WHERE server_id=" + this.id.to_string(); 302 | db.exec(sql); 303 | } 304 | } 305 | 306 | public class Channel{ 307 | public int id = -1; 308 | public int server_id; 309 | public string channel; 310 | public bool autoconnect; 311 | 312 | public void delete_channel () { 313 | string sql = "DELETE FROM channels WHERE server_id=" + this.server_id.to_string() + " AND channel=$NAME"; 314 | channel_query(sql); 315 | if (!servers.has_key(server_id)) { 316 | warning("No server with key " + server_id.to_string()); 317 | return; 318 | } 319 | servers[server_id].channels.remove(this); 320 | } 321 | 322 | public void add_channel () { 323 | if (this.server_id < 0) 324 | return; 325 | string sql = "INSERT INTO channels (server_id, channel) VALUES(" + this.server_id.to_string() + ", $CHANNEL)"; 326 | channel_query(sql); 327 | servers[server_id].channels.add(this); 328 | } 329 | 330 | public bool update_autoconnect (bool state) { 331 | string sql = "UPDATE channels SET autoconnect = " + bool_to (state).to_string() + " WHERE server_id = " + server_id.to_string() + " AND channel = $NAME"; 332 | channel_query(sql); 333 | autoconnect = state; 334 | return autoconnect; 335 | } 336 | 337 | private void channel_query (string sql) { 338 | Sqlite.Statement stmt; 339 | int ok = db.prepare_v2(sql, sql.length, out stmt); 340 | if (ok == Sqlite.ERROR) { 341 | critical (db.errmsg ()); 342 | return; 343 | } 344 | stmt.bind_text(1, this.channel); 345 | stmt.step(); 346 | } 347 | } 348 | 349 | 350 | private void add_tables () { 351 | string sql = """ 352 | CREATE TABLE IF NOT EXISTS "servers" ( 353 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, 354 | "host" TEXT NOT NULL, 355 | "port" INTEGER NOT NULL, 356 | "nickname" TEXT, 357 | "realname" TEXT, 358 | "username" TEXT, 359 | "password" TEXT, 360 | "on_connect" TEXT, 361 | "connect_cmds" TEXT, 362 | "encryption" BOOL, 363 | "autoconnect" BOOL, 364 | "validate_server" BOOL 365 | ); 366 | 367 | CREATE TABLE IF NOT EXISTS "channels" ( 368 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, 369 | "server_id" INTEGER, 370 | "channel" TEXT, 371 | "autoconnect" BOOL 372 | ); 373 | """; 374 | 375 | db.exec(sql); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/ui/relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agronick/Relay/b3f43969cd310e2a29ebbabfb56f8ac7d69b6c07/src/ui/relay.png -------------------------------------------------------------------------------- /src/ui/relay.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 37 | 39 | 40 | 42 | image/svg+xml 43 | 45 | 46 | 47 | 48 | 49 | 51 | 53 | 57 | 61 | 65 | 66 | 75 | 77 | 81 | 85 | 89 | 93 | 94 | 104 | 114 | 116 | 120 | 124 | 125 | 135 | 143 | 145 | 149 | 153 | 157 | 158 | 168 | 179 | 190 | 201 | 210 | 211 | 215 | 219 | 226 | 234 | 241 | 242 | 243 | 252 | 257 | 258 | -------------------------------------------------------------------------------- /src/ui/relay.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | gtk-properties 9 | 10 | 11 | True 12 | False 13 | Relay 14 | center 15 | 1111 16 | 600 17 | relay.svg 18 | center 19 | True 20 | 21 | 22 | True 23 | True 24 | 25 | 26 | True 27 | False 28 | vertical 29 | 30 | 31 | True 32 | False 33 | vertical 34 | 35 | 36 | Manage Servers 37 | True 38 | True 39 | True 40 | 41 | 42 | False 43 | True 44 | 0 45 | 46 | 47 | 48 | 49 | False 50 | True 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | False 60 | True 61 | 62 | 63 | 64 | 65 | True 66 | False 67 | vertical 68 | 69 | 70 | True 71 | False 72 | vertical 73 | 74 | 75 | 76 | 77 | 78 | True 79 | True 80 | 0 81 | 82 | 83 | 84 | 85 | True 86 | False 87 | 88 | 89 | 90 | 91 | 92 | True 93 | True 94 | True 95 | False 96 | image1 97 | 98 | 99 | False 100 | True 101 | end 102 | 1 103 | 104 | 105 | 106 | 107 | False 108 | True 109 | 1 110 | 111 | 112 | 113 | 114 | True 115 | True 116 | 117 | 118 | 119 | 120 | 121 | 122 | --------------------------------------------------------------------------------